diff options
Diffstat (limited to 'arch/arm/mach-exynos/sec_thermistor.c')
-rw-r--r-- | arch/arm/mach-exynos/sec_thermistor.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/sec_thermistor.c b/arch/arm/mach-exynos/sec_thermistor.c new file mode 100644 index 0000000..2ae7ef3 --- /dev/null +++ b/arch/arm/mach-exynos/sec_thermistor.c @@ -0,0 +1,285 @@ +/* sec_thermistor.c + * + * Copyright (C) 2011 Samsung Electronics + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <plat/adc.h> +#include <mach/sec_thermistor.h> + +#define ADC_SAMPLING_CNT 7 + +struct sec_therm_info { + struct device *dev; + struct sec_therm_platform_data *pdata; + struct s3c_adc_client *padc; + struct delayed_work polling_work; + + int curr_temperature; + int curr_temp_adc; +}; + +static ssize_t sec_therm_show_temperature(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_therm_info *info = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", info->curr_temperature); +} + +static ssize_t sec_therm_show_temp_adc(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_therm_info *info = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", info->curr_temp_adc); +} + +static DEVICE_ATTR(temperature, S_IRUGO, sec_therm_show_temperature, NULL); +static DEVICE_ATTR(temp_adc, S_IRUGO, sec_therm_show_temp_adc, NULL); + +static struct attribute *sec_therm_attributes[] = { + &dev_attr_temperature.attr, + &dev_attr_temp_adc.attr, + NULL +}; + +static const struct attribute_group sec_therm_group = { + .attrs = sec_therm_attributes, +}; + +static int sec_therm_get_adc_data(struct sec_therm_info *info) +{ + int adc_ch; + int adc_data; + int adc_max = 0; + int adc_min = 0; + int adc_total = 0; + int i; + int err_value; + + adc_ch = info->pdata->adc_channel; + + for (i = 0; i < ADC_SAMPLING_CNT; i++) { + adc_data = s3c_adc_read(info->padc, adc_ch); + + if (adc_data < 0) { + dev_err(info->dev, "%s : err(%d) returned, skip read\n", + __func__, adc_data); + err_value = adc_data; + goto err; + } + + if (i != 0) { + if (adc_data > adc_max) + adc_max = adc_data; + else if (adc_data < adc_min) + adc_min = adc_data; + } else { + adc_max = adc_data; + adc_min = adc_data; + } + adc_total += adc_data; + } + + return (adc_total - adc_max - adc_min) / (ADC_SAMPLING_CNT - 2); +err: + return err_value; +} + +static int convert_adc_to_temper(struct sec_therm_info *info, unsigned int adc) +{ + int low = 0; + int high = 0; + int mid = 0; + + if (!info->pdata->adc_table || !info->pdata->adc_arr_size) { + /* using fake temp */ + return 300; + } + + high = info->pdata->adc_arr_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (info->pdata->adc_table[mid].adc > adc) + high = mid - 1; + else if (info->pdata->adc_table[mid].adc < adc) + low = mid + 1; + else + break; + } + return info->pdata->adc_table[mid].temperature; +} + +static void notify_change_of_temperature(struct sec_therm_info *info) +{ + char temp_buf[20]; + char siop_buf[20]; + char *envp[2]; + int env_offset = 0; + int siop_level = -1; + + snprintf(temp_buf, sizeof(temp_buf), "TEMPERATURE=%d", + info->curr_temperature); + envp[env_offset++] = temp_buf; + + if (info->pdata->get_siop_level) + siop_level = + info->pdata->get_siop_level(info->curr_temperature); + if (siop_level >= 0) { + snprintf(siop_buf, sizeof(siop_buf), "SIOP_LEVEL=%d", + siop_level); + envp[env_offset++] = siop_buf; + dev_info(info->dev, "%s: uevent: %s\n", __func__, siop_buf); + } + envp[env_offset] = NULL; + + dev_info(info->dev, "%s: uevent: %s\n", __func__, temp_buf); + kobject_uevent_env(&info->dev->kobj, KOBJ_CHANGE, envp); +} + +static void sec_therm_polling_work(struct work_struct *work) +{ + struct sec_therm_info *info = + container_of(work, struct sec_therm_info, polling_work.work); + int adc; + int temper; + + adc = sec_therm_get_adc_data(info); + dev_dbg(info->dev, "%s: adc=%d\n", __func__, adc); + + if (adc < 0) + goto out; + + temper = convert_adc_to_temper(info, adc); + dev_dbg(info->dev, "%s: temper=%d\n", __func__, temper); + + /* if temperature was changed, notify to framework */ + if (info->curr_temperature != temper) { + info->curr_temp_adc = adc; + info->curr_temperature = temper; + notify_change_of_temperature(info); + } +out: + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->pdata->polling_interval)); +} + +static __devinit int sec_therm_probe(struct platform_device *pdev) +{ + struct sec_therm_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct sec_therm_info *info; + int ret = 0; + + dev_info(&pdev->dev, "%s: SEC Thermistor Driver Loading\n", __func__); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->dev = &pdev->dev; + info->pdata = pdata; + + info->padc = s3c_adc_register(pdev, NULL, NULL, 0); + + ret = sysfs_create_group(&info->dev->kobj, &sec_therm_group); + + if (ret) { + dev_err(info->dev, + "failed to create sysfs attribute group\n"); + } + + INIT_DELAYED_WORK_DEFERRABLE(&info->polling_work, + sec_therm_polling_work); + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->pdata->polling_interval)); + + return ret; +} + +static int __devexit sec_therm_remove(struct platform_device *pdev) +{ + struct sec_therm_info *info = platform_get_drvdata(pdev); + + if (!info) + return 0; + + sysfs_remove_group(&info->dev->kobj, &sec_therm_group); + + cancel_delayed_work(&info->polling_work); + s3c_adc_release(info->padc); + kfree(info); + + return 0; +} + +#ifdef CONFIG_PM +static int sec_therm_suspend(struct device *dev) +{ + struct sec_therm_info *info = dev_get_drvdata(dev); + + cancel_delayed_work(&info->polling_work); + + return 0; +} + +static int sec_therm_resume(struct device *dev) +{ + struct sec_therm_info *info = dev_get_drvdata(dev); + + schedule_delayed_work(&info->polling_work, + msecs_to_jiffies(info->pdata->polling_interval)); + return 0; +} +#else +#define sec_therm_suspend NULL +#define sec_therm_resume NULL +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops sec_thermistor_pm_ops = { + .suspend = sec_therm_suspend, + .resume = sec_therm_resume, +}; + +static struct platform_driver sec_thermistor_driver = { + .driver = { + .name = "sec-thermistor", + .owner = THIS_MODULE, + .pm = &sec_thermistor_pm_ops, + }, + .probe = sec_therm_probe, + .remove = __devexit_p(sec_therm_remove), +}; + +static int __init sec_therm_init(void) +{ + return platform_driver_register(&sec_thermistor_driver); +} +module_init(sec_therm_init); + +static void __exit sec_therm_exit(void) +{ + platform_driver_unregister(&sec_thermistor_driver); +} +module_exit(sec_therm_exit); + +MODULE_AUTHOR("ms925.kim@samsung.com"); +MODULE_DESCRIPTION("sec thermistor driver"); +MODULE_LICENSE("GPL"); |