/* 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 #include #include #include #include #include #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; }; #if defined(CONFIG_MACH_C1_KOR_SKT) || defined(CONFIG_MACH_C1_KOR_KT) || \ defined(CONFIG_MACH_C1_KOR_LGT) static void notify_change_of_temperature(struct sec_therm_info *info); int siopLevellimit; EXPORT_SYMBOL(siopLevellimit); #endif 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); } #if defined(CONFIG_MACH_C1_KOR_SKT) || defined(CONFIG_MACH_C1_KOR_KT) || \ defined(CONFIG_MACH_C1_KOR_LGT) static ssize_t sec_therm_show_sioplevel(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", siopLevellimit); } static ssize_t sec_therm_store_sioplevel(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { unsigned int val; struct sec_therm_info *info = dev_get_drvdata(dev); if (sscanf(buf, "%u", &val) == 1) siopLevellimit = val; notify_change_of_temperature(info); return n; } static DEVICE_ATTR(sioplevel, S_IWUSR | S_IRUGO, sec_therm_show_sioplevel, \ sec_therm_store_sioplevel); #endif 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, #if defined(CONFIG_MACH_C1_KOR_SKT) || defined(CONFIG_MACH_C1_KOR_KT) || \ defined(CONFIG_MACH_C1_KOR_LGT) &dev_attr_sioplevel.attr, #endif 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");