1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2022 Richtek Technology Corp. 4 * 5 * Author: ChiaEn Wu <chiaen_wu@richtek.com> 6 */ 7 8 #include <linux/backlight.h> 9 #include <linux/bitfield.h> 10 #include <linux/bits.h> 11 #include <linux/gpio/consumer.h> 12 #include <linux/kernel.h> 13 #include <linux/minmax.h> 14 #include <linux/module.h> 15 #include <linux/platform_device.h> 16 #include <linux/property.h> 17 #include <linux/regmap.h> 18 19 #define MT6370_REG_DEV_INFO 0x100 20 #define MT6370_REG_BL_EN 0x1A0 21 #define MT6370_REG_BL_BSTCTRL 0x1A1 22 #define MT6370_REG_BL_PWM 0x1A2 23 #define MT6370_REG_BL_DIM2 0x1A4 24 25 #define MT6370_VENID_MASK GENMASK(7, 4) 26 #define MT6370_BL_EXT_EN_MASK BIT(7) 27 #define MT6370_BL_EN_MASK BIT(6) 28 #define MT6370_BL_CODE_MASK BIT(0) 29 #define MT6370_BL_CH_MASK GENMASK(5, 2) 30 #define MT6370_BL_CH_SHIFT 2 31 #define MT6370_BL_DIM2_COMMON_MASK GENMASK(2, 0) 32 #define MT6370_BL_DIM2_COMMON_SHIFT 3 33 #define MT6370_BL_DIM2_6372_MASK GENMASK(5, 0) 34 #define MT6370_BL_DIM2_6372_SHIFT 6 35 #define MT6370_BL_PWM_EN_MASK BIT(7) 36 #define MT6370_BL_PWM_HYS_EN_MASK BIT(2) 37 #define MT6370_BL_PWM_HYS_SEL_MASK GENMASK(1, 0) 38 #define MT6370_BL_OVP_EN_MASK BIT(7) 39 #define MT6370_BL_OVP_SEL_MASK GENMASK(6, 5) 40 #define MT6370_BL_OVP_SEL_SHIFT 5 41 #define MT6370_BL_OC_EN_MASK BIT(3) 42 #define MT6370_BL_OC_SEL_MASK GENMASK(2, 1) 43 #define MT6370_BL_OC_SEL_SHIFT 1 44 45 #define MT6370_BL_PWM_HYS_TH_MIN_STEP 1 46 #define MT6370_BL_PWM_HYS_TH_MAX_STEP 64 47 #define MT6370_BL_OVP_MIN_UV 17000000 48 #define MT6370_BL_OVP_MAX_UV 29000000 49 #define MT6370_BL_OVP_STEP_UV 4000000 50 #define MT6370_BL_OCP_MIN_UA 900000 51 #define MT6370_BL_OCP_MAX_UA 1800000 52 #define MT6370_BL_OCP_STEP_UA 300000 53 #define MT6370_BL_MAX_COMMON_BRIGHTNESS 2048 54 #define MT6370_BL_MAX_6372_BRIGHTNESS 16384 55 #define MT6370_BL_MAX_CH 15 56 57 enum { 58 MT6370_VID_COMMON = 1, 59 MT6370_VID_6372, 60 }; 61 62 struct mt6370_priv { 63 u8 dim2_mask; 64 u8 dim2_shift; 65 int def_max_brightness; 66 struct backlight_device *bl; 67 struct device *dev; 68 struct gpio_desc *enable_gpio; 69 struct regmap *regmap; 70 }; 71 72 static int mt6370_bl_update_status(struct backlight_device *bl_dev) 73 { 74 struct mt6370_priv *priv = bl_get_data(bl_dev); 75 int brightness = backlight_get_brightness(bl_dev); 76 unsigned int enable_val; 77 u8 brightness_val[2]; 78 int ret; 79 80 if (brightness) { 81 brightness_val[0] = (brightness - 1) & priv->dim2_mask; 82 brightness_val[1] = (brightness - 1) >> priv->dim2_shift; 83 84 ret = regmap_raw_write(priv->regmap, MT6370_REG_BL_DIM2, 85 brightness_val, sizeof(brightness_val)); 86 if (ret) 87 return ret; 88 } 89 90 gpiod_set_value(priv->enable_gpio, !!brightness); 91 92 enable_val = brightness ? MT6370_BL_EN_MASK : 0; 93 return regmap_update_bits(priv->regmap, MT6370_REG_BL_EN, 94 MT6370_BL_EN_MASK, enable_val); 95 } 96 97 static int mt6370_bl_get_brightness(struct backlight_device *bl_dev) 98 { 99 struct mt6370_priv *priv = bl_get_data(bl_dev); 100 unsigned int enable; 101 u8 brightness_val[2]; 102 int brightness, ret; 103 104 ret = regmap_read(priv->regmap, MT6370_REG_BL_EN, &enable); 105 if (ret) 106 return ret; 107 108 if (!(enable & MT6370_BL_EN_MASK)) 109 return 0; 110 111 ret = regmap_raw_read(priv->regmap, MT6370_REG_BL_DIM2, 112 brightness_val, sizeof(brightness_val)); 113 if (ret) 114 return ret; 115 116 brightness = brightness_val[1] << priv->dim2_shift; 117 brightness += brightness_val[0] & priv->dim2_mask; 118 119 return brightness + 1; 120 } 121 122 static const struct backlight_ops mt6370_bl_ops = { 123 .options = BL_CORE_SUSPENDRESUME, 124 .update_status = mt6370_bl_update_status, 125 .get_brightness = mt6370_bl_get_brightness, 126 }; 127 128 static int mt6370_init_backlight_properties(struct mt6370_priv *priv, 129 struct backlight_properties *props) 130 { 131 struct device *dev = priv->dev; 132 u8 prop_val; 133 u32 brightness, ovp_uV, ocp_uA; 134 unsigned int mask, val; 135 int ret; 136 137 /* Vendor optional properties */ 138 val = 0; 139 if (device_property_read_bool(dev, "mediatek,bled-pwm-enable")) 140 val |= MT6370_BL_PWM_EN_MASK; 141 142 if (device_property_read_bool(dev, "mediatek,bled-pwm-hys-enable")) 143 val |= MT6370_BL_PWM_HYS_EN_MASK; 144 145 ret = device_property_read_u8(dev, 146 "mediatek,bled-pwm-hys-input-th-steps", 147 &prop_val); 148 if (!ret) { 149 prop_val = clamp_val(prop_val, 150 MT6370_BL_PWM_HYS_TH_MIN_STEP, 151 MT6370_BL_PWM_HYS_TH_MAX_STEP); 152 prop_val = prop_val <= 1 ? 0 : 153 prop_val <= 4 ? 1 : 154 prop_val <= 16 ? 2 : 3; 155 val |= prop_val; 156 } 157 158 ret = regmap_update_bits(priv->regmap, MT6370_REG_BL_PWM, 159 val, val); 160 if (ret) 161 return ret; 162 163 val = 0; 164 if (device_property_read_bool(dev, "mediatek,bled-ovp-shutdown")) 165 val |= MT6370_BL_OVP_EN_MASK; 166 167 ret = device_property_read_u32(dev, "mediatek,bled-ovp-microvolt", 168 &ovp_uV); 169 if (!ret) { 170 ovp_uV = clamp_val(ovp_uV, MT6370_BL_OVP_MIN_UV, 171 MT6370_BL_OVP_MAX_UV); 172 ovp_uV = DIV_ROUND_UP(ovp_uV - MT6370_BL_OVP_MIN_UV, 173 MT6370_BL_OVP_STEP_UV); 174 val |= ovp_uV << MT6370_BL_OVP_SEL_SHIFT; 175 } 176 177 if (device_property_read_bool(dev, "mediatek,bled-ocp-shutdown")) 178 val |= MT6370_BL_OC_EN_MASK; 179 180 ret = device_property_read_u32(dev, "mediatek,bled-ocp-microamp", 181 &ocp_uA); 182 if (!ret) { 183 ocp_uA = clamp_val(ocp_uA, MT6370_BL_OCP_MIN_UA, 184 MT6370_BL_OCP_MAX_UA); 185 ocp_uA = DIV_ROUND_UP(ocp_uA - MT6370_BL_OCP_MIN_UA, 186 MT6370_BL_OCP_STEP_UA); 187 val |= ocp_uA << MT6370_BL_OC_SEL_SHIFT; 188 } 189 190 ret = regmap_update_bits(priv->regmap, MT6370_REG_BL_BSTCTRL, 191 val, val); 192 if (ret) 193 return ret; 194 195 /* Common properties */ 196 ret = device_property_read_u32(dev, "max-brightness", &brightness); 197 if (ret) 198 brightness = priv->def_max_brightness; 199 200 props->max_brightness = min_t(u32, brightness, priv->def_max_brightness); 201 202 ret = device_property_read_u32(dev, "default-brightness", &brightness); 203 if (ret) 204 brightness = props->max_brightness; 205 206 props->brightness = min_t(u32, brightness, props->max_brightness); 207 208 val = 0; 209 if (device_property_read_bool(dev, "mediatek,bled-exponential-mode-enable")) { 210 val |= MT6370_BL_CODE_MASK; 211 props->scale = BACKLIGHT_SCALE_NON_LINEAR; 212 } else 213 props->scale = BACKLIGHT_SCALE_LINEAR; 214 215 ret = device_property_read_u8(dev, "mediatek,bled-channel-use", 216 &prop_val); 217 if (ret) { 218 dev_err(dev, "mediatek,bled-channel-use DT property missing\n"); 219 return ret; 220 } 221 222 if (!prop_val || prop_val > MT6370_BL_MAX_CH) { 223 dev_err(dev, 224 "No channel specified or over than upper bound (%d)\n", 225 prop_val); 226 return -EINVAL; 227 } 228 229 mask = MT6370_BL_EXT_EN_MASK | MT6370_BL_CH_MASK; 230 val |= prop_val << MT6370_BL_CH_SHIFT; 231 232 if (priv->enable_gpio) 233 val |= MT6370_BL_EXT_EN_MASK; 234 235 return regmap_update_bits(priv->regmap, MT6370_REG_BL_EN, mask, val); 236 } 237 238 static int mt6370_check_vendor_info(struct mt6370_priv *priv) 239 { 240 /* 241 * Because MT6372 uses 14 bits to control the brightness, 242 * MT6370 and MT6371 use 11 bits. This function is used 243 * to check the vendor's ID and set the relative hardware 244 * mask, shift and default maximum brightness value that 245 * should be used. 246 */ 247 unsigned int dev_info, hw_vid, of_vid; 248 int ret; 249 250 ret = regmap_read(priv->regmap, MT6370_REG_DEV_INFO, &dev_info); 251 if (ret) 252 return ret; 253 254 of_vid = (uintptr_t)device_get_match_data(priv->dev); 255 hw_vid = FIELD_GET(MT6370_VENID_MASK, dev_info); 256 hw_vid = (hw_vid == 0x9 || hw_vid == 0xb) ? MT6370_VID_6372 : MT6370_VID_COMMON; 257 if (hw_vid != of_vid) 258 return dev_err_probe(priv->dev, -EINVAL, 259 "Buggy DT, wrong compatible string\n"); 260 261 if (hw_vid == MT6370_VID_6372) { 262 priv->dim2_mask = MT6370_BL_DIM2_6372_MASK; 263 priv->dim2_shift = MT6370_BL_DIM2_6372_SHIFT; 264 priv->def_max_brightness = MT6370_BL_MAX_6372_BRIGHTNESS; 265 } else { 266 priv->dim2_mask = MT6370_BL_DIM2_COMMON_MASK; 267 priv->dim2_shift = MT6370_BL_DIM2_COMMON_SHIFT; 268 priv->def_max_brightness = MT6370_BL_MAX_COMMON_BRIGHTNESS; 269 } 270 271 return 0; 272 } 273 274 static int mt6370_bl_probe(struct platform_device *pdev) 275 { 276 struct backlight_properties props = { 277 .type = BACKLIGHT_RAW, 278 }; 279 struct device *dev = &pdev->dev; 280 struct mt6370_priv *priv; 281 int ret; 282 283 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 284 if (!priv) 285 return -ENOMEM; 286 287 priv->dev = dev; 288 289 priv->regmap = dev_get_regmap(dev->parent, NULL); 290 if (!priv->regmap) 291 return dev_err_probe(dev, -ENODEV, "Failed to get regmap\n"); 292 293 ret = mt6370_check_vendor_info(priv); 294 if (ret) 295 return dev_err_probe(dev, ret, "Failed to check vendor info\n"); 296 297 priv->enable_gpio = devm_gpiod_get_optional(dev, "enable", 298 GPIOD_OUT_HIGH); 299 if (IS_ERR(priv->enable_gpio)) 300 return dev_err_probe(dev, PTR_ERR(priv->enable_gpio), 301 "Failed to get 'enable' gpio\n"); 302 303 ret = mt6370_init_backlight_properties(priv, &props); 304 if (ret) 305 return dev_err_probe(dev, ret, 306 "Failed to init backlight properties\n"); 307 308 priv->bl = devm_backlight_device_register(dev, pdev->name, dev, priv, 309 &mt6370_bl_ops, &props); 310 if (IS_ERR(priv->bl)) 311 return dev_err_probe(dev, PTR_ERR(priv->bl), 312 "Failed to register backlight\n"); 313 314 backlight_update_status(priv->bl); 315 platform_set_drvdata(pdev, priv); 316 317 return 0; 318 } 319 320 static void mt6370_bl_remove(struct platform_device *pdev) 321 { 322 struct mt6370_priv *priv = platform_get_drvdata(pdev); 323 struct backlight_device *bl_dev = priv->bl; 324 325 bl_dev->props.brightness = 0; 326 backlight_update_status(priv->bl); 327 } 328 329 static const struct of_device_id mt6370_bl_of_match[] = { 330 { .compatible = "mediatek,mt6370-backlight", .data = (void *)MT6370_VID_COMMON }, 331 { .compatible = "mediatek,mt6372-backlight", .data = (void *)MT6370_VID_6372 }, 332 {} 333 }; 334 MODULE_DEVICE_TABLE(of, mt6370_bl_of_match); 335 336 static struct platform_driver mt6370_bl_driver = { 337 .driver = { 338 .name = "mt6370-backlight", 339 .of_match_table = mt6370_bl_of_match, 340 }, 341 .probe = mt6370_bl_probe, 342 .remove = mt6370_bl_remove, 343 }; 344 module_platform_driver(mt6370_bl_driver); 345 346 MODULE_AUTHOR("ChiaEn Wu <chiaen_wu@richtek.com>"); 347 MODULE_DESCRIPTION("MediaTek MT6370 Backlight Driver"); 348 MODULE_LICENSE("GPL v2"); 349