Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Guennadi Liakhovetski | 1984 | 96.17% | 2 | 28.57% |
Johan Hovold | 61 | 2.96% | 2 | 28.57% |
Jingoo Han | 12 | 0.58% | 1 | 14.29% |
Kuninori Morimoto | 3 | 0.15% | 1 | 14.29% |
Sam Ravnborg | 3 | 0.15% | 1 | 14.29% |
Total | 2063 | 7 |
// SPDX-License-Identifier: GPL-2.0 /* * AS3711 PMIC backlight driver, using DCDC Step Up Converters * * Copyright (C) 2012 Renesas Electronics Corporation * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de> */ #include <linux/backlight.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> #include <linux/fb.h> #include <linux/kernel.h> #include <linux/mfd/as3711.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/slab.h> enum as3711_bl_type { AS3711_BL_SU1, AS3711_BL_SU2, }; struct as3711_bl_data { bool powered; enum as3711_bl_type type; int brightness; struct backlight_device *bl; }; struct as3711_bl_supply { struct as3711_bl_data su1; struct as3711_bl_data su2; const struct as3711_bl_pdata *pdata; struct as3711 *as3711; }; static struct as3711_bl_supply *to_supply(struct as3711_bl_data *su) { switch (su->type) { case AS3711_BL_SU1: return container_of(su, struct as3711_bl_supply, su1); case AS3711_BL_SU2: return container_of(su, struct as3711_bl_supply, su2); } return NULL; } static int as3711_set_brightness_auto_i(struct as3711_bl_data *data, unsigned int brightness) { struct as3711_bl_supply *supply = to_supply(data); struct as3711 *as3711 = supply->as3711; const struct as3711_bl_pdata *pdata = supply->pdata; int ret = 0; /* Only all equal current values are supported */ if (pdata->su2_auto_curr1) ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE, brightness); if (!ret && pdata->su2_auto_curr2) ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE, brightness); if (!ret && pdata->su2_auto_curr3) ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE, brightness); return ret; } static int as3711_set_brightness_v(struct as3711 *as3711, unsigned int brightness, unsigned int reg) { if (brightness > 31) return -EINVAL; return regmap_update_bits(as3711->regmap, reg, 0xf0, brightness << 4); } static int as3711_bl_su2_reset(struct as3711_bl_supply *supply) { struct as3711 *as3711 = supply->as3711; int ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_5, 3, supply->pdata->su2_fbprot); if (!ret) ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_2, 1, 0); if (!ret) ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_2, 1, 1); return ret; } /* * Someone with less fragile or less expensive hardware could try to simplify * the brightness adjustment procedure. */ static int as3711_bl_update_status(struct backlight_device *bl) { struct as3711_bl_data *data = bl_get_data(bl); struct as3711_bl_supply *supply = to_supply(data); struct as3711 *as3711 = supply->as3711; int brightness; int ret = 0; brightness = backlight_get_brightness(bl); if (data->type == AS3711_BL_SU1) { ret = as3711_set_brightness_v(as3711, brightness, AS3711_STEPUP_CONTROL_1); } else { const struct as3711_bl_pdata *pdata = supply->pdata; switch (pdata->su2_feedback) { case AS3711_SU2_VOLTAGE: ret = as3711_set_brightness_v(as3711, brightness, AS3711_STEPUP_CONTROL_2); break; case AS3711_SU2_CURR_AUTO: ret = as3711_set_brightness_auto_i(data, brightness / 4); if (ret < 0) return ret; if (brightness) { ret = as3711_bl_su2_reset(supply); if (ret < 0) return ret; udelay(500); ret = as3711_set_brightness_auto_i(data, brightness); } else { ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_2, 1, 0); } break; /* Manual one current feedback pin below */ case AS3711_SU2_CURR1: ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE, brightness); break; case AS3711_SU2_CURR2: ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE, brightness); break; case AS3711_SU2_CURR3: ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE, brightness); break; default: ret = -EINVAL; } } if (!ret) data->brightness = brightness; return ret; } static int as3711_bl_get_brightness(struct backlight_device *bl) { struct as3711_bl_data *data = bl_get_data(bl); return data->brightness; } static const struct backlight_ops as3711_bl_ops = { .update_status = as3711_bl_update_status, .get_brightness = as3711_bl_get_brightness, }; static int as3711_bl_init_su2(struct as3711_bl_supply *supply) { struct as3711 *as3711 = supply->as3711; const struct as3711_bl_pdata *pdata = supply->pdata; u8 ctl = 0; int ret; dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback); /* Turn SU2 off */ ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0); if (ret < 0) return ret; switch (pdata->su2_feedback) { case AS3711_SU2_VOLTAGE: ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0); break; case AS3711_SU2_CURR1: ctl = 1; ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1); break; case AS3711_SU2_CURR2: ctl = 4; ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2); break; case AS3711_SU2_CURR3: ctl = 0x10; ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3); break; case AS3711_SU2_CURR_AUTO: if (pdata->su2_auto_curr1) ctl = 2; if (pdata->su2_auto_curr2) ctl |= 8; if (pdata->su2_auto_curr3) ctl |= 0x20; ret = 0; break; default: return -EINVAL; } if (!ret) ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl); return ret; } static int as3711_bl_register(struct platform_device *pdev, unsigned int max_brightness, struct as3711_bl_data *su) { struct backlight_properties props = {.type = BACKLIGHT_RAW,}; struct backlight_device *bl; /* max tuning I = 31uA for voltage- and 38250uA for current-feedback */ props.max_brightness = max_brightness; bl = devm_backlight_device_register(&pdev->dev, su->type == AS3711_BL_SU1 ? "as3711-su1" : "as3711-su2", &pdev->dev, su, &as3711_bl_ops, &props); if (IS_ERR(bl)) { dev_err(&pdev->dev, "failed to register backlight\n"); return PTR_ERR(bl); } bl->props.brightness = props.max_brightness; backlight_update_status(bl); su->bl = bl; return 0; } static int as3711_backlight_parse_dt(struct device *dev) { struct as3711_bl_pdata *pdata = dev_get_platdata(dev); struct device_node *bl, *fb; int ret; bl = of_get_child_by_name(dev->parent->of_node, "backlight"); if (!bl) { dev_dbg(dev, "backlight node not found\n"); return -ENODEV; } fb = of_parse_phandle(bl, "su1-dev", 0); if (fb) { of_node_put(fb); pdata->su1_fb = true; ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA); if (pdata->su1_max_uA <= 0) ret = -EINVAL; if (ret < 0) goto err_put_bl; } fb = of_parse_phandle(bl, "su2-dev", 0); if (fb) { int count = 0; of_node_put(fb); pdata->su2_fb = true; ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA); if (pdata->su2_max_uA <= 0) ret = -EINVAL; if (ret < 0) goto err_put_bl; if (of_find_property(bl, "su2-feedback-voltage", NULL)) { pdata->su2_feedback = AS3711_SU2_VOLTAGE; count++; } if (of_find_property(bl, "su2-feedback-curr1", NULL)) { pdata->su2_feedback = AS3711_SU2_CURR1; count++; } if (of_find_property(bl, "su2-feedback-curr2", NULL)) { pdata->su2_feedback = AS3711_SU2_CURR2; count++; } if (of_find_property(bl, "su2-feedback-curr3", NULL)) { pdata->su2_feedback = AS3711_SU2_CURR3; count++; } if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) { pdata->su2_feedback = AS3711_SU2_CURR_AUTO; count++; } if (count != 1) { ret = -EINVAL; goto err_put_bl; } count = 0; if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) { pdata->su2_fbprot = AS3711_SU2_LX_SD4; count++; } if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) { pdata->su2_fbprot = AS3711_SU2_GPIO2; count++; } if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) { pdata->su2_fbprot = AS3711_SU2_GPIO3; count++; } if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) { pdata->su2_fbprot = AS3711_SU2_GPIO4; count++; } if (count != 1) { ret = -EINVAL; goto err_put_bl; } count = 0; if (of_find_property(bl, "su2-auto-curr1", NULL)) { pdata->su2_auto_curr1 = true; count++; } if (of_find_property(bl, "su2-auto-curr2", NULL)) { pdata->su2_auto_curr2 = true; count++; } if (of_find_property(bl, "su2-auto-curr3", NULL)) { pdata->su2_auto_curr3 = true; count++; } /* * At least one su2-auto-curr* must be specified iff * AS3711_SU2_CURR_AUTO is used */ if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) { ret = -EINVAL; goto err_put_bl; } } of_node_put(bl); return 0; err_put_bl: of_node_put(bl); return ret; } static int as3711_backlight_probe(struct platform_device *pdev) { struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev); struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent); struct as3711_bl_supply *supply; struct as3711_bl_data *su; unsigned int max_brightness; int ret; if (!pdata) { dev_err(&pdev->dev, "No platform data, exiting...\n"); return -ENODEV; } if (pdev->dev.parent->of_node) { ret = as3711_backlight_parse_dt(&pdev->dev); if (ret < 0) { dev_err(&pdev->dev, "DT parsing failed: %d\n", ret); return ret; } } if (!pdata->su1_fb && !pdata->su2_fb) { dev_err(&pdev->dev, "No framebuffer specified\n"); return -EINVAL; } /* * Due to possible hardware damage I chose to block all modes, * unsupported on my hardware. Anyone, wishing to use any of those modes * will have to first review the code, then activate and test it. */ if (pdata->su1_fb || pdata->su2_fbprot != AS3711_SU2_GPIO4 || pdata->su2_feedback != AS3711_SU2_CURR_AUTO) { dev_warn(&pdev->dev, "Attention! An untested mode has been chosen!\n" "Please, review the code, enable, test, and report success:-)\n"); return -EINVAL; } supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL); if (!supply) return -ENOMEM; supply->as3711 = as3711; supply->pdata = pdata; if (pdata->su1_fb) { su = &supply->su1; su->type = AS3711_BL_SU1; max_brightness = min(pdata->su1_max_uA, 31); ret = as3711_bl_register(pdev, max_brightness, su); if (ret < 0) return ret; } if (pdata->su2_fb) { su = &supply->su2; su->type = AS3711_BL_SU2; switch (pdata->su2_fbprot) { case AS3711_SU2_GPIO2: case AS3711_SU2_GPIO3: case AS3711_SU2_GPIO4: case AS3711_SU2_LX_SD4: break; default: return -EINVAL; } switch (pdata->su2_feedback) { case AS3711_SU2_VOLTAGE: max_brightness = min(pdata->su2_max_uA, 31); break; case AS3711_SU2_CURR1: case AS3711_SU2_CURR2: case AS3711_SU2_CURR3: case AS3711_SU2_CURR_AUTO: max_brightness = min(pdata->su2_max_uA / 150, 255); break; default: return -EINVAL; } ret = as3711_bl_init_su2(supply); if (ret < 0) return ret; ret = as3711_bl_register(pdev, max_brightness, su); if (ret < 0) return ret; } platform_set_drvdata(pdev, supply); return 0; } static struct platform_driver as3711_backlight_driver = { .driver = { .name = "as3711-backlight", }, .probe = as3711_backlight_probe, }; module_platform_driver(as3711_backlight_driver); MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs"); MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:as3711-backlight");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1