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