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