]>
Commit | Line | Data |
---|---|---|
a7640bfa | 1 | /* |
3ddca062 | 2 | * Gas Gauge driver for SBS Compliant Batteries |
a7640bfa RK |
3 | * |
4 | * Copyright (c) 2010, NVIDIA Corporation. | |
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, but WITHOUT | |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
14 | * more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along | |
17 | * with this program; if not, write to the Free Software Foundation, Inc., | |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
19 | */ | |
20 | ||
21 | #include <linux/init.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/kernel.h> | |
24 | #include <linux/err.h> | |
25 | #include <linux/power_supply.h> | |
26 | #include <linux/i2c.h> | |
27 | #include <linux/slab.h> | |
bb879101 RK |
28 | #include <linux/interrupt.h> |
29 | #include <linux/gpio.h> | |
075ed03c | 30 | #include <linux/of.h> |
bb879101 | 31 | |
3ddca062 | 32 | #include <linux/power/sbs-battery.h> |
a7640bfa RK |
33 | |
34 | enum { | |
35 | REG_MANUFACTURER_DATA, | |
36 | REG_TEMPERATURE, | |
37 | REG_VOLTAGE, | |
38 | REG_CURRENT, | |
39 | REG_CAPACITY, | |
40 | REG_TIME_TO_EMPTY, | |
41 | REG_TIME_TO_FULL, | |
42 | REG_STATUS, | |
43 | REG_CYCLE_COUNT, | |
d3ab61ec RK |
44 | REG_SERIAL_NUMBER, |
45 | REG_REMAINING_CAPACITY, | |
51d07566 | 46 | REG_REMAINING_CAPACITY_CHARGE, |
d3ab61ec | 47 | REG_FULL_CHARGE_CAPACITY, |
51d07566 | 48 | REG_FULL_CHARGE_CAPACITY_CHARGE, |
d3ab61ec | 49 | REG_DESIGN_CAPACITY, |
51d07566 | 50 | REG_DESIGN_CAPACITY_CHARGE, |
d3ab61ec | 51 | REG_DESIGN_VOLTAGE, |
9ea89402 CYC |
52 | REG_MANUFACTURER, |
53 | REG_MODEL_NAME, | |
a7640bfa RK |
54 | }; |
55 | ||
51d07566 RK |
56 | /* Battery Mode defines */ |
57 | #define BATTERY_MODE_OFFSET 0x03 | |
58 | #define BATTERY_MODE_MASK 0x8000 | |
3ddca062 | 59 | enum sbs_battery_mode { |
51d07566 RK |
60 | BATTERY_MODE_AMPS, |
61 | BATTERY_MODE_WATTS | |
62 | }; | |
63 | ||
a7640bfa RK |
64 | /* manufacturer access defines */ |
65 | #define MANUFACTURER_ACCESS_STATUS 0x0006 | |
66 | #define MANUFACTURER_ACCESS_SLEEP 0x0011 | |
67 | ||
68 | /* battery status value bits */ | |
d3ab61ec | 69 | #define BATTERY_DISCHARGING 0x40 |
a7640bfa RK |
70 | #define BATTERY_FULL_CHARGED 0x20 |
71 | #define BATTERY_FULL_DISCHARGED 0x10 | |
72 | ||
9ea89402 | 73 | /* min_value and max_value are only valid for numerical data */ |
3ddca062 | 74 | #define SBS_DATA(_psp, _addr, _min_value, _max_value) { \ |
a7640bfa RK |
75 | .psp = _psp, \ |
76 | .addr = _addr, \ | |
77 | .min_value = _min_value, \ | |
78 | .max_value = _max_value, \ | |
79 | } | |
80 | ||
3ddca062 | 81 | static const struct chip_data { |
a7640bfa RK |
82 | enum power_supply_property psp; |
83 | u8 addr; | |
84 | int min_value; | |
85 | int max_value; | |
3ddca062 | 86 | } sbs_data[] = { |
a7640bfa | 87 | [REG_MANUFACTURER_DATA] = |
3ddca062 | 88 | SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), |
a7640bfa | 89 | [REG_TEMPERATURE] = |
3ddca062 | 90 | SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), |
a7640bfa | 91 | [REG_VOLTAGE] = |
3ddca062 | 92 | SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), |
a7640bfa | 93 | [REG_CURRENT] = |
3ddca062 | 94 | SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), |
a7640bfa | 95 | [REG_CAPACITY] = |
b1f092f6 | 96 | SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0D, 0, 100), |
d3ab61ec | 97 | [REG_REMAINING_CAPACITY] = |
3ddca062 | 98 | SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535), |
51d07566 | 99 | [REG_REMAINING_CAPACITY_CHARGE] = |
3ddca062 | 100 | SBS_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535), |
d3ab61ec | 101 | [REG_FULL_CHARGE_CAPACITY] = |
3ddca062 | 102 | SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), |
51d07566 | 103 | [REG_FULL_CHARGE_CAPACITY_CHARGE] = |
3ddca062 | 104 | SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), |
a7640bfa | 105 | [REG_TIME_TO_EMPTY] = |
3ddca062 | 106 | SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535), |
a7640bfa | 107 | [REG_TIME_TO_FULL] = |
3ddca062 | 108 | SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535), |
a7640bfa | 109 | [REG_STATUS] = |
3ddca062 | 110 | SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), |
a7640bfa | 111 | [REG_CYCLE_COUNT] = |
3ddca062 | 112 | SBS_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535), |
d3ab61ec | 113 | [REG_DESIGN_CAPACITY] = |
3ddca062 | 114 | SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535), |
51d07566 | 115 | [REG_DESIGN_CAPACITY_CHARGE] = |
3ddca062 | 116 | SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535), |
d3ab61ec | 117 | [REG_DESIGN_VOLTAGE] = |
3ddca062 | 118 | SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535), |
a7640bfa | 119 | [REG_SERIAL_NUMBER] = |
3ddca062 | 120 | SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), |
9ea89402 CYC |
121 | /* Properties of type `const char *' */ |
122 | [REG_MANUFACTURER] = | |
123 | SBS_DATA(POWER_SUPPLY_PROP_MANUFACTURER, 0x20, 0, 65535), | |
124 | [REG_MODEL_NAME] = | |
125 | SBS_DATA(POWER_SUPPLY_PROP_MODEL_NAME, 0x21, 0, 65535) | |
a7640bfa RK |
126 | }; |
127 | ||
3ddca062 | 128 | static enum power_supply_property sbs_properties[] = { |
a7640bfa RK |
129 | POWER_SUPPLY_PROP_STATUS, |
130 | POWER_SUPPLY_PROP_HEALTH, | |
131 | POWER_SUPPLY_PROP_PRESENT, | |
132 | POWER_SUPPLY_PROP_TECHNOLOGY, | |
133 | POWER_SUPPLY_PROP_CYCLE_COUNT, | |
134 | POWER_SUPPLY_PROP_VOLTAGE_NOW, | |
135 | POWER_SUPPLY_PROP_CURRENT_NOW, | |
136 | POWER_SUPPLY_PROP_CAPACITY, | |
137 | POWER_SUPPLY_PROP_TEMP, | |
138 | POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, | |
139 | POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, | |
140 | POWER_SUPPLY_PROP_SERIAL_NUMBER, | |
d3ab61ec RK |
141 | POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
142 | POWER_SUPPLY_PROP_ENERGY_NOW, | |
143 | POWER_SUPPLY_PROP_ENERGY_FULL, | |
144 | POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, | |
51d07566 RK |
145 | POWER_SUPPLY_PROP_CHARGE_NOW, |
146 | POWER_SUPPLY_PROP_CHARGE_FULL, | |
147 | POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, | |
9ea89402 CYC |
148 | /* Properties of type `const char *' */ |
149 | POWER_SUPPLY_PROP_MANUFACTURER, | |
150 | POWER_SUPPLY_PROP_MODEL_NAME | |
a7640bfa RK |
151 | }; |
152 | ||
3ddca062 | 153 | struct sbs_info { |
bb879101 RK |
154 | struct i2c_client *client; |
155 | struct power_supply power_supply; | |
3ddca062 | 156 | struct sbs_platform_data *pdata; |
bb879101 RK |
157 | bool is_present; |
158 | bool gpio_detect; | |
159 | bool enable_detection; | |
160 | int irq; | |
58ddafae RK |
161 | int last_state; |
162 | int poll_time; | |
163 | struct delayed_work work; | |
164 | int ignore_changes; | |
a7640bfa RK |
165 | }; |
166 | ||
9ea89402 CYC |
167 | static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; |
168 | static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1]; | |
169 | ||
3ddca062 | 170 | static int sbs_read_word_data(struct i2c_client *client, u8 address) |
d3ab61ec | 171 | { |
3ddca062 | 172 | struct sbs_info *chip = i2c_get_clientdata(client); |
ff28fcef RK |
173 | s32 ret = 0; |
174 | int retries = 1; | |
175 | ||
3ddca062 RK |
176 | if (chip->pdata) |
177 | retries = max(chip->pdata->i2c_retry_count + 1, 1); | |
ff28fcef RK |
178 | |
179 | while (retries > 0) { | |
180 | ret = i2c_smbus_read_word_data(client, address); | |
181 | if (ret >= 0) | |
182 | break; | |
183 | retries--; | |
184 | } | |
d3ab61ec | 185 | |
d3ab61ec | 186 | if (ret < 0) { |
a7d9ace4 | 187 | dev_dbg(&client->dev, |
d3ab61ec RK |
188 | "%s: i2c read at address 0x%x failed\n", |
189 | __func__, address); | |
190 | return ret; | |
191 | } | |
ff28fcef | 192 | |
d3ab61ec RK |
193 | return le16_to_cpu(ret); |
194 | } | |
195 | ||
9ea89402 CYC |
196 | static int sbs_read_string_data(struct i2c_client *client, u8 address, |
197 | char *values) | |
198 | { | |
199 | struct sbs_info *chip = i2c_get_clientdata(client); | |
200 | s32 ret = 0, block_length = 0; | |
201 | int retries_length = 1, retries_block = 1; | |
202 | u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; | |
203 | ||
204 | if (chip->pdata) { | |
205 | retries_length = max(chip->pdata->i2c_retry_count + 1, 1); | |
206 | retries_block = max(chip->pdata->i2c_retry_count + 1, 1); | |
207 | } | |
208 | ||
209 | /* Adapter needs to support these two functions */ | |
210 | if (!i2c_check_functionality(client->adapter, | |
211 | I2C_FUNC_SMBUS_BYTE_DATA | | |
212 | I2C_FUNC_SMBUS_I2C_BLOCK)){ | |
213 | return -ENODEV; | |
214 | } | |
215 | ||
216 | /* Get the length of block data */ | |
217 | while (retries_length > 0) { | |
218 | ret = i2c_smbus_read_byte_data(client, address); | |
219 | if (ret >= 0) | |
220 | break; | |
221 | retries_length--; | |
222 | } | |
223 | ||
224 | if (ret < 0) { | |
225 | dev_dbg(&client->dev, | |
226 | "%s: i2c read at address 0x%x failed\n", | |
227 | __func__, address); | |
228 | return ret; | |
229 | } | |
230 | ||
231 | /* block_length does not include NULL terminator */ | |
232 | block_length = ret; | |
233 | if (block_length > I2C_SMBUS_BLOCK_MAX) { | |
234 | dev_err(&client->dev, | |
235 | "%s: Returned block_length is longer than 0x%x\n", | |
236 | __func__, I2C_SMBUS_BLOCK_MAX); | |
237 | return -EINVAL; | |
238 | } | |
239 | ||
240 | /* Get the block data */ | |
241 | while (retries_block > 0) { | |
242 | ret = i2c_smbus_read_i2c_block_data( | |
243 | client, address, | |
244 | block_length + 1, block_buffer); | |
245 | if (ret >= 0) | |
246 | break; | |
247 | retries_block--; | |
248 | } | |
249 | ||
250 | if (ret < 0) { | |
251 | dev_dbg(&client->dev, | |
252 | "%s: i2c read at address 0x%x failed\n", | |
253 | __func__, address); | |
254 | return ret; | |
255 | } | |
256 | ||
257 | /* block_buffer[0] == block_length */ | |
258 | memcpy(values, block_buffer + 1, block_length); | |
259 | values[block_length] = '\0'; | |
260 | ||
261 | return le16_to_cpu(ret); | |
262 | } | |
263 | ||
3ddca062 | 264 | static int sbs_write_word_data(struct i2c_client *client, u8 address, |
d3ab61ec RK |
265 | u16 value) |
266 | { | |
3ddca062 | 267 | struct sbs_info *chip = i2c_get_clientdata(client); |
ff28fcef RK |
268 | s32 ret = 0; |
269 | int retries = 1; | |
270 | ||
3ddca062 RK |
271 | if (chip->pdata) |
272 | retries = max(chip->pdata->i2c_retry_count + 1, 1); | |
ff28fcef RK |
273 | |
274 | while (retries > 0) { | |
275 | ret = i2c_smbus_write_word_data(client, address, | |
276 | le16_to_cpu(value)); | |
277 | if (ret >= 0) | |
278 | break; | |
279 | retries--; | |
280 | } | |
d3ab61ec | 281 | |
d3ab61ec | 282 | if (ret < 0) { |
a7d9ace4 | 283 | dev_dbg(&client->dev, |
d3ab61ec RK |
284 | "%s: i2c write to address 0x%x failed\n", |
285 | __func__, address); | |
286 | return ret; | |
287 | } | |
ff28fcef | 288 | |
d3ab61ec RK |
289 | return 0; |
290 | } | |
291 | ||
3ddca062 | 292 | static int sbs_get_battery_presence_and_health( |
a7640bfa RK |
293 | struct i2c_client *client, enum power_supply_property psp, |
294 | union power_supply_propval *val) | |
295 | { | |
296 | s32 ret; | |
3ddca062 | 297 | struct sbs_info *chip = i2c_get_clientdata(client); |
bb879101 RK |
298 | |
299 | if (psp == POWER_SUPPLY_PROP_PRESENT && | |
3ddca062 RK |
300 | chip->gpio_detect) { |
301 | ret = gpio_get_value(chip->pdata->battery_detect); | |
302 | if (ret == chip->pdata->battery_detect_present) | |
bb879101 RK |
303 | val->intval = 1; |
304 | else | |
305 | val->intval = 0; | |
3ddca062 | 306 | chip->is_present = val->intval; |
bb879101 RK |
307 | return ret; |
308 | } | |
a7640bfa RK |
309 | |
310 | /* Write to ManufacturerAccess with | |
311 | * ManufacturerAccess command and then | |
312 | * read the status */ | |
3ddca062 RK |
313 | ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, |
314 | MANUFACTURER_ACCESS_STATUS); | |
bb879101 RK |
315 | if (ret < 0) { |
316 | if (psp == POWER_SUPPLY_PROP_PRESENT) | |
317 | val->intval = 0; /* battery removed */ | |
d3ab61ec | 318 | return ret; |
bb879101 | 319 | } |
a7640bfa | 320 | |
3ddca062 | 321 | ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); |
a7d9ace4 RK |
322 | if (ret < 0) |
323 | return ret; | |
324 | ||
3ddca062 RK |
325 | if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value || |
326 | ret > sbs_data[REG_MANUFACTURER_DATA].max_value) { | |
a7640bfa RK |
327 | val->intval = 0; |
328 | return 0; | |
329 | } | |
330 | ||
331 | /* Mask the upper nibble of 2nd byte and | |
332 | * lower byte of response then | |
333 | * shift the result by 8 to get status*/ | |
334 | ret &= 0x0F00; | |
335 | ret >>= 8; | |
336 | if (psp == POWER_SUPPLY_PROP_PRESENT) { | |
337 | if (ret == 0x0F) | |
338 | /* battery removed */ | |
339 | val->intval = 0; | |
340 | else | |
341 | val->intval = 1; | |
342 | } else if (psp == POWER_SUPPLY_PROP_HEALTH) { | |
343 | if (ret == 0x09) | |
344 | val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; | |
345 | else if (ret == 0x0B) | |
346 | val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; | |
347 | else if (ret == 0x0C) | |
348 | val->intval = POWER_SUPPLY_HEALTH_DEAD; | |
349 | else | |
350 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | |
351 | } | |
352 | ||
353 | return 0; | |
354 | } | |
355 | ||
3ddca062 | 356 | static int sbs_get_battery_property(struct i2c_client *client, |
a7640bfa RK |
357 | int reg_offset, enum power_supply_property psp, |
358 | union power_supply_propval *val) | |
359 | { | |
3ddca062 | 360 | struct sbs_info *chip = i2c_get_clientdata(client); |
a7640bfa RK |
361 | s32 ret; |
362 | ||
3ddca062 | 363 | ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); |
d3ab61ec RK |
364 | if (ret < 0) |
365 | return ret; | |
366 | ||
367 | /* returned values are 16 bit */ | |
3ddca062 | 368 | if (sbs_data[reg_offset].min_value < 0) |
d3ab61ec | 369 | ret = (s16)ret; |
a7640bfa | 370 | |
3ddca062 RK |
371 | if (ret >= sbs_data[reg_offset].min_value && |
372 | ret <= sbs_data[reg_offset].max_value) { | |
a7640bfa | 373 | val->intval = ret; |
58ddafae RK |
374 | if (psp != POWER_SUPPLY_PROP_STATUS) |
375 | return 0; | |
376 | ||
377 | if (ret & BATTERY_FULL_CHARGED) | |
378 | val->intval = POWER_SUPPLY_STATUS_FULL; | |
379 | else if (ret & BATTERY_FULL_DISCHARGED) | |
380 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; | |
381 | else if (ret & BATTERY_DISCHARGING) | |
382 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | |
383 | else | |
384 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | |
385 | ||
3ddca062 RK |
386 | if (chip->poll_time == 0) |
387 | chip->last_state = val->intval; | |
388 | else if (chip->last_state != val->intval) { | |
389 | cancel_delayed_work_sync(&chip->work); | |
390 | power_supply_changed(&chip->power_supply); | |
391 | chip->poll_time = 0; | |
a7640bfa | 392 | } |
a7640bfa RK |
393 | } else { |
394 | if (psp == POWER_SUPPLY_PROP_STATUS) | |
395 | val->intval = POWER_SUPPLY_STATUS_UNKNOWN; | |
396 | else | |
397 | val->intval = 0; | |
398 | } | |
399 | ||
400 | return 0; | |
401 | } | |
402 | ||
9ea89402 CYC |
403 | static int sbs_get_battery_string_property(struct i2c_client *client, |
404 | int reg_offset, enum power_supply_property psp, char *val) | |
405 | { | |
406 | s32 ret; | |
407 | ||
408 | ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val); | |
409 | ||
410 | if (ret < 0) | |
411 | return ret; | |
412 | ||
413 | return 0; | |
414 | } | |
415 | ||
3ddca062 | 416 | static void sbs_unit_adjustment(struct i2c_client *client, |
d3ab61ec RK |
417 | enum power_supply_property psp, union power_supply_propval *val) |
418 | { | |
419 | #define BASE_UNIT_CONVERSION 1000 | |
420 | #define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION) | |
909a78b3 BL |
421 | #define TIME_UNIT_CONVERSION 60 |
422 | #define TEMP_KELVIN_TO_CELSIUS 2731 | |
d3ab61ec RK |
423 | switch (psp) { |
424 | case POWER_SUPPLY_PROP_ENERGY_NOW: | |
425 | case POWER_SUPPLY_PROP_ENERGY_FULL: | |
426 | case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: | |
3ddca062 | 427 | /* sbs provides energy in units of 10mWh. |
909a78b3 BL |
428 | * Convert to µWh |
429 | */ | |
d3ab61ec RK |
430 | val->intval *= BATTERY_MODE_CAP_MULT_WATT; |
431 | break; | |
432 | ||
433 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | |
434 | case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: | |
435 | case POWER_SUPPLY_PROP_CURRENT_NOW: | |
51d07566 RK |
436 | case POWER_SUPPLY_PROP_CHARGE_NOW: |
437 | case POWER_SUPPLY_PROP_CHARGE_FULL: | |
438 | case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: | |
d3ab61ec RK |
439 | val->intval *= BASE_UNIT_CONVERSION; |
440 | break; | |
441 | ||
442 | case POWER_SUPPLY_PROP_TEMP: | |
3ddca062 | 443 | /* sbs provides battery temperature in 0.1K |
909a78b3 BL |
444 | * so convert it to 0.1°C |
445 | */ | |
446 | val->intval -= TEMP_KELVIN_TO_CELSIUS; | |
d3ab61ec RK |
447 | break; |
448 | ||
449 | case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: | |
450 | case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: | |
3ddca062 | 451 | /* sbs provides time to empty and time to full in minutes. |
909a78b3 BL |
452 | * Convert to seconds |
453 | */ | |
d3ab61ec RK |
454 | val->intval *= TIME_UNIT_CONVERSION; |
455 | break; | |
456 | ||
457 | default: | |
458 | dev_dbg(&client->dev, | |
459 | "%s: no need for unit conversion %d\n", __func__, psp); | |
460 | } | |
461 | } | |
462 | ||
3ddca062 RK |
463 | static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client, |
464 | enum sbs_battery_mode mode) | |
51d07566 RK |
465 | { |
466 | int ret, original_val; | |
467 | ||
3ddca062 | 468 | original_val = sbs_read_word_data(client, BATTERY_MODE_OFFSET); |
51d07566 RK |
469 | if (original_val < 0) |
470 | return original_val; | |
471 | ||
472 | if ((original_val & BATTERY_MODE_MASK) == mode) | |
473 | return mode; | |
474 | ||
475 | if (mode == BATTERY_MODE_AMPS) | |
476 | ret = original_val & ~BATTERY_MODE_MASK; | |
477 | else | |
478 | ret = original_val | BATTERY_MODE_MASK; | |
479 | ||
3ddca062 | 480 | ret = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret); |
51d07566 RK |
481 | if (ret < 0) |
482 | return ret; | |
483 | ||
484 | return original_val & BATTERY_MODE_MASK; | |
485 | } | |
486 | ||
3ddca062 | 487 | static int sbs_get_battery_capacity(struct i2c_client *client, |
d3ab61ec | 488 | int reg_offset, enum power_supply_property psp, |
a7640bfa RK |
489 | union power_supply_propval *val) |
490 | { | |
491 | s32 ret; | |
3ddca062 | 492 | enum sbs_battery_mode mode = BATTERY_MODE_WATTS; |
51d07566 RK |
493 | |
494 | if (power_supply_is_amp_property(psp)) | |
495 | mode = BATTERY_MODE_AMPS; | |
496 | ||
3ddca062 | 497 | mode = sbs_set_battery_mode(client, mode); |
51d07566 RK |
498 | if (mode < 0) |
499 | return mode; | |
a7640bfa | 500 | |
3ddca062 | 501 | ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); |
d3ab61ec RK |
502 | if (ret < 0) |
503 | return ret; | |
a7640bfa | 504 | |
d3ab61ec | 505 | if (psp == POWER_SUPPLY_PROP_CAPACITY) { |
3ddca062 | 506 | /* sbs spec says that this can be >100 % |
d3ab61ec RK |
507 | * even if max value is 100 % */ |
508 | val->intval = min(ret, 100); | |
509 | } else | |
510 | val->intval = ret; | |
511 | ||
3ddca062 | 512 | ret = sbs_set_battery_mode(client, mode); |
51d07566 RK |
513 | if (ret < 0) |
514 | return ret; | |
515 | ||
d3ab61ec RK |
516 | return 0; |
517 | } | |
518 | ||
3ddca062 RK |
519 | static char sbs_serial[5]; |
520 | static int sbs_get_battery_serial_number(struct i2c_client *client, | |
d3ab61ec RK |
521 | union power_supply_propval *val) |
522 | { | |
523 | int ret; | |
524 | ||
3ddca062 | 525 | ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr); |
d3ab61ec RK |
526 | if (ret < 0) |
527 | return ret; | |
528 | ||
3ddca062 RK |
529 | ret = sprintf(sbs_serial, "%04x", ret); |
530 | val->strval = sbs_serial; | |
a7640bfa RK |
531 | |
532 | return 0; | |
533 | } | |
534 | ||
3ddca062 | 535 | static int sbs_get_property_index(struct i2c_client *client, |
51d07566 RK |
536 | enum power_supply_property psp) |
537 | { | |
538 | int count; | |
3ddca062 RK |
539 | for (count = 0; count < ARRAY_SIZE(sbs_data); count++) |
540 | if (psp == sbs_data[count].psp) | |
51d07566 RK |
541 | return count; |
542 | ||
543 | dev_warn(&client->dev, | |
544 | "%s: Invalid Property - %d\n", __func__, psp); | |
545 | ||
546 | return -EINVAL; | |
547 | } | |
548 | ||
3ddca062 | 549 | static int sbs_get_property(struct power_supply *psy, |
a7640bfa RK |
550 | enum power_supply_property psp, |
551 | union power_supply_propval *val) | |
552 | { | |
bb879101 | 553 | int ret = 0; |
3ddca062 RK |
554 | struct sbs_info *chip = container_of(psy, |
555 | struct sbs_info, power_supply); | |
556 | struct i2c_client *client = chip->client; | |
a7640bfa RK |
557 | |
558 | switch (psp) { | |
559 | case POWER_SUPPLY_PROP_PRESENT: | |
560 | case POWER_SUPPLY_PROP_HEALTH: | |
3ddca062 | 561 | ret = sbs_get_battery_presence_and_health(client, psp, val); |
a7d9ace4 RK |
562 | if (psp == POWER_SUPPLY_PROP_PRESENT) |
563 | return 0; | |
a7640bfa RK |
564 | break; |
565 | ||
566 | case POWER_SUPPLY_PROP_TECHNOLOGY: | |
567 | val->intval = POWER_SUPPLY_TECHNOLOGY_LION; | |
5da50988 | 568 | goto done; /* don't trigger power_supply_changed()! */ |
a7640bfa | 569 | |
d3ab61ec RK |
570 | case POWER_SUPPLY_PROP_ENERGY_NOW: |
571 | case POWER_SUPPLY_PROP_ENERGY_FULL: | |
572 | case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: | |
51d07566 RK |
573 | case POWER_SUPPLY_PROP_CHARGE_NOW: |
574 | case POWER_SUPPLY_PROP_CHARGE_FULL: | |
575 | case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: | |
a7640bfa | 576 | case POWER_SUPPLY_PROP_CAPACITY: |
3ddca062 | 577 | ret = sbs_get_property_index(client, psp); |
bb879101 RK |
578 | if (ret < 0) |
579 | break; | |
d3ab61ec | 580 | |
3ddca062 | 581 | ret = sbs_get_battery_capacity(client, ret, psp, val); |
d3ab61ec RK |
582 | break; |
583 | ||
584 | case POWER_SUPPLY_PROP_SERIAL_NUMBER: | |
3ddca062 | 585 | ret = sbs_get_battery_serial_number(client, val); |
a7640bfa RK |
586 | break; |
587 | ||
588 | case POWER_SUPPLY_PROP_STATUS: | |
589 | case POWER_SUPPLY_PROP_CYCLE_COUNT: | |
590 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | |
591 | case POWER_SUPPLY_PROP_CURRENT_NOW: | |
592 | case POWER_SUPPLY_PROP_TEMP: | |
593 | case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: | |
594 | case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: | |
d3ab61ec | 595 | case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
3ddca062 | 596 | ret = sbs_get_property_index(client, psp); |
bb879101 RK |
597 | if (ret < 0) |
598 | break; | |
d3ab61ec | 599 | |
3ddca062 | 600 | ret = sbs_get_battery_property(client, ret, psp, val); |
a7640bfa RK |
601 | break; |
602 | ||
9ea89402 CYC |
603 | case POWER_SUPPLY_PROP_MODEL_NAME: |
604 | ret = sbs_get_property_index(client, psp); | |
605 | if (ret < 0) | |
606 | break; | |
607 | ||
608 | ret = sbs_get_battery_string_property(client, ret, psp, | |
609 | model_name); | |
610 | val->strval = model_name; | |
611 | break; | |
612 | ||
613 | case POWER_SUPPLY_PROP_MANUFACTURER: | |
614 | ret = sbs_get_property_index(client, psp); | |
615 | if (ret < 0) | |
616 | break; | |
617 | ||
618 | ret = sbs_get_battery_string_property(client, ret, psp, | |
619 | manufacturer); | |
620 | val->strval = manufacturer; | |
621 | break; | |
622 | ||
a7640bfa RK |
623 | default: |
624 | dev_err(&client->dev, | |
625 | "%s: INVALID property\n", __func__); | |
626 | return -EINVAL; | |
627 | } | |
628 | ||
3ddca062 | 629 | if (!chip->enable_detection) |
bb879101 RK |
630 | goto done; |
631 | ||
3ddca062 RK |
632 | if (!chip->gpio_detect && |
633 | chip->is_present != (ret >= 0)) { | |
634 | chip->is_present = (ret >= 0); | |
635 | power_supply_changed(&chip->power_supply); | |
bb879101 RK |
636 | } |
637 | ||
638 | done: | |
639 | if (!ret) { | |
640 | /* Convert units to match requirements for power supply class */ | |
3ddca062 | 641 | sbs_unit_adjustment(client, psp, val); |
bb879101 | 642 | } |
d3ab61ec | 643 | |
a7640bfa | 644 | dev_dbg(&client->dev, |
a7d9ace4 RK |
645 | "%s: property = %d, value = %x\n", __func__, psp, val->intval); |
646 | ||
3ddca062 | 647 | if (ret && chip->is_present) |
a7d9ace4 RK |
648 | return ret; |
649 | ||
650 | /* battery not present, so return NODATA for properties */ | |
651 | if (ret) | |
652 | return -ENODATA; | |
a7640bfa | 653 | |
a7d9ace4 | 654 | return 0; |
a7640bfa RK |
655 | } |
656 | ||
3ddca062 | 657 | static irqreturn_t sbs_irq(int irq, void *devid) |
bb879101 RK |
658 | { |
659 | struct power_supply *battery = devid; | |
660 | ||
661 | power_supply_changed(battery); | |
662 | ||
663 | return IRQ_HANDLED; | |
664 | } | |
665 | ||
3ddca062 | 666 | static void sbs_external_power_changed(struct power_supply *psy) |
58ddafae | 667 | { |
3ddca062 | 668 | struct sbs_info *chip; |
58ddafae | 669 | |
3ddca062 | 670 | chip = container_of(psy, struct sbs_info, power_supply); |
58ddafae | 671 | |
3ddca062 RK |
672 | if (chip->ignore_changes > 0) { |
673 | chip->ignore_changes--; | |
58ddafae RK |
674 | return; |
675 | } | |
676 | ||
677 | /* cancel outstanding work */ | |
3ddca062 | 678 | cancel_delayed_work_sync(&chip->work); |
58ddafae | 679 | |
3ddca062 RK |
680 | schedule_delayed_work(&chip->work, HZ); |
681 | chip->poll_time = chip->pdata->poll_retry_count; | |
58ddafae RK |
682 | } |
683 | ||
3ddca062 | 684 | static void sbs_delayed_work(struct work_struct *work) |
58ddafae | 685 | { |
3ddca062 | 686 | struct sbs_info *chip; |
58ddafae RK |
687 | s32 ret; |
688 | ||
3ddca062 | 689 | chip = container_of(work, struct sbs_info, work.work); |
58ddafae | 690 | |
3ddca062 | 691 | ret = sbs_read_word_data(chip->client, sbs_data[REG_STATUS].addr); |
58ddafae RK |
692 | /* if the read failed, give up on this work */ |
693 | if (ret < 0) { | |
3ddca062 | 694 | chip->poll_time = 0; |
58ddafae RK |
695 | return; |
696 | } | |
697 | ||
698 | if (ret & BATTERY_FULL_CHARGED) | |
699 | ret = POWER_SUPPLY_STATUS_FULL; | |
700 | else if (ret & BATTERY_FULL_DISCHARGED) | |
701 | ret = POWER_SUPPLY_STATUS_NOT_CHARGING; | |
702 | else if (ret & BATTERY_DISCHARGING) | |
703 | ret = POWER_SUPPLY_STATUS_DISCHARGING; | |
704 | else | |
705 | ret = POWER_SUPPLY_STATUS_CHARGING; | |
706 | ||
3ddca062 RK |
707 | if (chip->last_state != ret) { |
708 | chip->poll_time = 0; | |
709 | power_supply_changed(&chip->power_supply); | |
58ddafae RK |
710 | return; |
711 | } | |
3ddca062 RK |
712 | if (chip->poll_time > 0) { |
713 | schedule_delayed_work(&chip->work, HZ); | |
714 | chip->poll_time--; | |
58ddafae RK |
715 | return; |
716 | } | |
717 | } | |
718 | ||
6c75ea1e RK |
719 | #if defined(CONFIG_OF) |
720 | ||
721 | #include <linux/of_device.h> | |
722 | #include <linux/of_gpio.h> | |
723 | ||
3ddca062 RK |
724 | static const struct of_device_id sbs_dt_ids[] = { |
725 | { .compatible = "sbs,sbs-battery" }, | |
6c75ea1e RK |
726 | { .compatible = "ti,bq20z75" }, |
727 | { } | |
728 | }; | |
62df3935 | 729 | MODULE_DEVICE_TABLE(of, sbs_dt_ids); |
6c75ea1e | 730 | |
3ddca062 | 731 | static struct sbs_platform_data *sbs_of_populate_pdata( |
6c75ea1e RK |
732 | struct i2c_client *client) |
733 | { | |
734 | struct device_node *of_node = client->dev.of_node; | |
3ddca062 | 735 | struct sbs_platform_data *pdata = client->dev.platform_data; |
6c75ea1e RK |
736 | enum of_gpio_flags gpio_flags; |
737 | int rc; | |
738 | u32 prop; | |
739 | ||
740 | /* verify this driver matches this device */ | |
741 | if (!of_node) | |
742 | return NULL; | |
743 | ||
744 | /* if platform data is set, honor it */ | |
745 | if (pdata) | |
746 | return pdata; | |
747 | ||
748 | /* first make sure at least one property is set, otherwise | |
749 | * it won't change behavior from running without pdata. | |
750 | */ | |
3ddca062 RK |
751 | if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && |
752 | !of_get_property(of_node, "sbs,poll-retry-count", NULL) && | |
753 | !of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) | |
6c75ea1e RK |
754 | goto of_out; |
755 | ||
3ddca062 | 756 | pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), |
6c75ea1e RK |
757 | GFP_KERNEL); |
758 | if (!pdata) | |
759 | goto of_out; | |
760 | ||
3ddca062 | 761 | rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); |
6c75ea1e RK |
762 | if (!rc) |
763 | pdata->i2c_retry_count = prop; | |
764 | ||
3ddca062 | 765 | rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop); |
6c75ea1e RK |
766 | if (!rc) |
767 | pdata->poll_retry_count = prop; | |
768 | ||
3ddca062 | 769 | if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) { |
6c75ea1e RK |
770 | pdata->battery_detect = -1; |
771 | goto of_out; | |
772 | } | |
773 | ||
774 | pdata->battery_detect = of_get_named_gpio_flags(of_node, | |
3ddca062 | 775 | "sbs,battery-detect-gpios", 0, &gpio_flags); |
6c75ea1e RK |
776 | |
777 | if (gpio_flags & OF_GPIO_ACTIVE_LOW) | |
778 | pdata->battery_detect_present = 0; | |
779 | else | |
780 | pdata->battery_detect_present = 1; | |
781 | ||
782 | of_out: | |
783 | return pdata; | |
784 | } | |
785 | #else | |
3ddca062 | 786 | static struct sbs_platform_data *sbs_of_populate_pdata( |
6c75ea1e RK |
787 | struct i2c_client *client) |
788 | { | |
789 | return client->dev.platform_data; | |
790 | } | |
791 | #endif | |
792 | ||
c8afa640 | 793 | static int sbs_probe(struct i2c_client *client, |
a7640bfa RK |
794 | const struct i2c_device_id *id) |
795 | { | |
3ddca062 RK |
796 | struct sbs_info *chip; |
797 | struct sbs_platform_data *pdata = client->dev.platform_data; | |
a7640bfa | 798 | int rc; |
bb879101 | 799 | int irq; |
52f56c69 | 800 | char *name; |
a7640bfa | 801 | |
52f56c69 RK |
802 | name = kasprintf(GFP_KERNEL, "sbs-%s", dev_name(&client->dev)); |
803 | if (!name) { | |
804 | dev_err(&client->dev, "Failed to allocate device name\n"); | |
a7640bfa | 805 | return -ENOMEM; |
52f56c69 RK |
806 | } |
807 | ||
808 | chip = kzalloc(sizeof(struct sbs_info), GFP_KERNEL); | |
809 | if (!chip) { | |
810 | rc = -ENOMEM; | |
811 | goto exit_free_name; | |
812 | } | |
a7640bfa | 813 | |
3ddca062 RK |
814 | chip->client = client; |
815 | chip->enable_detection = false; | |
816 | chip->gpio_detect = false; | |
52f56c69 | 817 | chip->power_supply.name = name; |
3ddca062 RK |
818 | chip->power_supply.type = POWER_SUPPLY_TYPE_BATTERY; |
819 | chip->power_supply.properties = sbs_properties; | |
820 | chip->power_supply.num_properties = ARRAY_SIZE(sbs_properties); | |
821 | chip->power_supply.get_property = sbs_get_property; | |
89a22b1a | 822 | chip->power_supply.of_node = client->dev.of_node; |
58ddafae RK |
823 | /* ignore first notification of external change, it is generated |
824 | * from the power_supply_register call back | |
825 | */ | |
3ddca062 RK |
826 | chip->ignore_changes = 1; |
827 | chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; | |
828 | chip->power_supply.external_power_changed = sbs_external_power_changed; | |
a7640bfa | 829 | |
3ddca062 | 830 | pdata = sbs_of_populate_pdata(client); |
6c75ea1e | 831 | |
bb879101 | 832 | if (pdata) { |
3ddca062 RK |
833 | chip->gpio_detect = gpio_is_valid(pdata->battery_detect); |
834 | chip->pdata = pdata; | |
bb879101 RK |
835 | } |
836 | ||
3ddca062 | 837 | i2c_set_clientdata(client, chip); |
a7640bfa | 838 | |
3ddca062 | 839 | if (!chip->gpio_detect) |
bb879101 RK |
840 | goto skip_gpio; |
841 | ||
842 | rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); | |
843 | if (rc) { | |
844 | dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); | |
3ddca062 | 845 | chip->gpio_detect = false; |
bb879101 RK |
846 | goto skip_gpio; |
847 | } | |
848 | ||
849 | rc = gpio_direction_input(pdata->battery_detect); | |
850 | if (rc) { | |
851 | dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); | |
852 | gpio_free(pdata->battery_detect); | |
3ddca062 | 853 | chip->gpio_detect = false; |
bb879101 RK |
854 | goto skip_gpio; |
855 | } | |
856 | ||
857 | irq = gpio_to_irq(pdata->battery_detect); | |
858 | if (irq <= 0) { | |
859 | dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); | |
860 | gpio_free(pdata->battery_detect); | |
3ddca062 | 861 | chip->gpio_detect = false; |
bb879101 RK |
862 | goto skip_gpio; |
863 | } | |
864 | ||
3ddca062 | 865 | rc = request_irq(irq, sbs_irq, |
bb879101 | 866 | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
3ddca062 | 867 | dev_name(&client->dev), &chip->power_supply); |
bb879101 RK |
868 | if (rc) { |
869 | dev_warn(&client->dev, "Failed to request irq: %d\n", rc); | |
870 | gpio_free(pdata->battery_detect); | |
3ddca062 | 871 | chip->gpio_detect = false; |
bb879101 RK |
872 | goto skip_gpio; |
873 | } | |
874 | ||
3ddca062 | 875 | chip->irq = irq; |
bb879101 RK |
876 | |
877 | skip_gpio: | |
a22b41a3 OJ |
878 | /* |
879 | * Before we register, we need to make sure we can actually talk | |
880 | * to the battery. | |
881 | */ | |
882 | rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); | |
883 | if (rc < 0) { | |
884 | dev_err(&client->dev, "%s: Failed to get device status\n", | |
885 | __func__); | |
886 | goto exit_psupply; | |
887 | } | |
bb879101 | 888 | |
3ddca062 | 889 | rc = power_supply_register(&client->dev, &chip->power_supply); |
a7640bfa RK |
890 | if (rc) { |
891 | dev_err(&client->dev, | |
892 | "%s: Failed to register power supply\n", __func__); | |
bb879101 | 893 | goto exit_psupply; |
a7640bfa RK |
894 | } |
895 | ||
896 | dev_info(&client->dev, | |
897 | "%s: battery gas gauge device registered\n", client->name); | |
898 | ||
3ddca062 | 899 | INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); |
58ddafae | 900 | |
3ddca062 | 901 | chip->enable_detection = true; |
ee177d96 | 902 | |
a7640bfa | 903 | return 0; |
bb879101 RK |
904 | |
905 | exit_psupply: | |
3ddca062 RK |
906 | if (chip->irq) |
907 | free_irq(chip->irq, &chip->power_supply); | |
908 | if (chip->gpio_detect) | |
bb879101 RK |
909 | gpio_free(pdata->battery_detect); |
910 | ||
3ddca062 | 911 | kfree(chip); |
bb879101 | 912 | |
52f56c69 RK |
913 | exit_free_name: |
914 | kfree(name); | |
915 | ||
bb879101 | 916 | return rc; |
a7640bfa RK |
917 | } |
918 | ||
415ec69f | 919 | static int sbs_remove(struct i2c_client *client) |
a7640bfa | 920 | { |
3ddca062 | 921 | struct sbs_info *chip = i2c_get_clientdata(client); |
a7640bfa | 922 | |
3ddca062 RK |
923 | if (chip->irq) |
924 | free_irq(chip->irq, &chip->power_supply); | |
925 | if (chip->gpio_detect) | |
926 | gpio_free(chip->pdata->battery_detect); | |
bb879101 | 927 | |
3ddca062 | 928 | power_supply_unregister(&chip->power_supply); |
58ddafae | 929 | |
3ddca062 | 930 | cancel_delayed_work_sync(&chip->work); |
58ddafae | 931 | |
52f56c69 | 932 | kfree(chip->power_supply.name); |
3ddca062 RK |
933 | kfree(chip); |
934 | chip = NULL; | |
a7640bfa RK |
935 | |
936 | return 0; | |
937 | } | |
938 | ||
9c1d1af0 LPC |
939 | #if defined CONFIG_PM_SLEEP |
940 | ||
941 | static int sbs_suspend(struct device *dev) | |
a7640bfa | 942 | { |
9c1d1af0 | 943 | struct i2c_client *client = to_i2c_client(dev); |
3ddca062 | 944 | struct sbs_info *chip = i2c_get_clientdata(client); |
a7640bfa RK |
945 | s32 ret; |
946 | ||
3ddca062 RK |
947 | if (chip->poll_time > 0) |
948 | cancel_delayed_work_sync(&chip->work); | |
58ddafae | 949 | |
a7640bfa | 950 | /* write to manufacturer access with sleep command */ |
3ddca062 | 951 | ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, |
a7640bfa | 952 | MANUFACTURER_ACCESS_SLEEP); |
3ddca062 | 953 | if (chip->is_present && ret < 0) |
d3ab61ec | 954 | return ret; |
a7640bfa RK |
955 | |
956 | return 0; | |
957 | } | |
9c1d1af0 LPC |
958 | |
959 | static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL); | |
960 | #define SBS_PM_OPS (&sbs_pm_ops) | |
961 | ||
a7640bfa | 962 | #else |
9c1d1af0 | 963 | #define SBS_PM_OPS NULL |
a7640bfa | 964 | #endif |
a7640bfa | 965 | |
3ddca062 | 966 | static const struct i2c_device_id sbs_id[] = { |
a7640bfa | 967 | { "bq20z75", 0 }, |
3ddca062 | 968 | { "sbs-battery", 1 }, |
a7640bfa RK |
969 | {} |
970 | }; | |
3ddca062 RK |
971 | MODULE_DEVICE_TABLE(i2c, sbs_id); |
972 | ||
973 | static struct i2c_driver sbs_battery_driver = { | |
974 | .probe = sbs_probe, | |
28ea73f4 | 975 | .remove = sbs_remove, |
3ddca062 | 976 | .id_table = sbs_id, |
a7640bfa | 977 | .driver = { |
3ddca062 | 978 | .name = "sbs-battery", |
075ed03c | 979 | .of_match_table = of_match_ptr(sbs_dt_ids), |
9c1d1af0 | 980 | .pm = SBS_PM_OPS, |
a7640bfa RK |
981 | }, |
982 | }; | |
5ff92e7a | 983 | module_i2c_driver(sbs_battery_driver); |
a7640bfa | 984 | |
3ddca062 | 985 | MODULE_DESCRIPTION("SBS battery monitor driver"); |
a7640bfa | 986 | MODULE_LICENSE("GPL"); |