1f8449c8fSJianhua Lu // SPDX-License-Identifier: GPL-2.0-only
2f8449c8fSJianhua Lu /*
3f8449c8fSJianhua Lu * Backlight driver for the Kinetic KTZ8866
4f8449c8fSJianhua Lu *
5f8449c8fSJianhua Lu * Copyright (C) 2022, 2023 Jianhua Lu <lujianhua000@gmail.com>
6f8449c8fSJianhua Lu */
7f8449c8fSJianhua Lu
8f8449c8fSJianhua Lu #include <linux/backlight.h>
9f8449c8fSJianhua Lu #include <linux/err.h>
10f8449c8fSJianhua Lu #include <linux/gpio/consumer.h>
11f8449c8fSJianhua Lu #include <linux/i2c.h>
12f8449c8fSJianhua Lu #include <linux/module.h>
13f8449c8fSJianhua Lu #include <linux/of.h>
14f8449c8fSJianhua Lu #include <linux/regmap.h>
15f8449c8fSJianhua Lu
16f8449c8fSJianhua Lu #define DEFAULT_BRIGHTNESS 1500
17f8449c8fSJianhua Lu #define MAX_BRIGHTNESS 2047
18f8449c8fSJianhua Lu #define REG_MAX 0x15
19f8449c8fSJianhua Lu
20f8449c8fSJianhua Lu /* reg */
21f8449c8fSJianhua Lu #define DEVICE_ID 0x01
22f8449c8fSJianhua Lu #define BL_CFG1 0x02
23f8449c8fSJianhua Lu #define BL_CFG2 0x03
24f8449c8fSJianhua Lu #define BL_BRT_LSB 0x04
25f8449c8fSJianhua Lu #define BL_BRT_MSB 0x05
26f8449c8fSJianhua Lu #define BL_EN 0x08
27f8449c8fSJianhua Lu #define LCD_BIAS_CFG1 0x09
28f8449c8fSJianhua Lu #define LCD_BIAS_CFG2 0x0A
29f8449c8fSJianhua Lu #define LCD_BIAS_CFG3 0x0B
30f8449c8fSJianhua Lu #define LCD_BOOST_CFG 0x0C
31f8449c8fSJianhua Lu #define OUTP_CFG 0x0D
32f8449c8fSJianhua Lu #define OUTN_CFG 0x0E
33f8449c8fSJianhua Lu #define FLAG 0x0F
34f8449c8fSJianhua Lu #define BL_OPTION1 0x10
35f8449c8fSJianhua Lu #define BL_OPTION2 0x11
36f8449c8fSJianhua Lu #define PWM2DIG_LSBs 0x12
37f8449c8fSJianhua Lu #define PWM2DIG_MSBs 0x13
38f8449c8fSJianhua Lu #define BL_DIMMING 0x14
39f8449c8fSJianhua Lu #define PWM_RAMP_TIME 0x15
40f8449c8fSJianhua Lu
41f8449c8fSJianhua Lu /* definition */
42f8449c8fSJianhua Lu #define BL_EN_BIT BIT(6)
43f8449c8fSJianhua Lu #define LCD_BIAS_EN 0x9F
44f8449c8fSJianhua Lu #define PWM_HYST 0x5
45f8449c8fSJianhua Lu
46f8449c8fSJianhua Lu struct ktz8866 {
47f8449c8fSJianhua Lu struct i2c_client *client;
48f8449c8fSJianhua Lu struct regmap *regmap;
49f8449c8fSJianhua Lu bool led_on;
50f8449c8fSJianhua Lu struct gpio_desc *enable_gpio;
51f8449c8fSJianhua Lu };
52f8449c8fSJianhua Lu
53f8449c8fSJianhua Lu static const struct regmap_config ktz8866_regmap_config = {
54f8449c8fSJianhua Lu .reg_bits = 8,
55f8449c8fSJianhua Lu .val_bits = 8,
56f8449c8fSJianhua Lu .max_register = REG_MAX,
57f8449c8fSJianhua Lu };
58f8449c8fSJianhua Lu
ktz8866_write(struct ktz8866 * ktz,unsigned int reg,unsigned int val)59f8449c8fSJianhua Lu static int ktz8866_write(struct ktz8866 *ktz, unsigned int reg,
60f8449c8fSJianhua Lu unsigned int val)
61f8449c8fSJianhua Lu {
62f8449c8fSJianhua Lu return regmap_write(ktz->regmap, reg, val);
63f8449c8fSJianhua Lu }
64f8449c8fSJianhua Lu
ktz8866_update_bits(struct ktz8866 * ktz,unsigned int reg,unsigned int mask,unsigned int val)65f8449c8fSJianhua Lu static int ktz8866_update_bits(struct ktz8866 *ktz, unsigned int reg,
66f8449c8fSJianhua Lu unsigned int mask, unsigned int val)
67f8449c8fSJianhua Lu {
68f8449c8fSJianhua Lu return regmap_update_bits(ktz->regmap, reg, mask, val);
69f8449c8fSJianhua Lu }
70f8449c8fSJianhua Lu
ktz8866_backlight_update_status(struct backlight_device * backlight_dev)71f8449c8fSJianhua Lu static int ktz8866_backlight_update_status(struct backlight_device *backlight_dev)
72f8449c8fSJianhua Lu {
73f8449c8fSJianhua Lu struct ktz8866 *ktz = bl_get_data(backlight_dev);
74f8449c8fSJianhua Lu unsigned int brightness = backlight_get_brightness(backlight_dev);
75f8449c8fSJianhua Lu
76f8449c8fSJianhua Lu if (!ktz->led_on && brightness > 0) {
77f8449c8fSJianhua Lu ktz8866_update_bits(ktz, BL_EN, BL_EN_BIT, BL_EN_BIT);
78f8449c8fSJianhua Lu ktz->led_on = true;
79f8449c8fSJianhua Lu } else if (brightness == 0) {
80f8449c8fSJianhua Lu ktz8866_update_bits(ktz, BL_EN, BL_EN_BIT, 0);
81f8449c8fSJianhua Lu ktz->led_on = false;
82f8449c8fSJianhua Lu }
83f8449c8fSJianhua Lu
84f8449c8fSJianhua Lu /* Set brightness */
85f8449c8fSJianhua Lu ktz8866_write(ktz, BL_BRT_LSB, brightness & 0x7);
86f8449c8fSJianhua Lu ktz8866_write(ktz, BL_BRT_MSB, (brightness >> 3) & 0xFF);
87f8449c8fSJianhua Lu
88f8449c8fSJianhua Lu return 0;
89f8449c8fSJianhua Lu }
90f8449c8fSJianhua Lu
91f8449c8fSJianhua Lu static const struct backlight_ops ktz8866_backlight_ops = {
92f8449c8fSJianhua Lu .options = BL_CORE_SUSPENDRESUME,
93f8449c8fSJianhua Lu .update_status = ktz8866_backlight_update_status,
94f8449c8fSJianhua Lu };
95f8449c8fSJianhua Lu
ktz8866_init(struct ktz8866 * ktz)96f8449c8fSJianhua Lu static void ktz8866_init(struct ktz8866 *ktz)
97f8449c8fSJianhua Lu {
98f8449c8fSJianhua Lu unsigned int val = 0;
99f8449c8fSJianhua Lu
100f1ac3c98SJianhua Lu if (!of_property_read_u32(ktz->client->dev.of_node, "current-num-sinks", &val))
101f8449c8fSJianhua Lu ktz8866_write(ktz, BL_EN, BIT(val) - 1);
102f8449c8fSJianhua Lu else
103f8449c8fSJianhua Lu /* Enable all 6 current sinks if the number of current sinks isn't specified. */
104f8449c8fSJianhua Lu ktz8866_write(ktz, BL_EN, BIT(6) - 1);
105f8449c8fSJianhua Lu
106f1ac3c98SJianhua Lu if (!of_property_read_u32(ktz->client->dev.of_node, "kinetic,current-ramp-delay-ms", &val)) {
107f8449c8fSJianhua Lu if (val <= 128)
108f8449c8fSJianhua Lu ktz8866_write(ktz, BL_CFG2, BIT(7) | (ilog2(val) << 3) | PWM_HYST);
109f8449c8fSJianhua Lu else
110f8449c8fSJianhua Lu ktz8866_write(ktz, BL_CFG2, BIT(7) | ((5 + val / 64) << 3) | PWM_HYST);
111f8449c8fSJianhua Lu }
112f8449c8fSJianhua Lu
113f1ac3c98SJianhua Lu if (!of_property_read_u32(ktz->client->dev.of_node, "kinetic,led-enable-ramp-delay-ms", &val)) {
114f8449c8fSJianhua Lu if (val == 0)
115f8449c8fSJianhua Lu ktz8866_write(ktz, BL_DIMMING, 0);
116f8449c8fSJianhua Lu else {
117f8449c8fSJianhua Lu unsigned int ramp_off_time = ilog2(val) + 1;
118f8449c8fSJianhua Lu unsigned int ramp_on_time = ramp_off_time << 4;
119f8449c8fSJianhua Lu ktz8866_write(ktz, BL_DIMMING, ramp_on_time | ramp_off_time);
120f8449c8fSJianhua Lu }
121f8449c8fSJianhua Lu }
122f8449c8fSJianhua Lu
123f8449c8fSJianhua Lu if (of_property_read_bool(ktz->client->dev.of_node, "kinetic,enable-lcd-bias"))
124f8449c8fSJianhua Lu ktz8866_write(ktz, LCD_BIAS_CFG1, LCD_BIAS_EN);
125f8449c8fSJianhua Lu }
126f8449c8fSJianhua Lu
ktz8866_probe(struct i2c_client * client)127ad614f81SUwe Kleine-König static int ktz8866_probe(struct i2c_client *client)
128f8449c8fSJianhua Lu {
129f8449c8fSJianhua Lu struct backlight_device *backlight_dev;
130f8449c8fSJianhua Lu struct backlight_properties props;
131f8449c8fSJianhua Lu struct ktz8866 *ktz;
132f8449c8fSJianhua Lu int ret = 0;
133f8449c8fSJianhua Lu
134f8449c8fSJianhua Lu ktz = devm_kzalloc(&client->dev, sizeof(*ktz), GFP_KERNEL);
135f8449c8fSJianhua Lu if (!ktz)
136f8449c8fSJianhua Lu return -ENOMEM;
137f8449c8fSJianhua Lu
138f8449c8fSJianhua Lu ktz->client = client;
139f8449c8fSJianhua Lu ktz->regmap = devm_regmap_init_i2c(client, &ktz8866_regmap_config);
140f8449c8fSJianhua Lu if (IS_ERR(ktz->regmap))
141f8449c8fSJianhua Lu return dev_err_probe(&client->dev, PTR_ERR(ktz->regmap), "failed to init regmap\n");
142f8449c8fSJianhua Lu
143f8449c8fSJianhua Lu ret = devm_regulator_get_enable(&client->dev, "vddpos");
144f8449c8fSJianhua Lu if (ret)
145f8449c8fSJianhua Lu return dev_err_probe(&client->dev, ret, "get regulator vddpos failed\n");
146f8449c8fSJianhua Lu ret = devm_regulator_get_enable(&client->dev, "vddneg");
147f8449c8fSJianhua Lu if (ret)
148f8449c8fSJianhua Lu return dev_err_probe(&client->dev, ret, "get regulator vddneg failed\n");
149f8449c8fSJianhua Lu
150f8449c8fSJianhua Lu ktz->enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH);
151f8449c8fSJianhua Lu if (IS_ERR(ktz->enable_gpio))
152f8449c8fSJianhua Lu return PTR_ERR(ktz->enable_gpio);
153f8449c8fSJianhua Lu
154f8449c8fSJianhua Lu memset(&props, 0, sizeof(props));
155f8449c8fSJianhua Lu props.type = BACKLIGHT_RAW;
156f8449c8fSJianhua Lu props.max_brightness = MAX_BRIGHTNESS;
157f8449c8fSJianhua Lu props.brightness = DEFAULT_BRIGHTNESS;
158f8449c8fSJianhua Lu props.scale = BACKLIGHT_SCALE_LINEAR;
159f8449c8fSJianhua Lu
160f8449c8fSJianhua Lu backlight_dev = devm_backlight_device_register(&client->dev, "ktz8866-backlight",
161f8449c8fSJianhua Lu &client->dev, ktz, &ktz8866_backlight_ops, &props);
162f8449c8fSJianhua Lu if (IS_ERR(backlight_dev))
163f8449c8fSJianhua Lu return dev_err_probe(&client->dev, PTR_ERR(backlight_dev),
164f8449c8fSJianhua Lu "failed to register backlight device\n");
165f8449c8fSJianhua Lu
166f8449c8fSJianhua Lu ktz8866_init(ktz);
167f8449c8fSJianhua Lu
168f8449c8fSJianhua Lu i2c_set_clientdata(client, backlight_dev);
169f8449c8fSJianhua Lu backlight_update_status(backlight_dev);
170f8449c8fSJianhua Lu
171f8449c8fSJianhua Lu return 0;
172f8449c8fSJianhua Lu }
173f8449c8fSJianhua Lu
ktz8866_remove(struct i2c_client * client)174f8449c8fSJianhua Lu static void ktz8866_remove(struct i2c_client *client)
175f8449c8fSJianhua Lu {
176f8449c8fSJianhua Lu struct backlight_device *backlight_dev = i2c_get_clientdata(client);
177f8449c8fSJianhua Lu backlight_dev->props.brightness = 0;
178f8449c8fSJianhua Lu backlight_update_status(backlight_dev);
179f8449c8fSJianhua Lu }
180f8449c8fSJianhua Lu
181f8449c8fSJianhua Lu static const struct i2c_device_id ktz8866_ids[] = {
182*bfd35877SUwe Kleine-König { "ktz8866" },
183*bfd35877SUwe Kleine-König {}
184f8449c8fSJianhua Lu };
185f8449c8fSJianhua Lu MODULE_DEVICE_TABLE(i2c, ktz8866_ids);
186f8449c8fSJianhua Lu
187f8449c8fSJianhua Lu static const struct of_device_id ktz8866_match_table[] = {
188f8449c8fSJianhua Lu {
189f8449c8fSJianhua Lu .compatible = "kinetic,ktz8866",
190f8449c8fSJianhua Lu },
191f8449c8fSJianhua Lu {},
192f8449c8fSJianhua Lu };
193f8449c8fSJianhua Lu
194f8449c8fSJianhua Lu static struct i2c_driver ktz8866_driver = {
195f8449c8fSJianhua Lu .driver = {
196f8449c8fSJianhua Lu .name = "ktz8866",
197f8449c8fSJianhua Lu .of_match_table = ktz8866_match_table,
198f8449c8fSJianhua Lu },
19929554f2eSUwe Kleine-König .probe = ktz8866_probe,
200f8449c8fSJianhua Lu .remove = ktz8866_remove,
201f8449c8fSJianhua Lu .id_table = ktz8866_ids,
202f8449c8fSJianhua Lu };
203f8449c8fSJianhua Lu
204f8449c8fSJianhua Lu module_i2c_driver(ktz8866_driver);
205f8449c8fSJianhua Lu
206f8449c8fSJianhua Lu MODULE_DESCRIPTION("Kinetic KTZ8866 Backlight Driver");
207f8449c8fSJianhua Lu MODULE_AUTHOR("Jianhua Lu <lujianhua000@gmail.com>");
208f8449c8fSJianhua Lu MODULE_LICENSE("GPL");
209