twl4030_madc_battery.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /*
  2. * Dumb driver for LiIon batteries using TWL4030 madc.
  3. *
  4. * Copyright 2013 Golden Delicious Computers
  5. * Lukas Märdian <lukas@goldelico.com>
  6. *
  7. * Based on dumb driver for gta01 battery
  8. * Copyright 2009 Openmoko, Inc
  9. * Balaji Rao <balajirrao@openmoko.org>
  10. */
  11. #include <linux/module.h>
  12. #include <linux/param.h>
  13. #include <linux/delay.h>
  14. #include <linux/workqueue.h>
  15. #include <linux/platform_device.h>
  16. #include <linux/power_supply.h>
  17. #include <linux/slab.h>
  18. #include <linux/sort.h>
  19. #include <linux/i2c/twl4030-madc.h>
  20. #include <linux/power/twl4030_madc_battery.h>
  21. struct twl4030_madc_battery {
  22. struct power_supply psy;
  23. struct twl4030_madc_bat_platform_data *pdata;
  24. };
  25. static enum power_supply_property twl4030_madc_bat_props[] = {
  26. POWER_SUPPLY_PROP_PRESENT,
  27. POWER_SUPPLY_PROP_STATUS,
  28. POWER_SUPPLY_PROP_TECHNOLOGY,
  29. POWER_SUPPLY_PROP_VOLTAGE_NOW,
  30. POWER_SUPPLY_PROP_CURRENT_NOW,
  31. POWER_SUPPLY_PROP_CAPACITY,
  32. POWER_SUPPLY_PROP_CHARGE_FULL,
  33. POWER_SUPPLY_PROP_CHARGE_NOW,
  34. POWER_SUPPLY_PROP_TEMP,
  35. POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
  36. };
  37. static int madc_read(int index)
  38. {
  39. struct twl4030_madc_request req;
  40. int val;
  41. req.channels = index;
  42. req.method = TWL4030_MADC_SW2;
  43. req.type = TWL4030_MADC_WAIT;
  44. req.do_avg = 0;
  45. req.raw = false;
  46. req.func_cb = NULL;
  47. val = twl4030_madc_conversion(&req);
  48. if (val < 0)
  49. return val;
  50. return req.rbuf[ffs(index) - 1];
  51. }
  52. static int twl4030_madc_bat_get_charging_status(void)
  53. {
  54. return (madc_read(TWL4030_MADC_ICHG) > 0) ? 1 : 0;
  55. }
  56. static int twl4030_madc_bat_get_voltage(void)
  57. {
  58. return madc_read(TWL4030_MADC_VBAT);
  59. }
  60. static int twl4030_madc_bat_get_current(void)
  61. {
  62. return madc_read(TWL4030_MADC_ICHG) * 1000;
  63. }
  64. static int twl4030_madc_bat_get_temp(void)
  65. {
  66. return madc_read(TWL4030_MADC_BTEMP) * 10;
  67. }
  68. static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat,
  69. int volt)
  70. {
  71. struct twl4030_madc_bat_calibration *calibration;
  72. int i, res = 0;
  73. /* choose charging curve */
  74. if (twl4030_madc_bat_get_charging_status())
  75. calibration = bat->pdata->charging;
  76. else
  77. calibration = bat->pdata->discharging;
  78. if (volt > calibration[0].voltage) {
  79. res = calibration[0].level;
  80. } else {
  81. for (i = 0; calibration[i+1].voltage >= 0; i++) {
  82. if (volt <= calibration[i].voltage &&
  83. volt >= calibration[i+1].voltage) {
  84. /* interval found - interpolate within range */
  85. res = calibration[i].level -
  86. ((calibration[i].voltage - volt) *
  87. (calibration[i].level -
  88. calibration[i+1].level)) /
  89. (calibration[i].voltage -
  90. calibration[i+1].voltage);
  91. break;
  92. }
  93. }
  94. }
  95. return res;
  96. }
  97. static int twl4030_madc_bat_get_property(struct power_supply *psy,
  98. enum power_supply_property psp,
  99. union power_supply_propval *val)
  100. {
  101. struct twl4030_madc_battery *bat = container_of(psy,
  102. struct twl4030_madc_battery, psy);
  103. switch (psp) {
  104. case POWER_SUPPLY_PROP_STATUS:
  105. if (twl4030_madc_bat_voltscale(bat,
  106. twl4030_madc_bat_get_voltage()) > 95)
  107. val->intval = POWER_SUPPLY_STATUS_FULL;
  108. else {
  109. if (twl4030_madc_bat_get_charging_status())
  110. val->intval = POWER_SUPPLY_STATUS_CHARGING;
  111. else
  112. val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
  113. }
  114. break;
  115. case POWER_SUPPLY_PROP_VOLTAGE_NOW:
  116. val->intval = twl4030_madc_bat_get_voltage() * 1000;
  117. break;
  118. case POWER_SUPPLY_PROP_TECHNOLOGY:
  119. val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
  120. break;
  121. case POWER_SUPPLY_PROP_CURRENT_NOW:
  122. val->intval = twl4030_madc_bat_get_current();
  123. break;
  124. case POWER_SUPPLY_PROP_PRESENT:
  125. /* assume battery is always present */
  126. val->intval = 1;
  127. break;
  128. case POWER_SUPPLY_PROP_CHARGE_NOW: {
  129. int percent = twl4030_madc_bat_voltscale(bat,
  130. twl4030_madc_bat_get_voltage());
  131. val->intval = (percent * bat->pdata->capacity) / 100;
  132. break;
  133. }
  134. case POWER_SUPPLY_PROP_CAPACITY:
  135. val->intval = twl4030_madc_bat_voltscale(bat,
  136. twl4030_madc_bat_get_voltage());
  137. break;
  138. case POWER_SUPPLY_PROP_CHARGE_FULL:
  139. val->intval = bat->pdata->capacity;
  140. break;
  141. case POWER_SUPPLY_PROP_TEMP:
  142. val->intval = twl4030_madc_bat_get_temp();
  143. break;
  144. case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
  145. int percent = twl4030_madc_bat_voltscale(bat,
  146. twl4030_madc_bat_get_voltage());
  147. /* in mAh */
  148. int chg = (percent * (bat->pdata->capacity/1000))/100;
  149. /* assume discharge with 400 mA (ca. 1.5W) */
  150. val->intval = (3600l * chg) / 400;
  151. break;
  152. }
  153. default:
  154. return -EINVAL;
  155. }
  156. return 0;
  157. }
  158. static void twl4030_madc_bat_ext_changed(struct power_supply *psy)
  159. {
  160. struct twl4030_madc_battery *bat = container_of(psy,
  161. struct twl4030_madc_battery, psy);
  162. power_supply_changed(&bat->psy);
  163. }
  164. static int twl4030_cmp(const void *a, const void *b)
  165. {
  166. return ((struct twl4030_madc_bat_calibration *)b)->voltage -
  167. ((struct twl4030_madc_bat_calibration *)a)->voltage;
  168. }
  169. static int twl4030_madc_battery_probe(struct platform_device *pdev)
  170. {
  171. struct twl4030_madc_battery *twl4030_madc_bat;
  172. struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data;
  173. twl4030_madc_bat = kzalloc(sizeof(*twl4030_madc_bat), GFP_KERNEL);
  174. if (!twl4030_madc_bat)
  175. return -ENOMEM;
  176. twl4030_madc_bat->psy.name = "twl4030_battery";
  177. twl4030_madc_bat->psy.type = POWER_SUPPLY_TYPE_BATTERY;
  178. twl4030_madc_bat->psy.properties = twl4030_madc_bat_props;
  179. twl4030_madc_bat->psy.num_properties =
  180. ARRAY_SIZE(twl4030_madc_bat_props);
  181. twl4030_madc_bat->psy.get_property = twl4030_madc_bat_get_property;
  182. twl4030_madc_bat->psy.external_power_changed =
  183. twl4030_madc_bat_ext_changed;
  184. /* sort charging and discharging calibration data */
  185. sort(pdata->charging, pdata->charging_size,
  186. sizeof(struct twl4030_madc_bat_calibration),
  187. twl4030_cmp, NULL);
  188. sort(pdata->discharging, pdata->discharging_size,
  189. sizeof(struct twl4030_madc_bat_calibration),
  190. twl4030_cmp, NULL);
  191. twl4030_madc_bat->pdata = pdata;
  192. platform_set_drvdata(pdev, twl4030_madc_bat);
  193. power_supply_register(&pdev->dev, &twl4030_madc_bat->psy);
  194. return 0;
  195. }
  196. static int twl4030_madc_battery_remove(struct platform_device *pdev)
  197. {
  198. struct twl4030_madc_battery *bat = platform_get_drvdata(pdev);
  199. power_supply_unregister(&bat->psy);
  200. kfree(bat);
  201. return 0;
  202. }
  203. static struct platform_driver twl4030_madc_battery_driver = {
  204. .driver = {
  205. .name = "twl4030_madc_battery",
  206. },
  207. .probe = twl4030_madc_battery_probe,
  208. .remove = twl4030_madc_battery_remove,
  209. };
  210. module_platform_driver(twl4030_madc_battery_driver);
  211. MODULE_LICENSE("GPL");
  212. MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
  213. MODULE_DESCRIPTION("twl4030_madc battery driver");