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