108bad5a8SMark Brown /* 208bad5a8SMark Brown * drivers/hwmon/wm831x-hwmon.c - Wolfson Microelectronics WM831x PMIC 308bad5a8SMark Brown * hardware monitoring features. 408bad5a8SMark Brown * 508bad5a8SMark Brown * Copyright (C) 2009 Wolfson Microelectronics plc 608bad5a8SMark Brown * 708bad5a8SMark Brown * This program is free software; you can redistribute it and/or modify it 808bad5a8SMark Brown * under the terms of the GNU General Public License v2 as published by the 908bad5a8SMark Brown * Free Software Foundation. 1008bad5a8SMark Brown * 1108bad5a8SMark Brown * This program is distributed in the hope that it will be useful, but WITHOUT 1208bad5a8SMark Brown * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1308bad5a8SMark Brown * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 1408bad5a8SMark Brown * more details. 1508bad5a8SMark Brown * 1608bad5a8SMark Brown * You should have received a copy of the GNU General Public License along with 1708bad5a8SMark Brown * this program; if not, write to the Free Software Foundation, Inc., 1808bad5a8SMark Brown * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 1908bad5a8SMark Brown */ 2008bad5a8SMark Brown 2108bad5a8SMark Brown #include <linux/kernel.h> 2208bad5a8SMark Brown #include <linux/module.h> 2308bad5a8SMark Brown #include <linux/platform_device.h> 2408bad5a8SMark Brown #include <linux/err.h> 2508bad5a8SMark Brown #include <linux/hwmon.h> 2608bad5a8SMark Brown #include <linux/hwmon-sysfs.h> 27*5a0e3ad6STejun Heo #include <linux/slab.h> 2808bad5a8SMark Brown 2908bad5a8SMark Brown #include <linux/mfd/wm831x/core.h> 3008bad5a8SMark Brown #include <linux/mfd/wm831x/auxadc.h> 3108bad5a8SMark Brown 3208bad5a8SMark Brown struct wm831x_hwmon { 3308bad5a8SMark Brown struct wm831x *wm831x; 3408bad5a8SMark Brown struct device *classdev; 3508bad5a8SMark Brown }; 3608bad5a8SMark Brown 3708bad5a8SMark Brown static ssize_t show_name(struct device *dev, 3808bad5a8SMark Brown struct device_attribute *attr, char *buf) 3908bad5a8SMark Brown { 4008bad5a8SMark Brown return sprintf(buf, "wm831x\n"); 4108bad5a8SMark Brown } 4208bad5a8SMark Brown 4308bad5a8SMark Brown static const char *input_names[] = { 4408bad5a8SMark Brown [WM831X_AUX_SYSVDD] = "SYSVDD", 4508bad5a8SMark Brown [WM831X_AUX_USB] = "USB", 4608bad5a8SMark Brown [WM831X_AUX_BKUP_BATT] = "Backup battery", 4708bad5a8SMark Brown [WM831X_AUX_BATT] = "Battery", 4808bad5a8SMark Brown [WM831X_AUX_WALL] = "WALL", 4908bad5a8SMark Brown [WM831X_AUX_CHIP_TEMP] = "PMIC", 5008bad5a8SMark Brown [WM831X_AUX_BATT_TEMP] = "Battery", 5108bad5a8SMark Brown }; 5208bad5a8SMark Brown 5308bad5a8SMark Brown 5408bad5a8SMark Brown static ssize_t show_voltage(struct device *dev, 5508bad5a8SMark Brown struct device_attribute *attr, char *buf) 5608bad5a8SMark Brown { 5708bad5a8SMark Brown struct wm831x_hwmon *hwmon = dev_get_drvdata(dev); 5808bad5a8SMark Brown int channel = to_sensor_dev_attr(attr)->index; 5908bad5a8SMark Brown int ret; 6008bad5a8SMark Brown 6108bad5a8SMark Brown ret = wm831x_auxadc_read_uv(hwmon->wm831x, channel); 6208bad5a8SMark Brown if (ret < 0) 6308bad5a8SMark Brown return ret; 6408bad5a8SMark Brown 6508bad5a8SMark Brown return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(ret, 1000)); 6608bad5a8SMark Brown } 6708bad5a8SMark Brown 6808bad5a8SMark Brown static ssize_t show_chip_temp(struct device *dev, 6908bad5a8SMark Brown struct device_attribute *attr, char *buf) 7008bad5a8SMark Brown { 7108bad5a8SMark Brown struct wm831x_hwmon *hwmon = dev_get_drvdata(dev); 7208bad5a8SMark Brown int channel = to_sensor_dev_attr(attr)->index; 7308bad5a8SMark Brown int ret; 7408bad5a8SMark Brown 7508bad5a8SMark Brown ret = wm831x_auxadc_read(hwmon->wm831x, channel); 7608bad5a8SMark Brown if (ret < 0) 7708bad5a8SMark Brown return ret; 7808bad5a8SMark Brown 7908bad5a8SMark Brown /* Degrees celsius = (512.18-ret) / 1.0983 */ 8008bad5a8SMark Brown ret = 512180 - (ret * 1000); 8108bad5a8SMark Brown ret = DIV_ROUND_CLOSEST(ret * 10000, 10983); 8208bad5a8SMark Brown 8308bad5a8SMark Brown return sprintf(buf, "%d\n", ret); 8408bad5a8SMark Brown } 8508bad5a8SMark Brown 8608bad5a8SMark Brown static ssize_t show_label(struct device *dev, 8708bad5a8SMark Brown struct device_attribute *attr, char *buf) 8808bad5a8SMark Brown { 8908bad5a8SMark Brown int channel = to_sensor_dev_attr(attr)->index; 9008bad5a8SMark Brown 9108bad5a8SMark Brown return sprintf(buf, "%s\n", input_names[channel]); 9208bad5a8SMark Brown } 9308bad5a8SMark Brown 9408bad5a8SMark Brown #define WM831X_VOLTAGE(id, name) \ 9508bad5a8SMark Brown static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage, \ 9608bad5a8SMark Brown NULL, name) 9708bad5a8SMark Brown 9808bad5a8SMark Brown #define WM831X_NAMED_VOLTAGE(id, name) \ 9908bad5a8SMark Brown WM831X_VOLTAGE(id, name); \ 10008bad5a8SMark Brown static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \ 10108bad5a8SMark Brown NULL, name) 10208bad5a8SMark Brown 10308bad5a8SMark Brown static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); 10408bad5a8SMark Brown 10508bad5a8SMark Brown WM831X_VOLTAGE(0, WM831X_AUX_AUX1); 10608bad5a8SMark Brown WM831X_VOLTAGE(1, WM831X_AUX_AUX2); 10708bad5a8SMark Brown WM831X_VOLTAGE(2, WM831X_AUX_AUX3); 10808bad5a8SMark Brown WM831X_VOLTAGE(3, WM831X_AUX_AUX4); 10908bad5a8SMark Brown 11008bad5a8SMark Brown WM831X_NAMED_VOLTAGE(4, WM831X_AUX_SYSVDD); 11108bad5a8SMark Brown WM831X_NAMED_VOLTAGE(5, WM831X_AUX_USB); 11208bad5a8SMark Brown WM831X_NAMED_VOLTAGE(6, WM831X_AUX_BATT); 11308bad5a8SMark Brown WM831X_NAMED_VOLTAGE(7, WM831X_AUX_WALL); 11408bad5a8SMark Brown WM831X_NAMED_VOLTAGE(8, WM831X_AUX_BKUP_BATT); 11508bad5a8SMark Brown 11608bad5a8SMark Brown static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_chip_temp, NULL, 11708bad5a8SMark Brown WM831X_AUX_CHIP_TEMP); 11808bad5a8SMark Brown static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 11908bad5a8SMark Brown WM831X_AUX_CHIP_TEMP); 12008bad5a8SMark Brown /* Report as a voltage since conversion depends on external components 12108bad5a8SMark Brown * and that's what the ABI wants. */ 12208bad5a8SMark Brown static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_voltage, NULL, 12308bad5a8SMark Brown WM831X_AUX_BATT_TEMP); 12408bad5a8SMark Brown static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 12508bad5a8SMark Brown WM831X_AUX_BATT_TEMP); 12608bad5a8SMark Brown 12708bad5a8SMark Brown static struct attribute *wm831x_attributes[] = { 12808bad5a8SMark Brown &dev_attr_name.attr, 12908bad5a8SMark Brown 13008bad5a8SMark Brown &sensor_dev_attr_in0_input.dev_attr.attr, 13108bad5a8SMark Brown &sensor_dev_attr_in1_input.dev_attr.attr, 13208bad5a8SMark Brown &sensor_dev_attr_in2_input.dev_attr.attr, 13308bad5a8SMark Brown &sensor_dev_attr_in3_input.dev_attr.attr, 13408bad5a8SMark Brown 13508bad5a8SMark Brown &sensor_dev_attr_in4_input.dev_attr.attr, 13608bad5a8SMark Brown &sensor_dev_attr_in4_label.dev_attr.attr, 13708bad5a8SMark Brown &sensor_dev_attr_in5_input.dev_attr.attr, 13808bad5a8SMark Brown &sensor_dev_attr_in5_label.dev_attr.attr, 13908bad5a8SMark Brown &sensor_dev_attr_in6_input.dev_attr.attr, 14008bad5a8SMark Brown &sensor_dev_attr_in6_label.dev_attr.attr, 14108bad5a8SMark Brown &sensor_dev_attr_in7_input.dev_attr.attr, 14208bad5a8SMark Brown &sensor_dev_attr_in7_label.dev_attr.attr, 14308bad5a8SMark Brown &sensor_dev_attr_in8_input.dev_attr.attr, 14408bad5a8SMark Brown &sensor_dev_attr_in8_label.dev_attr.attr, 14508bad5a8SMark Brown 14608bad5a8SMark Brown &sensor_dev_attr_temp1_input.dev_attr.attr, 14708bad5a8SMark Brown &sensor_dev_attr_temp1_label.dev_attr.attr, 14808bad5a8SMark Brown &sensor_dev_attr_temp2_input.dev_attr.attr, 14908bad5a8SMark Brown &sensor_dev_attr_temp2_label.dev_attr.attr, 15008bad5a8SMark Brown 15108bad5a8SMark Brown NULL 15208bad5a8SMark Brown }; 15308bad5a8SMark Brown 15408bad5a8SMark Brown static const struct attribute_group wm831x_attr_group = { 15508bad5a8SMark Brown .attrs = wm831x_attributes, 15608bad5a8SMark Brown }; 15708bad5a8SMark Brown 15808bad5a8SMark Brown static int __devinit wm831x_hwmon_probe(struct platform_device *pdev) 15908bad5a8SMark Brown { 16008bad5a8SMark Brown struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); 16108bad5a8SMark Brown struct wm831x_hwmon *hwmon; 16208bad5a8SMark Brown int ret; 16308bad5a8SMark Brown 16408bad5a8SMark Brown hwmon = kzalloc(sizeof(struct wm831x_hwmon), GFP_KERNEL); 16508bad5a8SMark Brown if (!hwmon) 16608bad5a8SMark Brown return -ENOMEM; 16708bad5a8SMark Brown 16808bad5a8SMark Brown hwmon->wm831x = wm831x; 16908bad5a8SMark Brown 17008bad5a8SMark Brown ret = sysfs_create_group(&pdev->dev.kobj, &wm831x_attr_group); 17108bad5a8SMark Brown if (ret) 17208bad5a8SMark Brown goto err; 17308bad5a8SMark Brown 17408bad5a8SMark Brown hwmon->classdev = hwmon_device_register(&pdev->dev); 17508bad5a8SMark Brown if (IS_ERR(hwmon->classdev)) { 17608bad5a8SMark Brown ret = PTR_ERR(hwmon->classdev); 17708bad5a8SMark Brown goto err_sysfs; 17808bad5a8SMark Brown } 17908bad5a8SMark Brown 18008bad5a8SMark Brown platform_set_drvdata(pdev, hwmon); 18108bad5a8SMark Brown 18208bad5a8SMark Brown return 0; 18308bad5a8SMark Brown 18408bad5a8SMark Brown err_sysfs: 18508bad5a8SMark Brown sysfs_remove_group(&pdev->dev.kobj, &wm831x_attr_group); 18608bad5a8SMark Brown err: 18708bad5a8SMark Brown kfree(hwmon); 18808bad5a8SMark Brown return ret; 18908bad5a8SMark Brown } 19008bad5a8SMark Brown 19108bad5a8SMark Brown static int __devexit wm831x_hwmon_remove(struct platform_device *pdev) 19208bad5a8SMark Brown { 19308bad5a8SMark Brown struct wm831x_hwmon *hwmon = platform_get_drvdata(pdev); 19408bad5a8SMark Brown 19508bad5a8SMark Brown hwmon_device_unregister(hwmon->classdev); 19608bad5a8SMark Brown sysfs_remove_group(&pdev->dev.kobj, &wm831x_attr_group); 19708bad5a8SMark Brown platform_set_drvdata(pdev, NULL); 19808bad5a8SMark Brown kfree(hwmon); 19908bad5a8SMark Brown 20008bad5a8SMark Brown return 0; 20108bad5a8SMark Brown } 20208bad5a8SMark Brown 20308bad5a8SMark Brown static struct platform_driver wm831x_hwmon_driver = { 20408bad5a8SMark Brown .probe = wm831x_hwmon_probe, 20508bad5a8SMark Brown .remove = __devexit_p(wm831x_hwmon_remove), 20608bad5a8SMark Brown .driver = { 20708bad5a8SMark Brown .name = "wm831x-hwmon", 20808bad5a8SMark Brown .owner = THIS_MODULE, 20908bad5a8SMark Brown }, 21008bad5a8SMark Brown }; 21108bad5a8SMark Brown 21208bad5a8SMark Brown static int __init wm831x_hwmon_init(void) 21308bad5a8SMark Brown { 21408bad5a8SMark Brown return platform_driver_register(&wm831x_hwmon_driver); 21508bad5a8SMark Brown } 21608bad5a8SMark Brown module_init(wm831x_hwmon_init); 21708bad5a8SMark Brown 21808bad5a8SMark Brown static void __exit wm831x_hwmon_exit(void) 21908bad5a8SMark Brown { 22008bad5a8SMark Brown platform_driver_unregister(&wm831x_hwmon_driver); 22108bad5a8SMark Brown } 22208bad5a8SMark Brown module_exit(wm831x_hwmon_exit); 22308bad5a8SMark Brown 22408bad5a8SMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 22508bad5a8SMark Brown MODULE_DESCRIPTION("WM831x Hardware Monitoring"); 22608bad5a8SMark Brown MODULE_LICENSE("GPL"); 22708bad5a8SMark Brown MODULE_ALIAS("platform:wm831x-hwmon"); 228