summaryrefslogtreecommitdiff
path: root/drivers/video/backlight/sy7758.c
blob: 786589cb8df910be7a936d4cae241739fc1cdd49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Silergy SY7758 6-channel High Efficiency LED Driver
 *
 * Copyright (C) 2025 Kancy Joe <kancy2333@outlook.com>
 * Copyright (C) 2026 Linaro Limited
 * Author: Neil Armstrong <neil.armstrong@linaro.org>
 */
#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/bitfield.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>

#define DEFAULT_BRIGHTNESS	1024
#define MAX_BRIGHTNESS		4080
#define REG_MAX			0xAE

/* Registers */
#define REG_DEV_CTL		0x01
#define REG_DEV_ID		0x03
#define REG_BRT_12BIT_L		0x10
#define REG_BRT_12BIT_H		0x11

/* OTP memory */
#define REG_OTP_CFG0		0xA0
#define REG_OTP_CFG1		0xA1
#define REG_OTP_CFG2		0xA2
#define REG_OTP_CFG5		0xA5
#define REG_OTP_CFG9		0xA9

/* Fields */
#define BIT_DEV_CTL_FAST	BIT(7)
#define MSK_DEV_CTL_BRT_MODE	GENMASK(2, 1)
#define BIT_DEV_CTL_BL_CTLB	BIT(0)

#define MSK_BRT_12BIT_L		GENMASK(7, 0)
#define MSK_BRT_12BIT_H		GENMASK(3, 0)

#define MSK_CFG0_CURRENT_LOW	GENMASK(7, 0)

#define BIT_CFG1_PDET_STDBY	BIT(7)
#define MSK_CFG1_CURRENT_MAX	GENMASK(6, 4)
#define MSK_CFG1_CURRENT_HIGH	GENMASK(3, 0)

#define BIT_CFG2_UVLO_EN	BIT(5)
#define BIT_CFG2_UVLO_TH	BIT(4)
#define BIT_CFG2_BL_ON		BIT(3)
#define BIT_CFG2_ISET_EN	BIT(2)
#define BIT_CFG2_BST_ESET_EN	BIT(1)

#define BIT_CFG5_PWM_DIRECT	BIT(7)
#define MSK_CFG5_PS_MODE	GENMASK(6, 4)
#define MSK_CFG5_PWM_FREQ	GENMASK(3, 0)

#define MSK_CFG9_VBST_MAX	GENMASK(7, 5)
#define BIT_CFG9_JUMP_EN	BIT(4)
#define MSK_CFG9_JUMP_TH	GENMASK(3, 2)
#define MSK_CFG9_JUMP_VOLTAGE	GENMASK(1, 0)

struct sy7758 {
	struct i2c_client *client;
	struct regmap *regmap;
	struct gpio_desc *gpio;
	struct backlight_device *bl;
};

static const struct regmap_config sy7758_regmap_config = {
	.reg_bits = 8,
	.val_bits = 8,
	.max_register = REG_MAX,
};

static int sy7758_backlight_update_status(struct backlight_device *backlight_dev)
{
	struct sy7758 *sydev = bl_get_data(backlight_dev);
	unsigned int brightness = backlight_get_brightness(backlight_dev);
	int ret;

	ret = regmap_write(sydev->regmap, REG_BRT_12BIT_L,
			   FIELD_PREP(MSK_BRT_12BIT_L,
				      brightness & 0xff));
	if (ret)
		return ret;

	ret = regmap_write(sydev->regmap, REG_BRT_12BIT_H,
			   FIELD_PREP(MSK_BRT_12BIT_H,
				      (brightness >> 8) & 0xf));
	if (ret)
		return ret;

	return 0;
}

static const struct backlight_ops sy7758_backlight_ops = {
	.options = BL_CORE_SUSPENDRESUME,
	.update_status = sy7758_backlight_update_status,
};

