aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-exynos/sec_thermistor.c
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /arch/arm/mach-exynos/sec_thermistor.c
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'arch/arm/mach-exynos/sec_thermistor.c')
-rw-r--r--arch/arm/mach-exynos/sec_thermistor.c285
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");