diff options
author | Lily Zhang <r58066@freescale.com> | 2010-09-25 09:37:00 +0800 |
---|---|---|
committer | Alan Tull <r80115@freescale.com> | 2010-09-25 09:53:59 -0500 |
commit | 1a0d1a8bc4b1e39ab25488ed0e28f0e39eb71982 (patch) | |
tree | c637fb3e321bfe6f4e0e529e8cd8d7c0394ca7b7 /drivers | |
parent | 27d19e5176a0b235a610d9de564f424d484c4f9b (diff) |
ENGR00131906-1 split w1_ds2438 driver as two drivers
The kernel commit "5f487cd34" changes the function
power_supply_register. When calling power_supply_register
into ds2438_add_slave, it causes slave->master->mutex
dead lock. This patch splits w1_ds2438 driver as
w1_ds2438 (w1 slave) driver and ds2438_battery driver
to avoid dead lock in driver initialization.
Signed-off-by: Lily Zhang <r58066@freescale.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/power/Kconfig | 6 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/ds2438_battery.c | 567 | ||||
-rw-r--r-- | drivers/w1/slaves/Kconfig | 1 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2438.c | 537 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2438.h | 6 |
6 files changed, 659 insertions, 459 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index e0638956fe0b..d9426933f63a 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -163,4 +163,10 @@ config MXS_VBUS_CURRENT_DRAW Say Y to enable 100mA limitation when USB vbus power on system before enumeration to match USB2.0 requirement. +config BATTERY_DS2438 + tristate "DS2438 battery driver" + select W1 + select W1_SLAVE_DS2438 + help + Say Y here to enable support for batteries with ds2438 chip. endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 66a925fd9807..a02b76f11909 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -35,3 +35,4 @@ obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_BATTERY_Z2) += z2_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_MXS) += mxs/ +obj-$(CONFIG_BATTERY_DS2438) += ds2438_battery.o diff --git a/drivers/power/ds2438_battery.c b/drivers/power/ds2438_battery.c new file mode 100644 index 000000000000..03fa9b29cd1f --- /dev/null +++ b/drivers/power/ds2438_battery.c @@ -0,0 +1,567 @@ +/* +* Copyright (C) 2010 Freescale Semiconductor, Inc. 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 as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. + +* 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., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/idr.h> +#include <linux/power_supply.h> + +#include "../w1/w1.h" +#include "../w1/slaves/w1_ds2438.h" + +struct ds2438_device_info { + struct device *dev; + /* DS2438 data, valid after calling ds2438_battery_read_status() */ + unsigned long update_time; /* jiffies when data read */ + char raw[DS2438_PAGE_SIZE]; /* raw DS2438 data */ + int voltage_uV; + int current_uA; + int accum_current_uAh; + int temp_C; + int charge_status; + u8 init:1; + u8 setup:1; + u8 calibrate:1; + u8 input_src:1; + u8 ee_flg:1; + u8 resv_bit:3; + u8 threshold:8; + u16 resv_bytes; + u32 senser; + + struct power_supply bat; + struct device *w1_dev; + struct ds2438_ops ops; + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; +}; + +#define DS2438_SENSER 25 +#define to_ds2438_device_info(x) container_of((x), struct ds2438_device_info, \ + bat); + + +static enum power_supply_property ds2438_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_NOW, +}; + +static char ds2438_sensers_title[] = "DS2438 senserin thousands of resister:"; +static unsigned int cache_time = 1000; +module_param(cache_time, uint, 0644); +MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); + +static ssize_t ds2438_show_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + return sprintf(buf, "%s\n", di->input_src ? "1:VDD" : "0:VAD"); +} + +static ssize_t ds2438_show_senser(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int len; + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + len = sprintf(buf, "%s\n", ds2438_sensers_title); + len += sprintf(buf + len, "%d\n", di->senser); + return len; +} + +static ssize_t ds2438_show_ee(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + return sprintf(buf, "%d\n", di->ee_flg); +} + +static ssize_t ds2438_show_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + return sprintf(buf, "%d\n", di->threshold); +} + +static ssize_t ds2438_set_input(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + di->input_src = !!simple_strtoul(buf, NULL, 0); + return count; +} + +static ssize_t ds2438_set_senser(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u32 resister; + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + resister = simple_strtoul(buf, NULL, 0); + if (resister) + di->senser = resister; + return count; +} + +static ssize_t ds2438_set_ee(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + di->ee_flg = !!simple_strtoul(buf, NULL, 0); + di->setup = 1; + return count; +} + +static ssize_t ds2438_set_threshold(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int threshold; + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + threshold = simple_strtoul(buf, NULL, 0); + if (threshold < 256) { + di->threshold = threshold; + di->setup = 1; + return count; + } + return -EINVAL; +} + +static ssize_t ds2438_set_calibrate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + di->calibrate = !!simple_strtoul(buf, NULL, 0); + return count; +} + +static struct device_attribute ds2438_dev_attr[] = { + __ATTR(input_src, 0664, ds2438_show_input, ds2438_set_input), + __ATTR(senser, 0664, ds2438_show_senser, ds2438_set_senser), + __ATTR(ee_flg, 0664, ds2438_show_ee, ds2438_set_ee), + __ATTR(threshold, 0664, ds2438_show_threshold, ds2438_set_threshold), + __ATTR(calibrate, 0220, NULL, ds2438_set_calibrate), +}; + +static void ds2438_setup(struct ds2438_device_info *di) +{ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + if (di->init && di->setup) { + if (di->ee_flg) + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_EE; + else + di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_EE; + if (di->input_src) + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_AD; + else + di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_AD; + di->raw[PAGE0_THRESHOLD] = di->threshold; + } else { + di->ee_flg = !!(di->raw[PAGE0_STAT_CTRL] & DS2438_CTRL_EE); + di->input_src = !!(di->raw[PAGE0_STAT_CTRL] & DS2438_CTRL_AD); + di->threshold = di->raw[PAGE0_THRESHOLD]; + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD | DS2438_CTRL_CA; + } + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + if (!di->init) { + di->calibrate = 1; + di->init = 1; + } + di->setup = 0; +} + +static void ds2438_calibrate(struct ds2438_device_info *di) +{ + int current_raw; + /* disable ICA */ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_IAD; + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + + /* Zero offset */ + di->ops.load_sram(di->w1_dev, PAGE1_ETM); + di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); + ds2438_writew(di->raw + PAGE1_OFFSET_LSB, 0); + di->ops.drain_sram(di->w1_dev, PAGE1_ETM_BYTE0); + + /* enable ICA & read current */ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD; + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + /*wait current convert about 36HZ */ + mdelay(30); + /* disable ICA */ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_IAD; + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + /* read current value */ + current_raw = ds2438_readw(di->raw + PAGE0_CURRENT_LSB); + /* write offset by current value */ + di->ops.load_sram(di->w1_dev, PAGE1_ETM); + di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); + ds2438_writew(di->raw + PAGE1_OFFSET_LSB, current_raw << 8); + di->ops.write_page(di->w1_dev, PAGE1_ETM, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE1_ETM); + + /*enable ICA again */ + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD; + di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); + di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); + di->calibrate = 0; +} + +/* + * power supply temperture is in tenths of degree. + */ +static inline int ds2438_get_temp(u16 raw) +{ + int degree, s; + s = !!(raw & 0x8000); + + if (s) + raw = ((~raw & 0x7FFF) + 1); + degree = ((raw >> 8) * 10) + (((raw & 0xFF) * 5) + 63) / 128; + return s ? -degree : degree; +} + +/* + * power supply current is in uA. + */ +static inline int ds2438_get_current(u32 senser, u16 raw) +{ + int s, current_uA; + s = !!(raw & 0xFC00); + /* (x * 1000 * 1000)uA / (4096 * (Rsens / 1000)) */ + raw &= 0x3FF; + current_uA = raw * 125 * 125 * 125; + current_uA /= (senser << 3); + return s ? -current_uA : current_uA; +} + +/* + * power supply current is in uAh. + */ +static inline int ds2438_get_ica(u32 senser, u8 raw) +{ + int charge_uAh; + /* (x * 1000 * 1000)uA / (2048 * (Rsens / 1000)) */ + charge_uAh = (raw * 125 * 125 * 125) >> 4; + charge_uAh /= (senser << 4); + return charge_uAh; +} + +static int ds2438_battery_update_page1(struct ds2438_device_info *di) +{ + int ica_raw; + di->ops.load_sram(di->w1_dev, PAGE1_ETM); + di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); + ica_raw = di->raw[PAGE1_ICA]; + di->accum_current_uAh = ds2438_get_ica(di->senser, ica_raw); + return 0; +} + +static int ds2438_battery_read_status(struct ds2438_device_info *di) +{ + u8 status; + int temp_raw, voltage_raw, current_raw; + + if (!(di->init) || di->setup) + ds2438_setup(di); + + if (di->calibrate) + ds2438_calibrate(di); + + if (di->update_time && time_before(jiffies, di->update_time + + msecs_to_jiffies(cache_time))) + return 0; + + di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); + di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); + status = di->raw[PAGE0_STAT_CTRL]; + temp_raw = ds2438_readw(di->raw + PAGE0_TEMP_LSB); + voltage_raw = ds2438_readw(di->raw + PAGE0_VOLTAGE_LSB); + current_raw = ds2438_readw(di->raw + PAGE0_CURRENT_LSB); + di->temp_C = ds2438_get_temp(temp_raw); + di->voltage_uV = voltage_raw * 10000; + di->current_uA = ds2438_get_current(di->senser, current_raw); + + ds2438_battery_update_page1(di); + + if (!(status & DS2438_STAT_TB)) + di->ops.command(di->w1_dev, DS2438_CONVERT_TEMP, 0); + if (!(status & DS2438_STAT_ADB)) + di->ops.command(di->w1_dev, DS2438_CONVERT_VOLT, 0); + di->update_time = jiffies; + return 0; +} + +static void ds2438_battery_update_status(struct ds2438_device_info *di) +{ + int old_charge_status = di->charge_status; + + ds2438_battery_read_status(di); + + if (di->charge_status != old_charge_status) + power_supply_changed(&di->bat); +} + +static void ds2438_battery_work(struct work_struct *work) +{ + struct ds2438_device_info *di = container_of(work, + struct ds2438_device_info, + monitor_work.work); + const int interval = HZ * 60; + + dev_dbg(di->w1_dev, "%s\n", __func__); + + ds2438_battery_update_status(di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); +} + +static void ds2438_battery_external_power_changed(struct power_supply *psy) +{ + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + dev_dbg(di->w1_dev, "%s\n", __func__); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ / 10); +} + +static int ds2438_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ds2438_device_info *di = to_ds2438_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + return 0; + default: + break; + } + + ds2438_battery_read_status(di); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temp_C; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = di->accum_current_uAh; + break; + default: + return -EINVAL; + } + + return 0; +} + +static inline void ds2438_defaut_ops(struct ds2438_ops *ops) +{ + ops->read_page = w1_ds2438_read_page; + ops->write_page = w1_ds2438_write_page; + ops->drain_sram = w1_ds2438_drain_sram; + ops->load_sram = w1_ds2438_load_sram; + ops->command = w1_ds2438_command; +} + + +static int ds2438_battery_probe(struct platform_device *pdev) +{ + int i, retval = 0; + struct ds2438_device_info *di; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + di->dev = &pdev->dev; + di->w1_dev = pdev->dev.parent; + di->bat.name = dev_name(&pdev->dev); + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = ds2438_battery_props; + di->bat.num_properties = ARRAY_SIZE(ds2438_battery_props); + di->bat.get_property = ds2438_battery_get_property; + di->bat.external_power_changed = ds2438_battery_external_power_changed; + ds2438_defaut_ops(&di->ops); + di->senser = DS2438_SENSER; + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + retval = power_supply_register(&pdev->dev, &di->bat); + if (retval) { + dev_err(&pdev->dev, "failed to register battery\n"); + goto batt_failed; + } + + for (i = 0; i < ARRAY_SIZE(ds2438_dev_attr); i++) { + if (device_create_file(di->bat.dev, ds2438_dev_attr + i)) { + printk(KERN_ERR "Customize attribute file fail!\n"); + break; + } + } + + if (i != ARRAY_SIZE(ds2438_dev_attr)) { + for (; i >= 0; i++) + device_remove_file(di->bat.dev, ds2438_dev_attr + i); + goto workqueue_failed; + } + INIT_DELAYED_WORK(&di->monitor_work, ds2438_battery_work); + di->monitor_wqueue = create_singlethread_workqueue( + dev_name(&pdev->dev)); + if (!di->monitor_wqueue) { + retval = -ESRCH; + goto workqueue_failed; + } + + platform_set_drvdata(pdev, di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ / 2); + + goto success; + +workqueue_failed: + power_supply_unregister(&di->bat); +batt_failed: + kfree(di); +di_alloc_failed: +success: + return retval; +} + +static int ds2438_battery_remove(struct platform_device *pdev) +{ + struct ds2438_device_info *di = platform_get_drvdata(pdev); + + cancel_rearming_delayed_workqueue(di->monitor_wqueue, + &di->monitor_work); + destroy_workqueue(di->monitor_wqueue); + power_supply_unregister(&di->bat); + return 0; +} + +#ifdef CONFIG_PM +static int ds2438_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ds2438_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + return 0; +} + +static int ds2438_battery_resume(struct platform_device *pdev) +{ + struct ds2438_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + power_supply_changed(&di->bat); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ); + + return 0; +} + +#else + +#define ds2438_battery_suspend NULL +#define ds2438_battery_resume NULL + +#endif /* CONFIG_PM */ +static struct platform_driver ds2438_battery_driver = { + .driver = { + .name = DS2438_DEV_NAME, + }, + .probe = ds2438_battery_probe, + .remove = ds2438_battery_remove, + .suspend = ds2438_battery_suspend, + .resume = ds2438_battery_resume, +}; + +static int __init ds2438_battery_init(void) +{ + pr_info("ds2438 battery driver\n"); + return platform_driver_register(&ds2438_battery_driver); +} + +static void __exit ds2438_battery_exit(void) +{ + platform_driver_unregister(&ds2438_battery_driver); +} + +module_init(ds2438_battery_init); +module_exit(ds2438_battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductors Inc"); +MODULE_DESCRIPTION("DS2438 battery driver."); diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index ad77126801f5..58f3e64e600a 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -46,6 +46,7 @@ config W1_SLAVE_DS2433 config W1_SLAVE_DS2438 tristate "Smart Battery Monitor (DS2438)" + depends on W1 help Say Y here if you want to use a 1-wire Smart Battery Monitor family device (DS2438). diff --git a/drivers/w1/slaves/w1_ds2438.c b/drivers/w1/slaves/w1_ds2438.c index 251ca0e7f914..4212117d9424 100644 --- a/drivers/w1/slaves/w1_ds2438.c +++ b/drivers/w1/slaves/w1_ds2438.c @@ -14,411 +14,20 @@ #include <linux/kernel.h> #include <linux/types.h> #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/device.h> -#include <linux/jiffies.h> -#include <linux/workqueue.h> -#include <linux/pm.h> #include <linux/platform_device.h> -#include <linux/device.h> #include <linux/mutex.h> #include <linux/slab.h> -#include <linux/delay.h> #include <linux/err.h> #include <linux/idr.h> -#include <linux/power_supply.h> #include "../w1.h" #include "../w1_int.h" #include "../w1_family.h" #include "w1_ds2438.h" -struct ds2438_device_info { - /* DS2438 data, valid after calling ds2438_battery_read_status() */ - unsigned long update_time; /* jiffies when data read */ - char raw[DS2438_PAGE_SIZE]; /* raw DS2438 data */ - int voltage_uV; - int current_uA; - int accum_current_uAh; - int temp_C; - int charge_status; - u8 init:1; - u8 setup:1; - u8 calibrate:1; - u8 input_src:1; - u8 ee_flg:1; - u8 resv_bit:3; - u8 threshold:8; - u16 resv_bytes; - u32 senser; - - struct power_supply bat; - struct device *w1_dev; - struct ds2438_ops ops; - struct workqueue_struct *monitor_wqueue; - struct delayed_work monitor_work; -}; - -#define DS2438_SENSER 25 -#define to_ds2438_device_info(x) container_of((x), struct ds2438_device_info, \ - bat); - - -static enum power_supply_property ds2438_battery_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_CHARGE_NOW, -}; - -static char ds2438_sensers_title[] = "DS2438 senserin thousands of resister:"; -static unsigned int cache_time = 1000; -module_param(cache_time, uint, 0644); -MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); - -static ssize_t ds2438_show_input(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - return sprintf(buf, "%s\n", di->input_src ? "1:VDD" : "0:VAD"); -} - -static ssize_t ds2438_show_senser(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int len; - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - len = sprintf(buf, "%s\n", ds2438_sensers_title); - len += sprintf(buf + len, "%d\n", di->senser); - return len; -} - -static ssize_t ds2438_show_ee(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - return sprintf(buf, "%d\n", di->ee_flg); -} - -static ssize_t ds2438_show_threshold(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - return sprintf(buf, "%d\n", di->threshold); -} - -static ssize_t ds2438_set_input(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t count) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - di->input_src = !!simple_strtoul(buf, NULL, 0); - return count; -} - -static ssize_t ds2438_set_senser(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t count) -{ - u32 resister; - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - resister = simple_strtoul(buf, NULL, 0); - if (resister) - di->senser = resister; - return count; -} - -static ssize_t ds2438_set_ee(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - di->ee_flg = !!simple_strtoul(buf, NULL, 0); - di->setup = 1; - return count; -} - -static ssize_t ds2438_set_threshold(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int threshold; - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - threshold = simple_strtoul(buf, NULL, 0); - if (threshold < 256) { - di->threshold = threshold; - di->setup = 1; - return count; - } - return -EINVAL; -} - -static ssize_t ds2438_set_calibrate(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct power_supply *psy = dev_get_drvdata(dev); - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - di->calibrate = !!simple_strtoul(buf, NULL, 0); - return count; -} - -static struct device_attribute ds2438_dev_attr[] = { - __ATTR(input_src, 0664, ds2438_show_input, ds2438_set_input), - __ATTR(senser, 0664, ds2438_show_senser, ds2438_set_senser), - __ATTR(ee_flg, 0664, ds2438_show_ee, ds2438_set_ee), - __ATTR(threshold, 0664, ds2438_show_threshold, ds2438_set_threshold), - __ATTR(calibrate, 0220, NULL, ds2438_set_calibrate), -}; - -static void ds2438_setup(struct ds2438_device_info *di) -{ - di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); - di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); - if (di->init && di->setup) { - if (di->ee_flg) - di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_EE; - else - di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_EE; - if (di->input_src) - di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_AD; - else - di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_AD; - di->raw[PAGE0_THRESHOLD] = di->threshold; - } else { - di->ee_flg = !!(di->raw[PAGE0_STAT_CTRL] & DS2438_CTRL_EE); - di->input_src = !!(di->raw[PAGE0_STAT_CTRL] & DS2438_CTRL_AD); - di->threshold = di->raw[PAGE0_THRESHOLD]; - di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD | DS2438_CTRL_CA; - } - di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); - if (!di->init) { - di->calibrate = 1; - di->init = 1; - } - di->setup = 0; -} - -static void ds2438_calibrate(struct ds2438_device_info *di) -{ - int current_raw; - /* disable ICA */ - di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); - di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_IAD; - di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); - - /* Zero offset */ - di->ops.load_sram(di->w1_dev, PAGE1_ETM); - di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); - ds2438_writew(di->raw + PAGE1_OFFSET_LSB, 0); - di->ops.drain_sram(di->w1_dev, PAGE1_ETM_BYTE0); - - /* enable ICA & read current */ - di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); - di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD; - di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); - /*wait current convert about 36HZ */ - mdelay(30); - /* disable ICA */ - di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); - di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->raw[PAGE0_STAT_CTRL] &= ~DS2438_CTRL_IAD; - di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); - /* read current value */ - current_raw = ds2438_readw(di->raw + PAGE0_CURRENT_LSB); - /* write offset by current value */ - di->ops.load_sram(di->w1_dev, PAGE1_ETM); - di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); - ds2438_writew(di->raw + PAGE1_OFFSET_LSB, current_raw << 8); - di->ops.write_page(di->w1_dev, PAGE1_ETM, di->raw); - di->ops.drain_sram(di->w1_dev, PAGE1_ETM); - - /*enable ICA again */ - di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); - di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->raw[PAGE0_STAT_CTRL] |= DS2438_CTRL_IAD; - di->ops.write_page(di->w1_dev, PAGE0_CONTROL, di->raw); - di->ops.drain_sram(di->w1_dev, PAGE0_CONTROL); - di->calibrate = 0; -} - -/* - * power supply temperture is in tenths of degree. - */ -static inline int ds2438_get_temp(u16 raw) -{ - int degree, s; - s = !!(raw & 0x8000); - - if (s) - raw = ((~raw & 0x7FFF) + 1); - degree = ((raw >> 8) * 10) + (((raw & 0xFF) * 5) + 63) / 128; - return s ? -degree : degree; -} - -/* - * power supply current is in uA. - */ -static inline int ds2438_get_current(u32 senser, u16 raw) -{ - int s, current_uA; - s = !!(raw & 0xFC00); - /* (x * 1000 * 1000)uA / (4096 * (Rsens / 1000)) */ - raw &= 0x3FF; - current_uA = raw * 125 * 125 * 125; - current_uA /= (senser << 3); - return s ? -current_uA : current_uA; -} - -/* - * power supply current is in uAh. - */ -static inline int ds2438_get_ica(u32 senser, u8 raw) -{ - int charge_uAh; - /* (x * 1000 * 1000)uA / (2048 * (Rsens / 1000)) */ - charge_uAh = (raw * 125 * 125 * 125) >> 4; - charge_uAh /= (senser << 4); - return charge_uAh; -} - -static int ds2438_battery_update_page1(struct ds2438_device_info *di) -{ - int ica_raw; - di->ops.load_sram(di->w1_dev, PAGE1_ETM); - di->ops.read_page(di->w1_dev, PAGE1_ETM, di->raw); - ica_raw = di->raw[PAGE1_ICA]; - di->accum_current_uAh = ds2438_get_ica(di->senser, ica_raw); - return 0; -} - -static int ds2438_battery_read_status(struct ds2438_device_info *di) -{ - u8 status; - int temp_raw, voltage_raw, current_raw; - - if (!(di->init) || di->setup) - ds2438_setup(di); - - if (di->calibrate) - ds2438_calibrate(di); - - if (di->update_time && time_before(jiffies, di->update_time + - msecs_to_jiffies(cache_time))) - return 0; - - di->ops.load_sram(di->w1_dev, PAGE0_CONTROL); - di->ops.read_page(di->w1_dev, PAGE0_CONTROL, di->raw); - status = di->raw[PAGE0_STAT_CTRL]; - temp_raw = ds2438_readw(di->raw + PAGE0_TEMP_LSB); - voltage_raw = ds2438_readw(di->raw + PAGE0_VOLTAGE_LSB); - current_raw = ds2438_readw(di->raw + PAGE0_CURRENT_LSB); - di->temp_C = ds2438_get_temp(temp_raw); - di->voltage_uV = voltage_raw * 10000; - di->current_uA = ds2438_get_current(di->senser, current_raw); - - ds2438_battery_update_page1(di); - - if (!(status & DS2438_STAT_TB)) - di->ops.command(di->w1_dev, DS2438_CONVERT_TEMP, 0); - if (!(status & DS2438_STAT_ADB)) - di->ops.command(di->w1_dev, DS2438_CONVERT_VOLT, 0); - di->update_time = jiffies; - return 0; -} - -static void ds2438_battery_update_status(struct ds2438_device_info *di) -{ - int old_charge_status = di->charge_status; - - ds2438_battery_read_status(di); - - if (di->charge_status != old_charge_status) - power_supply_changed(&di->bat); -} - -static void ds2438_battery_work(struct work_struct *work) -{ - struct ds2438_device_info *di = container_of(work, - struct ds2438_device_info, - monitor_work.work); - const int interval = HZ * 60; - - dev_dbg(di->w1_dev, "%s\n", __func__); - - ds2438_battery_update_status(di); - queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); -} - -static void ds2438_battery_external_power_changed(struct power_supply *psy) -{ - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - dev_dbg(di->w1_dev, "%s\n", __func__); - - cancel_delayed_work(&di->monitor_work); - queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ / 10); -} - -static int ds2438_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct ds2438_device_info *di = to_ds2438_device_info(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = di->charge_status; - return 0; - default: - break; - } - - ds2438_battery_read_status(di); - - switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = di->voltage_uV; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = di->current_uA; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = di->temp_C; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = di->accum_current_uAh; - break; - default: - return -EINVAL; - } - - return 0; -} - /* W1 slave DS2438 famliy operations */ -static int ds2438_read_page(struct device *dev, u8 page, u8 *buf) +int w1_ds2438_read_page(struct device *dev, u8 page, u8 *buf) { struct w1_slave *slave = container_of(dev, struct w1_slave, dev); if ((page >= DS2438_PAGE_NUM) || (buf == NULL)) @@ -433,8 +42,9 @@ static int ds2438_read_page(struct device *dev, u8 page, u8 *buf) mutex_unlock(&slave->master->mutex); return 0; } +EXPORT_SYMBOL(w1_ds2438_read_page); -static int ds2438_write_page(struct device *dev, u8 page, u8 *buf) +int w1_ds2438_write_page(struct device *dev, u8 page, u8 *buf) { struct w1_slave *slave = container_of(dev, struct w1_slave, dev); if ((page >= DS2438_PAGE_NUM) || (buf == NULL)) @@ -449,8 +59,9 @@ static int ds2438_write_page(struct device *dev, u8 page, u8 *buf) mutex_unlock(&slave->master->mutex); return 0; } +EXPORT_SYMBOL(w1_ds2438_write_page); -static int ds2438_command(struct device *dev, u8 command, u8 data) +int w1_ds2438_command(struct device *dev, u8 command, u8 data) { struct w1_slave *slave = container_of(dev, struct w1_slave, dev); @@ -466,94 +77,100 @@ static int ds2438_command(struct device *dev, u8 command, u8 data) mutex_unlock(&slave->master->mutex); return 0; } +EXPORT_SYMBOL(w1_ds2438_command); -static int ds2438_drain_sram(struct device *dev, u8 page) +int w1_ds2438_drain_sram(struct device *dev, u8 page) { - return ds2438_command(dev, DS2438_COPY_SCRATCHPAD, page); + return w1_ds2438_command(dev, DS2438_COPY_SCRATCHPAD, page); } +EXPORT_SYMBOL(w1_ds2438_drain_sram); -static int ds2438_load_sram(struct device *dev, u8 page) +int w1_ds2438_load_sram(struct device *dev, u8 page) { - return ds2438_command(dev, DS2438_RECALL_MEMORY, page); + return w1_ds2438_command(dev, DS2438_RECALL_MEMORY, page); } +EXPORT_SYMBOL(w1_ds2438_load_sram); -static inline void ds2438_defaut_ops(struct ds2438_ops *ops) -{ - ops->read_page = ds2438_read_page; - ops->write_page = ds2438_write_page; - ops->drain_sram = ds2438_drain_sram; - ops->load_sram = ds2438_load_sram; - ops->command = ds2438_command; -} +static DEFINE_IDR(bat_idr); +static DEFINE_MUTEX(bat_idr_lock); -static int ds2438_add_slave(struct w1_slave *slave) +static int new_bat_id(void) { - int i, retval = 0; - struct ds2438_device_info *di; + int ret; - di = kzalloc(sizeof(*di), GFP_KERNEL); - if (!di) { - retval = -ENOMEM; - goto di_alloc_failed; - } + while (1) { + int id; - di->w1_dev = &slave->dev; - di->bat.name = dev_name(&slave->dev); - di->bat.type = POWER_SUPPLY_TYPE_BATTERY; - di->bat.properties = ds2438_battery_props; - di->bat.num_properties = ARRAY_SIZE(ds2438_battery_props); - di->bat.get_property = ds2438_battery_get_property; - di->bat.external_power_changed = ds2438_battery_external_power_changed; - ds2438_defaut_ops(&di->ops); - di->senser = DS2438_SENSER; - di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; - - retval = power_supply_register(&slave->dev, &di->bat); - if (retval) { - dev_err(&slave->dev, "failed to register battery\n"); - goto batt_failed; - } + ret = idr_pre_get(&bat_idr, GFP_KERNEL); + if (ret == 0) + return -ENOMEM; - for (i = 0; i < ARRAY_SIZE(ds2438_dev_attr); i++) { - if (device_create_file(di->bat.dev, ds2438_dev_attr + i)) { - printk(KERN_ERR "Customize attribute file fail!\n"); + mutex_lock(&bat_idr_lock); + ret = idr_get_new(&bat_idr, NULL, &id); + mutex_unlock(&bat_idr_lock); + + if (ret == 0) { + ret = id & MAX_ID_MASK; + break; + } else if (ret == -EAGAIN) { + continue; + } else { break; } } - if (i != ARRAY_SIZE(ds2438_dev_attr)) { - for (; i >= 0; i++) - device_remove_file(di->bat.dev, ds2438_dev_attr + i); - goto workqueue_failed; + return ret; +} + +static void release_bat_id(int id) +{ + mutex_lock(&bat_idr_lock); + idr_remove(&bat_idr, id); + mutex_unlock(&bat_idr_lock); +} + +static int ds2438_add_slave(struct w1_slave *slave) +{ + int ret; + int id; + struct platform_device *pdev; + + id = new_bat_id(); + if (id < 0) { + ret = id; + goto noid; } - INIT_DELAYED_WORK(&di->monitor_work, ds2438_battery_work); - di->monitor_wqueue = create_singlethread_workqueue(dev_name(&slave->dev)); - if (!di->monitor_wqueue) { - retval = -ESRCH; - goto workqueue_failed; + + pdev = platform_device_alloc(DS2438_DEV_NAME, id); + if (!pdev) { + ret = -ENOMEM; + goto pdev_alloc_failed; } - dev_set_drvdata(&slave->dev, di); - queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ / 2); - - goto success; - - workqueue_failed: - power_supply_unregister(&di->bat); - batt_failed: - kfree(di); - di_alloc_failed: - success: - return retval; + pdev->dev.parent = &slave->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + dev_set_drvdata(&slave->dev, pdev); + goto success; + +pdev_add_failed: + platform_device_unregister(pdev); +pdev_alloc_failed: + release_bat_id(id); +noid: +success: + return ret; } static void ds2438_remove_slave(struct w1_slave *slave) { - struct ds2438_device_info *di = dev_get_drvdata(&slave->dev); + struct platform_device *pdev = dev_get_drvdata(&slave->dev); + int id = pdev->id; - cancel_rearming_delayed_workqueue(di->monitor_wqueue, - &di->monitor_work); - destroy_workqueue(di->monitor_wqueue); - power_supply_unregister(&di->bat); + platform_device_unregister(pdev); + release_bat_id(id); } static struct w1_family_ops w1_ds2438_fops = { @@ -569,12 +186,14 @@ static struct w1_family w1_family_ds2438 = { static int __init w1_ds2438_init(void) { pr_info("1-wire driver for the DS2438 smart battery monitor\n"); + idr_init(&bat_idr); return w1_register_family(&w1_family_ds2438); } static void __exit w1_ds2438_fini(void) { w1_unregister_family(&w1_family_ds2438); + idr_destroy(&bat_idr); } module_init(w1_ds2438_init); diff --git a/drivers/w1/slaves/w1_ds2438.h b/drivers/w1/slaves/w1_ds2438.h index 40d48f25cc10..b9fb73cafea4 100644 --- a/drivers/w1/slaves/w1_ds2438.h +++ b/drivers/w1/slaves/w1_ds2438.h @@ -116,4 +116,10 @@ static inline void ds2438_writew(u8 *raw, u16 data) *raw++ = data & 0xFF; *raw = (data >> 8) & 0xFF; } + +extern int w1_ds2438_read_page(struct device *dev, u8 page, u8 *buf); +extern int w1_ds2438_write_page(struct device *dev, u8 page, u8 *buf); +extern int w1_ds2438_command(struct device *dev, u8 command, u8 data); +extern int w1_ds2438_drain_sram(struct device *dev, u8 page); +extern int w1_ds2438_load_sram(struct device *dev, u8 page); #endif /* __W1_DS2438_H__ */ |