1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ROHM Semiconductor BD6107 LED Driver |
4 | * |
5 | * Copyright (C) 2013 Ideas on board SPRL |
6 | * |
7 | * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> |
8 | */ |
9 | |
10 | #include <linux/backlight.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/err.h> |
13 | #include <linux/fb.h> |
14 | #include <linux/gpio/consumer.h> |
15 | #include <linux/i2c.h> |
16 | #include <linux/module.h> |
17 | #include <linux/platform_data/bd6107.h> |
18 | #include <linux/slab.h> |
19 | |
20 | #define BD6107_PSCNT1 0x00 |
21 | #define BD6107_PSCNT1_PSCNTREG2 (1 << 2) |
22 | #define BD6107_PSCNT1_PSCNTREG1 (1 << 0) |
23 | #define BD6107_REGVSET 0x02 |
24 | #define BD6107_REGVSET_REG1VSET_2_85V (1 << 2) |
25 | #define BD6107_REGVSET_REG1VSET_2_80V (0 << 2) |
26 | #define BD6107_LEDCNT1 0x03 |
27 | #define BD6107_LEDCNT1_LEDONOFF2 (1 << 1) |
28 | #define BD6107_LEDCNT1_LEDONOFF1 (1 << 0) |
29 | #define BD6107_PORTSEL 0x04 |
30 | #define BD6107_PORTSEL_LEDM(n) (1 << (n)) |
31 | #define BD6107_RGB1CNT1 0x05 |
32 | #define BD6107_RGB1CNT2 0x06 |
33 | #define BD6107_RGB1CNT3 0x07 |
34 | #define BD6107_RGB1CNT4 0x08 |
35 | #define BD6107_RGB1CNT5 0x09 |
36 | #define BD6107_RGB1FLM 0x0a |
37 | #define BD6107_RGB2CNT1 0x0b |
38 | #define BD6107_RGB2CNT2 0x0c |
39 | #define BD6107_RGB2CNT3 0x0d |
40 | #define BD6107_RGB2CNT4 0x0e |
41 | #define BD6107_RGB2CNT5 0x0f |
42 | #define BD6107_RGB2FLM 0x10 |
43 | #define BD6107_PSCONT3 0x11 |
44 | #define BD6107_SMMONCNT 0x12 |
45 | #define BD6107_DCDCCNT 0x13 |
46 | #define BD6107_IOSEL 0x14 |
47 | #define BD6107_OUT1 0x15 |
48 | #define BD6107_OUT2 0x16 |
49 | #define BD6107_MASK1 0x17 |
50 | #define BD6107_MASK2 0x18 |
51 | #define BD6107_FACTOR1 0x19 |
52 | #define BD6107_FACTOR2 0x1a |
53 | #define BD6107_CLRFACT1 0x1b |
54 | #define BD6107_CLRFACT2 0x1c |
55 | #define BD6107_STATE1 0x1d |
56 | #define BD6107_LSIVER 0x1e |
57 | #define BD6107_GRPSEL 0x1f |
58 | #define BD6107_LEDCNT2 0x20 |
59 | #define BD6107_LEDCNT3 0x21 |
60 | #define BD6107_MCURRENT 0x22 |
61 | #define BD6107_MAINCNT1 0x23 |
62 | #define BD6107_MAINCNT2 0x24 |
63 | #define BD6107_SLOPECNT 0x25 |
64 | #define BD6107_MSLOPE 0x26 |
65 | #define BD6107_RGBSLOPE 0x27 |
66 | #define BD6107_TEST 0x29 |
67 | #define BD6107_SFTRST 0x2a |
68 | #define BD6107_SFTRSTGD 0x2b |
69 | |
70 | struct bd6107 { |
71 | struct i2c_client *client; |
72 | struct backlight_device *backlight; |
73 | struct bd6107_platform_data *pdata; |
74 | struct gpio_desc *reset; |
75 | }; |
76 | |
77 | static int bd6107_write(struct bd6107 *bd, u8 reg, u8 data) |
78 | { |
79 | return i2c_smbus_write_byte_data(client: bd->client, command: reg, value: data); |
80 | } |
81 | |
82 | static int bd6107_backlight_update_status(struct backlight_device *backlight) |
83 | { |
84 | struct bd6107 *bd = bl_get_data(bl_dev: backlight); |
85 | int brightness = backlight_get_brightness(bd: backlight); |
86 | |
87 | if (brightness) { |
88 | bd6107_write(bd, BD6107_PORTSEL, BD6107_PORTSEL_LEDM(2) | |
89 | BD6107_PORTSEL_LEDM(1) | BD6107_PORTSEL_LEDM(0)); |
90 | bd6107_write(bd, BD6107_MAINCNT1, data: brightness); |
91 | bd6107_write(bd, BD6107_LEDCNT1, BD6107_LEDCNT1_LEDONOFF1); |
92 | } else { |
93 | /* Assert the reset line (gpiolib will handle active low) */ |
94 | gpiod_set_value(desc: bd->reset, value: 1); |
95 | msleep(msecs: 24); |
96 | gpiod_set_value(desc: bd->reset, value: 0); |
97 | } |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | static int bd6107_backlight_check_fb(struct backlight_device *backlight, |
103 | struct fb_info *info) |
104 | { |
105 | struct bd6107 *bd = bl_get_data(bl_dev: backlight); |
106 | |
107 | return !bd->pdata->dev || bd->pdata->dev == info->device; |
108 | } |
109 | |
110 | static const struct backlight_ops bd6107_backlight_ops = { |
111 | .options = BL_CORE_SUSPENDRESUME, |
112 | .update_status = bd6107_backlight_update_status, |
113 | .check_fb = bd6107_backlight_check_fb, |
114 | }; |
115 | |
116 | static int bd6107_probe(struct i2c_client *client) |
117 | { |
118 | struct bd6107_platform_data *pdata = dev_get_platdata(dev: &client->dev); |
119 | struct backlight_device *backlight; |
120 | struct backlight_properties props; |
121 | struct bd6107 *bd; |
122 | |
123 | if (pdata == NULL) { |
124 | dev_err(&client->dev, "No platform data\n" ); |
125 | return -EINVAL; |
126 | } |
127 | |
128 | if (!i2c_check_functionality(adap: client->adapter, |
129 | I2C_FUNC_SMBUS_BYTE_DATA)) { |
130 | dev_warn(&client->dev, |
131 | "I2C adapter doesn't support I2C_FUNC_SMBUS_BYTE\n" ); |
132 | return -EIO; |
133 | } |
134 | |
135 | bd = devm_kzalloc(dev: &client->dev, size: sizeof(*bd), GFP_KERNEL); |
136 | if (!bd) |
137 | return -ENOMEM; |
138 | |
139 | bd->client = client; |
140 | bd->pdata = pdata; |
141 | |
142 | /* |
143 | * Request the reset GPIO line with GPIOD_OUT_HIGH meaning asserted, |
144 | * so in the machine descriptor table (or other hardware description), |
145 | * the line should be flagged as active low so this will assert |
146 | * the reset. |
147 | */ |
148 | bd->reset = devm_gpiod_get(dev: &client->dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
149 | if (IS_ERR(ptr: bd->reset)) |
150 | return dev_err_probe(dev: &client->dev, err: PTR_ERR(ptr: bd->reset), |
151 | fmt: "unable to request reset GPIO\n" ); |
152 | |
153 | memset(&props, 0, sizeof(props)); |
154 | props.type = BACKLIGHT_RAW; |
155 | props.max_brightness = 128; |
156 | props.brightness = clamp_t(unsigned int, pdata->def_value, 0, |
157 | props.max_brightness); |
158 | |
159 | backlight = devm_backlight_device_register(dev: &client->dev, |
160 | name: dev_name(dev: &client->dev), |
161 | parent: &bd->client->dev, devdata: bd, |
162 | ops: &bd6107_backlight_ops, props: &props); |
163 | if (IS_ERR(ptr: backlight)) { |
164 | dev_err(&client->dev, "failed to register backlight\n" ); |
165 | return PTR_ERR(ptr: backlight); |
166 | } |
167 | |
168 | backlight_update_status(bd: backlight); |
169 | i2c_set_clientdata(client, data: backlight); |
170 | |
171 | return 0; |
172 | } |
173 | |
174 | static void bd6107_remove(struct i2c_client *client) |
175 | { |
176 | struct backlight_device *backlight = i2c_get_clientdata(client); |
177 | |
178 | backlight->props.brightness = 0; |
179 | backlight_update_status(bd: backlight); |
180 | } |
181 | |
182 | static const struct i2c_device_id bd6107_ids[] = { |
183 | { "bd6107" , 0 }, |
184 | { } |
185 | }; |
186 | MODULE_DEVICE_TABLE(i2c, bd6107_ids); |
187 | |
188 | static struct i2c_driver bd6107_driver = { |
189 | .driver = { |
190 | .name = "bd6107" , |
191 | }, |
192 | .probe = bd6107_probe, |
193 | .remove = bd6107_remove, |
194 | .id_table = bd6107_ids, |
195 | }; |
196 | |
197 | module_i2c_driver(bd6107_driver); |
198 | |
199 | MODULE_DESCRIPTION("Rohm BD6107 Backlight Driver" ); |
200 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>" ); |
201 | MODULE_LICENSE("GPL" ); |
202 | |