blob: d5c059b7603f3c24eb43ba00a36624ce5b0fc060 [file] [log] [blame]
/* Copyright (c) 2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/alarmtimer.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeup.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/rtc.h>
#define NX30P6093_ID_REG 0x0
#define NX30P6093_VENDOR_ID_MASK GENMASK(7, 3)
#define NX30P6093_VENDOR_ID_SHIFT 3
#define NX30P6093_VERSION_ID_MASK GENMASK(2, 0)
#define NX30P6093_ENABLE_REG 0x01
#define NX30P6093_DETECT_EN BIT(6)
#define NX30P6093_STATUS_REG 0x02
#define NX30P6093_PWRON_STS BIT(7)
#define NX30P6093_IMPEDANCE_MASK GENMASK(6, 5)
#define NX30P6093_IMPEDANCE_SHIFT 5
#define NX30P6093_IMPEDANCE_GOOD_VAL 1
#define NX30P6093_IMPEDANCE_BAD_VAL 3
#define NX30P6093_INTR_MASK_REG 0x04
#define NX30P6093_OVER_TAG_STS_INTR_MASK BIT(6)
#define NX30P6093_TMR_OUT_STS_INTR_MASK BIT(5)
#define NX30P6093_VIN_ISOURCE_REG 0x06
#define NX30P6093_VIN_ISOURCE_MASK GENMASK(3, 0)
#define NX30P6093_ISOURCE_TIMING_REG 0x07
#define NX30P6093_ISOURCE_TDET_MASK GENMASK(7, 4)
#define NX30P6093_ISOURCE_TDET_SHIFT 4
#define NX30P6093_ISOURCE_TDUTY_MASK GENMASK(3, 0)
#define NX30P6093_VIN_VOLTAGE_TAG_REG 0x09
#define NX30P6093_VIN_VOLTAGE_TAG_MASK GENMASK(7, 0)
#define NX30P6093_SLEW_RATE_TUNE_REG 0x0f
/* short duration is 5sec and long duration is 5hrs */
#define NX30P6093_LONG_WAKEUP_SEC 18000
#define NX30P6093_SHORT_WAKEUP_MS 5000
/* Default Tduty = 5mins when always-on detection is configured */
#define NX30P6093_ISOURCE_ALWAYS_ON_TDUTY_MS 300000
/* configuration data */
#define NX30P6093_VIN_ISOURCE_VAL 0xd
#define NX30P6093_VIN_VOLTAGE_TAG_VAL 0xad
#define NX30P6093_ISOURCE_TDET_VAL 0x5
#define NX30P6093_ISOURCE_TDUTY_VAL 0x0
#define NX30P6093_ISOURCE_TDET_MS 10
struct nx30p6093_info {
struct device *dev;
struct regmap *regmap;
struct power_supply *usb_psy;
struct alarm alarm_timer;
struct delayed_work config_impedance_detect;
struct mutex lock;
struct dentry *debugfs;
u8 tduty_val;
int irq;
/* status data */
bool irq_waiting;
bool high_impedance;
bool detection_on;
bool always_on;
bool use_alarm;
bool suspended;
/* timer configuration */
u64 long_wakeup_ms;
u64 short_wakeup_ms;
};
static const int nx30p6093_tduty_ms[] = {0, 10, 20, 50, 100, 200, 500, 1000,
2000, 3000, 6000, 12000, 30000, 60000,
120000, 300000};
static const struct regmap_config nx30p6093_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = NX30P6093_SLEW_RATE_TUNE_REG,
};
static int nx30p6093_dump_regs(struct nx30p6093_info *info)
{
unsigned int val;
int i, rc = 0;
for (i = 0; i <= NX30P6093_SLEW_RATE_TUNE_REG; ++i) {
rc = regmap_read(info->regmap, i, &val);
if (rc < 0)
return rc;
pr_debug("NX30P6093(0x%02x) = 0x%02x\n", i, (uint8_t)val);
}
return rc;
}
static inline void nx30p6093_config_alarm(struct nx30p6093_info *info,
u64 wakeup_ms)
{
if (!info->use_alarm)
return;
alarm_start_relative(&info->alarm_timer, ms_to_ktime(wakeup_ms));
}
static int nx30p6093_impedance_detect(struct nx30p6093_info *info, bool enable)
{
int rc = 0;
if (enable == info->detection_on)
return rc;
rc = regmap_update_bits(info->regmap, NX30P6093_ENABLE_REG,
NX30P6093_DETECT_EN,
enable ? NX30P6093_DETECT_EN : 0);
if (rc < 0) {
pr_err("failed to %s VIN impedance detection, rc=%d\n",
enable ? "enable" : "disable", rc);
return rc;
}
/* wait for 3ms for HW activation and enters detection standby mode */
usleep_range(3000, 3100);
/* config Isource to VIN */
rc = regmap_update_bits(info->regmap, NX30P6093_VIN_ISOURCE_REG,
NX30P6093_VIN_ISOURCE_MASK,
enable ? NX30P6093_VIN_ISOURCE_VAL : 0);
if (rc < 0) {
pr_err("failed to configure Vin Isource register, rc=%d\n", rc);
return rc;
}
info->detection_on = enable;
nx30p6093_dump_regs(info);
return rc;
}
static int nx30p6093_read_impedance_status(struct nx30p6093_info *info)
{
union power_supply_propval psp_val;
unsigned int val;
u8 impedance;
int rc;
/* Read status register */
rc = regmap_read(info->regmap, NX30P6093_STATUS_REG, &val);
if (rc < 0) {
pr_err("failed to read status register, rc=%d\n", rc);
return rc;
}
if (val & NX30P6093_PWRON_STS) {
/* VBUS present */
return rc;
}
impedance = (val & NX30P6093_IMPEDANCE_MASK)
>> NX30P6093_IMPEDANCE_SHIFT;
if (impedance == NX30P6093_IMPEDANCE_GOOD_VAL && info->high_impedance) {
info->high_impedance = false;
/* enable the type-C CC detection */
psp_val.intval = 0;
rc = power_supply_set_property(info->usb_psy,
POWER_SUPPLY_PROP_MOISTURE_DETECTED,
&psp_val);
} else if (impedance == NX30P6093_IMPEDANCE_BAD_VAL) {
info->high_impedance = true;
/* disable the type-C CC detection */
psp_val.intval = 1;
rc = power_supply_set_property(info->usb_psy,
POWER_SUPPLY_PROP_MOISTURE_DETECTED,
&psp_val);
}
return rc;
}
static irqreturn_t nx30p6093_irq_handler(int irq, void *data)
{
struct nx30p6093_info *info = data;
mutex_lock(&info->lock);
info->irq_waiting = true;
if (info->suspended) {
pr_debug("IRQ triggered before device-resume\n");
disable_irq_nosync(irq);
mutex_unlock(&info->lock);
return IRQ_HANDLED;
}
info->irq_waiting = false;
mutex_unlock(&info->lock);
nx30p6093_read_impedance_status(info);
if (info->high_impedance) {
disable_irq_nosync(irq);
/* set up next detection event */
nx30p6093_config_alarm(info,
NX30P6093_ISOURCE_ALWAYS_ON_TDUTY_MS);
}
return IRQ_HANDLED;
}
static int nx30p6093_trigger_impedance_detect(struct nx30p6093_info *info)
{
int rc;
if (!info->always_on) {
rc = nx30p6093_impedance_detect(info, true);
if (rc < 0) {
pr_err("start impedance detection failed, rc=%d\n", rc);
return rc;
}
/* wait for the detection complete(Tdet time). */
usleep_range(NX30P6093_ISOURCE_TDET_MS * USEC_PER_MSEC,
NX30P6093_ISOURCE_TDET_MS * USEC_PER_MSEC + 100);
}
/* Read and process the detection result. */
rc = nx30p6093_read_impedance_status(info);
if (rc < 0)
pr_err("impedance status read failed, rc=%d\n", rc);
if (!info->always_on) {
rc = nx30p6093_impedance_detect(info, false);
if (rc < 0)
pr_err("stop impedance detection failed, rc=%d\n", rc);
}
return rc;
}
static void nx30p6093_config_impedance_detect(struct work_struct *work)
{
struct nx30p6093_info *info = container_of(work, struct nx30p6093_info,
config_impedance_detect.work);
u64 wakeup_ms = 0;
mutex_lock(&info->lock);
if (info->suspended) {
/*
* Defer the work as the device is still in suspend state and
* not yet resumed.
*/
schedule_delayed_work(&info->config_impedance_detect,
msecs_to_jiffies(500));
mutex_unlock(&info->lock);
return;
}
nx30p6093_trigger_impedance_detect(info);
if (info->always_on) {
if (info->high_impedance) {
/*
* Bad impedance is not cleared yet.
* Set up a next detection event.
*/
nx30p6093_config_alarm(info,
NX30P6093_ISOURCE_ALWAYS_ON_TDUTY_MS);
} else {
/* Bad impedance is cleared. Enable detection IRQ */
enable_irq(info->irq);
}
} else {
wakeup_ms = info->high_impedance ? info->short_wakeup_ms
: info->long_wakeup_ms;
/* Set up a next detection event */
nx30p6093_config_alarm(info, wakeup_ms);
}
mutex_unlock(&info->lock);
pm_relax(info->dev);
}
static enum alarmtimer_restart
nx30p6093_process_alarm_event(struct alarm *alarm, ktime_t now)
{
struct nx30p6093_info *info = container_of(alarm, struct nx30p6093_info,
alarm_timer);
union power_supply_propval val;
int rc;
/* Read USB plugged-in */
rc = power_supply_get_property(info->usb_psy, POWER_SUPPLY_PROP_PRESENT,
&val);
if (rc < 0) {
pr_err("read usb present failed, rc=%d\n", rc);
return ALARMTIMER_RESTART;
}
if (val.intval) {
/*
* usb present - skip impedance detection and set up
* next detection event.
*/
nx30p6093_config_alarm(info, info->long_wakeup_ms);
} else {
pm_stay_awake(info->dev);
schedule_delayed_work(&info->config_impedance_detect, 0);
}
return ALARMTIMER_NORESTART;
}
static int nx30p6093_init_config(struct nx30p6093_info *info)
{
int rc;
u8 val;
/* Enable OVER_TAG_STATUS interrupt if always-on detection enabled */
rc = regmap_write(info->regmap, NX30P6093_INTR_MASK_REG,
info->always_on ? NX30P6093_OVER_TAG_STS_INTR_MASK
: 0);
if (rc < 0) {
pr_err("failed to enable timer out status interrupt, rc=%d\n",
rc);
return rc;
}
/* config Isource timing (Default: 10ms) */
val = NX30P6093_ISOURCE_TDET_VAL << NX30P6093_ISOURCE_TDET_SHIFT;
rc = regmap_update_bits(info->regmap, NX30P6093_ISOURCE_TIMING_REG,
NX30P6093_ISOURCE_TDET_MASK, val);
if (rc < 0) {
pr_err("failed to configure Isource timing, rc=%d\n", rc);
return rc;
}
/*
* config Isource Tduty timing;
* Default value:
* 1) One shot - if Periodic detection enabled
* 2) 5 mins - if Always-on detection enabled
*/
rc = regmap_update_bits(info->regmap, NX30P6093_ISOURCE_TIMING_REG,
NX30P6093_ISOURCE_TDUTY_MASK,
info->always_on ? info->tduty_val
: NX30P6093_ISOURCE_TDUTY_VAL);
if (rc < 0) {
pr_err("failed to configure Isource Tduty timing, rc=%d\n", rc);
return rc;
}
/* config VIN voltage tag (Default: 0xad) */
rc = regmap_update_bits(info->regmap, NX30P6093_VIN_VOLTAGE_TAG_REG,
NX30P6093_VIN_VOLTAGE_TAG_MASK,
NX30P6093_VIN_VOLTAGE_TAG_VAL);
if (rc < 0) {
pr_err("failed to configure Vin voltage tag register, rc=%d\n",
rc);
return rc;
}
return 0;
}
static int nx30p6093_dt_init(struct i2c_client *client,
struct nx30p6093_info *info)
{
struct device_node *of_node = client->dev.of_node;
int i, tduty_ms;
u32 long_wakeup_sec, short_wakeup_ms;
if (of_property_read_bool(of_node, "nxp,always-on-detect")) {
info->always_on = true;
tduty_ms = NX30P6093_ISOURCE_ALWAYS_ON_TDUTY_MS;
of_property_read_u32(of_node, "nxp,always-on-tduty-ms",
&tduty_ms);
for (i = 0; i < ARRAY_SIZE(nx30p6093_tduty_ms); i++) {
if (tduty_ms <= nx30p6093_tduty_ms[i]) {
info->tduty_val = (uint8_t)i;
break;
}
}
if (!info->tduty_val) {
pr_err("invalid nxp,always-on-tduty-ms = %d\n",
tduty_ms);
return -EINVAL;
}
} else {
long_wakeup_sec = NX30P6093_LONG_WAKEUP_SEC;
short_wakeup_ms = NX30P6093_SHORT_WAKEUP_MS;
of_property_read_u32(of_node, "nxp,long-wakeup-sec",
&long_wakeup_sec);
of_property_read_u32(of_node, "nxp,short-wakeup-ms",
&short_wakeup_ms);
if (!long_wakeup_sec || !short_wakeup_ms) {
pr_err("Invalid wakeup timings are configured\n");
return -EINVAL;
}
info->long_wakeup_ms = long_wakeup_sec * MSEC_PER_SEC;
info->short_wakeup_ms = short_wakeup_ms;
}
return 0;
}
static int nx30p6093_trigger_detect(struct seq_file *file, void *data)
{
struct nx30p6093_info *info = file->private;
if (info->always_on)
return 0;
mutex_lock(&info->lock);
nx30p6093_trigger_impedance_detect(info);
seq_printf(file, "%s impedance detected\n",
info->high_impedance ? "BAD" : "GOOD");
mutex_unlock(&info->lock);
return 0;
}
static int nx30p6093_trigger_detect_open(struct inode *inode, struct file *file)
{
return single_open(file, nx30p6093_trigger_detect, inode->i_private);
}
static const struct file_operations nx30p6093_trigger_detect_fops = {
.owner = THIS_MODULE,
.open = nx30p6093_trigger_detect_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void nx30p6093_debugfs_init(struct nx30p6093_info *info)
{
struct dentry *temp;
/* debugfs */
info->debugfs = debugfs_create_dir("nx30p6093", NULL);
if (!info->debugfs) {
pr_err("Couldn't create debug dir\n");
return;
}
temp = debugfs_create_file("trigger_detection", 0644, info->debugfs,
info, &nx30p6093_trigger_detect_fops);
if (IS_ERR_OR_NULL(temp)) {
pr_err("debugfs_nx30p6093_reg_addr_fops debugfs file creation failed\n");
debugfs_remove_recursive(info->debugfs);
}
}
#if CONFIG_PM
static int nx30p6093_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct nx30p6093_info *info = i2c_get_clientdata(client);
cancel_delayed_work_sync(&info->config_impedance_detect);
mutex_lock(&info->lock);
info->suspended = true;
mutex_unlock(&info->lock);
return 0;
}
static int nx30p6093_suspend_noirq(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct nx30p6093_info *info = i2c_get_clientdata(client);
if (info->irq_waiting) {
pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
return -EBUSY;
}
return 0;
}
static int nx30p6093_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct nx30p6093_info *info = i2c_get_clientdata(client);
mutex_lock(&info->lock);
info->suspended = false;
if (info->irq_waiting) {
mutex_unlock(&info->lock);
nx30p6093_irq_handler(client->irq, info);
enable_irq(client->irq);
} else {
mutex_unlock(&info->lock);
}
return 0;
}
#else
static int nx30p6093_suspend(struct device *dev)
{
return 0;
}
static int nx30p6093_resume(struct device *dev)
{
return 0;
}
#endif
static const struct dev_pm_ops nx30p6093_pm_ops = {
.suspend = nx30p6093_suspend,
.suspend_noirq = nx30p6093_suspend_noirq,
.resume = nx30p6093_resume,
};
static int nx30p6093_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct nx30p6093_info *info;
unsigned int val;
int vendor_id, version_id, rc = 0;
info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->dev = &client->dev;
info->regmap = devm_regmap_init_i2c(client, &nx30p6093_regmap_config);
if (IS_ERR(info->regmap)) {
pr_err("Error in allocating regmap, rc=%ld\n",
PTR_ERR(info->regmap));
return PTR_ERR(info->regmap);
}
rc = regmap_read(info->regmap, NX30P6093_ID_REG, &val);
if (rc < 0) {
pr_err("Unable to identify NX30P6093, rc=%d\n", rc);
return rc;
}
vendor_id = (val & NX30P6093_VENDOR_ID_MASK)
>> NX30P6093_VENDOR_ID_SHIFT;
version_id = val & NX30P6093_VERSION_ID_MASK;
info->usb_psy = power_supply_get_by_name("usb");
if (!info->usb_psy) {
pr_err("USB psy not found\n");
return -EPROBE_DEFER;
}
i2c_set_clientdata(client, info);
mutex_init(&info->lock);
INIT_DELAYED_WORK(&info->config_impedance_detect,
nx30p6093_config_impedance_detect);
rc = nx30p6093_dt_init(client, info);
if (rc < 0) {
pr_err("device tree parsing failed, rc=%d\n", rc);
return rc;
}
rc = nx30p6093_init_config(info);
if (rc < 0) {
pr_err("initial configuration programming failed, rc=%d\n", rc);
return rc;
}
if (alarmtimer_get_rtcdev()) {
/* Initialize alarm timer */
info->use_alarm = true;
alarm_init(&info->alarm_timer, ALARM_BOOTTIME,
nx30p6093_process_alarm_event);
} else {
pr_err("alarm initialization failed\n");
return -ENODEV;
}
if (info->always_on) {
/* Moisture detect irq configuration */
if (client->irq) {
info->irq = client->irq;
rc = devm_request_threaded_irq(&client->dev,
client->irq, NULL,
nx30p6093_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
client->name, info);
if (rc < 0) {
pr_err("Failed Moisture detect irq(%d) request, rc=%d\n",
client->irq, rc);
return rc;
}
enable_irq_wake(client->irq);
} else {
pr_err("Moisture detect irq not defined\n");
return -EINVAL;
}
nx30p6093_impedance_detect(info, true);
} else {
/* Run impedance detection for first time */
pm_stay_awake(info->dev);
schedule_delayed_work(&info->config_impedance_detect, 0);
}
nx30p6093_debugfs_init(info);
if (info->always_on)
pr_info("NXP NX30P6093 Vendor(%d), Version(%d), configured to Always-on detection\n",
vendor_id, version_id);
else
pr_info("NXP NX30P6093 Vendor(%d), Version(%d), configured to periodic detection with Short_wakeup = %llu ms and Long_wakeup = %llu sec\n",
vendor_id, version_id, info->short_wakeup_ms,
info->long_wakeup_ms / MSEC_PER_SEC);
return 0;
}
static int nx30p6093_remove(struct i2c_client *client)
{
struct nx30p6093_info *info = i2c_get_clientdata(client);
debugfs_remove_recursive(info->debugfs);
cancel_delayed_work_sync(&info->config_impedance_detect);
if (info->use_alarm)
alarm_cancel(&info->alarm_timer);
return 0;
}
static const struct of_device_id nx30p6093_table[] = {
{ .compatible = "nxp,nx30p6093" },
{ },
};
MODULE_DEVICE_TABLE(of, nx30p6093_table);
static const struct i2c_device_id nx30p6093_id[] = {
{"nx30p6093", -1},
{ },
};
MODULE_DEVICE_TABLE(i2c, nx30p6093_id);
static struct i2c_driver nx30p6093_driver = {
.driver = {
.name = "nxp,nx30p6093",
.owner = THIS_MODULE,
.of_match_table = nx30p6093_table,
.pm = &nx30p6093_pm_ops,
},
.probe = nx30p6093_probe,
.remove = nx30p6093_remove,
.id_table = nx30p6093_id,
};
module_i2c_driver(nx30p6093_driver);
MODULE_DESCRIPTION("NX30P6093 driver");
MODULE_LICENSE("GPL v2");