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/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 0x100 |
21 | #define MT6370_REG_BL_EN 0x1A0 |
22 | #define MT6370_REG_BL_BSTCTRL 0x1A1 |
23 | #define MT6370_REG_BL_PWM 0x1A2 |
24 | #define MT6370_REG_BL_DIM2 0x1A4 |
25 | |
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 2 |
32 | #define MT6370_BL_DIM2_COMMON_MASK GENMASK(2, 0) |
33 | #define MT6370_BL_DIM2_COMMON_SHIFT 3 |
34 | #define MT6370_BL_DIM2_6372_MASK GENMASK(5, 0) |
35 | #define MT6370_BL_DIM2_6372_SHIFT 6 |
36 | #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 5 |
42 | #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 1 |
45 | |
46 | #define MT6370_BL_PWM_HYS_TH_MIN_STEP 1 |
47 | #define MT6370_BL_PWM_HYS_TH_MAX_STEP 64 |
48 | #define MT6370_BL_OVP_MIN_UV 17000000 |
49 | #define MT6370_BL_OVP_MAX_UV 29000000 |
50 | #define MT6370_BL_OVP_STEP_UV 4000000 |
51 | #define MT6370_BL_OCP_MIN_UA 900000 |
52 | #define MT6370_BL_OCP_MAX_UA 1800000 |
53 | #define MT6370_BL_OCP_STEP_UA 300000 |
54 | #define MT6370_BL_MAX_COMMON_BRIGHTNESS 2048 |
55 | #define MT6370_BL_MAX_6372_BRIGHTNESS 16384 |
56 | #define MT6370_BL_MAX_CH 15 |
57 | |
58 | enum { |
59 | MT6370_VID_COMMON = 1, |
60 | MT6370_VID_6372, |
61 | }; |
62 | |
63 | struct 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 | |
73 | static 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(bd: 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(map: priv->regmap, MT6370_REG_BL_DIM2, |
86 | val: brightness_val, val_len: sizeof(brightness_val)); |
87 | if (ret) |
88 | return ret; |
89 | } |
90 | |
91 | gpiod_set_value(desc: priv->enable_gpio, value: !!brightness); |
92 | |
93 | enable_val = brightness ? MT6370_BL_EN_MASK : 0; |
94 | return regmap_update_bits(map: priv->regmap, MT6370_REG_BL_EN, |
95 | MT6370_BL_EN_MASK, val: enable_val); |
96 | } |
97 | |
98 | static 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(map: priv->regmap, MT6370_REG_BL_EN, val: &enable); |
106 | if (ret) |
107 | return ret; |
108 | |
109 | if (!(enable & MT6370_BL_EN_MASK)) |
110 | return 0; |
111 | |
112 | ret = regmap_raw_read(map: priv->regmap, MT6370_REG_BL_DIM2, |
113 | val: brightness_val, val_len: 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 | |
123 | static 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 | |
129 | static 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, propname: "mediatek,bled-pwm-enable" )) |
141 | val |= MT6370_BL_PWM_EN_MASK; |
142 | |
143 | if (device_property_read_bool(dev, propname: "mediatek,bled-pwm-hys-enable" )) |
144 | val |= MT6370_BL_PWM_HYS_EN_MASK; |
145 | |
146 | ret = device_property_read_u8(dev, |
147 | propname: "mediatek,bled-pwm-hys-input-th-steps" , |
148 | val: &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(map: priv->regmap, MT6370_REG_BL_PWM, |
160 | mask: val, val); |
161 | if (ret) |
162 | return ret; |
163 | |
164 | val = 0; |
165 | if (device_property_read_bool(dev, propname: "mediatek,bled-ovp-shutdown" )) |
166 | val |= MT6370_BL_OVP_EN_MASK; |
167 | |
168 | ret = device_property_read_u32(dev, propname: "mediatek,bled-ovp-microvolt" , |
169 | val: &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, propname: "mediatek,bled-ocp-shutdown" )) |
179 | val |= MT6370_BL_OC_EN_MASK; |
180 | |
181 | ret = device_property_read_u32(dev, propname: "mediatek,bled-ocp-microamp" , |
182 | val: &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(map: priv->regmap, MT6370_REG_BL_BSTCTRL, |
192 | mask: val, val); |
193 | if (ret) |
194 | return ret; |
195 | |
196 | /* Common properties */ |
197 | ret = device_property_read_u32(dev, propname: "max-brightness" , val: &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, propname: "default-brightness" , val: &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, propname: "mediatek,bled-exponential-mode-enable" )) { |
211 | val |= MT6370_BL_CODE_MASK; |
212 | props->scale = BACKLIGHT_SCALE_NON_LINEAR; |
213 | } else |
214 | props->scale = BACKLIGHT_SCALE_LINEAR; |
215 | |
216 | ret = device_property_read_u8(dev, propname: "mediatek,bled-channel-use" , |
217 | val: &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(map: priv->regmap, MT6370_REG_BL_EN, mask, val); |
237 | } |
238 | |
239 | static 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 used |
244 | * to check the vendor's ID and set the relative hardware |
245 | * mask, shift and default maximum brightness value that |
246 | * should be used. |
247 | */ |
248 | unsigned int dev_info, hw_vid, of_vid; |
249 | int ret; |
250 | |
251 | ret = regmap_read(map: priv->regmap, MT6370_REG_DEV_INFO, val: &dev_info); |
252 | if (ret) |
253 | return ret; |
254 | |
255 | of_vid = (uintptr_t)device_get_match_data(dev: 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(dev: priv->dev, err: -EINVAL, |
260 | fmt: "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 | |
275 | static 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(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
285 | if (!priv) |
286 | return -ENOMEM; |
287 | |
288 | priv->dev = dev; |
289 | |
290 | priv->regmap = dev_get_regmap(dev: dev->parent, NULL); |
291 | if (!priv->regmap) |
292 | return dev_err_probe(dev, err: -ENODEV, fmt: "Failed to get regmap\n" ); |
293 | |
294 | ret = mt6370_check_vendor_info(priv); |
295 | if (ret) |
296 | return dev_err_probe(dev, err: ret, fmt: "Failed to check vendor info\n" ); |
297 | |
298 | priv->enable_gpio = devm_gpiod_get_optional(dev, con_id: "enable" , |
299 | flags: GPIOD_OUT_HIGH); |
300 | if (IS_ERR(ptr: priv->enable_gpio)) |
301 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->enable_gpio), |
302 | fmt: "Failed to get 'enable' gpio\n" ); |
303 | |
304 | ret = mt6370_init_backlight_properties(priv, props: &props); |
305 | if (ret) |
306 | return dev_err_probe(dev, err: ret, |
307 | fmt: "Failed to init backlight properties\n" ); |
308 | |
309 | priv->bl = devm_backlight_device_register(dev, name: pdev->name, parent: dev, devdata: priv, |
310 | ops: &mt6370_bl_ops, props: &props); |
311 | if (IS_ERR(ptr: priv->bl)) |
312 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->bl), |
313 | fmt: "Failed to register backlight\n" ); |
314 | |
315 | backlight_update_status(bd: priv->bl); |
316 | platform_set_drvdata(pdev, data: priv); |
317 | |
318 | return 0; |
319 | } |
320 | |
321 | static 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(bd: priv->bl); |
328 | } |
329 | |
330 | static 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 | }; |
335 | MODULE_DEVICE_TABLE(of, mt6370_bl_of_match); |
336 | |
337 | static 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 | }; |
345 | module_platform_driver(mt6370_bl_driver); |
346 | |
347 | MODULE_AUTHOR("ChiaEn Wu <chiaen_wu@richtek.com>" ); |
348 | MODULE_DESCRIPTION("MediaTek MT6370 Backlight Driver" ); |
349 | MODULE_LICENSE("GPL v2" ); |
350 | |