117fd7a9dSStefan Mavrodiev // SPDX-License-Identifier: GPL-2.0+
217fd7a9dSStefan Mavrodiev /*
317fd7a9dSStefan Mavrodiev * LCD-OLinuXino support for panel driver
417fd7a9dSStefan Mavrodiev *
517fd7a9dSStefan Mavrodiev * Copyright (C) 2018 Olimex Ltd.
617fd7a9dSStefan Mavrodiev * Author: Stefan Mavrodiev <stefan@olimex.com>
717fd7a9dSStefan Mavrodiev */
817fd7a9dSStefan Mavrodiev
917fd7a9dSStefan Mavrodiev #include <linux/crc32.h>
1017fd7a9dSStefan Mavrodiev #include <linux/gpio/consumer.h>
1117fd7a9dSStefan Mavrodiev #include <linux/i2c.h>
1217fd7a9dSStefan Mavrodiev #include <linux/module.h>
1317fd7a9dSStefan Mavrodiev #include <linux/mutex.h>
1417fd7a9dSStefan Mavrodiev #include <linux/of.h>
1517fd7a9dSStefan Mavrodiev #include <linux/regulator/consumer.h>
1617fd7a9dSStefan Mavrodiev
1717fd7a9dSStefan Mavrodiev #include <video/videomode.h>
1817fd7a9dSStefan Mavrodiev #include <video/display_timing.h>
1917fd7a9dSStefan Mavrodiev
20cb23eae3SSam Ravnborg #include <drm/drm_device.h>
21cb23eae3SSam Ravnborg #include <drm/drm_modes.h>
22cb23eae3SSam Ravnborg #include <drm/drm_panel.h>
23cb23eae3SSam Ravnborg
2417fd7a9dSStefan Mavrodiev #define LCD_OLINUXINO_HEADER_MAGIC 0x4F4CB727
2517fd7a9dSStefan Mavrodiev #define LCD_OLINUXINO_DATA_LEN 256
2617fd7a9dSStefan Mavrodiev
2717fd7a9dSStefan Mavrodiev struct lcd_olinuxino_mode {
2817fd7a9dSStefan Mavrodiev u32 pixelclock;
2917fd7a9dSStefan Mavrodiev u32 hactive;
3017fd7a9dSStefan Mavrodiev u32 hfp;
3117fd7a9dSStefan Mavrodiev u32 hbp;
3217fd7a9dSStefan Mavrodiev u32 hpw;
3317fd7a9dSStefan Mavrodiev u32 vactive;
3417fd7a9dSStefan Mavrodiev u32 vfp;
3517fd7a9dSStefan Mavrodiev u32 vbp;
3617fd7a9dSStefan Mavrodiev u32 vpw;
3717fd7a9dSStefan Mavrodiev u32 refresh;
3817fd7a9dSStefan Mavrodiev u32 flags;
3917fd7a9dSStefan Mavrodiev };
4017fd7a9dSStefan Mavrodiev
4117fd7a9dSStefan Mavrodiev struct lcd_olinuxino_info {
4217fd7a9dSStefan Mavrodiev char name[32];
4317fd7a9dSStefan Mavrodiev u32 width_mm;
4417fd7a9dSStefan Mavrodiev u32 height_mm;
4517fd7a9dSStefan Mavrodiev u32 bpc;
4617fd7a9dSStefan Mavrodiev u32 bus_format;
4717fd7a9dSStefan Mavrodiev u32 bus_flag;
4817fd7a9dSStefan Mavrodiev } __attribute__((__packed__));
4917fd7a9dSStefan Mavrodiev
5017fd7a9dSStefan Mavrodiev struct lcd_olinuxino_eeprom {
5117fd7a9dSStefan Mavrodiev u32 header;
5217fd7a9dSStefan Mavrodiev u32 id;
5317fd7a9dSStefan Mavrodiev char revision[4];
5417fd7a9dSStefan Mavrodiev u32 serial;
5517fd7a9dSStefan Mavrodiev struct lcd_olinuxino_info info;
5617fd7a9dSStefan Mavrodiev u32 num_modes;
5717fd7a9dSStefan Mavrodiev u8 reserved[180];
5817fd7a9dSStefan Mavrodiev u32 checksum;
5917fd7a9dSStefan Mavrodiev } __attribute__((__packed__));
6017fd7a9dSStefan Mavrodiev
6117fd7a9dSStefan Mavrodiev struct lcd_olinuxino {
6217fd7a9dSStefan Mavrodiev struct drm_panel panel;
6317fd7a9dSStefan Mavrodiev struct device *dev;
6417fd7a9dSStefan Mavrodiev struct i2c_client *client;
6517fd7a9dSStefan Mavrodiev struct mutex mutex;
6617fd7a9dSStefan Mavrodiev
6717fd7a9dSStefan Mavrodiev bool prepared;
6817fd7a9dSStefan Mavrodiev bool enabled;
6917fd7a9dSStefan Mavrodiev
7017fd7a9dSStefan Mavrodiev struct regulator *supply;
7117fd7a9dSStefan Mavrodiev struct gpio_desc *enable_gpio;
7217fd7a9dSStefan Mavrodiev
7317fd7a9dSStefan Mavrodiev struct lcd_olinuxino_eeprom eeprom;
7417fd7a9dSStefan Mavrodiev };
7517fd7a9dSStefan Mavrodiev
to_lcd_olinuxino(struct drm_panel * panel)7617fd7a9dSStefan Mavrodiev static inline struct lcd_olinuxino *to_lcd_olinuxino(struct drm_panel *panel)
7717fd7a9dSStefan Mavrodiev {
7817fd7a9dSStefan Mavrodiev return container_of(panel, struct lcd_olinuxino, panel);
7917fd7a9dSStefan Mavrodiev }
8017fd7a9dSStefan Mavrodiev
lcd_olinuxino_disable(struct drm_panel * panel)8117fd7a9dSStefan Mavrodiev static int lcd_olinuxino_disable(struct drm_panel *panel)
8217fd7a9dSStefan Mavrodiev {
8317fd7a9dSStefan Mavrodiev struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
8417fd7a9dSStefan Mavrodiev
8517fd7a9dSStefan Mavrodiev if (!lcd->enabled)
8617fd7a9dSStefan Mavrodiev return 0;
8717fd7a9dSStefan Mavrodiev
8817fd7a9dSStefan Mavrodiev lcd->enabled = false;
8917fd7a9dSStefan Mavrodiev
9017fd7a9dSStefan Mavrodiev return 0;
9117fd7a9dSStefan Mavrodiev }
9217fd7a9dSStefan Mavrodiev
lcd_olinuxino_unprepare(struct drm_panel * panel)9317fd7a9dSStefan Mavrodiev static int lcd_olinuxino_unprepare(struct drm_panel *panel)
9417fd7a9dSStefan Mavrodiev {
9517fd7a9dSStefan Mavrodiev struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
9617fd7a9dSStefan Mavrodiev
9717fd7a9dSStefan Mavrodiev if (!lcd->prepared)
9817fd7a9dSStefan Mavrodiev return 0;
9917fd7a9dSStefan Mavrodiev
10017fd7a9dSStefan Mavrodiev gpiod_set_value_cansleep(lcd->enable_gpio, 0);
10117fd7a9dSStefan Mavrodiev regulator_disable(lcd->supply);
10217fd7a9dSStefan Mavrodiev
10317fd7a9dSStefan Mavrodiev lcd->prepared = false;
10417fd7a9dSStefan Mavrodiev
10517fd7a9dSStefan Mavrodiev return 0;
10617fd7a9dSStefan Mavrodiev }
10717fd7a9dSStefan Mavrodiev
lcd_olinuxino_prepare(struct drm_panel * panel)10817fd7a9dSStefan Mavrodiev static int lcd_olinuxino_prepare(struct drm_panel *panel)
10917fd7a9dSStefan Mavrodiev {
11017fd7a9dSStefan Mavrodiev struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
11117fd7a9dSStefan Mavrodiev int ret;
11217fd7a9dSStefan Mavrodiev
11317fd7a9dSStefan Mavrodiev if (lcd->prepared)
11417fd7a9dSStefan Mavrodiev return 0;
11517fd7a9dSStefan Mavrodiev
11617fd7a9dSStefan Mavrodiev ret = regulator_enable(lcd->supply);
11717fd7a9dSStefan Mavrodiev if (ret < 0)
11817fd7a9dSStefan Mavrodiev return ret;
11917fd7a9dSStefan Mavrodiev
12017fd7a9dSStefan Mavrodiev gpiod_set_value_cansleep(lcd->enable_gpio, 1);
12117fd7a9dSStefan Mavrodiev lcd->prepared = true;
12217fd7a9dSStefan Mavrodiev
12317fd7a9dSStefan Mavrodiev return 0;
12417fd7a9dSStefan Mavrodiev }
12517fd7a9dSStefan Mavrodiev
lcd_olinuxino_enable(struct drm_panel * panel)12617fd7a9dSStefan Mavrodiev static int lcd_olinuxino_enable(struct drm_panel *panel)
12717fd7a9dSStefan Mavrodiev {
12817fd7a9dSStefan Mavrodiev struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
12917fd7a9dSStefan Mavrodiev
13017fd7a9dSStefan Mavrodiev if (lcd->enabled)
13117fd7a9dSStefan Mavrodiev return 0;
13217fd7a9dSStefan Mavrodiev
13317fd7a9dSStefan Mavrodiev lcd->enabled = true;
13417fd7a9dSStefan Mavrodiev
13517fd7a9dSStefan Mavrodiev return 0;
13617fd7a9dSStefan Mavrodiev }
13717fd7a9dSStefan Mavrodiev
lcd_olinuxino_get_modes(struct drm_panel * panel,struct drm_connector * connector)1380ce8ddd8SSam Ravnborg static int lcd_olinuxino_get_modes(struct drm_panel *panel,
1390ce8ddd8SSam Ravnborg struct drm_connector *connector)
14017fd7a9dSStefan Mavrodiev {
14117fd7a9dSStefan Mavrodiev struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
14217fd7a9dSStefan Mavrodiev struct lcd_olinuxino_info *lcd_info = &lcd->eeprom.info;
14317fd7a9dSStefan Mavrodiev struct lcd_olinuxino_mode *lcd_mode;
14417fd7a9dSStefan Mavrodiev struct drm_display_mode *mode;
14517fd7a9dSStefan Mavrodiev u32 i, num = 0;
14617fd7a9dSStefan Mavrodiev
14717fd7a9dSStefan Mavrodiev for (i = 0; i < lcd->eeprom.num_modes; i++) {
14817fd7a9dSStefan Mavrodiev lcd_mode = (struct lcd_olinuxino_mode *)
14917fd7a9dSStefan Mavrodiev &lcd->eeprom.reserved[i * sizeof(*lcd_mode)];
15017fd7a9dSStefan Mavrodiev
151aa6c4364SSam Ravnborg mode = drm_mode_create(connector->dev);
15217fd7a9dSStefan Mavrodiev if (!mode) {
153aa6c4364SSam Ravnborg dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
15417fd7a9dSStefan Mavrodiev lcd_mode->hactive,
15517fd7a9dSStefan Mavrodiev lcd_mode->vactive,
15617fd7a9dSStefan Mavrodiev lcd_mode->refresh);
15717fd7a9dSStefan Mavrodiev continue;
15817fd7a9dSStefan Mavrodiev }
15917fd7a9dSStefan Mavrodiev
16017fd7a9dSStefan Mavrodiev mode->clock = lcd_mode->pixelclock;
16117fd7a9dSStefan Mavrodiev mode->hdisplay = lcd_mode->hactive;
16217fd7a9dSStefan Mavrodiev mode->hsync_start = lcd_mode->hactive + lcd_mode->hfp;
16317fd7a9dSStefan Mavrodiev mode->hsync_end = lcd_mode->hactive + lcd_mode->hfp +
16417fd7a9dSStefan Mavrodiev lcd_mode->hpw;
16517fd7a9dSStefan Mavrodiev mode->htotal = lcd_mode->hactive + lcd_mode->hfp +
16617fd7a9dSStefan Mavrodiev lcd_mode->hpw + lcd_mode->hbp;
16717fd7a9dSStefan Mavrodiev mode->vdisplay = lcd_mode->vactive;
16817fd7a9dSStefan Mavrodiev mode->vsync_start = lcd_mode->vactive + lcd_mode->vfp;
16917fd7a9dSStefan Mavrodiev mode->vsync_end = lcd_mode->vactive + lcd_mode->vfp +
17017fd7a9dSStefan Mavrodiev lcd_mode->vpw;
17117fd7a9dSStefan Mavrodiev mode->vtotal = lcd_mode->vactive + lcd_mode->vfp +
17217fd7a9dSStefan Mavrodiev lcd_mode->vpw + lcd_mode->vbp;
17317fd7a9dSStefan Mavrodiev
17417fd7a9dSStefan Mavrodiev /* Always make the first mode preferred */
17517fd7a9dSStefan Mavrodiev if (i == 0)
17617fd7a9dSStefan Mavrodiev mode->type |= DRM_MODE_TYPE_PREFERRED;
17717fd7a9dSStefan Mavrodiev mode->type |= DRM_MODE_TYPE_DRIVER;
17817fd7a9dSStefan Mavrodiev
17917fd7a9dSStefan Mavrodiev drm_mode_set_name(mode);
18017fd7a9dSStefan Mavrodiev drm_mode_probed_add(connector, mode);
18117fd7a9dSStefan Mavrodiev
18217fd7a9dSStefan Mavrodiev num++;
18317fd7a9dSStefan Mavrodiev }
18417fd7a9dSStefan Mavrodiev
18517fd7a9dSStefan Mavrodiev connector->display_info.width_mm = lcd_info->width_mm;
18617fd7a9dSStefan Mavrodiev connector->display_info.height_mm = lcd_info->height_mm;
18717fd7a9dSStefan Mavrodiev connector->display_info.bpc = lcd_info->bpc;
18817fd7a9dSStefan Mavrodiev
18917fd7a9dSStefan Mavrodiev if (lcd_info->bus_format)
19017fd7a9dSStefan Mavrodiev drm_display_info_set_bus_formats(&connector->display_info,
19117fd7a9dSStefan Mavrodiev &lcd_info->bus_format, 1);
19217fd7a9dSStefan Mavrodiev connector->display_info.bus_flags = lcd_info->bus_flag;
19317fd7a9dSStefan Mavrodiev
19417fd7a9dSStefan Mavrodiev return num;
19517fd7a9dSStefan Mavrodiev }
19617fd7a9dSStefan Mavrodiev
19717fd7a9dSStefan Mavrodiev static const struct drm_panel_funcs lcd_olinuxino_funcs = {
19817fd7a9dSStefan Mavrodiev .disable = lcd_olinuxino_disable,
19917fd7a9dSStefan Mavrodiev .unprepare = lcd_olinuxino_unprepare,
20017fd7a9dSStefan Mavrodiev .prepare = lcd_olinuxino_prepare,
20117fd7a9dSStefan Mavrodiev .enable = lcd_olinuxino_enable,
20217fd7a9dSStefan Mavrodiev .get_modes = lcd_olinuxino_get_modes,
20317fd7a9dSStefan Mavrodiev };
20417fd7a9dSStefan Mavrodiev
lcd_olinuxino_probe(struct i2c_client * client)20544675757SUwe Kleine-König static int lcd_olinuxino_probe(struct i2c_client *client)
20617fd7a9dSStefan Mavrodiev {
20717fd7a9dSStefan Mavrodiev struct device *dev = &client->dev;
20817fd7a9dSStefan Mavrodiev struct lcd_olinuxino *lcd;
20917fd7a9dSStefan Mavrodiev u32 checksum, i;
21017fd7a9dSStefan Mavrodiev int ret = 0;
21117fd7a9dSStefan Mavrodiev
21217fd7a9dSStefan Mavrodiev if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
21317fd7a9dSStefan Mavrodiev I2C_FUNC_SMBUS_READ_I2C_BLOCK))
21417fd7a9dSStefan Mavrodiev return -ENODEV;
21517fd7a9dSStefan Mavrodiev
21617fd7a9dSStefan Mavrodiev lcd = devm_kzalloc(dev, sizeof(*lcd), GFP_KERNEL);
21717fd7a9dSStefan Mavrodiev if (!lcd)
21817fd7a9dSStefan Mavrodiev return -ENOMEM;
21917fd7a9dSStefan Mavrodiev
22017fd7a9dSStefan Mavrodiev i2c_set_clientdata(client, lcd);
22117fd7a9dSStefan Mavrodiev lcd->dev = dev;
22217fd7a9dSStefan Mavrodiev lcd->client = client;
22317fd7a9dSStefan Mavrodiev
22417fd7a9dSStefan Mavrodiev mutex_init(&lcd->mutex);
22517fd7a9dSStefan Mavrodiev
22617fd7a9dSStefan Mavrodiev /* Copy data into buffer */
22717fd7a9dSStefan Mavrodiev for (i = 0; i < LCD_OLINUXINO_DATA_LEN; i += I2C_SMBUS_BLOCK_MAX) {
22817fd7a9dSStefan Mavrodiev mutex_lock(&lcd->mutex);
22917fd7a9dSStefan Mavrodiev ret = i2c_smbus_read_i2c_block_data(client,
23017fd7a9dSStefan Mavrodiev i,
23117fd7a9dSStefan Mavrodiev I2C_SMBUS_BLOCK_MAX,
23217fd7a9dSStefan Mavrodiev (u8 *)&lcd->eeprom + i);
23317fd7a9dSStefan Mavrodiev mutex_unlock(&lcd->mutex);
23417fd7a9dSStefan Mavrodiev if (ret < 0) {
23517fd7a9dSStefan Mavrodiev dev_err(dev, "error reading from device at %02x\n", i);
23617fd7a9dSStefan Mavrodiev return ret;
23717fd7a9dSStefan Mavrodiev }
23817fd7a9dSStefan Mavrodiev }
23917fd7a9dSStefan Mavrodiev
24017fd7a9dSStefan Mavrodiev /* Check configuration checksum */
24117fd7a9dSStefan Mavrodiev checksum = ~crc32(~0, (u8 *)&lcd->eeprom, 252);
24217fd7a9dSStefan Mavrodiev if (checksum != lcd->eeprom.checksum) {
24317fd7a9dSStefan Mavrodiev dev_err(dev, "configuration checksum does not match!\n");
24417fd7a9dSStefan Mavrodiev return -EINVAL;
24517fd7a9dSStefan Mavrodiev }
24617fd7a9dSStefan Mavrodiev
24717fd7a9dSStefan Mavrodiev /* Check magic header */
24817fd7a9dSStefan Mavrodiev if (lcd->eeprom.header != LCD_OLINUXINO_HEADER_MAGIC) {
24917fd7a9dSStefan Mavrodiev dev_err(dev, "magic header does not match\n");
25017fd7a9dSStefan Mavrodiev return -EINVAL;
25117fd7a9dSStefan Mavrodiev }
25217fd7a9dSStefan Mavrodiev
25317fd7a9dSStefan Mavrodiev dev_info(dev, "Detected %s, Rev. %s, Serial: %08x\n",
25417fd7a9dSStefan Mavrodiev lcd->eeprom.info.name,
25517fd7a9dSStefan Mavrodiev lcd->eeprom.revision,
25617fd7a9dSStefan Mavrodiev lcd->eeprom.serial);
25717fd7a9dSStefan Mavrodiev
25817fd7a9dSStefan Mavrodiev /*
25917fd7a9dSStefan Mavrodiev * The eeprom can hold up to 4 modes.
26017fd7a9dSStefan Mavrodiev * If the stored value is bigger, overwrite it.
26117fd7a9dSStefan Mavrodiev */
26217fd7a9dSStefan Mavrodiev if (lcd->eeprom.num_modes > 4) {
26317fd7a9dSStefan Mavrodiev dev_warn(dev, "invalid number of modes, falling back to 4\n");
26417fd7a9dSStefan Mavrodiev lcd->eeprom.num_modes = 4;
26517fd7a9dSStefan Mavrodiev }
26617fd7a9dSStefan Mavrodiev
26717fd7a9dSStefan Mavrodiev lcd->enabled = false;
26817fd7a9dSStefan Mavrodiev lcd->prepared = false;
26917fd7a9dSStefan Mavrodiev
27017fd7a9dSStefan Mavrodiev lcd->supply = devm_regulator_get(dev, "power");
27117fd7a9dSStefan Mavrodiev if (IS_ERR(lcd->supply))
27217fd7a9dSStefan Mavrodiev return PTR_ERR(lcd->supply);
27317fd7a9dSStefan Mavrodiev
27417fd7a9dSStefan Mavrodiev lcd->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
27517fd7a9dSStefan Mavrodiev if (IS_ERR(lcd->enable_gpio))
27617fd7a9dSStefan Mavrodiev return PTR_ERR(lcd->enable_gpio);
27717fd7a9dSStefan Mavrodiev
2789a2654c0SLaurent Pinchart drm_panel_init(&lcd->panel, dev, &lcd_olinuxino_funcs,
2799a2654c0SLaurent Pinchart DRM_MODE_CONNECTOR_DPI);
28017fd7a9dSStefan Mavrodiev
281d90b3b6eSSam Ravnborg ret = drm_panel_of_backlight(&lcd->panel);
282d90b3b6eSSam Ravnborg if (ret)
283d90b3b6eSSam Ravnborg return ret;
284d90b3b6eSSam Ravnborg
285c3ee8c65SBernard Zhao drm_panel_add(&lcd->panel);
286c3ee8c65SBernard Zhao
287c3ee8c65SBernard Zhao return 0;
28817fd7a9dSStefan Mavrodiev }
28917fd7a9dSStefan Mavrodiev
lcd_olinuxino_remove(struct i2c_client * client)290ed5c2f5fSUwe Kleine-König static void lcd_olinuxino_remove(struct i2c_client *client)
29117fd7a9dSStefan Mavrodiev {
29217fd7a9dSStefan Mavrodiev struct lcd_olinuxino *panel = i2c_get_clientdata(client);
29317fd7a9dSStefan Mavrodiev
29417fd7a9dSStefan Mavrodiev drm_panel_remove(&panel->panel);
29517fd7a9dSStefan Mavrodiev
296d90b3b6eSSam Ravnborg drm_panel_disable(&panel->panel);
297d90b3b6eSSam Ravnborg drm_panel_unprepare(&panel->panel);
29817fd7a9dSStefan Mavrodiev }
29917fd7a9dSStefan Mavrodiev
30017fd7a9dSStefan Mavrodiev static const struct of_device_id lcd_olinuxino_of_ids[] = {
30117fd7a9dSStefan Mavrodiev { .compatible = "olimex,lcd-olinuxino" },
30217fd7a9dSStefan Mavrodiev { }
30317fd7a9dSStefan Mavrodiev };
30417fd7a9dSStefan Mavrodiev MODULE_DEVICE_TABLE(of, lcd_olinuxino_of_ids);
30517fd7a9dSStefan Mavrodiev
30617fd7a9dSStefan Mavrodiev static struct i2c_driver lcd_olinuxino_driver = {
30717fd7a9dSStefan Mavrodiev .driver = {
30817fd7a9dSStefan Mavrodiev .name = "lcd_olinuxino",
30917fd7a9dSStefan Mavrodiev .of_match_table = lcd_olinuxino_of_ids,
31017fd7a9dSStefan Mavrodiev },
311*332af828SUwe Kleine-König .probe = lcd_olinuxino_probe,
31217fd7a9dSStefan Mavrodiev .remove = lcd_olinuxino_remove,
31317fd7a9dSStefan Mavrodiev };
31417fd7a9dSStefan Mavrodiev
31517fd7a9dSStefan Mavrodiev module_i2c_driver(lcd_olinuxino_driver);
31617fd7a9dSStefan Mavrodiev
31717fd7a9dSStefan Mavrodiev MODULE_AUTHOR("Stefan Mavrodiev <stefan@olimex.com>");
31817fd7a9dSStefan Mavrodiev MODULE_DESCRIPTION("LCD-OLinuXino driver");
31917fd7a9dSStefan Mavrodiev MODULE_LICENSE("GPL");
320