18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * LCD-OLinuXino support for panel driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2018 Olimex Ltd.
68c2ecf20Sopenharmony_ci *   Author: Stefan Mavrodiev <stefan@olimex.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/crc32.h>
108c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
118c2ecf20Sopenharmony_ci#include <linux/i2c.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/mutex.h>
148c2ecf20Sopenharmony_ci#include <linux/of.h>
158c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include <video/videomode.h>
188c2ecf20Sopenharmony_ci#include <video/display_timing.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include <drm/drm_device.h>
218c2ecf20Sopenharmony_ci#include <drm/drm_modes.h>
228c2ecf20Sopenharmony_ci#include <drm/drm_panel.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define LCD_OLINUXINO_HEADER_MAGIC	0x4F4CB727
258c2ecf20Sopenharmony_ci#define LCD_OLINUXINO_DATA_LEN		256
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistruct lcd_olinuxino_mode {
288c2ecf20Sopenharmony_ci	u32 pixelclock;
298c2ecf20Sopenharmony_ci	u32 hactive;
308c2ecf20Sopenharmony_ci	u32 hfp;
318c2ecf20Sopenharmony_ci	u32 hbp;
328c2ecf20Sopenharmony_ci	u32 hpw;
338c2ecf20Sopenharmony_ci	u32 vactive;
348c2ecf20Sopenharmony_ci	u32 vfp;
358c2ecf20Sopenharmony_ci	u32 vbp;
368c2ecf20Sopenharmony_ci	u32 vpw;
378c2ecf20Sopenharmony_ci	u32 refresh;
388c2ecf20Sopenharmony_ci	u32 flags;
398c2ecf20Sopenharmony_ci};
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistruct lcd_olinuxino_info {
428c2ecf20Sopenharmony_ci	char name[32];
438c2ecf20Sopenharmony_ci	u32 width_mm;
448c2ecf20Sopenharmony_ci	u32 height_mm;
458c2ecf20Sopenharmony_ci	u32 bpc;
468c2ecf20Sopenharmony_ci	u32 bus_format;
478c2ecf20Sopenharmony_ci	u32 bus_flag;
488c2ecf20Sopenharmony_ci} __attribute__((__packed__));
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistruct lcd_olinuxino_eeprom {
518c2ecf20Sopenharmony_ci	u32 header;
528c2ecf20Sopenharmony_ci	u32 id;
538c2ecf20Sopenharmony_ci	char revision[4];
548c2ecf20Sopenharmony_ci	u32 serial;
558c2ecf20Sopenharmony_ci	struct lcd_olinuxino_info info;
568c2ecf20Sopenharmony_ci	u32 num_modes;
578c2ecf20Sopenharmony_ci	u8 reserved[180];
588c2ecf20Sopenharmony_ci	u32 checksum;
598c2ecf20Sopenharmony_ci} __attribute__((__packed__));
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistruct lcd_olinuxino {
628c2ecf20Sopenharmony_ci	struct drm_panel panel;
638c2ecf20Sopenharmony_ci	struct device *dev;
648c2ecf20Sopenharmony_ci	struct i2c_client *client;
658c2ecf20Sopenharmony_ci	struct mutex mutex;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	bool prepared;
688c2ecf20Sopenharmony_ci	bool enabled;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	struct regulator *supply;
718c2ecf20Sopenharmony_ci	struct gpio_desc *enable_gpio;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	struct lcd_olinuxino_eeprom eeprom;
748c2ecf20Sopenharmony_ci};
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic inline struct lcd_olinuxino *to_lcd_olinuxino(struct drm_panel *panel)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	return container_of(panel, struct lcd_olinuxino, panel);
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic int lcd_olinuxino_disable(struct drm_panel *panel)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	if (!lcd->enabled)
868c2ecf20Sopenharmony_ci		return 0;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	lcd->enabled = false;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	return 0;
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic int lcd_olinuxino_unprepare(struct drm_panel *panel)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	if (!lcd->prepared)
988c2ecf20Sopenharmony_ci		return 0;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(lcd->enable_gpio, 0);
1018c2ecf20Sopenharmony_ci	regulator_disable(lcd->supply);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	lcd->prepared = false;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return 0;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic int lcd_olinuxino_prepare(struct drm_panel *panel)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
1118c2ecf20Sopenharmony_ci	int ret;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	if (lcd->prepared)
1148c2ecf20Sopenharmony_ci		return 0;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	ret = regulator_enable(lcd->supply);
1178c2ecf20Sopenharmony_ci	if (ret < 0)
1188c2ecf20Sopenharmony_ci		return ret;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(lcd->enable_gpio, 1);
1218c2ecf20Sopenharmony_ci	lcd->prepared = true;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	return 0;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic int lcd_olinuxino_enable(struct drm_panel *panel)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	if (lcd->enabled)
1318c2ecf20Sopenharmony_ci		return 0;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	lcd->enabled = true;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	return 0;
1368c2ecf20Sopenharmony_ci}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_cistatic int lcd_olinuxino_get_modes(struct drm_panel *panel,
1398c2ecf20Sopenharmony_ci				   struct drm_connector *connector)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	struct lcd_olinuxino *lcd = to_lcd_olinuxino(panel);
1428c2ecf20Sopenharmony_ci	struct lcd_olinuxino_info *lcd_info = &lcd->eeprom.info;
1438c2ecf20Sopenharmony_ci	struct lcd_olinuxino_mode *lcd_mode;
1448c2ecf20Sopenharmony_ci	struct drm_display_mode *mode;
1458c2ecf20Sopenharmony_ci	u32 i, num = 0;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	for (i = 0; i < lcd->eeprom.num_modes; i++) {
1488c2ecf20Sopenharmony_ci		lcd_mode = (struct lcd_olinuxino_mode *)
1498c2ecf20Sopenharmony_ci			   &lcd->eeprom.reserved[i * sizeof(*lcd_mode)];
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci		mode = drm_mode_create(connector->dev);
1528c2ecf20Sopenharmony_ci		if (!mode) {
1538c2ecf20Sopenharmony_ci			dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
1548c2ecf20Sopenharmony_ci				lcd_mode->hactive,
1558c2ecf20Sopenharmony_ci				lcd_mode->vactive,
1568c2ecf20Sopenharmony_ci				lcd_mode->refresh);
1578c2ecf20Sopenharmony_ci			continue;
1588c2ecf20Sopenharmony_ci		}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci		mode->clock = lcd_mode->pixelclock;
1618c2ecf20Sopenharmony_ci		mode->hdisplay = lcd_mode->hactive;
1628c2ecf20Sopenharmony_ci		mode->hsync_start = lcd_mode->hactive + lcd_mode->hfp;
1638c2ecf20Sopenharmony_ci		mode->hsync_end = lcd_mode->hactive + lcd_mode->hfp +
1648c2ecf20Sopenharmony_ci				  lcd_mode->hpw;
1658c2ecf20Sopenharmony_ci		mode->htotal = lcd_mode->hactive + lcd_mode->hfp +
1668c2ecf20Sopenharmony_ci			       lcd_mode->hpw + lcd_mode->hbp;
1678c2ecf20Sopenharmony_ci		mode->vdisplay = lcd_mode->vactive;
1688c2ecf20Sopenharmony_ci		mode->vsync_start = lcd_mode->vactive + lcd_mode->vfp;
1698c2ecf20Sopenharmony_ci		mode->vsync_end = lcd_mode->vactive + lcd_mode->vfp +
1708c2ecf20Sopenharmony_ci				  lcd_mode->vpw;
1718c2ecf20Sopenharmony_ci		mode->vtotal = lcd_mode->vactive + lcd_mode->vfp +
1728c2ecf20Sopenharmony_ci			       lcd_mode->vpw + lcd_mode->vbp;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		/* Always make the first mode preferred */
1758c2ecf20Sopenharmony_ci		if (i == 0)
1768c2ecf20Sopenharmony_ci			mode->type |= DRM_MODE_TYPE_PREFERRED;
1778c2ecf20Sopenharmony_ci		mode->type |= DRM_MODE_TYPE_DRIVER;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci		drm_mode_set_name(mode);
1808c2ecf20Sopenharmony_ci		drm_mode_probed_add(connector, mode);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci		num++;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	connector->display_info.width_mm = lcd_info->width_mm;
1868c2ecf20Sopenharmony_ci	connector->display_info.height_mm = lcd_info->height_mm;
1878c2ecf20Sopenharmony_ci	connector->display_info.bpc = lcd_info->bpc;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (lcd_info->bus_format)
1908c2ecf20Sopenharmony_ci		drm_display_info_set_bus_formats(&connector->display_info,
1918c2ecf20Sopenharmony_ci						 &lcd_info->bus_format, 1);
1928c2ecf20Sopenharmony_ci	connector->display_info.bus_flags = lcd_info->bus_flag;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	return num;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic const struct drm_panel_funcs lcd_olinuxino_funcs = {
1988c2ecf20Sopenharmony_ci	.disable = lcd_olinuxino_disable,
1998c2ecf20Sopenharmony_ci	.unprepare = lcd_olinuxino_unprepare,
2008c2ecf20Sopenharmony_ci	.prepare = lcd_olinuxino_prepare,
2018c2ecf20Sopenharmony_ci	.enable = lcd_olinuxino_enable,
2028c2ecf20Sopenharmony_ci	.get_modes = lcd_olinuxino_get_modes,
2038c2ecf20Sopenharmony_ci};
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic int lcd_olinuxino_probe(struct i2c_client *client,
2068c2ecf20Sopenharmony_ci			       const struct i2c_device_id *id)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	struct device *dev = &client->dev;
2098c2ecf20Sopenharmony_ci	struct lcd_olinuxino *lcd;
2108c2ecf20Sopenharmony_ci	u32 checksum, i;
2118c2ecf20Sopenharmony_ci	int ret = 0;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
2148c2ecf20Sopenharmony_ci				     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
2158c2ecf20Sopenharmony_ci		return -ENODEV;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	lcd = devm_kzalloc(dev, sizeof(*lcd), GFP_KERNEL);
2188c2ecf20Sopenharmony_ci	if (!lcd)
2198c2ecf20Sopenharmony_ci		return -ENOMEM;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	i2c_set_clientdata(client, lcd);
2228c2ecf20Sopenharmony_ci	lcd->dev = dev;
2238c2ecf20Sopenharmony_ci	lcd->client = client;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	mutex_init(&lcd->mutex);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	/* Copy data into buffer */
2288c2ecf20Sopenharmony_ci	for (i = 0; i < LCD_OLINUXINO_DATA_LEN; i += I2C_SMBUS_BLOCK_MAX) {
2298c2ecf20Sopenharmony_ci		mutex_lock(&lcd->mutex);
2308c2ecf20Sopenharmony_ci		ret = i2c_smbus_read_i2c_block_data(client,
2318c2ecf20Sopenharmony_ci						    i,
2328c2ecf20Sopenharmony_ci						    I2C_SMBUS_BLOCK_MAX,
2338c2ecf20Sopenharmony_ci						    (u8 *)&lcd->eeprom + i);
2348c2ecf20Sopenharmony_ci		mutex_unlock(&lcd->mutex);
2358c2ecf20Sopenharmony_ci		if (ret < 0) {
2368c2ecf20Sopenharmony_ci			dev_err(dev, "error reading from device at %02x\n", i);
2378c2ecf20Sopenharmony_ci			return ret;
2388c2ecf20Sopenharmony_ci		}
2398c2ecf20Sopenharmony_ci	}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	/* Check configuration checksum */
2428c2ecf20Sopenharmony_ci	checksum = ~crc32(~0, (u8 *)&lcd->eeprom, 252);
2438c2ecf20Sopenharmony_ci	if (checksum != lcd->eeprom.checksum) {
2448c2ecf20Sopenharmony_ci		dev_err(dev, "configuration checksum does not match!\n");
2458c2ecf20Sopenharmony_ci		return -EINVAL;
2468c2ecf20Sopenharmony_ci	}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	/* Check magic header */
2498c2ecf20Sopenharmony_ci	if (lcd->eeprom.header != LCD_OLINUXINO_HEADER_MAGIC) {
2508c2ecf20Sopenharmony_ci		dev_err(dev, "magic header does not match\n");
2518c2ecf20Sopenharmony_ci		return -EINVAL;
2528c2ecf20Sopenharmony_ci	}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	dev_info(dev, "Detected %s, Rev. %s, Serial: %08x\n",
2558c2ecf20Sopenharmony_ci		 lcd->eeprom.info.name,
2568c2ecf20Sopenharmony_ci		 lcd->eeprom.revision,
2578c2ecf20Sopenharmony_ci		 lcd->eeprom.serial);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	/*
2608c2ecf20Sopenharmony_ci	 * The eeprom can hold up to 4 modes.
2618c2ecf20Sopenharmony_ci	 * If the stored value is bigger, overwrite it.
2628c2ecf20Sopenharmony_ci	 */
2638c2ecf20Sopenharmony_ci	if (lcd->eeprom.num_modes > 4) {
2648c2ecf20Sopenharmony_ci		dev_warn(dev, "invalid number of modes, falling back to 4\n");
2658c2ecf20Sopenharmony_ci		lcd->eeprom.num_modes = 4;
2668c2ecf20Sopenharmony_ci	}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	lcd->enabled = false;
2698c2ecf20Sopenharmony_ci	lcd->prepared = false;
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	lcd->supply = devm_regulator_get(dev, "power");
2728c2ecf20Sopenharmony_ci	if (IS_ERR(lcd->supply))
2738c2ecf20Sopenharmony_ci		return PTR_ERR(lcd->supply);
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	lcd->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
2768c2ecf20Sopenharmony_ci	if (IS_ERR(lcd->enable_gpio))
2778c2ecf20Sopenharmony_ci		return PTR_ERR(lcd->enable_gpio);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	drm_panel_init(&lcd->panel, dev, &lcd_olinuxino_funcs,
2808c2ecf20Sopenharmony_ci		       DRM_MODE_CONNECTOR_DPI);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	ret = drm_panel_of_backlight(&lcd->panel);
2838c2ecf20Sopenharmony_ci	if (ret)
2848c2ecf20Sopenharmony_ci		return ret;
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	drm_panel_add(&lcd->panel);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	return 0;
2898c2ecf20Sopenharmony_ci}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_cistatic int lcd_olinuxino_remove(struct i2c_client *client)
2928c2ecf20Sopenharmony_ci{
2938c2ecf20Sopenharmony_ci	struct lcd_olinuxino *panel = i2c_get_clientdata(client);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	drm_panel_remove(&panel->panel);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	drm_panel_disable(&panel->panel);
2988c2ecf20Sopenharmony_ci	drm_panel_unprepare(&panel->panel);
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	return 0;
3018c2ecf20Sopenharmony_ci}
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic const struct of_device_id lcd_olinuxino_of_ids[] = {
3048c2ecf20Sopenharmony_ci	{ .compatible = "olimex,lcd-olinuxino" },
3058c2ecf20Sopenharmony_ci	{ }
3068c2ecf20Sopenharmony_ci};
3078c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, lcd_olinuxino_of_ids);
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_cistatic struct i2c_driver lcd_olinuxino_driver = {
3108c2ecf20Sopenharmony_ci	.driver = {
3118c2ecf20Sopenharmony_ci		.name = "lcd_olinuxino",
3128c2ecf20Sopenharmony_ci		.of_match_table = lcd_olinuxino_of_ids,
3138c2ecf20Sopenharmony_ci	},
3148c2ecf20Sopenharmony_ci	.probe = lcd_olinuxino_probe,
3158c2ecf20Sopenharmony_ci	.remove = lcd_olinuxino_remove,
3168c2ecf20Sopenharmony_ci};
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_cimodule_i2c_driver(lcd_olinuxino_driver);
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ciMODULE_AUTHOR("Stefan Mavrodiev <stefan@olimex.com>");
3218c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("LCD-OLinuXino driver");
3228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
323