static int sy7758_init(struct sy7758 *sydev)
{
	int ret = 0;

	ret = regmap_write(sydev->regmap, REG_DEV_CTL,
			   BIT_DEV_CTL_FAST | BIT_DEV_CTL_BL_CTLB |
			   FIELD_PREP(MSK_DEV_CTL_BRT_MODE, 2));
	if (ret)
		return ret;

	ret = regmap_write(sydev->regmap, REG_BRT_12BIT_L,
			   FIELD_PREP(MSK_BRT_12BIT_L,
				      DEFAULT_BRIGHTNESS & 0xff));
	if (ret)
		return ret;

	ret = regmap_write(sydev->regmap, REG_BRT_12BIT_H,
			   FIELD_PREP(MSK_BRT_12BIT_H,
				      (DEFAULT_BRIGHTNESS >> 8)));
	if (ret)
		return ret;

	ret = regmap_write(sydev->regmap, REG_OTP_CFG5,
			   FIELD_PREP(MSK_CFG5_PS_MODE, 6) |
			   FIELD_PREP(MSK_CFG5_PWM_FREQ, 4));
	if (ret)
		return ret;

	ret = regmap_write(sydev->regmap, REG_OTP_CFG0,
			   FIELD_PREP(MSK_CFG0_CURRENT_LOW, 85));
	if (ret)
		return ret;

	ret = regmap_write(sydev->regmap, REG_OTP_CFG1,
			   BIT_CFG1_PDET_STDBY |
			   FIELD_PREP(MSK_CFG1_CURRENT_MAX, 1) |
			   FIELD_PREP(MSK_CFG1_CURRENT_HIGH, 10));
	if (ret)
		return ret;

	ret = regmap_write(sydev->regmap, REG_OTP_CFG9,
			   FIELD_PREP(MSK_CFG9_VBST_MAX, 4));
	if (ret)
		return ret;

	ret = regmap_write(sydev->regmap, REG_OTP_CFG2,
			   BIT_CFG2_BL_ON | BIT_CFG2_UVLO_EN);
	if (ret)
		return ret;

	return 0;
}

static int sy7758_probe(struct i2c_client *client)
{
	struct backlight_properties props = { };
	struct device *dev = &client->dev;
	struct sy7758 *sydev;
	unsigned int dev_id;
	int ret;

	sydev = devm_kzalloc(dev, sizeof(*sydev), GFP_KERNEL);
	if (!sydev)
		return -ENOMEM;

	i2c_set_clientdata(client, sydev);

	/* Initialize regmap */
	sydev->client = client;
	sydev->regmap = devm_regmap_init_i2c(client, &sy7758_regmap_config);
	if (IS_ERR(sydev->regmap))
		return dev_err_probe(dev, PTR_ERR(sydev->regmap),
				     "failed to init regmap\n");

	/* Get and enable regulator */
	ret = devm_regulator_get_enable(dev, "vdd");
	if (ret)
		return dev_err_probe(dev, ret, "failed to get regulator\n");

	fsleep(100);

	/* Get enable GPIO and set to high */
	sydev->gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);
	if (IS_ERR(sydev->gpio))
		return dev_err_probe(dev, PTR_ERR(sydev->gpio),
				     "failed to get enable GPIO\n");

	/* Let some time for HW to settle */
	fsleep(10000);

	/* try read and check device id */
	ret = regmap_read(sydev->regmap, REG_DEV_ID, &dev_id);
	if (ret < 0)
		return dev_err_probe(dev, ret, "failed to read device id\n");
	if (dev_id != 0x63) {
		dev_err(dev, "unexpected device id: 0x%02x\n", dev_id);
		return -ENODEV;
	}

	/* Initialize and set default brightness */
	ret = sy7758_init(sydev);
	if (ret)
		return ret;

	props.type = BACKLIGHT_RAW;
	props.max_brightness = MAX_BRIGHTNESS;
	props.brightness = DEFAULT_BRIGHTNESS;
	props.scale = BACKLIGHT_SCALE_LINEAR;

	sydev->bl = devm_backlight_device_register(dev, "sy7758-backlight",
						   dev, sydev, &sy7758_backlight_ops,
						   &props);
	if (IS_ERR(sydev->bl))
		return dev_err_probe(dev, PTR_ERR(sydev->bl),
				     "failed to register backlight device\n");

	return backlight_update_status(sydev->bl);
}

static void sy7758_remove(struct i2c_client *client)
{
	struct sy7758 *sydev = i2c_get_clientdata(client);

	backlight_disable(sydev->bl);
}

static const struct i2c_device_id sy7758_ids[] = {
	{ "sy7758" },
	{ }
};
MODULE_DEVICE_TABLE(i2c, sy7758_ids);

static const struct of_device_id sy7758_match_table[] = {
	{ .compatible = "silergy,sy7758", },
	{ },
};
MODULE_DEVICE_TABLE(of, sy7758_match_table);

static struct i2c_driver sy7758_driver = {
	.driver = {
		.name = "sy7758",
		.of_match_table = sy7758_match_table,
	},
	.probe = sy7758_probe,
	.remove = sy7758_remove,
	.id_table = sy7758_ids,
};

module_i2c_driver(sy7758_driver);

MODULE_DESCRIPTION("Silergy SY7758 Backlight Driver");
MODULE_AUTHOR("Kancy Joe <kancy2333@outlook.com>");
MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>");
MODULE_LICENSE("GPL");