as3711_bl.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * AS3711 PMIC backlight driver, using DCDC Step Up Converters
  4. *
  5. * Copyright (C) 2012 Renesas Electronics Corporation
  6. * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de>
  7. */
  8. #include <linux/backlight.h>
  9. #include <linux/delay.h>
  10. #include <linux/device.h>
  11. #include <linux/err.h>
  12. #include <linux/fb.h>
  13. #include <linux/kernel.h>
  14. #include <linux/mfd/as3711.h>
  15. #include <linux/module.h>
  16. #include <linux/platform_device.h>
  17. #include <linux/regmap.h>
  18. #include <linux/slab.h>
  19. enum as3711_bl_type {
  20. AS3711_BL_SU1,
  21. AS3711_BL_SU2,
  22. };
  23. struct as3711_bl_data {
  24. bool powered;
  25. enum as3711_bl_type type;
  26. int brightness;
  27. struct backlight_device *bl;
  28. };
  29. struct as3711_bl_supply {
  30. struct as3711_bl_data su1;
  31. struct as3711_bl_data su2;
  32. const struct as3711_bl_pdata *pdata;
  33. struct as3711 *as3711;
  34. };
  35. static struct as3711_bl_supply *to_supply(struct as3711_bl_data *su)
  36. {
  37. switch (su->type) {
  38. case AS3711_BL_SU1:
  39. return container_of(su, struct as3711_bl_supply, su1);
  40. case AS3711_BL_SU2:
  41. return container_of(su, struct as3711_bl_supply, su2);
  42. }
  43. return NULL;
  44. }
  45. static int as3711_set_brightness_auto_i(struct as3711_bl_data *data,
  46. unsigned int brightness)
  47. {
  48. struct as3711_bl_supply *supply = to_supply(data);
  49. struct as3711 *as3711 = supply->as3711;
  50. const struct as3711_bl_pdata *pdata = supply->pdata;
  51. int ret = 0;
  52. /* Only all equal current values are supported */
  53. if (pdata->su2_auto_curr1)
  54. ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
  55. brightness);
  56. if (!ret && pdata->su2_auto_curr2)
  57. ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
  58. brightness);
  59. if (!ret && pdata->su2_auto_curr3)
  60. ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
  61. brightness);
  62. return ret;
  63. }
  64. static int as3711_set_brightness_v(struct as3711 *as3711,
  65. unsigned int brightness,
  66. unsigned int reg)
  67. {
  68. if (brightness > 31)
  69. return -EINVAL;
  70. return regmap_update_bits(as3711->regmap, reg, 0xf0,
  71. brightness << 4);
  72. }
  73. static int as3711_bl_su2_reset(struct as3711_bl_supply *supply)
  74. {
  75. struct as3711 *as3711 = supply->as3711;
  76. int ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_5,
  77. 3, supply->pdata->su2_fbprot);
  78. if (!ret)
  79. ret = regmap_update_bits(as3711->regmap,
  80. AS3711_STEPUP_CONTROL_2, 1, 0);
  81. if (!ret)
  82. ret = regmap_update_bits(as3711->regmap,
  83. AS3711_STEPUP_CONTROL_2, 1, 1);
  84. return ret;
  85. }
  86. /*
  87. * Someone with less fragile or less expensive hardware could try to simplify
  88. * the brightness adjustment procedure.
  89. */
  90. static int as3711_bl_update_status(struct backlight_device *bl)
  91. {
  92. struct as3711_bl_data *data = bl_get_data(bl);
  93. struct as3711_bl_supply *supply = to_supply(data);
  94. struct as3711 *as3711 = supply->as3711;
  95. int brightness;
  96. int ret = 0;
  97. brightness = backlight_get_brightness(bl);
  98. if (data->type == AS3711_BL_SU1) {
  99. ret = as3711_set_brightness_v(as3711, brightness,
  100. AS3711_STEPUP_CONTROL_1);
  101. } else {
  102. const struct as3711_bl_pdata *pdata = supply->pdata;
  103. switch (pdata->su2_feedback) {
  104. case AS3711_SU2_VOLTAGE:
  105. ret = as3711_set_brightness_v(as3711, brightness,
  106. AS3711_STEPUP_CONTROL_2);
  107. break;
  108. case AS3711_SU2_CURR_AUTO:
  109. ret = as3711_set_brightness_auto_i(data, brightness / 4);
  110. if (ret < 0)
  111. return ret;
  112. if (brightness) {
  113. ret = as3711_bl_su2_reset(supply);
  114. if (ret < 0)
  115. return ret;
  116. udelay(500);
  117. ret = as3711_set_brightness_auto_i(data, brightness);
  118. } else {
  119. ret = regmap_update_bits(as3711->regmap,
  120. AS3711_STEPUP_CONTROL_2, 1, 0);
  121. }
  122. break;
  123. /* Manual one current feedback pin below */
  124. case AS3711_SU2_CURR1:
  125. ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
  126. brightness);
  127. break;
  128. case AS3711_SU2_CURR2:
  129. ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
  130. brightness);
  131. break;
  132. case AS3711_SU2_CURR3:
  133. ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
  134. brightness);
  135. break;
  136. default:
  137. ret = -EINVAL;
  138. }
  139. }
  140. if (!ret)
  141. data->brightness = brightness;
  142. return ret;
  143. }
  144. static int as3711_bl_get_brightness(struct backlight_device *bl)
  145. {
  146. struct as3711_bl_data *data = bl_get_data(bl);
  147. return data->brightness;
  148. }
  149. static const struct backlight_ops as3711_bl_ops = {
  150. .update_status = as3711_bl_update_status,
  151. .get_brightness = as3711_bl_get_brightness,
  152. };
  153. static int as3711_bl_init_su2(struct as3711_bl_supply *supply)
  154. {
  155. struct as3711 *as3711 = supply->as3711;
  156. const struct as3711_bl_pdata *pdata = supply->pdata;
  157. u8 ctl = 0;
  158. int ret;
  159. dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback);
  160. /* Turn SU2 off */
  161. ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0);
  162. if (ret < 0)
  163. return ret;
  164. switch (pdata->su2_feedback) {
  165. case AS3711_SU2_VOLTAGE:
  166. ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0);
  167. break;
  168. case AS3711_SU2_CURR1:
  169. ctl = 1;
  170. ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1);
  171. break;
  172. case AS3711_SU2_CURR2:
  173. ctl = 4;
  174. ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2);
  175. break;
  176. case AS3711_SU2_CURR3:
  177. ctl = 0x10;
  178. ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3);
  179. break;
  180. case AS3711_SU2_CURR_AUTO:
  181. if (pdata->su2_auto_curr1)
  182. ctl = 2;
  183. if (pdata->su2_auto_curr2)
  184. ctl |= 8;
  185. if (pdata->su2_auto_curr3)
  186. ctl |= 0x20;
  187. ret = 0;
  188. break;
  189. default:
  190. return -EINVAL;
  191. }
  192. if (!ret)
  193. ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl);
  194. return ret;
  195. }
  196. static int as3711_bl_register(struct platform_device *pdev,
  197. unsigned int max_brightness, struct as3711_bl_data *su)
  198. {
  199. struct backlight_properties props = {.type = BACKLIGHT_RAW,};
  200. struct backlight_device *bl;
  201. /* max tuning I = 31uA for voltage- and 38250uA for current-feedback */
  202. props.max_brightness = max_brightness;
  203. bl = devm_backlight_device_register(&pdev->dev,
  204. su->type == AS3711_BL_SU1 ?
  205. "as3711-su1" : "as3711-su2",
  206. &pdev->dev, su,
  207. &as3711_bl_ops, &props);
  208. if (IS_ERR(bl)) {
  209. dev_err(&pdev->dev, "failed to register backlight\n");
  210. return PTR_ERR(bl);
  211. }
  212. bl->props.brightness = props.max_brightness;
  213. backlight_update_status(bl);
  214. su->bl = bl;
  215. return 0;
  216. }
  217. static int as3711_backlight_parse_dt(struct device *dev)
  218. {
  219. struct as3711_bl_pdata *pdata = dev_get_platdata(dev);
  220. struct device_node *bl, *fb;
  221. int ret;
  222. bl = of_get_child_by_name(dev->parent->of_node, "backlight");
  223. if (!bl) {
  224. dev_dbg(dev, "backlight node not found\n");
  225. return -ENODEV;
  226. }
  227. fb = of_parse_phandle(bl, "su1-dev", 0);
  228. if (fb) {
  229. of_node_put(fb);
  230. pdata->su1_fb = true;
  231. ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA);
  232. if (pdata->su1_max_uA <= 0)
  233. ret = -EINVAL;
  234. if (ret < 0)
  235. goto err_put_bl;
  236. }
  237. fb = of_parse_phandle(bl, "su2-dev", 0);
  238. if (fb) {
  239. int count = 0;
  240. of_node_put(fb);
  241. pdata->su2_fb = true;
  242. ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA);
  243. if (pdata->su2_max_uA <= 0)
  244. ret = -EINVAL;
  245. if (ret < 0)
  246. goto err_put_bl;
  247. if (of_find_property(bl, "su2-feedback-voltage", NULL)) {
  248. pdata->su2_feedback = AS3711_SU2_VOLTAGE;
  249. count++;
  250. }
  251. if (of_find_property(bl, "su2-feedback-curr1", NULL)) {
  252. pdata->su2_feedback = AS3711_SU2_CURR1;
  253. count++;
  254. }
  255. if (of_find_property(bl, "su2-feedback-curr2", NULL)) {
  256. pdata->su2_feedback = AS3711_SU2_CURR2;
  257. count++;
  258. }
  259. if (of_find_property(bl, "su2-feedback-curr3", NULL)) {
  260. pdata->su2_feedback = AS3711_SU2_CURR3;
  261. count++;
  262. }
  263. if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) {
  264. pdata->su2_feedback = AS3711_SU2_CURR_AUTO;
  265. count++;
  266. }
  267. if (count != 1) {
  268. ret = -EINVAL;
  269. goto err_put_bl;
  270. }
  271. count = 0;
  272. if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) {
  273. pdata->su2_fbprot = AS3711_SU2_LX_SD4;
  274. count++;
  275. }
  276. if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) {
  277. pdata->su2_fbprot = AS3711_SU2_GPIO2;
  278. count++;
  279. }
  280. if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) {
  281. pdata->su2_fbprot = AS3711_SU2_GPIO3;
  282. count++;
  283. }
  284. if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) {
  285. pdata->su2_fbprot = AS3711_SU2_GPIO4;
  286. count++;
  287. }
  288. if (count != 1) {
  289. ret = -EINVAL;
  290. goto err_put_bl;
  291. }
  292. count = 0;
  293. if (of_find_property(bl, "su2-auto-curr1", NULL)) {
  294. pdata->su2_auto_curr1 = true;
  295. count++;
  296. }
  297. if (of_find_property(bl, "su2-auto-curr2", NULL)) {
  298. pdata->su2_auto_curr2 = true;
  299. count++;
  300. }
  301. if (of_find_property(bl, "su2-auto-curr3", NULL)) {
  302. pdata->su2_auto_curr3 = true;
  303. count++;
  304. }
  305. /*
  306. * At least one su2-auto-curr* must be specified iff
  307. * AS3711_SU2_CURR_AUTO is used
  308. */
  309. if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) {
  310. ret = -EINVAL;
  311. goto err_put_bl;
  312. }
  313. }
  314. of_node_put(bl);
  315. return 0;
  316. err_put_bl:
  317. of_node_put(bl);
  318. return ret;
  319. }
  320. static int as3711_backlight_probe(struct platform_device *pdev)
  321. {
  322. struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
  323. struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent);
  324. struct as3711_bl_supply *supply;
  325. struct as3711_bl_data *su;
  326. unsigned int max_brightness;
  327. int ret;
  328. if (!pdata) {
  329. dev_err(&pdev->dev, "No platform data, exiting...\n");
  330. return -ENODEV;
  331. }
  332. if (pdev->dev.parent->of_node) {
  333. ret = as3711_backlight_parse_dt(&pdev->dev);
  334. if (ret < 0) {
  335. dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
  336. return ret;
  337. }
  338. }
  339. if (!pdata->su1_fb && !pdata->su2_fb) {
  340. dev_err(&pdev->dev, "No framebuffer specified\n");
  341. return -EINVAL;
  342. }
  343. /*
  344. * Due to possible hardware damage I chose to block all modes,
  345. * unsupported on my hardware. Anyone, wishing to use any of those modes
  346. * will have to first review the code, then activate and test it.
  347. */
  348. if (pdata->su1_fb ||
  349. pdata->su2_fbprot != AS3711_SU2_GPIO4 ||
  350. pdata->su2_feedback != AS3711_SU2_CURR_AUTO) {
  351. dev_warn(&pdev->dev,
  352. "Attention! An untested mode has been chosen!\n"
  353. "Please, review the code, enable, test, and report success:-)\n");
  354. return -EINVAL;
  355. }
  356. supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL);
  357. if (!supply)
  358. return -ENOMEM;
  359. supply->as3711 = as3711;
  360. supply->pdata = pdata;
  361. if (pdata->su1_fb) {
  362. su = &supply->su1;
  363. su->type = AS3711_BL_SU1;
  364. max_brightness = min(pdata->su1_max_uA, 31);
  365. ret = as3711_bl_register(pdev, max_brightness, su);
  366. if (ret < 0)
  367. return ret;
  368. }
  369. if (pdata->su2_fb) {
  370. su = &supply->su2;
  371. su->type = AS3711_BL_SU2;
  372. switch (pdata->su2_fbprot) {
  373. case AS3711_SU2_GPIO2:
  374. case AS3711_SU2_GPIO3:
  375. case AS3711_SU2_GPIO4:
  376. case AS3711_SU2_LX_SD4:
  377. break;
  378. default:
  379. return -EINVAL;
  380. }
  381. switch (pdata->su2_feedback) {
  382. case AS3711_SU2_VOLTAGE:
  383. max_brightness = min(pdata->su2_max_uA, 31);
  384. break;
  385. case AS3711_SU2_CURR1:
  386. case AS3711_SU2_CURR2:
  387. case AS3711_SU2_CURR3:
  388. case AS3711_SU2_CURR_AUTO:
  389. max_brightness = min(pdata->su2_max_uA / 150, 255);
  390. break;
  391. default:
  392. return -EINVAL;
  393. }
  394. ret = as3711_bl_init_su2(supply);
  395. if (ret < 0)
  396. return ret;
  397. ret = as3711_bl_register(pdev, max_brightness, su);
  398. if (ret < 0)
  399. return ret;
  400. }
  401. platform_set_drvdata(pdev, supply);
  402. return 0;
  403. }
  404. static struct platform_driver as3711_backlight_driver = {
  405. .driver = {
  406. .name = "as3711-backlight",
  407. },
  408. .probe = as3711_backlight_probe,
  409. };
  410. module_platform_driver(as3711_backlight_driver);
  411. MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs");
  412. MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de");
  413. MODULE_LICENSE("GPL v2");
  414. MODULE_ALIAS("platform:as3711-backlight");