xref: /linux/drivers/video/backlight/sy7758.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Silergy SY7758 6-channel High Efficiency LED Driver
4  *
5  * Copyright (C) 2025 Kancy Joe <kancy2333@outlook.com>
6  * Copyright (C) 2026 Linaro Limited
7  * Author: Neil Armstrong <neil.armstrong@linaro.org>
8  */
9 #include <linux/backlight.h>
10 #include <linux/module.h>
11 #include <linux/i2c.h>
12 #include <linux/of.h>
13 #include <linux/err.h>
14 #include <linux/bits.h>
15 #include <linux/delay.h>
16 #include <linux/regmap.h>
17 #include <linux/bitfield.h>
18 #include <linux/gpio/consumer.h>
19 #include <linux/regulator/consumer.h>
20 
21 #define DEFAULT_BRIGHTNESS	1024
22 #define MAX_BRIGHTNESS		4080
23 #define REG_MAX			0xAE
24 
25 /* Registers */
26 #define REG_DEV_CTL		0x01
27 #define REG_DEV_ID		0x03
28 #define REG_BRT_12BIT_L		0x10
29 #define REG_BRT_12BIT_H		0x11
30 
31 /* OTP memory */
32 #define REG_OTP_CFG0		0xA0
33 #define REG_OTP_CFG1		0xA1
34 #define REG_OTP_CFG2		0xA2
35 #define REG_OTP_CFG5		0xA5
36 #define REG_OTP_CFG9		0xA9
37 
38 /* Fields */
39 #define BIT_DEV_CTL_FAST	BIT(7)
40 #define MSK_DEV_CTL_BRT_MODE	GENMASK(2, 1)
41 #define BIT_DEV_CTL_BL_CTLB	BIT(0)
42 
43 #define MSK_BRT_12BIT_L		GENMASK(7, 0)
44 #define MSK_BRT_12BIT_H		GENMASK(3, 0)
45 
46 #define MSK_CFG0_CURRENT_LOW	GENMASK(7, 0)
47 
48 #define BIT_CFG1_PDET_STDBY	BIT(7)
49 #define MSK_CFG1_CURRENT_MAX	GENMASK(6, 4)
50 #define MSK_CFG1_CURRENT_HIGH	GENMASK(3, 0)
51 
52 #define BIT_CFG2_UVLO_EN	BIT(5)
53 #define BIT_CFG2_UVLO_TH	BIT(4)
54 #define BIT_CFG2_BL_ON		BIT(3)
55 #define BIT_CFG2_ISET_EN	BIT(2)
56 #define BIT_CFG2_BST_ESET_EN	BIT(1)
57 
58 #define BIT_CFG5_PWM_DIRECT	BIT(7)
59 #define MSK_CFG5_PS_MODE	GENMASK(6, 4)
60 #define MSK_CFG5_PWM_FREQ	GENMASK(3, 0)
61 
62 #define MSK_CFG9_VBST_MAX	GENMASK(7, 5)
63 #define BIT_CFG9_JUMP_EN	BIT(4)
64 #define MSK_CFG9_JUMP_TH	GENMASK(3, 2)
65 #define MSK_CFG9_JUMP_VOLTAGE	GENMASK(1, 0)
66 
67 struct sy7758 {
68 	struct i2c_client *client;
69 	struct regmap *regmap;
70 	struct gpio_desc *gpio;
71 	struct backlight_device *bl;
72 };
73 
74 static const struct regmap_config sy7758_regmap_config = {
75 	.reg_bits = 8,
76 	.val_bits = 8,
77 	.max_register = REG_MAX,
78 };
79 
80 static int sy7758_backlight_update_status(struct backlight_device *backlight_dev)
81 {
82 	struct sy7758 *sydev = bl_get_data(backlight_dev);
83 	unsigned int brightness = backlight_get_brightness(backlight_dev);
84 	int ret;
85 
86 	ret = regmap_write(sydev->regmap, REG_BRT_12BIT_L,
87 			   FIELD_PREP(MSK_BRT_12BIT_L,
88 				      brightness & 0xff));
89 	if (ret)
90 		return ret;
91 
92 	ret = regmap_write(sydev->regmap, REG_BRT_12BIT_H,
93 			   FIELD_PREP(MSK_BRT_12BIT_H,
94 				      (brightness >> 8) & 0xf));
95 	if (ret)
96 		return ret;
97 
98 	return 0;
99 }
100 
101 static const struct backlight_ops sy7758_backlight_ops = {
102 	.options = BL_CORE_SUSPENDRESUME,
103 	.update_status = sy7758_backlight_update_status,
104 };
105 
106 static int sy7758_init(struct sy7758 *sydev)
107 {
108 	int ret = 0;
109 
110 	ret = regmap_write(sydev->regmap, REG_DEV_CTL,
111 			   BIT_DEV_CTL_FAST | BIT_DEV_CTL_BL_CTLB |
112 			   FIELD_PREP(MSK_DEV_CTL_BRT_MODE, 2));
113 	if (ret)
114 		return ret;
115 
116 	ret = regmap_write(sydev->regmap, REG_BRT_12BIT_L,
117 			   FIELD_PREP(MSK_BRT_12BIT_L,
118 				      DEFAULT_BRIGHTNESS & 0xff));
119 	if (ret)
120 		return ret;
121 
122 	ret = regmap_write(sydev->regmap, REG_BRT_12BIT_H,
123 			   FIELD_PREP(MSK_BRT_12BIT_H,
124 				      (DEFAULT_BRIGHTNESS >> 8)));
125 	if (ret)
126 		return ret;
127 
128 	ret = regmap_write(sydev->regmap, REG_OTP_CFG5,
129 			   FIELD_PREP(MSK_CFG5_PS_MODE, 6) |
130 			   FIELD_PREP(MSK_CFG5_PWM_FREQ, 4));
131 	if (ret)
132 		return ret;
133 
134 	ret = regmap_write(sydev->regmap, REG_OTP_CFG0,
135 			   FIELD_PREP(MSK_CFG0_CURRENT_LOW, 85));
136 	if (ret)
137 		return ret;
138 
139 	ret = regmap_write(sydev->regmap, REG_OTP_CFG1,
140 			   BIT_CFG1_PDET_STDBY |
141 			   FIELD_PREP(MSK_CFG1_CURRENT_MAX, 1) |
142 			   FIELD_PREP(MSK_CFG1_CURRENT_HIGH, 10));
143 	if (ret)
144 		return ret;
145 
146 	ret = regmap_write(sydev->regmap, REG_OTP_CFG9,
147 			   FIELD_PREP(MSK_CFG9_VBST_MAX, 4));
148 	if (ret)
149 		return ret;
150 
151 	ret = regmap_write(sydev->regmap, REG_OTP_CFG2,
152 			   BIT_CFG2_BL_ON | BIT_CFG2_UVLO_EN);
153 	if (ret)
154 		return ret;
155 
156 	return 0;
157 }
158 
159 static int sy7758_probe(struct i2c_client *client)
160 {
161 	struct backlight_properties props = { };
162 	struct device *dev = &client->dev;
163 	struct sy7758 *sydev;
164 	unsigned int dev_id;
165 	int ret;
166 
167 	sydev = devm_kzalloc(dev, sizeof(*sydev), GFP_KERNEL);
168 	if (!sydev)
169 		return -ENOMEM;
170 
171 	i2c_set_clientdata(client, sydev);
172 
173 	/* Initialize regmap */
174 	sydev->client = client;
175 	sydev->regmap = devm_regmap_init_i2c(client, &sy7758_regmap_config);
176 	if (IS_ERR(sydev->regmap))
177 		return dev_err_probe(dev, PTR_ERR(sydev->regmap),
178 				     "failed to init regmap\n");
179 
180 	/* Get and enable regulator */
181 	ret = devm_regulator_get_enable(dev, "vdd");
182 	if (ret)
183 		return dev_err_probe(dev, ret, "failed to get regulator\n");
184 
185 	fsleep(100);
186 
187 	/* Get enable GPIO and set to high */
188 	sydev->gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);
189 	if (IS_ERR(sydev->gpio))
190 		return dev_err_probe(dev, PTR_ERR(sydev->gpio),
191 				     "failed to get enable GPIO\n");
192 
193 	/* Let some time for HW to settle */
194 	fsleep(10000);
195 
196 	/* try read and check device id */
197 	ret = regmap_read(sydev->regmap, REG_DEV_ID, &dev_id);
198 	if (ret < 0)
199 		return dev_err_probe(dev, ret, "failed to read device id\n");
200 	if (dev_id != 0x63) {
201 		dev_err(dev, "unexpected device id: 0x%02x\n", dev_id);
202 		return -ENODEV;
203 	}
204 
205 	/* Initialize and set default brightness */
206 	ret = sy7758_init(sydev);
207 	if (ret)
208 		return ret;
209 
210 	props.type = BACKLIGHT_RAW;
211 	props.max_brightness = MAX_BRIGHTNESS;
212 	props.brightness = DEFAULT_BRIGHTNESS;
213 	props.scale = BACKLIGHT_SCALE_LINEAR;
214 
215 	sydev->bl = devm_backlight_device_register(dev, "sy7758-backlight",
216 						   dev, sydev, &sy7758_backlight_ops,
217 						   &props);
218 	if (IS_ERR(sydev->bl))
219 		return dev_err_probe(dev, PTR_ERR(sydev->bl),
220 				     "failed to register backlight device\n");
221 
222 	return backlight_update_status(sydev->bl);
223 }
224 
225 static void sy7758_remove(struct i2c_client *client)
226 {
227 	struct sy7758 *sydev = i2c_get_clientdata(client);
228 
229 	backlight_disable(sydev->bl);
230 }
231 
232 static const struct i2c_device_id sy7758_ids[] = {
233 	{ "sy7758" },
234 	{ }
235 };
236 MODULE_DEVICE_TABLE(i2c, sy7758_ids);
237 
238 static const struct of_device_id sy7758_match_table[] = {
239 	{ .compatible = "silergy,sy7758", },
240 	{ },
241 };
242 MODULE_DEVICE_TABLE(of, sy7758_match_table);
243 
244 static struct i2c_driver sy7758_driver = {
245 	.driver = {
246 		.name = "sy7758",
247 		.of_match_table = sy7758_match_table,
248 	},
249 	.probe = sy7758_probe,
250 	.remove = sy7758_remove,
251 	.id_table = sy7758_ids,
252 };
253 
254 module_i2c_driver(sy7758_driver);
255 
256 MODULE_DESCRIPTION("Silergy SY7758 Backlight Driver");
257 MODULE_AUTHOR("Kancy Joe <kancy2333@outlook.com>");
258 MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>");
259 MODULE_LICENSE("GPL");
260