162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <dt-bindings/leds/rt4831-backlight.h> 462306a36Sopenharmony_ci#include <linux/backlight.h> 562306a36Sopenharmony_ci#include <linux/bitops.h> 662306a36Sopenharmony_ci#include <linux/kernel.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/platform_device.h> 962306a36Sopenharmony_ci#include <linux/property.h> 1062306a36Sopenharmony_ci#include <linux/regmap.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define RT4831_REG_BLCFG 0x02 1362306a36Sopenharmony_ci#define RT4831_REG_BLDIML 0x04 1462306a36Sopenharmony_ci#define RT4831_REG_ENABLE 0x08 1562306a36Sopenharmony_ci#define RT4831_REG_BLOPT2 0x11 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define RT4831_BLMAX_BRIGHTNESS 2048 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define RT4831_BLOVP_MASK GENMASK(7, 5) 2062306a36Sopenharmony_ci#define RT4831_BLOVP_SHIFT 5 2162306a36Sopenharmony_ci#define RT4831_BLPWMEN_MASK BIT(0) 2262306a36Sopenharmony_ci#define RT4831_BLEN_MASK BIT(4) 2362306a36Sopenharmony_ci#define RT4831_BLCH_MASK GENMASK(3, 0) 2462306a36Sopenharmony_ci#define RT4831_BLDIML_MASK GENMASK(2, 0) 2562306a36Sopenharmony_ci#define RT4831_BLDIMH_MASK GENMASK(10, 3) 2662306a36Sopenharmony_ci#define RT4831_BLDIMH_SHIFT 3 2762306a36Sopenharmony_ci#define RT4831_BLOCP_MASK GENMASK(1, 0) 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define RT4831_BLOCP_MINUA 900000 3062306a36Sopenharmony_ci#define RT4831_BLOCP_MAXUA 1800000 3162306a36Sopenharmony_ci#define RT4831_BLOCP_STEPUA 300000 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistruct rt4831_priv { 3462306a36Sopenharmony_ci struct device *dev; 3562306a36Sopenharmony_ci struct regmap *regmap; 3662306a36Sopenharmony_ci struct backlight_device *bl; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic int rt4831_bl_update_status(struct backlight_device *bl_dev) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci struct rt4831_priv *priv = bl_get_data(bl_dev); 4262306a36Sopenharmony_ci int brightness = backlight_get_brightness(bl_dev); 4362306a36Sopenharmony_ci unsigned int enable = brightness ? RT4831_BLEN_MASK : 0; 4462306a36Sopenharmony_ci u8 v[2]; 4562306a36Sopenharmony_ci int ret; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci if (brightness) { 4862306a36Sopenharmony_ci v[0] = (brightness - 1) & RT4831_BLDIML_MASK; 4962306a36Sopenharmony_ci v[1] = ((brightness - 1) & RT4831_BLDIMH_MASK) >> RT4831_BLDIMH_SHIFT; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci ret = regmap_raw_write(priv->regmap, RT4831_REG_BLDIML, v, sizeof(v)); 5262306a36Sopenharmony_ci if (ret) 5362306a36Sopenharmony_ci return ret; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci return regmap_update_bits(priv->regmap, RT4831_REG_ENABLE, RT4831_BLEN_MASK, enable); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int rt4831_bl_get_brightness(struct backlight_device *bl_dev) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct rt4831_priv *priv = bl_get_data(bl_dev); 6362306a36Sopenharmony_ci unsigned int val; 6462306a36Sopenharmony_ci u8 v[2]; 6562306a36Sopenharmony_ci int ret; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci ret = regmap_read(priv->regmap, RT4831_REG_ENABLE, &val); 6862306a36Sopenharmony_ci if (ret) 6962306a36Sopenharmony_ci return ret; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci if (!(val & RT4831_BLEN_MASK)) 7262306a36Sopenharmony_ci return 0; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci ret = regmap_raw_read(priv->regmap, RT4831_REG_BLDIML, v, sizeof(v)); 7562306a36Sopenharmony_ci if (ret) 7662306a36Sopenharmony_ci return ret; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci ret = (v[1] << RT4831_BLDIMH_SHIFT) + (v[0] & RT4831_BLDIML_MASK) + 1; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci return ret; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic const struct backlight_ops rt4831_bl_ops = { 8462306a36Sopenharmony_ci .options = BL_CORE_SUSPENDRESUME, 8562306a36Sopenharmony_ci .update_status = rt4831_bl_update_status, 8662306a36Sopenharmony_ci .get_brightness = rt4831_bl_get_brightness, 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int rt4831_parse_backlight_properties(struct rt4831_priv *priv, 9062306a36Sopenharmony_ci struct backlight_properties *bl_props) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct device *dev = priv->dev; 9362306a36Sopenharmony_ci u8 propval; 9462306a36Sopenharmony_ci u32 brightness, ocp_uA; 9562306a36Sopenharmony_ci unsigned int val = 0; 9662306a36Sopenharmony_ci int ret; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci /* common properties */ 9962306a36Sopenharmony_ci ret = device_property_read_u32(dev, "max-brightness", &brightness); 10062306a36Sopenharmony_ci if (ret) 10162306a36Sopenharmony_ci brightness = RT4831_BLMAX_BRIGHTNESS; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci bl_props->max_brightness = min_t(u32, brightness, RT4831_BLMAX_BRIGHTNESS); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci ret = device_property_read_u32(dev, "default-brightness", &brightness); 10662306a36Sopenharmony_ci if (ret) 10762306a36Sopenharmony_ci brightness = bl_props->max_brightness; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci bl_props->brightness = min_t(u32, brightness, bl_props->max_brightness); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* vendor properties */ 11262306a36Sopenharmony_ci if (device_property_read_bool(dev, "richtek,pwm-enable")) 11362306a36Sopenharmony_ci val = RT4831_BLPWMEN_MASK; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci ret = regmap_update_bits(priv->regmap, RT4831_REG_BLCFG, RT4831_BLPWMEN_MASK, val); 11662306a36Sopenharmony_ci if (ret) 11762306a36Sopenharmony_ci return ret; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci ret = device_property_read_u8(dev, "richtek,bled-ovp-sel", &propval); 12062306a36Sopenharmony_ci if (ret) 12162306a36Sopenharmony_ci propval = RT4831_BLOVPLVL_21V; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci propval = min_t(u8, propval, RT4831_BLOVPLVL_29V); 12462306a36Sopenharmony_ci ret = regmap_update_bits(priv->regmap, RT4831_REG_BLCFG, RT4831_BLOVP_MASK, 12562306a36Sopenharmony_ci propval << RT4831_BLOVP_SHIFT); 12662306a36Sopenharmony_ci if (ret) 12762306a36Sopenharmony_ci return ret; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* 13062306a36Sopenharmony_ci * This OCP level is used to protect and limit the inductor current. 13162306a36Sopenharmony_ci * If inductor peak current reach the level, low-side MOSFET will be 13262306a36Sopenharmony_ci * turned off. Meanwhile, the output channel current may be limited. 13362306a36Sopenharmony_ci * To match the configured channel current, the inductor chosen must 13462306a36Sopenharmony_ci * be higher than the OCP level. 13562306a36Sopenharmony_ci * 13662306a36Sopenharmony_ci * Not like the OVP level, the default 21V can be used in the most 13762306a36Sopenharmony_ci * application. But if the chosen OCP level is smaller than needed, 13862306a36Sopenharmony_ci * it will also affect the backlight channel output current to be 13962306a36Sopenharmony_ci * smaller than the register setting. 14062306a36Sopenharmony_ci */ 14162306a36Sopenharmony_ci ret = device_property_read_u32(dev, "richtek,bled-ocp-microamp", 14262306a36Sopenharmony_ci &ocp_uA); 14362306a36Sopenharmony_ci if (!ret) { 14462306a36Sopenharmony_ci ocp_uA = clamp_val(ocp_uA, RT4831_BLOCP_MINUA, 14562306a36Sopenharmony_ci RT4831_BLOCP_MAXUA); 14662306a36Sopenharmony_ci val = DIV_ROUND_UP(ocp_uA - RT4831_BLOCP_MINUA, 14762306a36Sopenharmony_ci RT4831_BLOCP_STEPUA); 14862306a36Sopenharmony_ci ret = regmap_update_bits(priv->regmap, RT4831_REG_BLOPT2, 14962306a36Sopenharmony_ci RT4831_BLOCP_MASK, val); 15062306a36Sopenharmony_ci if (ret) 15162306a36Sopenharmony_ci return ret; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci ret = device_property_read_u8(dev, "richtek,channel-use", &propval); 15562306a36Sopenharmony_ci if (ret) { 15662306a36Sopenharmony_ci dev_err(dev, "richtek,channel-use DT property missing\n"); 15762306a36Sopenharmony_ci return ret; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (!(propval & RT4831_BLCH_MASK)) { 16162306a36Sopenharmony_ci dev_err(dev, "No channel specified\n"); 16262306a36Sopenharmony_ci return -EINVAL; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci return regmap_update_bits(priv->regmap, RT4831_REG_ENABLE, RT4831_BLCH_MASK, propval); 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic int rt4831_bl_probe(struct platform_device *pdev) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci struct rt4831_priv *priv; 17162306a36Sopenharmony_ci struct backlight_properties bl_props = { .type = BACKLIGHT_RAW, 17262306a36Sopenharmony_ci .scale = BACKLIGHT_SCALE_LINEAR }; 17362306a36Sopenharmony_ci int ret; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 17662306a36Sopenharmony_ci if (!priv) 17762306a36Sopenharmony_ci return -ENOMEM; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci priv->dev = &pdev->dev; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci priv->regmap = dev_get_regmap(pdev->dev.parent, NULL); 18262306a36Sopenharmony_ci if (!priv->regmap) { 18362306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to init regmap\n"); 18462306a36Sopenharmony_ci return -ENODEV; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci ret = rt4831_parse_backlight_properties(priv, &bl_props); 18862306a36Sopenharmony_ci if (ret) { 18962306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to parse backlight properties\n"); 19062306a36Sopenharmony_ci return ret; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci priv->bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev, priv, 19462306a36Sopenharmony_ci &rt4831_bl_ops, &bl_props); 19562306a36Sopenharmony_ci if (IS_ERR(priv->bl)) { 19662306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to register backlight\n"); 19762306a36Sopenharmony_ci return PTR_ERR(priv->bl); 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci backlight_update_status(priv->bl); 20162306a36Sopenharmony_ci platform_set_drvdata(pdev, priv); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci return 0; 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic void rt4831_bl_remove(struct platform_device *pdev) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci struct rt4831_priv *priv = platform_get_drvdata(pdev); 20962306a36Sopenharmony_ci struct backlight_device *bl_dev = priv->bl; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci bl_dev->props.brightness = 0; 21262306a36Sopenharmony_ci backlight_update_status(priv->bl); 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic const struct of_device_id __maybe_unused rt4831_bl_of_match[] = { 21662306a36Sopenharmony_ci { .compatible = "richtek,rt4831-backlight", }, 21762306a36Sopenharmony_ci {} 21862306a36Sopenharmony_ci}; 21962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rt4831_bl_of_match); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic struct platform_driver rt4831_bl_driver = { 22262306a36Sopenharmony_ci .driver = { 22362306a36Sopenharmony_ci .name = "rt4831-backlight", 22462306a36Sopenharmony_ci .of_match_table = rt4831_bl_of_match, 22562306a36Sopenharmony_ci }, 22662306a36Sopenharmony_ci .probe = rt4831_bl_probe, 22762306a36Sopenharmony_ci .remove_new = rt4831_bl_remove, 22862306a36Sopenharmony_ci}; 22962306a36Sopenharmony_cimodule_platform_driver(rt4831_bl_driver); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ciMODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); 23262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 233