OLD | NEW |
---|---|
(Empty) | |
1 /* Quanta I2C Battery Driver | |
2 * | |
3 * Copyright (C) 2009 Quanta Computer Inc. | |
4 * | |
5 * This software is licensed under the terms of the GNU General Public | |
6 * License version 2, as published by the Free Software Foundation, and | |
7 * may be copied, distributed, and modified under those terms. | |
8 * | |
9 * This program is distributed in the hope that it will be useful, | |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 * GNU General Public License for more details. | |
13 * | |
14 */ | |
15 | |
16 /* | |
17 * | |
18 * The Driver with I/O communications via the I2C Interface for ST15 platform. | |
19 * And it is only working on the nuvoTon WPCE775x Embedded Controller. | |
20 * | |
21 */ | |
22 | |
23 #include <linux/module.h> | |
24 #include <linux/err.h> | |
25 #include <linux/platform_device.h> | |
26 #include <linux/power_supply.h> | |
27 #include <linux/sched.h> | |
28 #include <linux/gpio.h> | |
29 #include <linux/i2c.h> | |
30 #include <linux/wpce775x.h> | |
31 | |
32 #include "qci_battery.h" | |
33 | |
34 struct qci_bat_info { | |
35 u8 type_id; | |
36 u8 power_flag; | |
37 u8 ec_ver_lsb; | |
38 u8 ec_ver_msb; | |
39 u8 mbat_rsoc; | |
40 u8 mbat_volt_lsb; | |
41 u8 mbat_volt_msb; | |
42 u8 mbat_status; | |
43 u8 mbchg_status; | |
44 u8 mbat_temp_lsb; | |
45 u8 mbat_temp_msb; | |
46 }; | |
47 | |
48 /* General structure to hold the driver data */ | |
49 struct i2cbat_drv_data { | |
50 struct i2c_client *bi2c_client; | |
51 struct work_struct work; | |
52 char batt_data[I2C_BAT_BUFFER_LEN+1]; | |
53 unsigned int qcibat_irq; | |
54 unsigned int qcibat_gpio; | |
55 struct qci_bat_info bif; | |
56 }; | |
57 | |
58 static struct i2cbat_drv_data context; | |
59 /********************************************************************* | |
60 * Power | |
61 *********************************************************************/ | |
62 | |
63 static int qci_ac_get_prop(struct power_supply *psy, | |
64 enum power_supply_property psp, | |
65 union power_supply_propval *val) | |
66 { | |
67 int ret = 0; | |
68 switch (psp) { | |
69 case POWER_SUPPLY_PROP_ONLINE: | |
70 if (context.bif.power_flag & EC_FLAG_ADAPTER_IN) | |
71 val->intval = EC_ADAPTER_PRESENT; | |
72 else | |
73 val->intval = EC_ADAPTER_NOT_PRESENT; | |
74 break; | |
75 default: | |
76 ret = -EINVAL; | |
77 break; | |
78 } | |
79 return ret; | |
80 } | |
81 | |
82 static enum power_supply_property qci_ac_props[] = { | |
83 POWER_SUPPLY_PROP_ONLINE, | |
84 }; | |
85 | |
86 static enum power_supply_property qci_bat_props[] = { | |
87 POWER_SUPPLY_PROP_STATUS, | |
88 POWER_SUPPLY_PROP_PRESENT, | |
89 POWER_SUPPLY_PROP_HEALTH, | |
90 POWER_SUPPLY_PROP_TECHNOLOGY, | |
91 POWER_SUPPLY_PROP_VOLTAGE_AVG, | |
92 POWER_SUPPLY_PROP_CURRENT_AVG, | |
93 POWER_SUPPLY_PROP_CAPACITY, | |
94 POWER_SUPPLY_PROP_TEMP, | |
95 POWER_SUPPLY_PROP_TEMP_AMBIENT, | |
96 POWER_SUPPLY_PROP_MANUFACTURER, | |
97 POWER_SUPPLY_PROP_SERIAL_NUMBER, | |
98 POWER_SUPPLY_PROP_CHARGE_COUNTER, | |
99 }; | |
100 | |
101 static int qbat_get_status(union power_supply_propval *val) | |
102 { | |
103 if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) | |
104 val->intval = POWER_SUPPLY_STATUS_UNKNOWN; | |
105 else if (context.bif.mbchg_status & CHG_STATUS_BAT_INCHARGE) | |
106 val->intval = POWER_SUPPLY_STATUS_CHARGING; | |
107 else if (context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_FULL) | |
108 val->intval = POWER_SUPPLY_STATUS_FULL; | |
109 else | |
110 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
111 | |
112 return 0; | |
113 } | |
114 | |
115 static int qbat_get_present(union power_supply_propval *val) | |
116 { | |
117 if (context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) | |
118 val->intval = EC_BAT_PRESENT; | |
119 else | |
120 val->intval = EC_BAT_NOT_PRESENT; | |
121 return 0; | |
122 } | |
123 | |
124 static int qbat_get_health(union power_supply_propval *val) | |
125 { | |
126 if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) | |
127 val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; | |
128 else | |
129 val->intval = POWER_SUPPLY_HEALTH_GOOD; | |
130 return 0; | |
131 } | |
132 | |
133 static int qbat_get_voltage_avg(union power_supply_propval *val) | |
134 { | |
135 val->intval = (context.bif.mbat_volt_msb << 8 | | |
136 context.bif.mbat_volt_lsb); | |
137 return 0; | |
138 } | |
139 | |
140 static int qbat_get_capacity(union power_supply_propval *val) | |
141 { | |
142 if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) | |
143 val->intval = 0xFF; | |
144 else | |
145 val->intval = context.bif.mbat_rsoc; | |
146 return 0; | |
147 } | |
148 | |
149 static int qbat_get_temp_avg(union power_supply_propval *val) | |
150 { | |
151 if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) | |
152 val->intval = 0xFFFF; | |
153 else | |
154 val->intval = ((context.bif.mbat_temp_msb << 8) | | |
155 context.bif.mbat_temp_lsb) - 2731; | |
156 return 0; | |
157 } | |
158 | |
159 static int qbat_get_mfr(union power_supply_propval *val) | |
160 { | |
161 val->strval = "Unknown"; | |
162 return 0; | |
163 } | |
164 | |
165 static int qbat_get_tech(union power_supply_propval *val) | |
166 { | |
167 if ((context.bif.mbat_status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) | |
168 val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; | |
169 else | |
170 val->intval = POWER_SUPPLY_TECHNOLOGY_LION; | |
171 return 0; | |
172 } | |
173 | |
174 /********************************************************************* | |
175 * Battery properties | |
176 *********************************************************************/ | |
177 static int qbat_get_property(struct power_supply *psy, | |
178 enum power_supply_property psp, | |
179 union power_supply_propval *val) | |
180 { | |
181 int ret = 0; | |
182 switch (psp) { | |
183 case POWER_SUPPLY_PROP_STATUS: | |
184 ret = qbat_get_status(val); | |
185 break; | |
186 case POWER_SUPPLY_PROP_PRESENT: | |
187 ret = qbat_get_present(val); | |
188 break; | |
189 case POWER_SUPPLY_PROP_HEALTH: | |
190 ret = qbat_get_health(val); | |
191 break; | |
192 case POWER_SUPPLY_PROP_MANUFACTURER: | |
193 ret = qbat_get_mfr(val); | |
194 break; | |
195 case POWER_SUPPLY_PROP_TECHNOLOGY: | |
196 ret = qbat_get_tech(val); | |
197 break; | |
198 case POWER_SUPPLY_PROP_VOLTAGE_AVG: | |
199 ret = qbat_get_voltage_avg(val); | |
200 break; | |
201 case POWER_SUPPLY_PROP_CURRENT_AVG: | |
202 break; | |
203 case POWER_SUPPLY_PROP_CAPACITY: | |
204 ret = qbat_get_capacity(val); | |
205 break; | |
206 case POWER_SUPPLY_PROP_TEMP: | |
207 ret = qbat_get_temp_avg(val); | |
208 break; | |
209 case POWER_SUPPLY_PROP_TEMP_AMBIENT: | |
210 ret = qbat_get_temp_avg(val); | |
211 break; | |
212 case POWER_SUPPLY_PROP_CHARGE_COUNTER: | |
213 break; | |
214 case POWER_SUPPLY_PROP_SERIAL_NUMBER: | |
215 break; | |
216 default: | |
217 ret = -EINVAL; | |
218 break; | |
219 } | |
220 | |
221 return ret; | |
222 } | |
223 | |
224 /********************************************************************* | |
225 * Initialisation | |
226 *********************************************************************/ | |
227 | |
228 static struct power_supply qci_ac = { | |
229 .name = "ac", | |
230 .type = POWER_SUPPLY_TYPE_MAINS, | |
231 .properties = qci_ac_props, | |
232 .num_properties = ARRAY_SIZE(qci_ac_props), | |
233 .get_property = qci_ac_get_prop, | |
234 }; | |
235 | |
236 static struct power_supply qci_bat = { | |
237 .name = "battery", | |
238 .type = POWER_SUPPLY_TYPE_BATTERY, | |
239 .properties = qci_bat_props, | |
240 .num_properties = ARRAY_SIZE(qci_bat_props), | |
241 .get_property = qbat_get_property, | |
242 .use_for_apm = 1, | |
243 }; | |
244 | |
245 static irqreturn_t qbat_interrupt(int irq, void *dev_id) | |
246 { | |
247 struct i2cbat_drv_data *ibat_drv_data = dev_id; | |
248 schedule_work(&ibat_drv_data->work); | |
249 return IRQ_HANDLED; | |
250 } | |
251 | |
252 static int qci_get_bat_info(struct i2c_client *client, char *ec_data) | |
253 { | |
254 struct i2c_msg bat_msg; | |
255 bat_msg.addr = client->addr; | |
256 bat_msg.flags = I2C_M_RD; | |
257 bat_msg.len = I2C_BAT_BUFFER_LEN; | |
258 bat_msg.buf = ec_data; | |
259 return i2c_transfer(client->adapter, &bat_msg, 1); | |
260 } | |
261 | |
262 static void qbat_work(struct work_struct *_work) | |
263 { | |
264 struct i2cbat_drv_data *ibat_drv_data = | |
265 container_of(_work, struct i2cbat_drv_data, work); | |
266 struct i2c_client *ibatclient = ibat_drv_data->bi2c_client; | |
267 | |
268 qci_get_bat_info(ibatclient, ibat_drv_data->batt_data); | |
269 memcpy(&context.bif, | |
270 ibat_drv_data->batt_data, | |
271 sizeof(struct qci_bat_info)); | |
272 power_supply_changed(&qci_ac); | |
273 power_supply_changed(&qci_bat); | |
274 } | |
275 | |
276 static struct platform_device *bat_pdev; | |
277 | |
278 static int __init qbat_init(void) | |
279 { | |
280 int err = 0; | |
281 | |
282 context.bi2c_client = wpce_get_i2c_client(); | |
Mandeep Singh Baines
2010/05/11 16:59:08
It would be better if wpce775x exported a high-lev
| |
283 if (context.bi2c_client == NULL) | |
284 return -1; | |
285 | |
286 i2c_set_clientdata(context.bi2c_client, &context); | |
287 context.qcibat_gpio = context.bi2c_client->irq; | |
288 | |
289 /*battery device register*/ | |
290 bat_pdev = platform_device_register_simple("battery", 0, NULL, 0); | |
291 if (IS_ERR(bat_pdev)) | |
292 return PTR_ERR(bat_pdev); | |
293 | |
294 err = power_supply_register(&bat_pdev->dev, &qci_ac); | |
295 if (err) | |
296 goto ac_failed; | |
297 | |
298 qci_bat.name = bat_pdev->name; | |
299 err = power_supply_register(&bat_pdev->dev, &qci_bat); | |
300 if (err) | |
301 goto battery_failed; | |
302 | |
303 /*battery irq configure*/ | |
304 INIT_WORK(&context.work, qbat_work); | |
305 err = gpio_request(context.qcibat_gpio, "qci-bat"); | |
306 if (err) { | |
307 dev_err(&context.bi2c_client->dev, | |
308 "[BAT] err gpio request\n"); | |
309 goto gpio_request_fail; | |
310 } | |
311 context.qcibat_irq = gpio_to_irq(context.qcibat_gpio); | |
312 err = request_irq(context.qcibat_irq, qbat_interrupt, | |
313 IRQF_TRIGGER_FALLING, BATTERY_ID_NAME, &context); | |
314 if (err) { | |
315 dev_err(&context.bi2c_client->dev, | |
316 "[BAT] unable to get IRQ\n"); | |
317 goto request_irq_fail; | |
318 } | |
319 err = qci_get_bat_info(context.bi2c_client, context.batt_data); | |
320 | |
321 goto success; | |
322 | |
323 request_irq_fail: | |
324 gpio_free(context.qcibat_gpio); | |
325 | |
326 gpio_request_fail: | |
327 power_supply_unregister(&qci_bat); | |
328 | |
329 battery_failed: | |
330 power_supply_unregister(&qci_ac); | |
331 | |
332 ac_failed: | |
333 platform_device_unregister(bat_pdev); | |
334 | |
335 i2c_set_clientdata(context.bi2c_client, NULL); | |
336 success: | |
337 return err; | |
338 } | |
339 | |
340 static void __exit qbat_exit(void) | |
341 { | |
342 free_irq(context.qcibat_irq, &context); | |
343 gpio_free(context.qcibat_gpio); | |
344 power_supply_unregister(&qci_bat); | |
345 power_supply_unregister(&qci_ac); | |
346 platform_device_unregister(bat_pdev); | |
347 i2c_set_clientdata(context.bi2c_client, NULL); | |
348 } | |
349 | |
350 late_initcall(qbat_init); | |
Mandeep Singh Baines
2010/05/11 16:59:08
Not sure this is a good idea. Another way to guara
| |
351 module_exit(qbat_exit); | |
352 | |
353 MODULE_AUTHOR("Quanta Computer Inc."); | |
354 MODULE_DESCRIPTION("Quanta Embedded Controller I2C Battery Driver"); | |
355 MODULE_LICENSE("GPL v2"); | |
356 | |
OLD | NEW |