1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * LCD-OLinuXino support for panel driver |
4 | * |
5 | * Copyright (C) 2018 Olimex Ltd. |
6 | * Author: Stefan Mavrodiev <stefan@olimex.com> |
7 | */ |
8 | |
9 | #include <linux/crc32.h> |
10 | #include <linux/gpio/consumer.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/module.h> |
13 | #include <linux/mutex.h> |
14 | #include <linux/of.h> |
15 | #include <linux/regulator/consumer.h> |
16 | |
17 | #include <video/videomode.h> |
18 | #include <video/display_timing.h> |
19 | |
20 | #include <drm/drm_device.h> |
21 | #include <drm/drm_modes.h> |
22 | #include <drm/drm_panel.h> |
23 | |
24 | #define 0x4F4CB727 |
25 | #define LCD_OLINUXINO_DATA_LEN 256 |
26 | |
27 | struct lcd_olinuxino_mode { |
28 | u32 pixelclock; |
29 | u32 hactive; |
30 | u32 hfp; |
31 | u32 hbp; |
32 | u32 hpw; |
33 | u32 vactive; |
34 | u32 vfp; |
35 | u32 vbp; |
36 | u32 vpw; |
37 | u32 refresh; |
38 | u32 flags; |
39 | }; |
40 | |
41 | struct lcd_olinuxino_info { |
42 | char name[32]; |
43 | u32 width_mm; |
44 | u32 height_mm; |
45 | u32 bpc; |
46 | u32 bus_format; |
47 | u32 bus_flag; |
48 | } __attribute__((__packed__)); |
49 | |
50 | struct lcd_olinuxino_eeprom { |
51 | u32 ; |
52 | u32 id; |
53 | char revision[4]; |
54 | u32 serial; |
55 | struct lcd_olinuxino_info info; |
56 | u32 num_modes; |
57 | u8 reserved[180]; |
58 | u32 checksum; |
59 | } __attribute__((__packed__)); |
60 | |
61 | struct lcd_olinuxino { |
62 | struct drm_panel panel; |
63 | struct device *dev; |
64 | struct i2c_client *client; |
65 | struct mutex mutex; |
66 | |
67 | bool prepared; |
68 | bool enabled; |
69 | |
70 | struct regulator *supply; |
71 | struct gpio_desc *enable_gpio; |
72 | |
73 | struct lcd_olinuxino_eeprom eeprom; |
74 | }; |
75 | |
76 | static inline struct lcd_olinuxino *to_lcd_olinuxino(struct drm_panel *panel) |
77 | { |
78 | return container_of(panel, struct lcd_olinuxino, panel); |
79 | } |
80 | |
81 | static int lcd_olinuxino_disable(struct drm_panel *panel) |
82 | { |
83 | struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); |
84 | |
85 | if (!lcd->enabled) |
86 | return 0; |
87 | |
88 | lcd->enabled = false; |
89 | |
90 | return 0; |
91 | } |
92 | |
93 | static int lcd_olinuxino_unprepare(struct drm_panel *panel) |
94 | { |
95 | struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); |
96 | |
97 | if (!lcd->prepared) |
98 | return 0; |
99 | |
100 | gpiod_set_value_cansleep(desc: lcd->enable_gpio, value: 0); |
101 | regulator_disable(regulator: lcd->supply); |
102 | |
103 | lcd->prepared = false; |
104 | |
105 | return 0; |
106 | } |
107 | |
108 | static int lcd_olinuxino_prepare(struct drm_panel *panel) |
109 | { |
110 | struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); |
111 | int ret; |
112 | |
113 | if (lcd->prepared) |
114 | return 0; |
115 | |
116 | ret = regulator_enable(regulator: lcd->supply); |
117 | if (ret < 0) |
118 | return ret; |
119 | |
120 | gpiod_set_value_cansleep(desc: lcd->enable_gpio, value: 1); |
121 | lcd->prepared = true; |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static int lcd_olinuxino_enable(struct drm_panel *panel) |
127 | { |
128 | struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); |
129 | |
130 | if (lcd->enabled) |
131 | return 0; |
132 | |
133 | lcd->enabled = true; |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static int lcd_olinuxino_get_modes(struct drm_panel *panel, |
139 | struct drm_connector *connector) |
140 | { |
141 | struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel); |
142 | struct lcd_olinuxino_info *lcd_info = &lcd->eeprom.info; |
143 | struct lcd_olinuxino_mode *lcd_mode; |
144 | struct drm_display_mode *mode; |
145 | u32 i, num = 0; |
146 | |
147 | for (i = 0; i < lcd->eeprom.num_modes; i++) { |
148 | lcd_mode = (struct lcd_olinuxino_mode *) |
149 | &lcd->eeprom.reserved[i * sizeof(*lcd_mode)]; |
150 | |
151 | mode = drm_mode_create(dev: connector->dev); |
152 | if (!mode) { |
153 | dev_err(panel->dev, "failed to add mode %ux%u@%u\n" , |
154 | lcd_mode->hactive, |
155 | lcd_mode->vactive, |
156 | lcd_mode->refresh); |
157 | continue; |
158 | } |
159 | |
160 | mode->clock = lcd_mode->pixelclock; |
161 | mode->hdisplay = lcd_mode->hactive; |
162 | mode->hsync_start = lcd_mode->hactive + lcd_mode->hfp; |
163 | mode->hsync_end = lcd_mode->hactive + lcd_mode->hfp + |
164 | lcd_mode->hpw; |
165 | mode->htotal = lcd_mode->hactive + lcd_mode->hfp + |
166 | lcd_mode->hpw + lcd_mode->hbp; |
167 | mode->vdisplay = lcd_mode->vactive; |
168 | mode->vsync_start = lcd_mode->vactive + lcd_mode->vfp; |
169 | mode->vsync_end = lcd_mode->vactive + lcd_mode->vfp + |
170 | lcd_mode->vpw; |
171 | mode->vtotal = lcd_mode->vactive + lcd_mode->vfp + |
172 | lcd_mode->vpw + lcd_mode->vbp; |
173 | |
174 | /* Always make the first mode preferred */ |
175 | if (i == 0) |
176 | mode->type |= DRM_MODE_TYPE_PREFERRED; |
177 | mode->type |= DRM_MODE_TYPE_DRIVER; |
178 | |
179 | drm_mode_set_name(mode); |
180 | drm_mode_probed_add(connector, mode); |
181 | |
182 | num++; |
183 | } |
184 | |
185 | connector->display_info.width_mm = lcd_info->width_mm; |
186 | connector->display_info.height_mm = lcd_info->height_mm; |
187 | connector->display_info.bpc = lcd_info->bpc; |
188 | |
189 | if (lcd_info->bus_format) |
190 | drm_display_info_set_bus_formats(info: &connector->display_info, |
191 | formats: &lcd_info->bus_format, num_formats: 1); |
192 | connector->display_info.bus_flags = lcd_info->bus_flag; |
193 | |
194 | return num; |
195 | } |
196 | |
197 | static const struct drm_panel_funcs lcd_olinuxino_funcs = { |
198 | .disable = lcd_olinuxino_disable, |
199 | .unprepare = lcd_olinuxino_unprepare, |
200 | .prepare = lcd_olinuxino_prepare, |
201 | .enable = lcd_olinuxino_enable, |
202 | .get_modes = lcd_olinuxino_get_modes, |
203 | }; |
204 | |
205 | static int lcd_olinuxino_probe(struct i2c_client *client) |
206 | { |
207 | struct device *dev = &client->dev; |
208 | struct lcd_olinuxino *lcd; |
209 | u32 checksum, i; |
210 | int ret = 0; |
211 | |
212 | if (!i2c_check_functionality(adap: client->adapter, I2C_FUNC_I2C | |
213 | I2C_FUNC_SMBUS_READ_I2C_BLOCK)) |
214 | return -ENODEV; |
215 | |
216 | lcd = devm_kzalloc(dev, size: sizeof(*lcd), GFP_KERNEL); |
217 | if (!lcd) |
218 | return -ENOMEM; |
219 | |
220 | i2c_set_clientdata(client, data: lcd); |
221 | lcd->dev = dev; |
222 | lcd->client = client; |
223 | |
224 | mutex_init(&lcd->mutex); |
225 | |
226 | /* Copy data into buffer */ |
227 | for (i = 0; i < LCD_OLINUXINO_DATA_LEN; i += I2C_SMBUS_BLOCK_MAX) { |
228 | mutex_lock(&lcd->mutex); |
229 | ret = i2c_smbus_read_i2c_block_data(client, |
230 | command: i, |
231 | I2C_SMBUS_BLOCK_MAX, |
232 | values: (u8 *)&lcd->eeprom + i); |
233 | mutex_unlock(lock: &lcd->mutex); |
234 | if (ret < 0) { |
235 | dev_err(dev, "error reading from device at %02x\n" , i); |
236 | return ret; |
237 | } |
238 | } |
239 | |
240 | /* Check configuration checksum */ |
241 | checksum = ~crc32(~0, (u8 *)&lcd->eeprom, 252); |
242 | if (checksum != lcd->eeprom.checksum) { |
243 | dev_err(dev, "configuration checksum does not match!\n" ); |
244 | return -EINVAL; |
245 | } |
246 | |
247 | /* Check magic header */ |
248 | if (lcd->eeprom.header != LCD_OLINUXINO_HEADER_MAGIC) { |
249 | dev_err(dev, "magic header does not match\n" ); |
250 | return -EINVAL; |
251 | } |
252 | |
253 | dev_info(dev, "Detected %s, Rev. %s, Serial: %08x\n" , |
254 | lcd->eeprom.info.name, |
255 | lcd->eeprom.revision, |
256 | lcd->eeprom.serial); |
257 | |
258 | /* |
259 | * The eeprom can hold up to 4 modes. |
260 | * If the stored value is bigger, overwrite it. |
261 | */ |
262 | if (lcd->eeprom.num_modes > 4) { |
263 | dev_warn(dev, "invalid number of modes, falling back to 4\n" ); |
264 | lcd->eeprom.num_modes = 4; |
265 | } |
266 | |
267 | lcd->enabled = false; |
268 | lcd->prepared = false; |
269 | |
270 | lcd->supply = devm_regulator_get(dev, id: "power" ); |
271 | if (IS_ERR(ptr: lcd->supply)) |
272 | return PTR_ERR(ptr: lcd->supply); |
273 | |
274 | lcd->enable_gpio = devm_gpiod_get(dev, con_id: "enable" , flags: GPIOD_OUT_LOW); |
275 | if (IS_ERR(ptr: lcd->enable_gpio)) |
276 | return PTR_ERR(ptr: lcd->enable_gpio); |
277 | |
278 | drm_panel_init(panel: &lcd->panel, dev, funcs: &lcd_olinuxino_funcs, |
279 | DRM_MODE_CONNECTOR_DPI); |
280 | |
281 | ret = drm_panel_of_backlight(panel: &lcd->panel); |
282 | if (ret) |
283 | return ret; |
284 | |
285 | drm_panel_add(panel: &lcd->panel); |
286 | |
287 | return 0; |
288 | } |
289 | |
290 | static void lcd_olinuxino_remove(struct i2c_client *client) |
291 | { |
292 | struct lcd_olinuxino *panel = i2c_get_clientdata(client); |
293 | |
294 | drm_panel_remove(panel: &panel->panel); |
295 | |
296 | drm_panel_disable(panel: &panel->panel); |
297 | drm_panel_unprepare(panel: &panel->panel); |
298 | } |
299 | |
300 | static const struct of_device_id lcd_olinuxino_of_ids[] = { |
301 | { .compatible = "olimex,lcd-olinuxino" }, |
302 | { } |
303 | }; |
304 | MODULE_DEVICE_TABLE(of, lcd_olinuxino_of_ids); |
305 | |
306 | static struct i2c_driver lcd_olinuxino_driver = { |
307 | .driver = { |
308 | .name = "lcd_olinuxino" , |
309 | .of_match_table = lcd_olinuxino_of_ids, |
310 | }, |
311 | .probe = lcd_olinuxino_probe, |
312 | .remove = lcd_olinuxino_remove, |
313 | }; |
314 | |
315 | module_i2c_driver(lcd_olinuxino_driver); |
316 | |
317 | MODULE_AUTHOR("Stefan Mavrodiev <stefan@olimex.com>" ); |
318 | MODULE_DESCRIPTION("LCD-OLinuXino driver" ); |
319 | MODULE_LICENSE("GPL" ); |
320 | |