0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <linux/backlight.h>
0010 #include <linux/delay.h>
0011 #include <linux/device.h>
0012 #include <linux/err.h>
0013 #include <linux/fb.h>
0014 #include <linux/kernel.h>
0015 #include <linux/mfd/as3711.h>
0016 #include <linux/module.h>
0017 #include <linux/platform_device.h>
0018 #include <linux/regmap.h>
0019 #include <linux/slab.h>
0020
0021 enum as3711_bl_type {
0022 AS3711_BL_SU1,
0023 AS3711_BL_SU2,
0024 };
0025
0026 struct as3711_bl_data {
0027 bool powered;
0028 enum as3711_bl_type type;
0029 int brightness;
0030 struct backlight_device *bl;
0031 };
0032
0033 struct as3711_bl_supply {
0034 struct as3711_bl_data su1;
0035 struct as3711_bl_data su2;
0036 const struct as3711_bl_pdata *pdata;
0037 struct as3711 *as3711;
0038 };
0039
0040 static struct as3711_bl_supply *to_supply(struct as3711_bl_data *su)
0041 {
0042 switch (su->type) {
0043 case AS3711_BL_SU1:
0044 return container_of(su, struct as3711_bl_supply, su1);
0045 case AS3711_BL_SU2:
0046 return container_of(su, struct as3711_bl_supply, su2);
0047 }
0048 return NULL;
0049 }
0050
0051 static int as3711_set_brightness_auto_i(struct as3711_bl_data *data,
0052 unsigned int brightness)
0053 {
0054 struct as3711_bl_supply *supply = to_supply(data);
0055 struct as3711 *as3711 = supply->as3711;
0056 const struct as3711_bl_pdata *pdata = supply->pdata;
0057 int ret = 0;
0058
0059
0060 if (pdata->su2_auto_curr1)
0061 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
0062 brightness);
0063 if (!ret && pdata->su2_auto_curr2)
0064 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
0065 brightness);
0066 if (!ret && pdata->su2_auto_curr3)
0067 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
0068 brightness);
0069
0070 return ret;
0071 }
0072
0073 static int as3711_set_brightness_v(struct as3711 *as3711,
0074 unsigned int brightness,
0075 unsigned int reg)
0076 {
0077 if (brightness > 31)
0078 return -EINVAL;
0079
0080 return regmap_update_bits(as3711->regmap, reg, 0xf0,
0081 brightness << 4);
0082 }
0083
0084 static int as3711_bl_su2_reset(struct as3711_bl_supply *supply)
0085 {
0086 struct as3711 *as3711 = supply->as3711;
0087 int ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_5,
0088 3, supply->pdata->su2_fbprot);
0089 if (!ret)
0090 ret = regmap_update_bits(as3711->regmap,
0091 AS3711_STEPUP_CONTROL_2, 1, 0);
0092 if (!ret)
0093 ret = regmap_update_bits(as3711->regmap,
0094 AS3711_STEPUP_CONTROL_2, 1, 1);
0095 return ret;
0096 }
0097
0098
0099
0100
0101
0102 static int as3711_bl_update_status(struct backlight_device *bl)
0103 {
0104 struct as3711_bl_data *data = bl_get_data(bl);
0105 struct as3711_bl_supply *supply = to_supply(data);
0106 struct as3711 *as3711 = supply->as3711;
0107 int brightness;
0108 int ret = 0;
0109
0110 brightness = backlight_get_brightness(bl);
0111
0112 if (data->type == AS3711_BL_SU1) {
0113 ret = as3711_set_brightness_v(as3711, brightness,
0114 AS3711_STEPUP_CONTROL_1);
0115 } else {
0116 const struct as3711_bl_pdata *pdata = supply->pdata;
0117
0118 switch (pdata->su2_feedback) {
0119 case AS3711_SU2_VOLTAGE:
0120 ret = as3711_set_brightness_v(as3711, brightness,
0121 AS3711_STEPUP_CONTROL_2);
0122 break;
0123 case AS3711_SU2_CURR_AUTO:
0124 ret = as3711_set_brightness_auto_i(data, brightness / 4);
0125 if (ret < 0)
0126 return ret;
0127 if (brightness) {
0128 ret = as3711_bl_su2_reset(supply);
0129 if (ret < 0)
0130 return ret;
0131 udelay(500);
0132 ret = as3711_set_brightness_auto_i(data, brightness);
0133 } else {
0134 ret = regmap_update_bits(as3711->regmap,
0135 AS3711_STEPUP_CONTROL_2, 1, 0);
0136 }
0137 break;
0138
0139 case AS3711_SU2_CURR1:
0140 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
0141 brightness);
0142 break;
0143 case AS3711_SU2_CURR2:
0144 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
0145 brightness);
0146 break;
0147 case AS3711_SU2_CURR3:
0148 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
0149 brightness);
0150 break;
0151 default:
0152 ret = -EINVAL;
0153 }
0154 }
0155 if (!ret)
0156 data->brightness = brightness;
0157
0158 return ret;
0159 }
0160
0161 static int as3711_bl_get_brightness(struct backlight_device *bl)
0162 {
0163 struct as3711_bl_data *data = bl_get_data(bl);
0164
0165 return data->brightness;
0166 }
0167
0168 static const struct backlight_ops as3711_bl_ops = {
0169 .update_status = as3711_bl_update_status,
0170 .get_brightness = as3711_bl_get_brightness,
0171 };
0172
0173 static int as3711_bl_init_su2(struct as3711_bl_supply *supply)
0174 {
0175 struct as3711 *as3711 = supply->as3711;
0176 const struct as3711_bl_pdata *pdata = supply->pdata;
0177 u8 ctl = 0;
0178 int ret;
0179
0180 dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback);
0181
0182
0183 ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0);
0184 if (ret < 0)
0185 return ret;
0186
0187 switch (pdata->su2_feedback) {
0188 case AS3711_SU2_VOLTAGE:
0189 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0);
0190 break;
0191 case AS3711_SU2_CURR1:
0192 ctl = 1;
0193 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1);
0194 break;
0195 case AS3711_SU2_CURR2:
0196 ctl = 4;
0197 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2);
0198 break;
0199 case AS3711_SU2_CURR3:
0200 ctl = 0x10;
0201 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3);
0202 break;
0203 case AS3711_SU2_CURR_AUTO:
0204 if (pdata->su2_auto_curr1)
0205 ctl = 2;
0206 if (pdata->su2_auto_curr2)
0207 ctl |= 8;
0208 if (pdata->su2_auto_curr3)
0209 ctl |= 0x20;
0210 ret = 0;
0211 break;
0212 default:
0213 return -EINVAL;
0214 }
0215
0216 if (!ret)
0217 ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl);
0218
0219 return ret;
0220 }
0221
0222 static int as3711_bl_register(struct platform_device *pdev,
0223 unsigned int max_brightness, struct as3711_bl_data *su)
0224 {
0225 struct backlight_properties props = {.type = BACKLIGHT_RAW,};
0226 struct backlight_device *bl;
0227
0228
0229 props.max_brightness = max_brightness;
0230
0231 bl = devm_backlight_device_register(&pdev->dev,
0232 su->type == AS3711_BL_SU1 ?
0233 "as3711-su1" : "as3711-su2",
0234 &pdev->dev, su,
0235 &as3711_bl_ops, &props);
0236 if (IS_ERR(bl)) {
0237 dev_err(&pdev->dev, "failed to register backlight\n");
0238 return PTR_ERR(bl);
0239 }
0240
0241 bl->props.brightness = props.max_brightness;
0242
0243 backlight_update_status(bl);
0244
0245 su->bl = bl;
0246
0247 return 0;
0248 }
0249
0250 static int as3711_backlight_parse_dt(struct device *dev)
0251 {
0252 struct as3711_bl_pdata *pdata = dev_get_platdata(dev);
0253 struct device_node *bl, *fb;
0254 int ret;
0255
0256 bl = of_get_child_by_name(dev->parent->of_node, "backlight");
0257 if (!bl) {
0258 dev_dbg(dev, "backlight node not found\n");
0259 return -ENODEV;
0260 }
0261
0262 fb = of_parse_phandle(bl, "su1-dev", 0);
0263 if (fb) {
0264 of_node_put(fb);
0265
0266 pdata->su1_fb = true;
0267
0268 ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA);
0269 if (pdata->su1_max_uA <= 0)
0270 ret = -EINVAL;
0271 if (ret < 0)
0272 goto err_put_bl;
0273 }
0274
0275 fb = of_parse_phandle(bl, "su2-dev", 0);
0276 if (fb) {
0277 int count = 0;
0278
0279 of_node_put(fb);
0280
0281 pdata->su2_fb = true;
0282
0283 ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA);
0284 if (pdata->su2_max_uA <= 0)
0285 ret = -EINVAL;
0286 if (ret < 0)
0287 goto err_put_bl;
0288
0289 if (of_find_property(bl, "su2-feedback-voltage", NULL)) {
0290 pdata->su2_feedback = AS3711_SU2_VOLTAGE;
0291 count++;
0292 }
0293 if (of_find_property(bl, "su2-feedback-curr1", NULL)) {
0294 pdata->su2_feedback = AS3711_SU2_CURR1;
0295 count++;
0296 }
0297 if (of_find_property(bl, "su2-feedback-curr2", NULL)) {
0298 pdata->su2_feedback = AS3711_SU2_CURR2;
0299 count++;
0300 }
0301 if (of_find_property(bl, "su2-feedback-curr3", NULL)) {
0302 pdata->su2_feedback = AS3711_SU2_CURR3;
0303 count++;
0304 }
0305 if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) {
0306 pdata->su2_feedback = AS3711_SU2_CURR_AUTO;
0307 count++;
0308 }
0309 if (count != 1) {
0310 ret = -EINVAL;
0311 goto err_put_bl;
0312 }
0313
0314 count = 0;
0315 if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) {
0316 pdata->su2_fbprot = AS3711_SU2_LX_SD4;
0317 count++;
0318 }
0319 if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) {
0320 pdata->su2_fbprot = AS3711_SU2_GPIO2;
0321 count++;
0322 }
0323 if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) {
0324 pdata->su2_fbprot = AS3711_SU2_GPIO3;
0325 count++;
0326 }
0327 if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) {
0328 pdata->su2_fbprot = AS3711_SU2_GPIO4;
0329 count++;
0330 }
0331 if (count != 1) {
0332 ret = -EINVAL;
0333 goto err_put_bl;
0334 }
0335
0336 count = 0;
0337 if (of_find_property(bl, "su2-auto-curr1", NULL)) {
0338 pdata->su2_auto_curr1 = true;
0339 count++;
0340 }
0341 if (of_find_property(bl, "su2-auto-curr2", NULL)) {
0342 pdata->su2_auto_curr2 = true;
0343 count++;
0344 }
0345 if (of_find_property(bl, "su2-auto-curr3", NULL)) {
0346 pdata->su2_auto_curr3 = true;
0347 count++;
0348 }
0349
0350
0351
0352
0353
0354 if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) {
0355 ret = -EINVAL;
0356 goto err_put_bl;
0357 }
0358 }
0359
0360 of_node_put(bl);
0361
0362 return 0;
0363
0364 err_put_bl:
0365 of_node_put(bl);
0366
0367 return ret;
0368 }
0369
0370 static int as3711_backlight_probe(struct platform_device *pdev)
0371 {
0372 struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
0373 struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent);
0374 struct as3711_bl_supply *supply;
0375 struct as3711_bl_data *su;
0376 unsigned int max_brightness;
0377 int ret;
0378
0379 if (!pdata) {
0380 dev_err(&pdev->dev, "No platform data, exiting...\n");
0381 return -ENODEV;
0382 }
0383
0384 if (pdev->dev.parent->of_node) {
0385 ret = as3711_backlight_parse_dt(&pdev->dev);
0386 if (ret < 0) {
0387 dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
0388 return ret;
0389 }
0390 }
0391
0392 if (!pdata->su1_fb && !pdata->su2_fb) {
0393 dev_err(&pdev->dev, "No framebuffer specified\n");
0394 return -EINVAL;
0395 }
0396
0397
0398
0399
0400
0401
0402 if (pdata->su1_fb ||
0403 pdata->su2_fbprot != AS3711_SU2_GPIO4 ||
0404 pdata->su2_feedback != AS3711_SU2_CURR_AUTO) {
0405 dev_warn(&pdev->dev,
0406 "Attention! An untested mode has been chosen!\n"
0407 "Please, review the code, enable, test, and report success:-)\n");
0408 return -EINVAL;
0409 }
0410
0411 supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL);
0412 if (!supply)
0413 return -ENOMEM;
0414
0415 supply->as3711 = as3711;
0416 supply->pdata = pdata;
0417
0418 if (pdata->su1_fb) {
0419 su = &supply->su1;
0420 su->type = AS3711_BL_SU1;
0421
0422 max_brightness = min(pdata->su1_max_uA, 31);
0423 ret = as3711_bl_register(pdev, max_brightness, su);
0424 if (ret < 0)
0425 return ret;
0426 }
0427
0428 if (pdata->su2_fb) {
0429 su = &supply->su2;
0430 su->type = AS3711_BL_SU2;
0431
0432 switch (pdata->su2_fbprot) {
0433 case AS3711_SU2_GPIO2:
0434 case AS3711_SU2_GPIO3:
0435 case AS3711_SU2_GPIO4:
0436 case AS3711_SU2_LX_SD4:
0437 break;
0438 default:
0439 return -EINVAL;
0440 }
0441
0442 switch (pdata->su2_feedback) {
0443 case AS3711_SU2_VOLTAGE:
0444 max_brightness = min(pdata->su2_max_uA, 31);
0445 break;
0446 case AS3711_SU2_CURR1:
0447 case AS3711_SU2_CURR2:
0448 case AS3711_SU2_CURR3:
0449 case AS3711_SU2_CURR_AUTO:
0450 max_brightness = min(pdata->su2_max_uA / 150, 255);
0451 break;
0452 default:
0453 return -EINVAL;
0454 }
0455
0456 ret = as3711_bl_init_su2(supply);
0457 if (ret < 0)
0458 return ret;
0459
0460 ret = as3711_bl_register(pdev, max_brightness, su);
0461 if (ret < 0)
0462 return ret;
0463 }
0464
0465 platform_set_drvdata(pdev, supply);
0466
0467 return 0;
0468 }
0469
0470 static struct platform_driver as3711_backlight_driver = {
0471 .driver = {
0472 .name = "as3711-backlight",
0473 },
0474 .probe = as3711_backlight_probe,
0475 };
0476
0477 module_platform_driver(as3711_backlight_driver);
0478
0479 MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs");
0480 MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de");
0481 MODULE_LICENSE("GPL v2");
0482 MODULE_ALIAS("platform:as3711-backlight");