162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Toppoly TD043MTEA1 Panel Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2019 Texas Instruments Incorporated 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Based on the omapdrm-specific panel-tpo-td043mtea1 driver 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Author: Gražvydas Ignotas <notasas@gmail.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/delay.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1562306a36Sopenharmony_ci#include <linux/spi/spi.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <drm/drm_connector.h> 1862306a36Sopenharmony_ci#include <drm/drm_modes.h> 1962306a36Sopenharmony_ci#include <drm/drm_panel.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define TPO_R02_MODE(x) ((x) & 7) 2262306a36Sopenharmony_ci#define TPO_R02_MODE_800x480 7 2362306a36Sopenharmony_ci#define TPO_R02_NCLK_RISING BIT(3) 2462306a36Sopenharmony_ci#define TPO_R02_HSYNC_HIGH BIT(4) 2562306a36Sopenharmony_ci#define TPO_R02_VSYNC_HIGH BIT(5) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define TPO_R03_NSTANDBY BIT(0) 2862306a36Sopenharmony_ci#define TPO_R03_EN_CP_CLK BIT(1) 2962306a36Sopenharmony_ci#define TPO_R03_EN_VGL_PUMP BIT(2) 3062306a36Sopenharmony_ci#define TPO_R03_EN_PWM BIT(3) 3162306a36Sopenharmony_ci#define TPO_R03_DRIVING_CAP_100 BIT(4) 3262306a36Sopenharmony_ci#define TPO_R03_EN_PRE_CHARGE BIT(6) 3362306a36Sopenharmony_ci#define TPO_R03_SOFTWARE_CTL BIT(7) 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define TPO_R04_NFLIP_H BIT(0) 3662306a36Sopenharmony_ci#define TPO_R04_NFLIP_V BIT(1) 3762306a36Sopenharmony_ci#define TPO_R04_CP_CLK_FREQ_1H BIT(2) 3862306a36Sopenharmony_ci#define TPO_R04_VGL_FREQ_1H BIT(4) 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define TPO_R03_VAL_NORMAL \ 4162306a36Sopenharmony_ci (TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | TPO_R03_EN_VGL_PUMP | \ 4262306a36Sopenharmony_ci TPO_R03_EN_PWM | TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ 4362306a36Sopenharmony_ci TPO_R03_SOFTWARE_CTL) 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#define TPO_R03_VAL_STANDBY \ 4662306a36Sopenharmony_ci (TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ 4762306a36Sopenharmony_ci TPO_R03_SOFTWARE_CTL) 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic const u16 td043mtea1_def_gamma[12] = { 5062306a36Sopenharmony_ci 105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistruct td043mtea1_panel { 5462306a36Sopenharmony_ci struct drm_panel panel; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci struct spi_device *spi; 5762306a36Sopenharmony_ci struct regulator *vcc_reg; 5862306a36Sopenharmony_ci struct gpio_desc *reset_gpio; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci unsigned int mode; 6162306a36Sopenharmony_ci u16 gamma[12]; 6262306a36Sopenharmony_ci bool vmirror; 6362306a36Sopenharmony_ci bool powered_on; 6462306a36Sopenharmony_ci bool spi_suspended; 6562306a36Sopenharmony_ci bool power_on_resume; 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci#define to_td043mtea1_device(p) container_of(p, struct td043mtea1_panel, panel) 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 7162306a36Sopenharmony_ci * Hardware Access 7262306a36Sopenharmony_ci */ 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic int td043mtea1_write(struct td043mtea1_panel *lcd, u8 addr, u8 value) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct spi_message msg; 7762306a36Sopenharmony_ci struct spi_transfer xfer; 7862306a36Sopenharmony_ci u16 data; 7962306a36Sopenharmony_ci int ret; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci spi_message_init(&msg); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci memset(&xfer, 0, sizeof(xfer)); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci data = ((u16)addr << 10) | (1 << 8) | value; 8662306a36Sopenharmony_ci xfer.tx_buf = &data; 8762306a36Sopenharmony_ci xfer.bits_per_word = 16; 8862306a36Sopenharmony_ci xfer.len = 2; 8962306a36Sopenharmony_ci spi_message_add_tail(&xfer, &msg); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci ret = spi_sync(lcd->spi, &msg); 9262306a36Sopenharmony_ci if (ret < 0) 9362306a36Sopenharmony_ci dev_warn(&lcd->spi->dev, "failed to write to LCD reg (%d)\n", 9462306a36Sopenharmony_ci ret); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci return ret; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic void td043mtea1_write_gamma(struct td043mtea1_panel *lcd) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci const u16 *gamma = lcd->gamma; 10262306a36Sopenharmony_ci unsigned int i; 10362306a36Sopenharmony_ci u8 val; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci /* gamma bits [9:8] */ 10662306a36Sopenharmony_ci for (val = i = 0; i < 4; i++) 10762306a36Sopenharmony_ci val |= (gamma[i] & 0x300) >> ((i + 1) * 2); 10862306a36Sopenharmony_ci td043mtea1_write(lcd, 0x11, val); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci for (val = i = 0; i < 4; i++) 11162306a36Sopenharmony_ci val |= (gamma[i + 4] & 0x300) >> ((i + 1) * 2); 11262306a36Sopenharmony_ci td043mtea1_write(lcd, 0x12, val); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci for (val = i = 0; i < 4; i++) 11562306a36Sopenharmony_ci val |= (gamma[i + 8] & 0x300) >> ((i + 1) * 2); 11662306a36Sopenharmony_ci td043mtea1_write(lcd, 0x13, val); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* gamma bits [7:0] */ 11962306a36Sopenharmony_ci for (i = 0; i < 12; i++) 12062306a36Sopenharmony_ci td043mtea1_write(lcd, 0x14 + i, gamma[i] & 0xff); 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic int td043mtea1_write_mirror(struct td043mtea1_panel *lcd) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V | 12662306a36Sopenharmony_ci TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H; 12762306a36Sopenharmony_ci if (lcd->vmirror) 12862306a36Sopenharmony_ci reg4 &= ~TPO_R04_NFLIP_V; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci return td043mtea1_write(lcd, 4, reg4); 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic int td043mtea1_power_on(struct td043mtea1_panel *lcd) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci int ret; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (lcd->powered_on) 13862306a36Sopenharmony_ci return 0; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci ret = regulator_enable(lcd->vcc_reg); 14162306a36Sopenharmony_ci if (ret < 0) 14262306a36Sopenharmony_ci return ret; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci /* Wait for the panel to stabilize. */ 14562306a36Sopenharmony_ci msleep(160); 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci gpiod_set_value(lcd->reset_gpio, 0); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci td043mtea1_write(lcd, 2, TPO_R02_MODE(lcd->mode) | TPO_R02_NCLK_RISING); 15062306a36Sopenharmony_ci td043mtea1_write(lcd, 3, TPO_R03_VAL_NORMAL); 15162306a36Sopenharmony_ci td043mtea1_write(lcd, 0x20, 0xf0); 15262306a36Sopenharmony_ci td043mtea1_write(lcd, 0x21, 0xf0); 15362306a36Sopenharmony_ci td043mtea1_write_mirror(lcd); 15462306a36Sopenharmony_ci td043mtea1_write_gamma(lcd); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci lcd->powered_on = true; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return 0; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic void td043mtea1_power_off(struct td043mtea1_panel *lcd) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci if (!lcd->powered_on) 16462306a36Sopenharmony_ci return; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci gpiod_set_value(lcd->reset_gpio, 1); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* wait for at least 2 vsyncs before cutting off power */ 17162306a36Sopenharmony_ci msleep(50); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci regulator_disable(lcd->vcc_reg); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci lcd->powered_on = false; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 18162306a36Sopenharmony_ci * sysfs 18262306a36Sopenharmony_ci */ 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic ssize_t vmirror_show(struct device *dev, struct device_attribute *attr, 18562306a36Sopenharmony_ci char *buf) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci struct td043mtea1_panel *lcd = dev_get_drvdata(dev); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", lcd->vmirror); 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic ssize_t vmirror_store(struct device *dev, struct device_attribute *attr, 19362306a36Sopenharmony_ci const char *buf, size_t count) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci struct td043mtea1_panel *lcd = dev_get_drvdata(dev); 19662306a36Sopenharmony_ci int val; 19762306a36Sopenharmony_ci int ret; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci ret = kstrtoint(buf, 0, &val); 20062306a36Sopenharmony_ci if (ret < 0) 20162306a36Sopenharmony_ci return ret; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci lcd->vmirror = !!val; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci ret = td043mtea1_write_mirror(lcd); 20662306a36Sopenharmony_ci if (ret < 0) 20762306a36Sopenharmony_ci return ret; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return count; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic ssize_t mode_show(struct device *dev, struct device_attribute *attr, 21362306a36Sopenharmony_ci char *buf) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci struct td043mtea1_panel *lcd = dev_get_drvdata(dev); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", lcd->mode); 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic ssize_t mode_store(struct device *dev, struct device_attribute *attr, 22162306a36Sopenharmony_ci const char *buf, size_t count) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci struct td043mtea1_panel *lcd = dev_get_drvdata(dev); 22462306a36Sopenharmony_ci long val; 22562306a36Sopenharmony_ci int ret; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci ret = kstrtol(buf, 0, &val); 22862306a36Sopenharmony_ci if (ret != 0 || val & ~7) 22962306a36Sopenharmony_ci return -EINVAL; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci lcd->mode = val; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci val |= TPO_R02_NCLK_RISING; 23462306a36Sopenharmony_ci td043mtea1_write(lcd, 2, val); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci return count; 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic ssize_t gamma_show(struct device *dev, struct device_attribute *attr, 24062306a36Sopenharmony_ci char *buf) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci struct td043mtea1_panel *lcd = dev_get_drvdata(dev); 24362306a36Sopenharmony_ci ssize_t len = 0; 24462306a36Sopenharmony_ci unsigned int i; 24562306a36Sopenharmony_ci int ret; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(lcd->gamma); i++) { 24862306a36Sopenharmony_ci ret = snprintf(buf + len, PAGE_SIZE - len, "%u ", 24962306a36Sopenharmony_ci lcd->gamma[i]); 25062306a36Sopenharmony_ci if (ret < 0) 25162306a36Sopenharmony_ci return ret; 25262306a36Sopenharmony_ci len += ret; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci buf[len - 1] = '\n'; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci return len; 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic ssize_t gamma_store(struct device *dev, struct device_attribute *attr, 26062306a36Sopenharmony_ci const char *buf, size_t count) 26162306a36Sopenharmony_ci{ 26262306a36Sopenharmony_ci struct td043mtea1_panel *lcd = dev_get_drvdata(dev); 26362306a36Sopenharmony_ci unsigned int g[12]; 26462306a36Sopenharmony_ci unsigned int i; 26562306a36Sopenharmony_ci int ret; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u", 26862306a36Sopenharmony_ci &g[0], &g[1], &g[2], &g[3], &g[4], &g[5], 26962306a36Sopenharmony_ci &g[6], &g[7], &g[8], &g[9], &g[10], &g[11]); 27062306a36Sopenharmony_ci if (ret != 12) 27162306a36Sopenharmony_ci return -EINVAL; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci for (i = 0; i < 12; i++) 27462306a36Sopenharmony_ci lcd->gamma[i] = g[i]; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci td043mtea1_write_gamma(lcd); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci return count; 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(vmirror); 28262306a36Sopenharmony_cistatic DEVICE_ATTR_RW(mode); 28362306a36Sopenharmony_cistatic DEVICE_ATTR_RW(gamma); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic struct attribute *td043mtea1_attrs[] = { 28662306a36Sopenharmony_ci &dev_attr_vmirror.attr, 28762306a36Sopenharmony_ci &dev_attr_mode.attr, 28862306a36Sopenharmony_ci &dev_attr_gamma.attr, 28962306a36Sopenharmony_ci NULL, 29062306a36Sopenharmony_ci}; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_cistatic const struct attribute_group td043mtea1_attr_group = { 29362306a36Sopenharmony_ci .attrs = td043mtea1_attrs, 29462306a36Sopenharmony_ci}; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 29762306a36Sopenharmony_ci * Panel Operations 29862306a36Sopenharmony_ci */ 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cistatic int td043mtea1_unprepare(struct drm_panel *panel) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci struct td043mtea1_panel *lcd = to_td043mtea1_device(panel); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci if (!lcd->spi_suspended) 30562306a36Sopenharmony_ci td043mtea1_power_off(lcd); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci return 0; 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cistatic int td043mtea1_prepare(struct drm_panel *panel) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci struct td043mtea1_panel *lcd = to_td043mtea1_device(panel); 31362306a36Sopenharmony_ci int ret; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci /* 31662306a36Sopenharmony_ci * If we are resuming from system suspend, SPI might not be enabled 31762306a36Sopenharmony_ci * yet, so we'll program the LCD from SPI PM resume callback. 31862306a36Sopenharmony_ci */ 31962306a36Sopenharmony_ci if (lcd->spi_suspended) 32062306a36Sopenharmony_ci return 0; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci ret = td043mtea1_power_on(lcd); 32362306a36Sopenharmony_ci if (ret) { 32462306a36Sopenharmony_ci dev_err(&lcd->spi->dev, "%s: power on failed (%d)\n", 32562306a36Sopenharmony_ci __func__, ret); 32662306a36Sopenharmony_ci return ret; 32762306a36Sopenharmony_ci } 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci return 0; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic const struct drm_display_mode td043mtea1_mode = { 33362306a36Sopenharmony_ci .clock = 36000, 33462306a36Sopenharmony_ci .hdisplay = 800, 33562306a36Sopenharmony_ci .hsync_start = 800 + 68, 33662306a36Sopenharmony_ci .hsync_end = 800 + 68 + 1, 33762306a36Sopenharmony_ci .htotal = 800 + 68 + 1 + 214, 33862306a36Sopenharmony_ci .vdisplay = 480, 33962306a36Sopenharmony_ci .vsync_start = 480 + 39, 34062306a36Sopenharmony_ci .vsync_end = 480 + 39 + 1, 34162306a36Sopenharmony_ci .vtotal = 480 + 39 + 1 + 34, 34262306a36Sopenharmony_ci .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, 34362306a36Sopenharmony_ci .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, 34462306a36Sopenharmony_ci .width_mm = 94, 34562306a36Sopenharmony_ci .height_mm = 56, 34662306a36Sopenharmony_ci}; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic int td043mtea1_get_modes(struct drm_panel *panel, 34962306a36Sopenharmony_ci struct drm_connector *connector) 35062306a36Sopenharmony_ci{ 35162306a36Sopenharmony_ci struct drm_display_mode *mode; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, &td043mtea1_mode); 35462306a36Sopenharmony_ci if (!mode) 35562306a36Sopenharmony_ci return -ENOMEM; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci drm_mode_set_name(mode); 35862306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci connector->display_info.width_mm = td043mtea1_mode.width_mm; 36162306a36Sopenharmony_ci connector->display_info.height_mm = td043mtea1_mode.height_mm; 36262306a36Sopenharmony_ci /* 36362306a36Sopenharmony_ci * FIXME: According to the datasheet sync signals are sampled on the 36462306a36Sopenharmony_ci * rising edge of the clock, but the code running on the OMAP3 Pandora 36562306a36Sopenharmony_ci * indicates sampling on the falling edge. This should be tested on a 36662306a36Sopenharmony_ci * real device. 36762306a36Sopenharmony_ci */ 36862306a36Sopenharmony_ci connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH 36962306a36Sopenharmony_ci | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE 37062306a36Sopenharmony_ci | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci return 1; 37362306a36Sopenharmony_ci} 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_cistatic const struct drm_panel_funcs td043mtea1_funcs = { 37662306a36Sopenharmony_ci .unprepare = td043mtea1_unprepare, 37762306a36Sopenharmony_ci .prepare = td043mtea1_prepare, 37862306a36Sopenharmony_ci .get_modes = td043mtea1_get_modes, 37962306a36Sopenharmony_ci}; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 38262306a36Sopenharmony_ci * Power Management, Probe and Remove 38362306a36Sopenharmony_ci */ 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_cistatic int __maybe_unused td043mtea1_suspend(struct device *dev) 38662306a36Sopenharmony_ci{ 38762306a36Sopenharmony_ci struct td043mtea1_panel *lcd = dev_get_drvdata(dev); 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci if (lcd->powered_on) { 39062306a36Sopenharmony_ci td043mtea1_power_off(lcd); 39162306a36Sopenharmony_ci lcd->powered_on = true; 39262306a36Sopenharmony_ci } 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci lcd->spi_suspended = true; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci return 0; 39762306a36Sopenharmony_ci} 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_cistatic int __maybe_unused td043mtea1_resume(struct device *dev) 40062306a36Sopenharmony_ci{ 40162306a36Sopenharmony_ci struct td043mtea1_panel *lcd = dev_get_drvdata(dev); 40262306a36Sopenharmony_ci int ret; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci lcd->spi_suspended = false; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci if (lcd->powered_on) { 40762306a36Sopenharmony_ci lcd->powered_on = false; 40862306a36Sopenharmony_ci ret = td043mtea1_power_on(lcd); 40962306a36Sopenharmony_ci if (ret) 41062306a36Sopenharmony_ci return ret; 41162306a36Sopenharmony_ci } 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci return 0; 41462306a36Sopenharmony_ci} 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(td043mtea1_pm_ops, td043mtea1_suspend, 41762306a36Sopenharmony_ci td043mtea1_resume); 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_cistatic int td043mtea1_probe(struct spi_device *spi) 42062306a36Sopenharmony_ci{ 42162306a36Sopenharmony_ci struct td043mtea1_panel *lcd; 42262306a36Sopenharmony_ci int ret; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); 42562306a36Sopenharmony_ci if (lcd == NULL) 42662306a36Sopenharmony_ci return -ENOMEM; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci spi_set_drvdata(spi, lcd); 42962306a36Sopenharmony_ci lcd->spi = spi; 43062306a36Sopenharmony_ci lcd->mode = TPO_R02_MODE_800x480; 43162306a36Sopenharmony_ci memcpy(lcd->gamma, td043mtea1_def_gamma, sizeof(lcd->gamma)); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci lcd->vcc_reg = devm_regulator_get(&spi->dev, "vcc"); 43462306a36Sopenharmony_ci if (IS_ERR(lcd->vcc_reg)) 43562306a36Sopenharmony_ci return dev_err_probe(&spi->dev, PTR_ERR(lcd->vcc_reg), 43662306a36Sopenharmony_ci "failed to get VCC regulator\n"); 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH); 43962306a36Sopenharmony_ci if (IS_ERR(lcd->reset_gpio)) 44062306a36Sopenharmony_ci return dev_err_probe(&spi->dev, PTR_ERR(lcd->reset_gpio), 44162306a36Sopenharmony_ci "failed to get reset GPIO\n"); 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci spi->bits_per_word = 16; 44462306a36Sopenharmony_ci spi->mode = SPI_MODE_0; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci ret = spi_setup(spi); 44762306a36Sopenharmony_ci if (ret < 0) { 44862306a36Sopenharmony_ci dev_err(&spi->dev, "failed to setup SPI: %d\n", ret); 44962306a36Sopenharmony_ci return ret; 45062306a36Sopenharmony_ci } 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci ret = sysfs_create_group(&spi->dev.kobj, &td043mtea1_attr_group); 45362306a36Sopenharmony_ci if (ret < 0) { 45462306a36Sopenharmony_ci dev_err(&spi->dev, "failed to create sysfs files\n"); 45562306a36Sopenharmony_ci return ret; 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci drm_panel_init(&lcd->panel, &lcd->spi->dev, &td043mtea1_funcs, 45962306a36Sopenharmony_ci DRM_MODE_CONNECTOR_DPI); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci drm_panel_add(&lcd->panel); 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci return 0; 46462306a36Sopenharmony_ci} 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_cistatic void td043mtea1_remove(struct spi_device *spi) 46762306a36Sopenharmony_ci{ 46862306a36Sopenharmony_ci struct td043mtea1_panel *lcd = spi_get_drvdata(spi); 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci drm_panel_remove(&lcd->panel); 47162306a36Sopenharmony_ci drm_panel_disable(&lcd->panel); 47262306a36Sopenharmony_ci drm_panel_unprepare(&lcd->panel); 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group); 47562306a36Sopenharmony_ci} 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_cistatic const struct of_device_id td043mtea1_of_match[] = { 47862306a36Sopenharmony_ci { .compatible = "tpo,td043mtea1", }, 47962306a36Sopenharmony_ci { /* sentinel */ }, 48062306a36Sopenharmony_ci}; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, td043mtea1_of_match); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_cistatic const struct spi_device_id td043mtea1_ids[] = { 48562306a36Sopenharmony_ci { "td043mtea1", 0 }, 48662306a36Sopenharmony_ci { /* sentinel */ } 48762306a36Sopenharmony_ci}; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(spi, td043mtea1_ids); 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_cistatic struct spi_driver td043mtea1_driver = { 49262306a36Sopenharmony_ci .probe = td043mtea1_probe, 49362306a36Sopenharmony_ci .remove = td043mtea1_remove, 49462306a36Sopenharmony_ci .id_table = td043mtea1_ids, 49562306a36Sopenharmony_ci .driver = { 49662306a36Sopenharmony_ci .name = "panel-tpo-td043mtea1", 49762306a36Sopenharmony_ci .pm = &td043mtea1_pm_ops, 49862306a36Sopenharmony_ci .of_match_table = td043mtea1_of_match, 49962306a36Sopenharmony_ci }, 50062306a36Sopenharmony_ci}; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cimodule_spi_driver(td043mtea1_driver); 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ciMODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>"); 50562306a36Sopenharmony_ciMODULE_DESCRIPTION("TPO TD043MTEA1 Panel Driver"); 50662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 507