Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * AS3711 PMIC backlight driver, using DCDC Step Up Converters
0004  *
0005  * Copyright (C) 2012 Renesas Electronics Corporation
0006  * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de>
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     /* Only all equal current values are supported */
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  * Someone with less fragile or less expensive hardware could try to simplify
0100  * the brightness adjustment procedure.
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         /* Manual one current feedback pin below */
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     /* Turn SU2 off */
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     /* max tuning I = 31uA for voltage- and 38250uA for current-feedback */
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          * At least one su2-auto-curr* must be specified iff
0352          * AS3711_SU2_CURR_AUTO is used
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      * Due to possible hardware damage I chose to block all modes,
0399      * unsupported on my hardware. Anyone, wishing to use any of those modes
0400      * will have to first review the code, then activate and test it.
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");