blob: 37cbdcaa11ce950eaafc2267e09b69ac6d67897a [file] [log] [blame]
/*
* Copyright (C) 2014 Motorola Mobility LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307, USA
*/
#include <linux/alarmtimer.h>
#include <linux/debugfs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/m4sensorhub.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/power_supply.h>
#include <linux/slab.h>
#include <linux/switch.h>
#include <linux/wakelock.h>
#include <linux/workqueue.h>
#include <linux/wakeup_source_notify.h>
/*
* Detect when a device is placed on a wireless charger and report this to user
* space. Use two gpio lines for the detection, chg_gpio and det_gpio. The gpios
* are in the following states depending on the device state and h/w revision:
*
* Docked Charging chg_gpio det_gpio (new h/w) det_gpio (old h/w)
* Y Y High High High
* Y N Low Periodic high pulse High
* N N/A Low Low High
*
* For new h/w, if charging is stopped while the device is docked, det_gpio may
* stay high for some time before the periodic pulse starts.
*
* For new h/w, the state of det_gpio alone is sufficinet to determine docked
* state. We have to use state of chg_gpo for compatibality with old h/w.
*
* Provide an option to use motion to report undocking. If this option is
* enabled, then udocking is reported only if motion has been detected.
*/
/*
* from device tree node
* chg_gpio, det_gpio detection lines
* undocked_delay time to wait for a pulse on det_gpio line
* chg_name name of the wireless charger power_supply device
* switch_name name of the wireless charger switch device
* supplied_to batteries where this charger supplies power
* num_supplicants number of entries in the supplied_to array, the value
* is derived from from the supplied_to array
* uevent_wakelock_timeout time to hold wake_lock to ensure user space has
* processed new docked state
* old_hw h/w where det_gpio is always high
* use_motion use motion to detect undocking
*/
struct bq5105x_detect_dts {
int chg_gpio;
int det_gpio;
unsigned long undocked_delay;
const char* chg_name;
const char* switch_name;
char **supplied_to;
size_t num_supplicants;
unsigned long uevent_wakelock_timeout;
bool old_hw;
bool use_motion;
};
struct bq5105x_detect {
const struct bq5105x_detect_dts *dts_data;
struct platform_device *pdev;
bool docked; /* based on chg_gpio and det_gpio states */
bool reported_docked; /* based on docked state and motion */
unsigned int chg_irq;
unsigned int det_irq;
bool det_irq_enabled;
struct power_supply charger;
struct switch_dev *sdev;
struct workqueue_struct *wq;
struct work_struct chg_irq_work;
struct work_struct det_irq_work;
struct work_struct undocked_work;
struct alarm undocked_alarm;
ktime_t alarm_time;
/* Separate wakelock for each work item */
struct wake_lock chg_irq_wakelock;
struct wake_lock det_irq_wakelock;
struct wake_lock undocked_wakelock;
/* Wakelock to allow user space process new docked state */
struct wake_lock uevent_wakelock;
/* Wakelock to prevent suspend while pulse is present on det_gpio */
struct wake_lock pulse_wakelock;
u32 disable_pulse_wakelock; /* for disabling via debugfs */
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_root;
#endif
/* For using motion to detect undocking */
struct m4sensorhub_data *m4shub;
bool in_motion; /* has there been some motion lately ? */
struct mutex motion_mutex;
};
static enum power_supply_property bq5105x_detect_chg_prop[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static int bq5105x_detect_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct bq5105x_detect *chip = container_of(psy, struct bq5105x_detect,
charger);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
/* TODO: Atomic access to the state */
val->intval = chip->reported_docked;
break;
default:
return -EINVAL;
}
return 0;
}
static void bq5105x_detect_m4_track_motion(struct bq5105x_detect *chip, bool en)
{
int ret;
if (!chip->m4shub) {
dev_dbg(&chip->pdev->dev, "m4 sensor hub is not ready to track motion\n");
return;
}
dev_dbg(&chip->pdev->dev, "motion tracking is %s\n", en ? "on" : "off");
if (en) {
ret = m4sensorhub_irq_enable(chip->m4shub,
M4SH_IRQ_MOTION_DETECTED);
if (ret < 0) {
dev_err(&chip->pdev->dev, "failed to enable motion detection\n");
goto err_m4_track;
}
ret = m4sensorhub_irq_enable(chip->m4shub,
M4SH_IRQ_STILL_DETECTED);
if (ret < 0) {
dev_err(&chip->pdev->dev, "failed to enable still mode detection\n");
goto err_m4_track;
}
} else {
ret = m4sensorhub_irq_disable(chip->m4shub,
M4SH_IRQ_MOTION_DETECTED);
if (ret < 0) {
dev_err(&chip->pdev->dev, "failed to disable motion detection\n");
goto err_m4_track;
}
ret = m4sensorhub_irq_disable(chip->m4shub,
M4SH_IRQ_STILL_DETECTED);
if (ret < 0) {
dev_err(&chip->pdev->dev, "failed to disable still mode detection\n");
goto err_m4_track;
}
}
return;
err_m4_track:
/* Unregister from interrupts and assume to be in motion */
chip->in_motion = true;
m4sensorhub_irq_unregister(chip->m4shub, M4SH_IRQ_MOTION_DETECTED);
m4sensorhub_irq_unregister(chip->m4shub, M4SH_IRQ_STILL_DETECTED);
chip->m4shub = NULL;
}
static bool bq5105x_detect_m4_motion(struct bq5105x_detect *chip)
{
bool motion;
unsigned char val;
int ret;
/*
* In motion if sensor hub is not ready or error occured when reading
* the current state.
*/
if (!chip->m4shub) {
dev_dbg(&chip->pdev->dev, "m4 sensor hub is not ready to report motion state\n");
ret = -1;
} else {
ret = m4sensorhub_reg_read(chip->m4shub,
M4SH_REG_POWER_DEVICESTATE, &val);
}
motion = (ret < 0 || val);
dev_dbg(&chip->pdev->dev, "current motion=%d\n", motion);
return motion;
}
static void bq5105x_detect_report(struct bq5105x_detect *chip, bool docked)
{
if (chip->reported_docked != docked) {
dev_dbg(&chip->pdev->dev, "report docked=%d\n", docked);
#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY
wakeup_source_notify_subscriber(docked
? DISPLAY_WAKE_EVENT_DOCKON
: DISPLAY_WAKE_EVENT_DOCKOFF);
#endif
chip->reported_docked = docked;
power_supply_changed(&chip->charger);
switch_set_state(chip->sdev, docked);
if (chip->dts_data->use_motion) {
/* Track motion state while docked */
if (docked) {
bq5105x_detect_m4_track_motion(chip, true);
chip->in_motion =
bq5105x_detect_m4_motion(chip);
} else {
bq5105x_detect_m4_track_motion(chip, false);
}
}
/* TODO: Release wakelock when switch state is read */
wake_lock_timeout(&chip->uevent_wakelock,
chip->dts_data->uevent_wakelock_timeout);
}
}
static void bq5105x_detect_set_docked(struct bq5105x_detect *chip, bool docked)
{
if (chip->docked != docked) {
dev_dbg(&chip->pdev->dev, "docked=%d\n", docked);
if (chip->dts_data->use_motion) {
/* Report undocked only if there has been some motion */
mutex_lock(&chip->motion_mutex);
chip->docked = docked;
if (chip->docked || chip->in_motion)
bq5105x_detect_report(chip, docked);
mutex_unlock(&chip->motion_mutex);
} else {
chip->docked = docked;
bq5105x_detect_report(chip, docked);
}
}
}
static void bq5105x_detect_chg_irq_work(struct work_struct *work)
{
struct bq5105x_detect *chip = container_of(work, struct bq5105x_detect,
chg_irq_work);
/*
* When chg_gpio is high, declare docked and do not monitor det_gpio
* line. When chg_gpio is low, monitor pulses on det_gpio line after it
* transitions to low unless old h/w is used. Detect presense of a pulse
* by setting an alarm. If det_gpio goes from high to low before the
* alarm goes off, the pulse is present. If there is no pulse, declare
* un-docked.
*/
if (gpio_get_value_cansleep(chip->dts_data->chg_gpio)) {
dev_dbg(&chip->pdev->dev, "charging\n");
if (chip->det_irq_enabled) {
disable_irq(chip->det_irq);
chip->det_irq_enabled = false;
}
cancel_work_sync(&chip->det_irq_work);
wake_unlock(&chip->pulse_wakelock);
wake_unlock(&chip->det_irq_wakelock);
alarm_cancel(&chip->undocked_alarm);
cancel_work_sync(&chip->undocked_work);
wake_unlock(&chip->undocked_wakelock);
bq5105x_detect_set_docked(chip, true);
} else {
dev_dbg(&chip->pdev->dev, "not charging\n");
if (!chip->dts_data->old_hw) {
/* Less power is drawn with wakelock when looking for a
* pulse */
if (!chip->disable_pulse_wakelock)
wake_lock(&chip->pulse_wakelock);
if (!chip->det_irq_enabled) {
enable_irq(chip->det_irq);
chip->det_irq_enabled = true;
}
if (!gpio_get_value_cansleep(chip->dts_data->det_gpio))
alarm_start_relative(&chip->undocked_alarm,
chip->alarm_time);
} else {
bq5105x_detect_set_docked(chip, false);
}
}
wake_unlock(&chip->chg_irq_wakelock);
}
static irqreturn_t bq5105x_detect_chg_irq(int irq, void *devid)
{
struct bq5105x_detect *chip = devid;
wake_lock(&chip->chg_irq_wakelock);
queue_work(chip->wq, &chip->chg_irq_work);
return IRQ_HANDLED;
}
static void bq5105x_detect_det_irq_work(struct work_struct *work)
{
struct bq5105x_detect *chip = container_of(work, struct bq5105x_detect,
det_irq_work);
if (alarm_cancel(&chip->undocked_alarm)) {
dev_dbg(&chip->pdev->dev, "pulse\n");
bq5105x_detect_set_docked(chip, true);
} else {
dev_dbg(&chip->pdev->dev, "first pulse\n");
}
alarm_start_relative(&chip->undocked_alarm, chip->alarm_time);
/* Less power is drawn if wakelock is held while pulse is present */
if (!chip->disable_pulse_wakelock)
wake_lock(&chip->pulse_wakelock);
wake_unlock(&chip->det_irq_wakelock);
}
static irqreturn_t bq5105x_detect_det_irq(int irq, void *devid)
{
struct bq5105x_detect *chip = devid;
wake_lock(&chip->det_irq_wakelock);
queue_work(chip->wq, &chip->det_irq_work);
return IRQ_HANDLED;
}
static void bq5105x_detect_undocked_work(struct work_struct *work)
{
struct bq5105x_detect *chip = container_of(work, struct bq5105x_detect,
undocked_work);
dev_dbg(&chip->pdev->dev, "no pulse\n");
bq5105x_detect_set_docked(chip, false);
if (gpio_get_value_cansleep(chip->dts_data->det_gpio))
dev_dbg(&chip->pdev->dev,
"timer expired, but det_gpio is high\n");
wake_unlock(&chip->pulse_wakelock);
wake_unlock(&chip->undocked_wakelock);
}
static enum alarmtimer_restart
bq5105x_detect_undocked_alarm(struct alarm *alrm, ktime_t now)
{
struct bq5105x_detect *chip = container_of(alrm, struct bq5105x_detect,
undocked_alarm);
wake_lock(&chip->undocked_wakelock);
queue_work(chip->wq, &chip->undocked_work);
return ALARMTIMER_NORESTART;
}
static void
bq5105x_detect_m4_motion_changed(enum m4sensorhub_irqs int_event, void *data)
{
struct bq5105x_detect *chip = (struct bq5105x_detect *)data;
mutex_lock(&chip->motion_mutex);
/* Assume in motion if enabling/disabling IRQs has previously failed */
if (chip->m4shub)
chip->in_motion = (int_event == M4SH_IRQ_MOTION_DETECTED);
else
chip->in_motion = true;
dev_dbg(&chip->pdev->dev, "new motion=%d\n", chip->in_motion);
/* Report undocked if in motion and undocking has been detected */
if (chip->in_motion && !chip->docked)
bq5105x_detect_report(chip, false);
mutex_unlock(&chip->motion_mutex);
}
static int bq5105x_detect_m4_init(struct init_calldata *p_arg)
{
struct bq5105x_detect *chip = (struct bq5105x_detect *) p_arg->p_data;
int ret;
mutex_lock(&chip->motion_mutex);
/*
* Register motion and still mode detection callbacks. If docked,
* determine the current state and enable callbacks to detect future
* state changes.
*/
chip->m4shub = p_arg->p_m4sensorhub_data;
if (!chip->m4shub) {
dev_err(&chip->pdev->dev, "invalid handle to sensor hub\n");
ret = -EINVAL;
goto err_m4_handle;
}
ret = m4sensorhub_irq_register(chip->m4shub, M4SH_IRQ_MOTION_DETECTED,
bq5105x_detect_m4_motion_changed,
(void *)chip, 0);
if (ret < 0) {
dev_err(&chip->pdev->dev,
"failed to register motion detection\n");
goto err_m4_motion;
}
ret = m4sensorhub_irq_register(chip->m4shub, M4SH_IRQ_STILL_DETECTED,
bq5105x_detect_m4_motion_changed,
(void *)chip, 0);
if (ret < 0) {
dev_err(&chip->pdev->dev,
"failed to register still mode detection\n");
goto err_m4_still;
}
dev_dbg(&chip->pdev->dev, "m4 sensor hub is ready\n");
if (chip->docked) {
bq5105x_detect_m4_track_motion(chip, true);
chip->in_motion = bq5105x_detect_m4_motion(chip);
}
mutex_unlock(&chip->motion_mutex);
return 0;
err_m4_still:
m4sensorhub_irq_unregister(chip->m4shub, M4SH_IRQ_MOTION_DETECTED);
err_m4_motion:
chip->m4shub = NULL;
err_m4_handle:
mutex_unlock(&chip->motion_mutex);
return ret;
}
static struct bq5105x_detect_dts *
of_bq5105x_detect(struct platform_device *pdev)
{
struct bq5105x_detect_dts *dts_data;
struct device_node *np = pdev->dev.of_node;
u32 val;
int i, size, count;
const char *string;
if (!np) {
dev_err(&pdev->dev, "devtree data not found\n");
return NULL;
}
dts_data = devm_kzalloc(&pdev->dev, sizeof(*dts_data), GFP_KERNEL);
if (!dts_data) {
dev_err(&pdev->dev, "failed to alloc dts data\n");
return NULL;
}
/* Required properties */
dts_data->chg_gpio = of_get_named_gpio(np, "charge-gpio", 0);
if (!gpio_is_valid(dts_data->chg_gpio)) {
dev_err(&pdev->dev, "no valid charge-gpio property\n");
return NULL;
}
dts_data->det_gpio = of_get_named_gpio(np, "detect-gpio", 0);
if (!gpio_is_valid(dts_data->det_gpio)) {
dev_err(&pdev->dev, "no valid detect-gpio property\n");
return NULL;
}
if (of_property_read_u32(np, "undocked-delay,ms", &val) !=0) {
dev_err(&pdev->dev, "no valid 'undocked-delay,ms' property\n");
return NULL;
}
dts_data->undocked_delay = val;
/* Optional properties */
of_property_read_string(np, "charger-name", &dts_data->chg_name);
of_property_read_string(np, "switch-name", &dts_data->switch_name);
count = of_property_count_strings(np, "supplied_to");
if (count > 0) {
size = count * sizeof(*dts_data->supplied_to);
dts_data->supplied_to = devm_kzalloc(&pdev->dev, size,
GFP_KERNEL);
if (!dts_data->supplied_to) {
dev_err(&pdev->dev, "Failed to alloc supplied_to\n");
return NULL;
}
/* Make copies of the DT strings for const-correctness */
for (i = 0; i < count; i++) {
if (of_property_read_string_index(np, "supplied_to", i,
&string)) {
dev_err(&pdev->dev, "Failed to read supplied_to"
" supplied_to[%d]\n", i);
goto free;
}
dts_data->supplied_to[i] = kstrdup(string, GFP_KERNEL);
if (!dts_data->supplied_to[i]) {
dev_err(&pdev->dev, "Failed to alloc space for"
" supplied_to[%d]\n", i);
goto free;
}
}
dts_data->num_supplicants = count;
}
if (of_property_read_u32(np, "uevent-wakelock-timeout,ms", &val) !=0 ) {
dev_err(&pdev->dev,
"no valid 'uevent-wakelock-timeout,ms' property\n");
goto free;
}
dts_data->uevent_wakelock_timeout = msecs_to_jiffies(val);
dts_data->old_hw = of_property_read_bool(np, "old-hw");
dts_data->use_motion = of_property_read_bool(np, "use-motion");
return dts_data;
free:
for (i = 0; i < count; i++)
kfree(dts_data->supplied_to[i]);
return NULL;
}
#ifdef CONFIG_DEBUG_FS
static int bq5105x_detect_debugfs_create(struct bq5105x_detect *chip)
{
chip->debugfs_root = debugfs_create_dir(dev_name(&chip->pdev->dev),
NULL);
if (!chip->debugfs_root)
return -ENOMEM;
if (!debugfs_create_bool("disable_pulse_wakelock", S_IRUGO | S_IWUSR,
chip->debugfs_root,
&chip->disable_pulse_wakelock))
goto err_debugfs;
return 0;
err_debugfs:
debugfs_remove_recursive(chip->debugfs_root);
chip->debugfs_root = NULL;
return -ENOMEM;
}
#endif
static int bq5105x_detect_probe(struct platform_device *pdev)
{
int i, ret;
struct bq5105x_detect *chip;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip) {
dev_err(&pdev->dev, "failed to alloc driver structure\n");
return -ENOMEM;
}
chip->dts_data = of_bq5105x_detect(pdev);
if (!chip->dts_data) {
dev_err(&pdev->dev, "failed to parse devtree node\n");
return -ENODEV;
}
chip->pdev = pdev;
ret = gpio_request_one(chip->dts_data->chg_gpio,
GPIOF_IN | GPIOF_EXPORT, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to request charge gpio pin: %d\n",
ret);
goto err_chg_gpio;
}
ret = gpio_request_one(chip->dts_data->det_gpio,
GPIOF_IN | GPIOF_EXPORT, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to request detect gpio pin: %d\n",
ret);
goto err_det_gpio;
}
chip->charger.name = chip->dts_data->chg_name ?
chip->dts_data->chg_name : "bq5105x";
chip->charger.type = POWER_SUPPLY_TYPE_WIRELESS;
chip->charger.properties = bq5105x_detect_chg_prop;
chip->charger.num_properties = ARRAY_SIZE(bq5105x_detect_chg_prop);
chip->charger.get_property = bq5105x_detect_get_property;
chip->charger.supplied_to = chip->dts_data->supplied_to;
chip->charger.num_supplicants = chip->dts_data->num_supplicants;
ret = power_supply_register(&pdev->dev, &chip->charger);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register power supply: %d\n",
ret);
goto err_pwr_supply;
}
if (chip->dts_data->switch_name ) {
chip->sdev = devm_kzalloc(&pdev->dev, sizeof(*chip->sdev),
GFP_KERNEL);
if (!chip->sdev) {
dev_err(&pdev->dev, "failed to alloc switch device\n");
ret = -ENOMEM;
goto err_switch;
}
chip->sdev->name = chip->dts_data->switch_name;
ret = switch_dev_register(chip->sdev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register switch device:"
" %d\n", ret);
goto err_switch;
}
}
/* Use ordered workqueue to process irqs and alarms one at a time */
chip->wq = alloc_ordered_workqueue("bq5105x-detect", 0);
if (!chip->wq) {
dev_err(&pdev->dev, "failed to alloc workqueue\n");
ret = -ENOMEM;
goto err_workqueue;
}
INIT_WORK(&chip->chg_irq_work, bq5105x_detect_chg_irq_work);
INIT_WORK(&chip->det_irq_work, bq5105x_detect_det_irq_work);
INIT_WORK(&chip->undocked_work, bq5105x_detect_undocked_work);
alarm_init(&chip->undocked_alarm, ALARM_BOOTTIME,
bq5105x_detect_undocked_alarm);
chip->alarm_time = ns_to_ktime(chip->dts_data->undocked_delay *
NSEC_PER_MSEC);
wake_lock_init(&chip->chg_irq_wakelock, WAKE_LOCK_SUSPEND, "chg-irq");
wake_lock_init(&chip->det_irq_wakelock, WAKE_LOCK_SUSPEND, "det-irq");
wake_lock_init(&chip->undocked_wakelock, WAKE_LOCK_SUSPEND,
"undocked-alarm");
wake_lock_init(&chip->uevent_wakelock, WAKE_LOCK_SUSPEND,
"docked-state-uevent");
wake_lock_init(&chip->pulse_wakelock, WAKE_LOCK_SUSPEND, "det-pulse");
if (chip->dts_data->use_motion) {
/* Initialize mutex before irqs are enabled and register with
* sensor hub to be called when it is setup and running */
mutex_init(&chip->motion_mutex);
ret = m4sensorhub_register_initcall(bq5105x_detect_m4_init,
(void *)chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register with sensor hub\n");
goto err_m4shub;
}
}
/* Do not enable det_gpio interrupt on old h/w. Moreover, if the IRQ got
* processed on old h/w, the detection logic will be broken */
if (!chip->dts_data->old_hw) {
/*
* Request det_irq before chg_irq to set det_irq_enabled flag
* before it can be accessed from chg_irq work function. Use the
* flag to ensure correct nesteness of enabling and disabling
* det_irq.
*/
chip->det_irq = gpio_to_irq(chip->dts_data->det_gpio);
chip->det_irq_enabled = true;
ret = request_any_context_irq(chip->det_irq,
bq5105x_detect_det_irq,
IRQF_TRIGGER_FALLING, dev_name(&pdev->dev), chip);
if (ret < 0) {
dev_err(&pdev->dev,
"failed to request detect gpio irq: %d\n", ret);
goto err_det_irq;
}
}
chip->chg_irq = gpio_to_irq(chip->dts_data->chg_gpio);
ret = request_any_context_irq(chip->chg_irq, bq5105x_detect_chg_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(&pdev->dev), chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request charge gpio irq: %d\n",
ret);
goto err_chg_irq;
}
#ifdef CONFIG_DEBUG_FS
if (bq5105x_detect_debugfs_create(chip))
dev_warn(&pdev->dev, "cannot create debugfs\n");
#endif
platform_set_drvdata(pdev, chip);
/* Set initial state */
queue_work(chip->wq, &chip->chg_irq_work);
return 0;
err_chg_irq:
if (!chip->dts_data->old_hw)
free_irq(chip->det_irq, chip);
err_det_irq:
if (chip->dts_data->use_motion)
m4sensorhub_unregister_initcall(bq5105x_detect_m4_init);
err_m4shub:
alarm_cancel(&chip->undocked_alarm);
destroy_workqueue(chip->wq);
/* Destroy mutex after workqueue since work functions use the mutex */
if (chip->dts_data->use_motion)
mutex_destroy(&chip->motion_mutex);
wake_lock_destroy(&chip->chg_irq_wakelock);
wake_lock_destroy(&chip->det_irq_wakelock);
wake_lock_destroy(&chip->undocked_wakelock);
wake_lock_destroy(&chip->uevent_wakelock);
wake_lock_destroy(&chip->pulse_wakelock);
err_workqueue:
switch_dev_unregister(chip->sdev);
err_switch:
power_supply_unregister(&chip->charger);
err_pwr_supply:
gpio_free(chip->dts_data->det_gpio);
err_det_gpio:
gpio_free(chip->dts_data->chg_gpio);
err_chg_gpio:
for (i = 0; i < chip->dts_data->num_supplicants; i++)
kfree(chip->dts_data->supplied_to[i]);
return ret;
}
static int bq5105x_detect_remove(struct platform_device *pdev)
{
struct bq5105x_detect* chip = platform_get_drvdata(pdev);
int i;
#ifdef CONFIG_DEBUG_FS
debugfs_remove_recursive(chip->debugfs_root);
#endif
free_irq(chip->chg_irq, chip);
free_irq(chip->det_irq, chip);
alarm_cancel(&chip->undocked_alarm);
destroy_workqueue(chip->wq);
wake_lock_destroy(&chip->chg_irq_wakelock);
wake_lock_destroy(&chip->det_irq_wakelock);
wake_lock_destroy(&chip->undocked_wakelock);
wake_lock_destroy(&chip->uevent_wakelock);
wake_lock_destroy(&chip->pulse_wakelock);
switch_dev_unregister(chip->sdev);
power_supply_unregister(&chip->charger);
gpio_free(chip->dts_data->det_gpio);
gpio_free(chip->dts_data->chg_gpio);
for (i = 0; i < chip->dts_data->num_supplicants; i++)
kfree(chip->dts_data->supplied_to[i]);
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct of_device_id bq5105x_detect_of_match[] = {
{ .compatible = "mmi,bq5105x-detect", },
{ }
};
MODULE_DEVICE_TABLE(of, bq5105x_detect_of_match);
static struct platform_driver bq5105x_detect_driver = {
.probe = bq5105x_detect_probe,
.remove = bq5105x_detect_remove,
.driver = {
.name = "bq5105x-detect",
.owner = THIS_MODULE,
.of_match_table = bq5105x_detect_of_match,
},
};
module_platform_driver(bq5105x_detect_driver);
MODULE_ALIAS("platform:bq5105x_detect");
MODULE_AUTHOR("Motorola Mobility LLC");
MODULE_DESCRIPTION("BQ5105x Detection Driver");
MODULE_LICENSE("GPL");