diff options
author | Guenter Roeck <guenter.roeck@ericsson.com> | 2011-07-30 01:21:53 -0400 |
---|---|---|
committer | Guenter Roeck <guenter.roeck@ericsson.com> | 2011-10-24 14:09:40 -0400 |
commit | 200855e52db1b1834121ba57fbd89c5b4911e02c (patch) | |
tree | fa0c572e5c40b8558ae2e8fc5069a41c2c503668 /drivers/hwmon | |
parent | da8e48ab483e1f54c1099bed91bfd2c302bc7ddf (diff) |
hwmon: (pmbus) Add support for Intersil power management chips
Add support for Intersil / Zilker Labs ZL2004, ZL2006, ZL2008, ZL2105, ZL2106,
ZL6100, and ZL6105.
Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com>
Reviewed-by: Robert Coulson <robert.coulson@ericsson.com>
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/pmbus/Kconfig | 11 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/zl6100.c | 256 |
3 files changed, 268 insertions, 0 deletions
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 37575582e51a..efaf340651a0 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig | |||
@@ -98,4 +98,15 @@ config SENSORS_UCD9200 | |||
98 | This driver can also be built as a module. If so, the module will | 98 | This driver can also be built as a module. If so, the module will |
99 | be called ucd9200. | 99 | be called ucd9200. |
100 | 100 | ||
101 | config SENSORS_ZL6100 | ||
102 | tristate "Intersil ZL6100 and compatibles" | ||
103 | default n | ||
104 | help | ||
105 | If you say yes here you get hardware monitoring support for Intersil | ||
106 | ZL2004, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, and ZL6105 Digital | ||
107 | DC/DC Controllers. | ||
108 | |||
109 | This driver can also be built as a module. If so, the module will | ||
110 | be called zl6100. | ||
111 | |||
101 | endif # PMBUS | 112 | endif # PMBUS |
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 623eedb1ed9a..b9e4fb421f6c 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile | |||
@@ -11,3 +11,4 @@ obj-$(CONFIG_SENSORS_MAX34440) += max34440.o | |||
11 | obj-$(CONFIG_SENSORS_MAX8688) += max8688.o | 11 | obj-$(CONFIG_SENSORS_MAX8688) += max8688.o |
12 | obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o | 12 | obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o |
13 | obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o | 13 | obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o |
14 | obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o | ||
diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c new file mode 100644 index 000000000000..2bc980006f83 --- /dev/null +++ b/drivers/hwmon/pmbus/zl6100.c | |||
@@ -0,0 +1,256 @@ | |||
1 | /* | ||
2 | * Hardware monitoring driver for ZL6100 and compatibles | ||
3 | * | ||
4 | * Copyright (c) 2011 Ericsson AB. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
19 | */ | ||
20 | |||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/err.h> | ||
25 | #include <linux/slab.h> | ||
26 | #include <linux/i2c.h> | ||
27 | #include <linux/ktime.h> | ||
28 | #include <linux/delay.h> | ||
29 | #include "pmbus.h" | ||
30 | |||
31 | enum chips { zl2004, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105 }; | ||
32 | |||
33 | struct zl6100_data { | ||
34 | int id; | ||
35 | ktime_t access; /* chip access time */ | ||
36 | struct pmbus_driver_info info; | ||
37 | }; | ||
38 | |||
39 | #define to_zl6100_data(x) container_of(x, struct zl6100_data, info) | ||
40 | |||
41 | #define ZL6100_DEVICE_ID 0xe4 | ||
42 | |||
43 | #define ZL6100_WAIT_TIME 1000 /* uS */ | ||
44 | |||
45 | static ushort delay = ZL6100_WAIT_TIME; | ||
46 | module_param(delay, ushort, 0644); | ||
47 | MODULE_PARM_DESC(delay, "Delay between chip accesses in uS"); | ||
48 | |||
49 | /* Some chips need a delay between accesses */ | ||
50 | static inline void zl6100_wait(const struct zl6100_data *data) | ||
51 | { | ||
52 | if (delay) { | ||
53 | s64 delta = ktime_us_delta(ktime_get(), data->access); | ||
54 | if (delta < delay) | ||
55 | udelay(delay - delta); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | static int zl6100_read_word_data(struct i2c_client *client, int page, int reg) | ||
60 | { | ||
61 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | ||
62 | struct zl6100_data *data = to_zl6100_data(info); | ||
63 | int ret; | ||
64 | |||
65 | if (page || reg >= PMBUS_VIRT_BASE) | ||
66 | return -ENXIO; | ||
67 | |||
68 | zl6100_wait(data); | ||
69 | ret = pmbus_read_word_data(client, page, reg); | ||
70 | data->access = ktime_get(); | ||
71 | |||
72 | return ret; | ||
73 | } | ||
74 | |||
75 | static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg) | ||
76 | { | ||
77 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | ||
78 | struct zl6100_data *data = to_zl6100_data(info); | ||
79 | int ret; | ||
80 | |||
81 | if (page > 0) | ||
82 | return -ENXIO; | ||
83 | |||
84 | zl6100_wait(data); | ||
85 | ret = pmbus_read_byte_data(client, page, reg); | ||
86 | data->access = ktime_get(); | ||
87 | |||
88 | return ret; | ||
89 | } | ||
90 | |||
91 | static int zl6100_write_word_data(struct i2c_client *client, int page, int reg, | ||
92 | u16 word) | ||
93 | { | ||
94 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | ||
95 | struct zl6100_data *data = to_zl6100_data(info); | ||
96 | int ret; | ||
97 | |||
98 | if (page || reg >= PMBUS_VIRT_BASE) | ||
99 | return -ENXIO; | ||
100 | |||
101 | zl6100_wait(data); | ||
102 | ret = pmbus_write_word_data(client, page, reg, word); | ||
103 | data->access = ktime_get(); | ||
104 | |||
105 | return ret; | ||
106 | } | ||
107 | |||
108 | static int zl6100_write_byte(struct i2c_client *client, int page, u8 value) | ||
109 | { | ||
110 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | ||
111 | struct zl6100_data *data = to_zl6100_data(info); | ||
112 | int ret; | ||
113 | |||
114 | if (page > 0) | ||
115 | return -ENXIO; | ||
116 | |||
117 | zl6100_wait(data); | ||
118 | ret = pmbus_write_byte(client, page, value); | ||
119 | data->access = ktime_get(); | ||
120 | |||
121 | return ret; | ||
122 | } | ||
123 | |||
124 | static const struct i2c_device_id zl6100_id[] = { | ||
125 | {"zl2004", zl2004}, | ||
126 | {"zl2006", zl2006}, | ||
127 | {"zl2008", zl2008}, | ||
128 | {"zl2105", zl2105}, | ||
129 | {"zl2106", zl2106}, | ||
130 | {"zl6100", zl6100}, | ||
131 | {"zl6105", zl6105}, | ||
132 | { } | ||
133 | }; | ||
134 | MODULE_DEVICE_TABLE(i2c, zl6100_id); | ||
135 | |||
136 | static int zl6100_probe(struct i2c_client *client, | ||
137 | const struct i2c_device_id *id) | ||
138 | { | ||
139 | int ret; | ||
140 | struct zl6100_data *data; | ||
141 | struct pmbus_driver_info *info; | ||
142 | u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; | ||
143 | const struct i2c_device_id *mid; | ||
144 | |||
145 | if (!i2c_check_functionality(client->adapter, | ||
146 | I2C_FUNC_SMBUS_READ_BYTE_DATA | ||
147 | | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) | ||
148 | return -ENODEV; | ||
149 | |||
150 | ret = i2c_smbus_read_block_data(client, ZL6100_DEVICE_ID, | ||
151 | device_id); | ||
152 | if (ret < 0) { | ||
153 | dev_err(&client->dev, "Failed to read device ID\n"); | ||
154 | return ret; | ||
155 | } | ||
156 | device_id[ret] = '\0'; | ||
157 | dev_info(&client->dev, "Device ID %s\n", device_id); | ||
158 | |||
159 | mid = NULL; | ||
160 | for (mid = zl6100_id; mid->name[0]; mid++) { | ||
161 | if (!strncasecmp(mid->name, device_id, strlen(mid->name))) | ||
162 | break; | ||
163 | } | ||
164 | if (!mid->name[0]) { | ||
165 | dev_err(&client->dev, "Unsupported device\n"); | ||
166 | return -ENODEV; | ||
167 | } | ||
168 | if (id->driver_data != mid->driver_data) | ||
169 | dev_notice(&client->dev, | ||
170 | "Device mismatch: Configured %s, detected %s\n", | ||
171 | id->name, mid->name); | ||
172 | |||
173 | data = kzalloc(sizeof(struct zl6100_data), GFP_KERNEL); | ||
174 | if (!data) | ||
175 | return -ENOMEM; | ||
176 | |||
177 | data->id = mid->driver_data; | ||
178 | |||
179 | /* | ||
180 | * ZL2008, ZL2105, and ZL6100 are known to require a wait time | ||
181 | * between I2C accesses. ZL2004 and ZL6105 are known to be safe. | ||
182 | * | ||
183 | * Only clear the wait time for chips known to be safe. The wait time | ||
184 | * can be cleared later for additional chips if tests show that it | ||
185 | * is not needed (in other words, better be safe than sorry). | ||
186 | */ | ||
187 | if (data->id == zl2004 || data->id == zl6105) | ||
188 | delay = 0; | ||
189 | |||
190 | /* | ||
191 | * Since there was a direct I2C device access above, wait before | ||
192 | * accessing the chip again. | ||
193 | * Set the timestamp, wait, then set it again. This should provide | ||
194 | * enough buffer time to be safe. | ||
195 | */ | ||
196 | data->access = ktime_get(); | ||
197 | zl6100_wait(data); | ||
198 | data->access = ktime_get(); | ||
199 | |||
200 | info = &data->info; | ||
201 | |||
202 | info->pages = 1; | ||
203 | info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | ||
204 | | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ||
205 | | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ||
206 | | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; | ||
207 | |||
208 | info->read_word_data = zl6100_read_word_data; | ||
209 | info->read_byte_data = zl6100_read_byte_data; | ||
210 | info->write_word_data = zl6100_write_word_data; | ||
211 | info->write_byte = zl6100_write_byte; | ||
212 | |||
213 | ret = pmbus_do_probe(client, mid, info); | ||
214 | if (ret) | ||
215 | goto err_mem; | ||
216 | return 0; | ||
217 | |||
218 | err_mem: | ||
219 | kfree(data); | ||
220 | return ret; | ||
221 | } | ||
222 | |||
223 | static int zl6100_remove(struct i2c_client *client) | ||
224 | { | ||
225 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | ||
226 | const struct zl6100_data *data = to_zl6100_data(info); | ||
227 | |||
228 | pmbus_do_remove(client); | ||
229 | kfree(data); | ||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | static struct i2c_driver zl6100_driver = { | ||
234 | .driver = { | ||
235 | .name = "zl6100", | ||
236 | }, | ||
237 | .probe = zl6100_probe, | ||
238 | .remove = zl6100_remove, | ||
239 | .id_table = zl6100_id, | ||
240 | }; | ||
241 | |||
242 | static int __init zl6100_init(void) | ||
243 | { | ||
244 | return i2c_add_driver(&zl6100_driver); | ||
245 | } | ||
246 | |||
247 | static void __exit zl6100_exit(void) | ||
248 | { | ||
249 | i2c_del_driver(&zl6100_driver); | ||
250 | } | ||
251 | |||
252 | MODULE_AUTHOR("Guenter Roeck"); | ||
253 | MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles"); | ||
254 | MODULE_LICENSE("GPL"); | ||
255 | module_init(zl6100_init); | ||
256 | module_exit(zl6100_exit); | ||