1*190ccab3SChiYuan Huang // SPDX-License-Identifier: GPL-2.0-only 2*190ccab3SChiYuan Huang 3*190ccab3SChiYuan Huang #include <dt-bindings/leds/rt4831-backlight.h> 4*190ccab3SChiYuan Huang #include <linux/backlight.h> 5*190ccab3SChiYuan Huang #include <linux/bitops.h> 6*190ccab3SChiYuan Huang #include <linux/kernel.h> 7*190ccab3SChiYuan Huang #include <linux/module.h> 8*190ccab3SChiYuan Huang #include <linux/platform_device.h> 9*190ccab3SChiYuan Huang #include <linux/property.h> 10*190ccab3SChiYuan Huang #include <linux/regmap.h> 11*190ccab3SChiYuan Huang 12*190ccab3SChiYuan Huang #define RT4831_REG_BLCFG 0x02 13*190ccab3SChiYuan Huang #define RT4831_REG_BLDIML 0x04 14*190ccab3SChiYuan Huang #define RT4831_REG_ENABLE 0x08 15*190ccab3SChiYuan Huang 16*190ccab3SChiYuan Huang #define RT4831_BLMAX_BRIGHTNESS 2048 17*190ccab3SChiYuan Huang 18*190ccab3SChiYuan Huang #define RT4831_BLOVP_MASK GENMASK(7, 5) 19*190ccab3SChiYuan Huang #define RT4831_BLOVP_SHIFT 5 20*190ccab3SChiYuan Huang #define RT4831_BLPWMEN_MASK BIT(0) 21*190ccab3SChiYuan Huang #define RT4831_BLEN_MASK BIT(4) 22*190ccab3SChiYuan Huang #define RT4831_BLCH_MASK GENMASK(3, 0) 23*190ccab3SChiYuan Huang #define RT4831_BLDIML_MASK GENMASK(2, 0) 24*190ccab3SChiYuan Huang #define RT4831_BLDIMH_MASK GENMASK(10, 3) 25*190ccab3SChiYuan Huang #define RT4831_BLDIMH_SHIFT 3 26*190ccab3SChiYuan Huang 27*190ccab3SChiYuan Huang struct rt4831_priv { 28*190ccab3SChiYuan Huang struct device *dev; 29*190ccab3SChiYuan Huang struct regmap *regmap; 30*190ccab3SChiYuan Huang struct backlight_device *bl; 31*190ccab3SChiYuan Huang }; 32*190ccab3SChiYuan Huang 33*190ccab3SChiYuan Huang static int rt4831_bl_update_status(struct backlight_device *bl_dev) 34*190ccab3SChiYuan Huang { 35*190ccab3SChiYuan Huang struct rt4831_priv *priv = bl_get_data(bl_dev); 36*190ccab3SChiYuan Huang int brightness = backlight_get_brightness(bl_dev); 37*190ccab3SChiYuan Huang unsigned int enable = brightness ? RT4831_BLEN_MASK : 0; 38*190ccab3SChiYuan Huang u8 v[2]; 39*190ccab3SChiYuan Huang int ret; 40*190ccab3SChiYuan Huang 41*190ccab3SChiYuan Huang if (brightness) { 42*190ccab3SChiYuan Huang v[0] = (brightness - 1) & RT4831_BLDIML_MASK; 43*190ccab3SChiYuan Huang v[1] = ((brightness - 1) & RT4831_BLDIMH_MASK) >> RT4831_BLDIMH_SHIFT; 44*190ccab3SChiYuan Huang 45*190ccab3SChiYuan Huang ret = regmap_raw_write(priv->regmap, RT4831_REG_BLDIML, v, sizeof(v)); 46*190ccab3SChiYuan Huang if (ret) 47*190ccab3SChiYuan Huang return ret; 48*190ccab3SChiYuan Huang } 49*190ccab3SChiYuan Huang 50*190ccab3SChiYuan Huang return regmap_update_bits(priv->regmap, RT4831_REG_ENABLE, RT4831_BLEN_MASK, enable); 51*190ccab3SChiYuan Huang 52*190ccab3SChiYuan Huang } 53*190ccab3SChiYuan Huang 54*190ccab3SChiYuan Huang static int rt4831_bl_get_brightness(struct backlight_device *bl_dev) 55*190ccab3SChiYuan Huang { 56*190ccab3SChiYuan Huang struct rt4831_priv *priv = bl_get_data(bl_dev); 57*190ccab3SChiYuan Huang unsigned int val; 58*190ccab3SChiYuan Huang u8 v[2]; 59*190ccab3SChiYuan Huang int ret; 60*190ccab3SChiYuan Huang 61*190ccab3SChiYuan Huang ret = regmap_read(priv->regmap, RT4831_REG_ENABLE, &val); 62*190ccab3SChiYuan Huang if (ret) 63*190ccab3SChiYuan Huang return ret; 64*190ccab3SChiYuan Huang 65*190ccab3SChiYuan Huang if (!(val & RT4831_BLEN_MASK)) 66*190ccab3SChiYuan Huang return 0; 67*190ccab3SChiYuan Huang 68*190ccab3SChiYuan Huang ret = regmap_raw_read(priv->regmap, RT4831_REG_BLDIML, v, sizeof(v)); 69*190ccab3SChiYuan Huang if (ret) 70*190ccab3SChiYuan Huang return ret; 71*190ccab3SChiYuan Huang 72*190ccab3SChiYuan Huang ret = (v[1] << RT4831_BLDIMH_SHIFT) + (v[0] & RT4831_BLDIML_MASK) + 1; 73*190ccab3SChiYuan Huang 74*190ccab3SChiYuan Huang return ret; 75*190ccab3SChiYuan Huang } 76*190ccab3SChiYuan Huang 77*190ccab3SChiYuan Huang static const struct backlight_ops rt4831_bl_ops = { 78*190ccab3SChiYuan Huang .options = BL_CORE_SUSPENDRESUME, 79*190ccab3SChiYuan Huang .update_status = rt4831_bl_update_status, 80*190ccab3SChiYuan Huang .get_brightness = rt4831_bl_get_brightness, 81*190ccab3SChiYuan Huang }; 82*190ccab3SChiYuan Huang 83*190ccab3SChiYuan Huang static int rt4831_parse_backlight_properties(struct rt4831_priv *priv, 84*190ccab3SChiYuan Huang struct backlight_properties *bl_props) 85*190ccab3SChiYuan Huang { 86*190ccab3SChiYuan Huang struct device *dev = priv->dev; 87*190ccab3SChiYuan Huang u8 propval; 88*190ccab3SChiYuan Huang u32 brightness; 89*190ccab3SChiYuan Huang unsigned int val = 0; 90*190ccab3SChiYuan Huang int ret; 91*190ccab3SChiYuan Huang 92*190ccab3SChiYuan Huang /* common properties */ 93*190ccab3SChiYuan Huang ret = device_property_read_u32(dev, "max-brightness", &brightness); 94*190ccab3SChiYuan Huang if (ret) 95*190ccab3SChiYuan Huang brightness = RT4831_BLMAX_BRIGHTNESS; 96*190ccab3SChiYuan Huang 97*190ccab3SChiYuan Huang bl_props->max_brightness = min_t(u32, brightness, RT4831_BLMAX_BRIGHTNESS); 98*190ccab3SChiYuan Huang 99*190ccab3SChiYuan Huang ret = device_property_read_u32(dev, "default-brightness", &brightness); 100*190ccab3SChiYuan Huang if (ret) 101*190ccab3SChiYuan Huang brightness = bl_props->max_brightness; 102*190ccab3SChiYuan Huang 103*190ccab3SChiYuan Huang bl_props->brightness = min_t(u32, brightness, bl_props->max_brightness); 104*190ccab3SChiYuan Huang 105*190ccab3SChiYuan Huang /* vendor properties */ 106*190ccab3SChiYuan Huang if (device_property_read_bool(dev, "richtek,pwm-enable")) 107*190ccab3SChiYuan Huang val = RT4831_BLPWMEN_MASK; 108*190ccab3SChiYuan Huang 109*190ccab3SChiYuan Huang ret = regmap_update_bits(priv->regmap, RT4831_REG_BLCFG, RT4831_BLPWMEN_MASK, val); 110*190ccab3SChiYuan Huang if (ret) 111*190ccab3SChiYuan Huang return ret; 112*190ccab3SChiYuan Huang 113*190ccab3SChiYuan Huang ret = device_property_read_u8(dev, "richtek,bled-ovp-sel", &propval); 114*190ccab3SChiYuan Huang if (ret) 115*190ccab3SChiYuan Huang propval = RT4831_BLOVPLVL_21V; 116*190ccab3SChiYuan Huang 117*190ccab3SChiYuan Huang propval = min_t(u8, propval, RT4831_BLOVPLVL_29V); 118*190ccab3SChiYuan Huang ret = regmap_update_bits(priv->regmap, RT4831_REG_BLCFG, RT4831_BLOVP_MASK, 119*190ccab3SChiYuan Huang propval << RT4831_BLOVP_SHIFT); 120*190ccab3SChiYuan Huang if (ret) 121*190ccab3SChiYuan Huang return ret; 122*190ccab3SChiYuan Huang 123*190ccab3SChiYuan Huang ret = device_property_read_u8(dev, "richtek,channel-use", &propval); 124*190ccab3SChiYuan Huang if (ret) { 125*190ccab3SChiYuan Huang dev_err(dev, "richtek,channel-use DT property missing\n"); 126*190ccab3SChiYuan Huang return ret; 127*190ccab3SChiYuan Huang } 128*190ccab3SChiYuan Huang 129*190ccab3SChiYuan Huang if (!(propval & RT4831_BLCH_MASK)) { 130*190ccab3SChiYuan Huang dev_err(dev, "No channel specified\n"); 131*190ccab3SChiYuan Huang return -EINVAL; 132*190ccab3SChiYuan Huang } 133*190ccab3SChiYuan Huang 134*190ccab3SChiYuan Huang return regmap_update_bits(priv->regmap, RT4831_REG_ENABLE, RT4831_BLCH_MASK, propval); 135*190ccab3SChiYuan Huang } 136*190ccab3SChiYuan Huang 137*190ccab3SChiYuan Huang static int rt4831_bl_probe(struct platform_device *pdev) 138*190ccab3SChiYuan Huang { 139*190ccab3SChiYuan Huang struct rt4831_priv *priv; 140*190ccab3SChiYuan Huang struct backlight_properties bl_props = { .type = BACKLIGHT_RAW, 141*190ccab3SChiYuan Huang .scale = BACKLIGHT_SCALE_LINEAR }; 142*190ccab3SChiYuan Huang int ret; 143*190ccab3SChiYuan Huang 144*190ccab3SChiYuan Huang priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 145*190ccab3SChiYuan Huang if (!priv) 146*190ccab3SChiYuan Huang return -ENOMEM; 147*190ccab3SChiYuan Huang 148*190ccab3SChiYuan Huang priv->dev = &pdev->dev; 149*190ccab3SChiYuan Huang 150*190ccab3SChiYuan Huang priv->regmap = dev_get_regmap(pdev->dev.parent, NULL); 151*190ccab3SChiYuan Huang if (!priv->regmap) { 152*190ccab3SChiYuan Huang dev_err(&pdev->dev, "Failed to init regmap\n"); 153*190ccab3SChiYuan Huang return -ENODEV; 154*190ccab3SChiYuan Huang } 155*190ccab3SChiYuan Huang 156*190ccab3SChiYuan Huang ret = rt4831_parse_backlight_properties(priv, &bl_props); 157*190ccab3SChiYuan Huang if (ret) { 158*190ccab3SChiYuan Huang dev_err(&pdev->dev, "Failed to parse backlight properties\n"); 159*190ccab3SChiYuan Huang return ret; 160*190ccab3SChiYuan Huang } 161*190ccab3SChiYuan Huang 162*190ccab3SChiYuan Huang priv->bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev, priv, 163*190ccab3SChiYuan Huang &rt4831_bl_ops, &bl_props); 164*190ccab3SChiYuan Huang if (IS_ERR(priv->bl)) { 165*190ccab3SChiYuan Huang dev_err(&pdev->dev, "Failed to register backlight\n"); 166*190ccab3SChiYuan Huang return PTR_ERR(priv->bl); 167*190ccab3SChiYuan Huang } 168*190ccab3SChiYuan Huang 169*190ccab3SChiYuan Huang backlight_update_status(priv->bl); 170*190ccab3SChiYuan Huang platform_set_drvdata(pdev, priv); 171*190ccab3SChiYuan Huang 172*190ccab3SChiYuan Huang return 0; 173*190ccab3SChiYuan Huang } 174*190ccab3SChiYuan Huang 175*190ccab3SChiYuan Huang static int rt4831_bl_remove(struct platform_device *pdev) 176*190ccab3SChiYuan Huang { 177*190ccab3SChiYuan Huang struct rt4831_priv *priv = platform_get_drvdata(pdev); 178*190ccab3SChiYuan Huang struct backlight_device *bl_dev = priv->bl; 179*190ccab3SChiYuan Huang 180*190ccab3SChiYuan Huang bl_dev->props.brightness = 0; 181*190ccab3SChiYuan Huang backlight_update_status(priv->bl); 182*190ccab3SChiYuan Huang 183*190ccab3SChiYuan Huang return 0; 184*190ccab3SChiYuan Huang } 185*190ccab3SChiYuan Huang 186*190ccab3SChiYuan Huang static const struct of_device_id __maybe_unused rt4831_bl_of_match[] = { 187*190ccab3SChiYuan Huang { .compatible = "richtek,rt4831-backlight", }, 188*190ccab3SChiYuan Huang {} 189*190ccab3SChiYuan Huang }; 190*190ccab3SChiYuan Huang MODULE_DEVICE_TABLE(of, rt4831_bl_of_match); 191*190ccab3SChiYuan Huang 192*190ccab3SChiYuan Huang static struct platform_driver rt4831_bl_driver = { 193*190ccab3SChiYuan Huang .driver = { 194*190ccab3SChiYuan Huang .name = "rt4831-backlight", 195*190ccab3SChiYuan Huang .of_match_table = rt4831_bl_of_match, 196*190ccab3SChiYuan Huang }, 197*190ccab3SChiYuan Huang .probe = rt4831_bl_probe, 198*190ccab3SChiYuan Huang .remove = rt4831_bl_remove, 199*190ccab3SChiYuan Huang }; 200*190ccab3SChiYuan Huang module_platform_driver(rt4831_bl_driver); 201*190ccab3SChiYuan Huang 202*190ccab3SChiYuan Huang MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); 203*190ccab3SChiYuan Huang MODULE_LICENSE("GPL v2"); 204