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