diff options
Diffstat (limited to 'drivers/hwmon/ab8500.c')
-rw-r--r-- | drivers/hwmon/ab8500.c | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 000000000000..d844dc806853 --- /dev/null +++ b/drivers/hwmon/ab8500.c | |||
@@ -0,0 +1,206 @@ | |||
1 | /* | ||
2 | * Copyright (C) ST-Ericsson 2010 - 2013 | ||
3 | * Author: Martin Persson <martin.persson@stericsson.com> | ||
4 | * Hongbo Zhang <hongbo.zhang@linaro.org> | ||
5 | * License Terms: GNU General Public License v2 | ||
6 | * | ||
7 | * When the AB8500 thermal warning temperature is reached (threshold cannot | ||
8 | * be changed by SW), an interrupt is set, and if no further action is taken | ||
9 | * within a certain time frame, pm_power off will be called. | ||
10 | * | ||
11 | * When AB8500 thermal shutdown temperature is reached a hardware shutdown of | ||
12 | * the AB8500 will occur. | ||
13 | */ | ||
14 | |||
15 | #include <linux/err.h> | ||
16 | #include <linux/hwmon.h> | ||
17 | #include <linux/hwmon-sysfs.h> | ||
18 | #include <linux/mfd/abx500.h> | ||
19 | #include <linux/mfd/abx500/ab8500-bm.h> | ||
20 | #include <linux/mfd/abx500/ab8500-gpadc.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/power/ab8500.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/sysfs.h> | ||
26 | #include "abx500.h" | ||
27 | |||
28 | #define DEFAULT_POWER_OFF_DELAY (HZ * 10) | ||
29 | #define THERMAL_VCC 1800 | ||
30 | #define PULL_UP_RESISTOR 47000 | ||
31 | /* Number of monitored sensors should not greater than NUM_SENSORS */ | ||
32 | #define NUM_MONITORED_SENSORS 4 | ||
33 | |||
34 | struct ab8500_gpadc_cfg { | ||
35 | const struct abx500_res_to_temp *temp_tbl; | ||
36 | int tbl_sz; | ||
37 | int vcc; | ||
38 | int r_up; | ||
39 | }; | ||
40 | |||
41 | struct ab8500_temp { | ||
42 | struct ab8500_gpadc *gpadc; | ||
43 | struct ab8500_btemp *btemp; | ||
44 | struct delayed_work power_off_work; | ||
45 | struct ab8500_gpadc_cfg cfg; | ||
46 | struct abx500_temp *abx500_data; | ||
47 | }; | ||
48 | |||
49 | /* | ||
50 | * The hardware connection is like this: | ||
51 | * VCC----[ R_up ]-----[ NTC ]----GND | ||
52 | * where R_up is pull-up resistance, and GPADC measures voltage on NTC. | ||
53 | * and res_to_temp table is strictly sorted by falling resistance values. | ||
54 | */ | ||
55 | static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg, | ||
56 | int v_ntc, int *temp) | ||
57 | { | ||
58 | int r_ntc, i = 0, tbl_sz = cfg->tbl_sz; | ||
59 | const struct abx500_res_to_temp *tbl = cfg->temp_tbl; | ||
60 | |||
61 | if (cfg->vcc < 0 || v_ntc >= cfg->vcc) | ||
62 | return -EINVAL; | ||
63 | |||
64 | r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc); | ||
65 | if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist) | ||
66 | return -EINVAL; | ||
67 | |||
68 | while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist) && | ||
69 | i < tbl_sz - 2) | ||
70 | i++; | ||
71 | |||
72 | /* return milli-Celsius */ | ||
73 | *temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 * | ||
74 | (r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); | ||
75 | |||
76 | return 0; | ||
77 | } | ||
78 | |||
79 | static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp) | ||
80 | { | ||
81 | int voltage, ret; | ||
82 | struct ab8500_temp *ab8500_data = data->plat_data; | ||
83 | |||
84 | if (sensor == BAT_CTRL) { | ||
85 | *temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp); | ||
86 | } else if (sensor == BTEMP_BALL) { | ||
87 | *temp = ab8500_btemp_get_temp(ab8500_data->btemp); | ||
88 | } else { | ||
89 | voltage = ab8500_gpadc_convert(ab8500_data->gpadc, sensor); | ||
90 | if (voltage < 0) | ||
91 | return voltage; | ||
92 | |||
93 | ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp); | ||
94 | if (ret < 0) | ||
95 | return ret; | ||
96 | } | ||
97 | |||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | static void ab8500_thermal_power_off(struct work_struct *work) | ||
102 | { | ||
103 | struct ab8500_temp *ab8500_data = container_of(work, | ||
104 | struct ab8500_temp, power_off_work.work); | ||
105 | struct abx500_temp *abx500_data = ab8500_data->abx500_data; | ||
106 | |||
107 | dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n"); | ||
108 | |||
109 | pm_power_off(); | ||
110 | } | ||
111 | |||
112 | static ssize_t ab8500_show_name(struct device *dev, | ||
113 | struct device_attribute *devattr, char *buf) | ||
114 | { | ||
115 | return sprintf(buf, "ab8500\n"); | ||
116 | } | ||
117 | |||
118 | static ssize_t ab8500_show_label(struct device *dev, | ||
119 | struct device_attribute *devattr, char *buf) | ||
120 | { | ||
121 | char *label; | ||
122 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
123 | int index = attr->index; | ||
124 | |||
125 | switch (index) { | ||
126 | case 1: | ||
127 | label = "ext_adc1"; | ||
128 | break; | ||
129 | case 2: | ||
130 | label = "ext_adc2"; | ||
131 | break; | ||
132 | case 3: | ||
133 | label = "bat_temp"; | ||
134 | break; | ||
135 | case 4: | ||
136 | label = "bat_ctrl"; | ||
137 | break; | ||
138 | default: | ||
139 | return -EINVAL; | ||
140 | } | ||
141 | |||
142 | return sprintf(buf, "%s\n", label); | ||
143 | } | ||
144 | |||
145 | static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) | ||
146 | { | ||
147 | struct ab8500_temp *ab8500_data = data->plat_data; | ||
148 | |||
149 | dev_warn(&data->pdev->dev, "Power off in %d s\n", | ||
150 | DEFAULT_POWER_OFF_DELAY / HZ); | ||
151 | |||
152 | schedule_delayed_work(&ab8500_data->power_off_work, | ||
153 | DEFAULT_POWER_OFF_DELAY); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | int abx500_hwmon_init(struct abx500_temp *data) | ||
158 | { | ||
159 | struct ab8500_temp *ab8500_data; | ||
160 | |||
161 | ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data), | ||
162 | GFP_KERNEL); | ||
163 | if (!ab8500_data) | ||
164 | return -ENOMEM; | ||
165 | |||
166 | ab8500_data->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); | ||
167 | if (IS_ERR(ab8500_data->gpadc)) | ||
168 | return PTR_ERR(ab8500_data->gpadc); | ||
169 | |||
170 | ab8500_data->btemp = ab8500_btemp_get(); | ||
171 | if (IS_ERR(ab8500_data->btemp)) | ||
172 | return PTR_ERR(ab8500_data->btemp); | ||
173 | |||
174 | INIT_DELAYED_WORK(&ab8500_data->power_off_work, | ||
175 | ab8500_thermal_power_off); | ||
176 | |||
177 | ab8500_data->cfg.vcc = THERMAL_VCC; | ||
178 | ab8500_data->cfg.r_up = PULL_UP_RESISTOR; | ||
179 | ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a_thermistor; | ||
180 | ab8500_data->cfg.tbl_sz = ab8500_temp_tbl_a_size; | ||
181 | |||
182 | data->plat_data = ab8500_data; | ||
183 | |||
184 | /* | ||
185 | * ADC_AUX1 and ADC_AUX2, connected to external NTC | ||
186 | * BTEMP_BALL and BAT_CTRL, fixed usage | ||
187 | */ | ||
188 | data->gpadc_addr[0] = ADC_AUX1; | ||
189 | data->gpadc_addr[1] = ADC_AUX2; | ||
190 | data->gpadc_addr[2] = BTEMP_BALL; | ||
191 | data->gpadc_addr[3] = BAT_CTRL; | ||
192 | data->monitored_sensors = NUM_MONITORED_SENSORS; | ||
193 | |||
194 | data->ops.read_sensor = ab8500_read_sensor; | ||
195 | data->ops.irq_handler = ab8500_temp_irq_handler; | ||
196 | data->ops.show_name = ab8500_show_name; | ||
197 | data->ops.show_label = ab8500_show_label; | ||
198 | data->ops.is_visible = NULL; | ||
199 | |||
200 | return 0; | ||
201 | } | ||
202 | EXPORT_SYMBOL(abx500_hwmon_init); | ||
203 | |||
204 | MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@linaro.org>"); | ||
205 | MODULE_DESCRIPTION("AB8500 temperature driver"); | ||
206 | MODULE_LICENSE("GPL"); | ||