1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | |
3 | #include <dt-bindings/leds/rt4831-backlight.h> |
4 | #include <linux/backlight.h> |
5 | #include <linux/bitops.h> |
6 | #include <linux/kernel.h> |
7 | #include <linux/module.h> |
8 | #include <linux/platform_device.h> |
9 | #include <linux/property.h> |
10 | #include <linux/regmap.h> |
11 | |
12 | #define RT4831_REG_BLCFG 0x02 |
13 | #define RT4831_REG_BLDIML 0x04 |
14 | #define RT4831_REG_ENABLE 0x08 |
15 | #define RT4831_REG_BLOPT2 0x11 |
16 | |
17 | #define RT4831_BLMAX_BRIGHTNESS 2048 |
18 | |
19 | #define RT4831_BLOVP_MASK GENMASK(7, 5) |
20 | #define RT4831_BLOVP_SHIFT 5 |
21 | #define RT4831_BLPWMEN_MASK BIT(0) |
22 | #define RT4831_BLEN_MASK BIT(4) |
23 | #define RT4831_BLCH_MASK GENMASK(3, 0) |
24 | #define RT4831_BLDIML_MASK GENMASK(2, 0) |
25 | #define RT4831_BLDIMH_MASK GENMASK(10, 3) |
26 | #define RT4831_BLDIMH_SHIFT 3 |
27 | #define RT4831_BLOCP_MASK GENMASK(1, 0) |
28 | |
29 | #define RT4831_BLOCP_MINUA 900000 |
30 | #define RT4831_BLOCP_MAXUA 1800000 |
31 | #define RT4831_BLOCP_STEPUA 300000 |
32 | |
33 | struct rt4831_priv { |
34 | struct device *dev; |
35 | struct regmap *regmap; |
36 | struct backlight_device *bl; |
37 | }; |
38 | |
39 | static int rt4831_bl_update_status(struct backlight_device *bl_dev) |
40 | { |
41 | struct rt4831_priv *priv = bl_get_data(bl_dev); |
42 | int brightness = backlight_get_brightness(bd: bl_dev); |
43 | unsigned int enable = brightness ? RT4831_BLEN_MASK : 0; |
44 | u8 v[2]; |
45 | int ret; |
46 | |
47 | if (brightness) { |
48 | v[0] = (brightness - 1) & RT4831_BLDIML_MASK; |
49 | v[1] = ((brightness - 1) & RT4831_BLDIMH_MASK) >> RT4831_BLDIMH_SHIFT; |
50 | |
51 | ret = regmap_raw_write(map: priv->regmap, RT4831_REG_BLDIML, val: v, val_len: sizeof(v)); |
52 | if (ret) |
53 | return ret; |
54 | } |
55 | |
56 | return regmap_update_bits(map: priv->regmap, RT4831_REG_ENABLE, RT4831_BLEN_MASK, val: enable); |
57 | |
58 | } |
59 | |
60 | static int rt4831_bl_get_brightness(struct backlight_device *bl_dev) |
61 | { |
62 | struct rt4831_priv *priv = bl_get_data(bl_dev); |
63 | unsigned int val; |
64 | u8 v[2]; |
65 | int ret; |
66 | |
67 | ret = regmap_read(map: priv->regmap, RT4831_REG_ENABLE, val: &val); |
68 | if (ret) |
69 | return ret; |
70 | |
71 | if (!(val & RT4831_BLEN_MASK)) |
72 | return 0; |
73 | |
74 | ret = regmap_raw_read(map: priv->regmap, RT4831_REG_BLDIML, val: v, val_len: sizeof(v)); |
75 | if (ret) |
76 | return ret; |
77 | |
78 | ret = (v[1] << RT4831_BLDIMH_SHIFT) + (v[0] & RT4831_BLDIML_MASK) + 1; |
79 | |
80 | return ret; |
81 | } |
82 | |
83 | static const struct backlight_ops rt4831_bl_ops = { |
84 | .options = BL_CORE_SUSPENDRESUME, |
85 | .update_status = rt4831_bl_update_status, |
86 | .get_brightness = rt4831_bl_get_brightness, |
87 | }; |
88 | |
89 | static int rt4831_parse_backlight_properties(struct rt4831_priv *priv, |
90 | struct backlight_properties *bl_props) |
91 | { |
92 | struct device *dev = priv->dev; |
93 | u8 propval; |
94 | u32 brightness, ocp_uA; |
95 | unsigned int val = 0; |
96 | int ret; |
97 | |
98 | /* common properties */ |
99 | ret = device_property_read_u32(dev, propname: "max-brightness" , val: &brightness); |
100 | if (ret) |
101 | brightness = RT4831_BLMAX_BRIGHTNESS; |
102 | |
103 | bl_props->max_brightness = min_t(u32, brightness, RT4831_BLMAX_BRIGHTNESS); |
104 | |
105 | ret = device_property_read_u32(dev, propname: "default-brightness" , val: &brightness); |
106 | if (ret) |
107 | brightness = bl_props->max_brightness; |
108 | |
109 | bl_props->brightness = min_t(u32, brightness, bl_props->max_brightness); |
110 | |
111 | /* vendor properties */ |
112 | if (device_property_read_bool(dev, propname: "richtek,pwm-enable" )) |
113 | val = RT4831_BLPWMEN_MASK; |
114 | |
115 | ret = regmap_update_bits(map: priv->regmap, RT4831_REG_BLCFG, RT4831_BLPWMEN_MASK, val); |
116 | if (ret) |
117 | return ret; |
118 | |
119 | ret = device_property_read_u8(dev, propname: "richtek,bled-ovp-sel" , val: &propval); |
120 | if (ret) |
121 | propval = RT4831_BLOVPLVL_21V; |
122 | |
123 | propval = min_t(u8, propval, RT4831_BLOVPLVL_29V); |
124 | ret = regmap_update_bits(map: priv->regmap, RT4831_REG_BLCFG, RT4831_BLOVP_MASK, |
125 | val: propval << RT4831_BLOVP_SHIFT); |
126 | if (ret) |
127 | return ret; |
128 | |
129 | /* |
130 | * This OCP level is used to protect and limit the inductor current. |
131 | * If inductor peak current reach the level, low-side MOSFET will be |
132 | * turned off. Meanwhile, the output channel current may be limited. |
133 | * To match the configured channel current, the inductor chosen must |
134 | * be higher than the OCP level. |
135 | * |
136 | * Not like the OVP level, the default 21V can be used in the most |
137 | * application. But if the chosen OCP level is smaller than needed, |
138 | * it will also affect the backlight channel output current to be |
139 | * smaller than the register setting. |
140 | */ |
141 | ret = device_property_read_u32(dev, propname: "richtek,bled-ocp-microamp" , |
142 | val: &ocp_uA); |
143 | if (!ret) { |
144 | ocp_uA = clamp_val(ocp_uA, RT4831_BLOCP_MINUA, |
145 | RT4831_BLOCP_MAXUA); |
146 | val = DIV_ROUND_UP(ocp_uA - RT4831_BLOCP_MINUA, |
147 | RT4831_BLOCP_STEPUA); |
148 | ret = regmap_update_bits(map: priv->regmap, RT4831_REG_BLOPT2, |
149 | RT4831_BLOCP_MASK, val); |
150 | if (ret) |
151 | return ret; |
152 | } |
153 | |
154 | ret = device_property_read_u8(dev, propname: "richtek,channel-use" , val: &propval); |
155 | if (ret) { |
156 | dev_err(dev, "richtek,channel-use DT property missing\n" ); |
157 | return ret; |
158 | } |
159 | |
160 | if (!(propval & RT4831_BLCH_MASK)) { |
161 | dev_err(dev, "No channel specified\n" ); |
162 | return -EINVAL; |
163 | } |
164 | |
165 | return regmap_update_bits(map: priv->regmap, RT4831_REG_ENABLE, RT4831_BLCH_MASK, val: propval); |
166 | } |
167 | |
168 | static int rt4831_bl_probe(struct platform_device *pdev) |
169 | { |
170 | struct rt4831_priv *priv; |
171 | struct backlight_properties bl_props = { .type = BACKLIGHT_RAW, |
172 | .scale = BACKLIGHT_SCALE_LINEAR }; |
173 | int ret; |
174 | |
175 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
176 | if (!priv) |
177 | return -ENOMEM; |
178 | |
179 | priv->dev = &pdev->dev; |
180 | |
181 | priv->regmap = dev_get_regmap(dev: pdev->dev.parent, NULL); |
182 | if (!priv->regmap) { |
183 | dev_err(&pdev->dev, "Failed to init regmap\n" ); |
184 | return -ENODEV; |
185 | } |
186 | |
187 | ret = rt4831_parse_backlight_properties(priv, bl_props: &bl_props); |
188 | if (ret) { |
189 | dev_err(&pdev->dev, "Failed to parse backlight properties\n" ); |
190 | return ret; |
191 | } |
192 | |
193 | priv->bl = devm_backlight_device_register(dev: &pdev->dev, name: pdev->name, parent: &pdev->dev, devdata: priv, |
194 | ops: &rt4831_bl_ops, props: &bl_props); |
195 | if (IS_ERR(ptr: priv->bl)) { |
196 | dev_err(&pdev->dev, "Failed to register backlight\n" ); |
197 | return PTR_ERR(ptr: priv->bl); |
198 | } |
199 | |
200 | backlight_update_status(bd: priv->bl); |
201 | platform_set_drvdata(pdev, data: priv); |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | static void rt4831_bl_remove(struct platform_device *pdev) |
207 | { |
208 | struct rt4831_priv *priv = platform_get_drvdata(pdev); |
209 | struct backlight_device *bl_dev = priv->bl; |
210 | |
211 | bl_dev->props.brightness = 0; |
212 | backlight_update_status(bd: priv->bl); |
213 | } |
214 | |
215 | static const struct of_device_id __maybe_unused rt4831_bl_of_match[] = { |
216 | { .compatible = "richtek,rt4831-backlight" , }, |
217 | {} |
218 | }; |
219 | MODULE_DEVICE_TABLE(of, rt4831_bl_of_match); |
220 | |
221 | static struct platform_driver rt4831_bl_driver = { |
222 | .driver = { |
223 | .name = "rt4831-backlight" , |
224 | .of_match_table = rt4831_bl_of_match, |
225 | }, |
226 | .probe = rt4831_bl_probe, |
227 | .remove_new = rt4831_bl_remove, |
228 | }; |
229 | module_platform_driver(rt4831_bl_driver); |
230 | |
231 | MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>" ); |
232 | MODULE_LICENSE("GPL v2" ); |
233 | |