162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * TPO TD043MTEA1 Panel driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Gražvydas Ignotas <notasas@gmail.com> 662306a36Sopenharmony_ci * Converted to new DSS device model: Tomi Valkeinen <tomi.valkeinen@ti.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/delay.h> 1162306a36Sopenharmony_ci#include <linux/spi/spi.h> 1262306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1362306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1462306a36Sopenharmony_ci#include <linux/err.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <video/omapfb_dss.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define TPO_R02_MODE(x) ((x) & 7) 2062306a36Sopenharmony_ci#define TPO_R02_MODE_800x480 7 2162306a36Sopenharmony_ci#define TPO_R02_NCLK_RISING BIT(3) 2262306a36Sopenharmony_ci#define TPO_R02_HSYNC_HIGH BIT(4) 2362306a36Sopenharmony_ci#define TPO_R02_VSYNC_HIGH BIT(5) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define TPO_R03_NSTANDBY BIT(0) 2662306a36Sopenharmony_ci#define TPO_R03_EN_CP_CLK BIT(1) 2762306a36Sopenharmony_ci#define TPO_R03_EN_VGL_PUMP BIT(2) 2862306a36Sopenharmony_ci#define TPO_R03_EN_PWM BIT(3) 2962306a36Sopenharmony_ci#define TPO_R03_DRIVING_CAP_100 BIT(4) 3062306a36Sopenharmony_ci#define TPO_R03_EN_PRE_CHARGE BIT(6) 3162306a36Sopenharmony_ci#define TPO_R03_SOFTWARE_CTL BIT(7) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define TPO_R04_NFLIP_H BIT(0) 3462306a36Sopenharmony_ci#define TPO_R04_NFLIP_V BIT(1) 3562306a36Sopenharmony_ci#define TPO_R04_CP_CLK_FREQ_1H BIT(2) 3662306a36Sopenharmony_ci#define TPO_R04_VGL_FREQ_1H BIT(4) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define TPO_R03_VAL_NORMAL (TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | \ 3962306a36Sopenharmony_ci TPO_R03_EN_VGL_PUMP | TPO_R03_EN_PWM | \ 4062306a36Sopenharmony_ci TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ 4162306a36Sopenharmony_ci TPO_R03_SOFTWARE_CTL) 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#define TPO_R03_VAL_STANDBY (TPO_R03_DRIVING_CAP_100 | \ 4462306a36Sopenharmony_ci TPO_R03_EN_PRE_CHARGE | TPO_R03_SOFTWARE_CTL) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic const u16 tpo_td043_def_gamma[12] = { 4762306a36Sopenharmony_ci 105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023 4862306a36Sopenharmony_ci}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistruct panel_drv_data { 5162306a36Sopenharmony_ci struct omap_dss_device dssdev; 5262306a36Sopenharmony_ci struct omap_dss_device *in; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci struct omap_video_timings videomode; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci int data_lines; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci struct spi_device *spi; 5962306a36Sopenharmony_ci struct regulator *vcc_reg; 6062306a36Sopenharmony_ci struct gpio_desc *reset_gpio; 6162306a36Sopenharmony_ci u16 gamma[12]; 6262306a36Sopenharmony_ci u32 mode; 6362306a36Sopenharmony_ci u32 hmirror:1; 6462306a36Sopenharmony_ci u32 vmirror:1; 6562306a36Sopenharmony_ci u32 powered_on:1; 6662306a36Sopenharmony_ci u32 spi_suspended:1; 6762306a36Sopenharmony_ci u32 power_on_resume:1; 6862306a36Sopenharmony_ci}; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic const struct omap_video_timings tpo_td043_timings = { 7162306a36Sopenharmony_ci .x_res = 800, 7262306a36Sopenharmony_ci .y_res = 480, 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci .pixelclock = 36000000, 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci .hsw = 1, 7762306a36Sopenharmony_ci .hfp = 68, 7862306a36Sopenharmony_ci .hbp = 214, 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci .vsw = 1, 8162306a36Sopenharmony_ci .vfp = 39, 8262306a36Sopenharmony_ci .vbp = 34, 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, 8562306a36Sopenharmony_ci .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, 8662306a36Sopenharmony_ci .data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE, 8762306a36Sopenharmony_ci .de_level = OMAPDSS_SIG_ACTIVE_HIGH, 8862306a36Sopenharmony_ci .sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, 8962306a36Sopenharmony_ci}; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic int tpo_td043_write(struct spi_device *spi, u8 addr, u8 data) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci struct spi_message m; 9662306a36Sopenharmony_ci struct spi_transfer xfer; 9762306a36Sopenharmony_ci u16 w; 9862306a36Sopenharmony_ci int r; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci spi_message_init(&m); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci memset(&xfer, 0, sizeof(xfer)); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci w = ((u16)addr << 10) | (1 << 8) | data; 10562306a36Sopenharmony_ci xfer.tx_buf = &w; 10662306a36Sopenharmony_ci xfer.bits_per_word = 16; 10762306a36Sopenharmony_ci xfer.len = 2; 10862306a36Sopenharmony_ci spi_message_add_tail(&xfer, &m); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci r = spi_sync(spi, &m); 11162306a36Sopenharmony_ci if (r < 0) 11262306a36Sopenharmony_ci dev_warn(&spi->dev, "failed to write to LCD reg (%d)\n", r); 11362306a36Sopenharmony_ci return r; 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic void tpo_td043_write_gamma(struct spi_device *spi, u16 gamma[12]) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci u8 i, val; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci /* gamma bits [9:8] */ 12162306a36Sopenharmony_ci for (val = i = 0; i < 4; i++) 12262306a36Sopenharmony_ci val |= (gamma[i] & 0x300) >> ((i + 1) * 2); 12362306a36Sopenharmony_ci tpo_td043_write(spi, 0x11, val); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci for (val = i = 0; i < 4; i++) 12662306a36Sopenharmony_ci val |= (gamma[i+4] & 0x300) >> ((i + 1) * 2); 12762306a36Sopenharmony_ci tpo_td043_write(spi, 0x12, val); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci for (val = i = 0; i < 4; i++) 13062306a36Sopenharmony_ci val |= (gamma[i+8] & 0x300) >> ((i + 1) * 2); 13162306a36Sopenharmony_ci tpo_td043_write(spi, 0x13, val); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci /* gamma bits [7:0] */ 13462306a36Sopenharmony_ci for (val = i = 0; i < 12; i++) 13562306a36Sopenharmony_ci tpo_td043_write(spi, 0x14 + i, gamma[i] & 0xff); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic int tpo_td043_write_mirror(struct spi_device *spi, bool h, bool v) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V | 14162306a36Sopenharmony_ci TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H; 14262306a36Sopenharmony_ci if (h) 14362306a36Sopenharmony_ci reg4 &= ~TPO_R04_NFLIP_H; 14462306a36Sopenharmony_ci if (v) 14562306a36Sopenharmony_ci reg4 &= ~TPO_R04_NFLIP_V; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci return tpo_td043_write(spi, 4, reg4); 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic int tpo_td043_set_hmirror(struct omap_dss_device *dssdev, bool enable) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci ddata->hmirror = enable; 15562306a36Sopenharmony_ci return tpo_td043_write_mirror(ddata->spi, ddata->hmirror, 15662306a36Sopenharmony_ci ddata->vmirror); 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic bool tpo_td043_get_hmirror(struct omap_dss_device *dssdev) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return ddata->hmirror; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic ssize_t tpo_td043_vmirror_show(struct device *dev, 16762306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dev); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", ddata->vmirror); 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic ssize_t tpo_td043_vmirror_store(struct device *dev, 17562306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dev); 17862306a36Sopenharmony_ci int val; 17962306a36Sopenharmony_ci int ret; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci ret = kstrtoint(buf, 0, &val); 18262306a36Sopenharmony_ci if (ret < 0) 18362306a36Sopenharmony_ci return ret; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci val = !!val; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci ret = tpo_td043_write_mirror(ddata->spi, ddata->hmirror, val); 18862306a36Sopenharmony_ci if (ret < 0) 18962306a36Sopenharmony_ci return ret; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci ddata->vmirror = val; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci return count; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic ssize_t tpo_td043_mode_show(struct device *dev, 19762306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dev); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", ddata->mode); 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic ssize_t tpo_td043_mode_store(struct device *dev, 20562306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dev); 20862306a36Sopenharmony_ci long val; 20962306a36Sopenharmony_ci int ret; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci ret = kstrtol(buf, 0, &val); 21262306a36Sopenharmony_ci if (ret != 0 || val & ~7) 21362306a36Sopenharmony_ci return -EINVAL; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci ddata->mode = val; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci val |= TPO_R02_NCLK_RISING; 21862306a36Sopenharmony_ci tpo_td043_write(ddata->spi, 2, val); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci return count; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic ssize_t tpo_td043_gamma_show(struct device *dev, 22462306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dev); 22762306a36Sopenharmony_ci ssize_t len = 0; 22862306a36Sopenharmony_ci int ret; 22962306a36Sopenharmony_ci int i; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ddata->gamma); i++) { 23262306a36Sopenharmony_ci ret = snprintf(buf + len, PAGE_SIZE - len, "%u ", 23362306a36Sopenharmony_ci ddata->gamma[i]); 23462306a36Sopenharmony_ci if (ret < 0) 23562306a36Sopenharmony_ci return ret; 23662306a36Sopenharmony_ci len += ret; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci buf[len - 1] = '\n'; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci return len; 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic ssize_t tpo_td043_gamma_store(struct device *dev, 24462306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dev); 24762306a36Sopenharmony_ci unsigned int g[12]; 24862306a36Sopenharmony_ci int ret; 24962306a36Sopenharmony_ci int i; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u", 25262306a36Sopenharmony_ci &g[0], &g[1], &g[2], &g[3], &g[4], &g[5], 25362306a36Sopenharmony_ci &g[6], &g[7], &g[8], &g[9], &g[10], &g[11]); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci if (ret != 12) 25662306a36Sopenharmony_ci return -EINVAL; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci for (i = 0; i < 12; i++) 25962306a36Sopenharmony_ci ddata->gamma[i] = g[i]; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci tpo_td043_write_gamma(ddata->spi, ddata->gamma); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci return count; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic DEVICE_ATTR(vmirror, S_IRUGO | S_IWUSR, 26762306a36Sopenharmony_ci tpo_td043_vmirror_show, tpo_td043_vmirror_store); 26862306a36Sopenharmony_cistatic DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, 26962306a36Sopenharmony_ci tpo_td043_mode_show, tpo_td043_mode_store); 27062306a36Sopenharmony_cistatic DEVICE_ATTR(gamma, S_IRUGO | S_IWUSR, 27162306a36Sopenharmony_ci tpo_td043_gamma_show, tpo_td043_gamma_store); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_cistatic struct attribute *tpo_td043_attrs[] = { 27462306a36Sopenharmony_ci &dev_attr_vmirror.attr, 27562306a36Sopenharmony_ci &dev_attr_mode.attr, 27662306a36Sopenharmony_ci &dev_attr_gamma.attr, 27762306a36Sopenharmony_ci NULL, 27862306a36Sopenharmony_ci}; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic const struct attribute_group tpo_td043_attr_group = { 28162306a36Sopenharmony_ci .attrs = tpo_td043_attrs, 28262306a36Sopenharmony_ci}; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic int tpo_td043_power_on(struct panel_drv_data *ddata) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci int r; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci if (ddata->powered_on) 28962306a36Sopenharmony_ci return 0; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci r = regulator_enable(ddata->vcc_reg); 29262306a36Sopenharmony_ci if (r != 0) 29362306a36Sopenharmony_ci return r; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci /* wait for panel to stabilize */ 29662306a36Sopenharmony_ci msleep(160); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci gpiod_set_value_cansleep(ddata->reset_gpio, 0); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci tpo_td043_write(ddata->spi, 2, 30162306a36Sopenharmony_ci TPO_R02_MODE(ddata->mode) | TPO_R02_NCLK_RISING); 30262306a36Sopenharmony_ci tpo_td043_write(ddata->spi, 3, TPO_R03_VAL_NORMAL); 30362306a36Sopenharmony_ci tpo_td043_write(ddata->spi, 0x20, 0xf0); 30462306a36Sopenharmony_ci tpo_td043_write(ddata->spi, 0x21, 0xf0); 30562306a36Sopenharmony_ci tpo_td043_write_mirror(ddata->spi, ddata->hmirror, 30662306a36Sopenharmony_ci ddata->vmirror); 30762306a36Sopenharmony_ci tpo_td043_write_gamma(ddata->spi, ddata->gamma); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci ddata->powered_on = 1; 31062306a36Sopenharmony_ci return 0; 31162306a36Sopenharmony_ci} 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_cistatic void tpo_td043_power_off(struct panel_drv_data *ddata) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci if (!ddata->powered_on) 31662306a36Sopenharmony_ci return; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci tpo_td043_write(ddata->spi, 3, 31962306a36Sopenharmony_ci TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci gpiod_set_value_cansleep(ddata->reset_gpio, 1); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci /* wait for at least 2 vsyncs before cutting off power */ 32462306a36Sopenharmony_ci msleep(50); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci tpo_td043_write(ddata->spi, 3, TPO_R03_VAL_STANDBY); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci regulator_disable(ddata->vcc_reg); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci ddata->powered_on = 0; 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistatic int tpo_td043_connect(struct omap_dss_device *dssdev) 33462306a36Sopenharmony_ci{ 33562306a36Sopenharmony_ci struct panel_drv_data *ddata = to_panel_data(dssdev); 33662306a36Sopenharmony_ci struct omap_dss_device *in = ddata->in; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci if (omapdss_device_is_connected(dssdev)) 33962306a36Sopenharmony_ci return 0; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci return in->ops.dpi->connect(in, dssdev); 34262306a36Sopenharmony_ci} 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_cistatic void tpo_td043_disconnect(struct omap_dss_device *dssdev) 34562306a36Sopenharmony_ci{ 34662306a36Sopenharmony_ci struct panel_drv_data *ddata = to_panel_data(dssdev); 34762306a36Sopenharmony_ci struct omap_dss_device *in = ddata->in; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci if (!omapdss_device_is_connected(dssdev)) 35062306a36Sopenharmony_ci return; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci in->ops.dpi->disconnect(in, dssdev); 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_cistatic int tpo_td043_enable(struct omap_dss_device *dssdev) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci struct panel_drv_data *ddata = to_panel_data(dssdev); 35862306a36Sopenharmony_ci struct omap_dss_device *in = ddata->in; 35962306a36Sopenharmony_ci int r; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci if (!omapdss_device_is_connected(dssdev)) 36262306a36Sopenharmony_ci return -ENODEV; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci if (omapdss_device_is_enabled(dssdev)) 36562306a36Sopenharmony_ci return 0; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci if (ddata->data_lines) 36862306a36Sopenharmony_ci in->ops.dpi->set_data_lines(in, ddata->data_lines); 36962306a36Sopenharmony_ci in->ops.dpi->set_timings(in, &ddata->videomode); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci r = in->ops.dpi->enable(in); 37262306a36Sopenharmony_ci if (r) 37362306a36Sopenharmony_ci return r; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci /* 37662306a36Sopenharmony_ci * If we are resuming from system suspend, SPI clocks might not be 37762306a36Sopenharmony_ci * enabled yet, so we'll program the LCD from SPI PM resume callback. 37862306a36Sopenharmony_ci */ 37962306a36Sopenharmony_ci if (!ddata->spi_suspended) { 38062306a36Sopenharmony_ci r = tpo_td043_power_on(ddata); 38162306a36Sopenharmony_ci if (r) { 38262306a36Sopenharmony_ci in->ops.dpi->disable(in); 38362306a36Sopenharmony_ci return r; 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci return 0; 39062306a36Sopenharmony_ci} 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_cistatic void tpo_td043_disable(struct omap_dss_device *dssdev) 39362306a36Sopenharmony_ci{ 39462306a36Sopenharmony_ci struct panel_drv_data *ddata = to_panel_data(dssdev); 39562306a36Sopenharmony_ci struct omap_dss_device *in = ddata->in; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci if (!omapdss_device_is_enabled(dssdev)) 39862306a36Sopenharmony_ci return; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci in->ops.dpi->disable(in); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci if (!ddata->spi_suspended) 40362306a36Sopenharmony_ci tpo_td043_power_off(ddata); 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci dssdev->state = OMAP_DSS_DISPLAY_DISABLED; 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_cistatic void tpo_td043_set_timings(struct omap_dss_device *dssdev, 40962306a36Sopenharmony_ci struct omap_video_timings *timings) 41062306a36Sopenharmony_ci{ 41162306a36Sopenharmony_ci struct panel_drv_data *ddata = to_panel_data(dssdev); 41262306a36Sopenharmony_ci struct omap_dss_device *in = ddata->in; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci ddata->videomode = *timings; 41562306a36Sopenharmony_ci dssdev->panel.timings = *timings; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci in->ops.dpi->set_timings(in, timings); 41862306a36Sopenharmony_ci} 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_cistatic void tpo_td043_get_timings(struct omap_dss_device *dssdev, 42162306a36Sopenharmony_ci struct omap_video_timings *timings) 42262306a36Sopenharmony_ci{ 42362306a36Sopenharmony_ci struct panel_drv_data *ddata = to_panel_data(dssdev); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci *timings = ddata->videomode; 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_cistatic int tpo_td043_check_timings(struct omap_dss_device *dssdev, 42962306a36Sopenharmony_ci struct omap_video_timings *timings) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci struct panel_drv_data *ddata = to_panel_data(dssdev); 43262306a36Sopenharmony_ci struct omap_dss_device *in = ddata->in; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci return in->ops.dpi->check_timings(in, timings); 43562306a36Sopenharmony_ci} 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_cistatic struct omap_dss_driver tpo_td043_ops = { 43862306a36Sopenharmony_ci .connect = tpo_td043_connect, 43962306a36Sopenharmony_ci .disconnect = tpo_td043_disconnect, 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci .enable = tpo_td043_enable, 44262306a36Sopenharmony_ci .disable = tpo_td043_disable, 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci .set_timings = tpo_td043_set_timings, 44562306a36Sopenharmony_ci .get_timings = tpo_td043_get_timings, 44662306a36Sopenharmony_ci .check_timings = tpo_td043_check_timings, 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci .set_mirror = tpo_td043_set_hmirror, 44962306a36Sopenharmony_ci .get_mirror = tpo_td043_get_hmirror, 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci .get_resolution = omapdss_default_get_resolution, 45262306a36Sopenharmony_ci}; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_cistatic int tpo_td043_probe(struct spi_device *spi) 45562306a36Sopenharmony_ci{ 45662306a36Sopenharmony_ci struct panel_drv_data *ddata; 45762306a36Sopenharmony_ci struct omap_dss_device *dssdev; 45862306a36Sopenharmony_ci int r; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci dev_dbg(&spi->dev, "%s\n", __func__); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci if (!spi->dev.of_node) 46362306a36Sopenharmony_ci return -ENODEV; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci spi->bits_per_word = 16; 46662306a36Sopenharmony_ci spi->mode = SPI_MODE_0; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci r = spi_setup(spi); 46962306a36Sopenharmony_ci if (r < 0) { 47062306a36Sopenharmony_ci dev_err(&spi->dev, "spi_setup failed: %d\n", r); 47162306a36Sopenharmony_ci return r; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL); 47562306a36Sopenharmony_ci if (ddata == NULL) 47662306a36Sopenharmony_ci return -ENOMEM; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci dev_set_drvdata(&spi->dev, ddata); 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci ddata->spi = spi; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci ddata->in = omapdss_of_find_source_for_first_ep(spi->dev.of_node); 48362306a36Sopenharmony_ci r = PTR_ERR_OR_ZERO(ddata->in); 48462306a36Sopenharmony_ci if (r) { 48562306a36Sopenharmony_ci dev_err(&spi->dev, "failed to find video source: %d\n", r); 48662306a36Sopenharmony_ci return r; 48762306a36Sopenharmony_ci } 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci ddata->mode = TPO_R02_MODE_800x480; 49062306a36Sopenharmony_ci memcpy(ddata->gamma, tpo_td043_def_gamma, sizeof(ddata->gamma)); 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci ddata->vcc_reg = devm_regulator_get(&spi->dev, "vcc"); 49362306a36Sopenharmony_ci if (IS_ERR(ddata->vcc_reg)) { 49462306a36Sopenharmony_ci r = dev_err_probe(&spi->dev, PTR_ERR(ddata->vcc_reg), 49562306a36Sopenharmony_ci "failed to get LCD VCC regulator\n"); 49662306a36Sopenharmony_ci goto err_regulator; 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci ddata->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH); 50062306a36Sopenharmony_ci r = PTR_ERR_OR_ZERO(ddata->reset_gpio); 50162306a36Sopenharmony_ci if (r) { 50262306a36Sopenharmony_ci dev_err(&spi->dev, "couldn't request reset GPIO\n"); 50362306a36Sopenharmony_ci goto err_gpio_req; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci gpiod_set_consumer_name(ddata->reset_gpio, "lcd reset"); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci r = sysfs_create_group(&spi->dev.kobj, &tpo_td043_attr_group); 50962306a36Sopenharmony_ci if (r) { 51062306a36Sopenharmony_ci dev_err(&spi->dev, "failed to create sysfs files\n"); 51162306a36Sopenharmony_ci goto err_sysfs; 51262306a36Sopenharmony_ci } 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci ddata->videomode = tpo_td043_timings; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci dssdev = &ddata->dssdev; 51762306a36Sopenharmony_ci dssdev->dev = &spi->dev; 51862306a36Sopenharmony_ci dssdev->driver = &tpo_td043_ops; 51962306a36Sopenharmony_ci dssdev->type = OMAP_DISPLAY_TYPE_DPI; 52062306a36Sopenharmony_ci dssdev->owner = THIS_MODULE; 52162306a36Sopenharmony_ci dssdev->panel.timings = ddata->videomode; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci r = omapdss_register_display(dssdev); 52462306a36Sopenharmony_ci if (r) { 52562306a36Sopenharmony_ci dev_err(&spi->dev, "Failed to register panel\n"); 52662306a36Sopenharmony_ci goto err_reg; 52762306a36Sopenharmony_ci } 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci return 0; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_cierr_reg: 53262306a36Sopenharmony_ci sysfs_remove_group(&spi->dev.kobj, &tpo_td043_attr_group); 53362306a36Sopenharmony_cierr_sysfs: 53462306a36Sopenharmony_cierr_gpio_req: 53562306a36Sopenharmony_cierr_regulator: 53662306a36Sopenharmony_ci omap_dss_put_device(ddata->in); 53762306a36Sopenharmony_ci return r; 53862306a36Sopenharmony_ci} 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_cistatic void tpo_td043_remove(struct spi_device *spi) 54162306a36Sopenharmony_ci{ 54262306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); 54362306a36Sopenharmony_ci struct omap_dss_device *dssdev = &ddata->dssdev; 54462306a36Sopenharmony_ci struct omap_dss_device *in = ddata->in; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci dev_dbg(&ddata->spi->dev, "%s\n", __func__); 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci omapdss_unregister_display(dssdev); 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci tpo_td043_disable(dssdev); 55162306a36Sopenharmony_ci tpo_td043_disconnect(dssdev); 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci omap_dss_put_device(in); 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci sysfs_remove_group(&spi->dev.kobj, &tpo_td043_attr_group); 55662306a36Sopenharmony_ci} 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 55962306a36Sopenharmony_cistatic int tpo_td043_spi_suspend(struct device *dev) 56062306a36Sopenharmony_ci{ 56162306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dev); 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci dev_dbg(dev, "tpo_td043_spi_suspend, tpo %p\n", ddata); 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci ddata->power_on_resume = ddata->powered_on; 56662306a36Sopenharmony_ci tpo_td043_power_off(ddata); 56762306a36Sopenharmony_ci ddata->spi_suspended = 1; 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci return 0; 57062306a36Sopenharmony_ci} 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_cistatic int tpo_td043_spi_resume(struct device *dev) 57362306a36Sopenharmony_ci{ 57462306a36Sopenharmony_ci struct panel_drv_data *ddata = dev_get_drvdata(dev); 57562306a36Sopenharmony_ci int ret; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci dev_dbg(dev, "tpo_td043_spi_resume\n"); 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci if (ddata->power_on_resume) { 58062306a36Sopenharmony_ci ret = tpo_td043_power_on(ddata); 58162306a36Sopenharmony_ci if (ret) 58262306a36Sopenharmony_ci return ret; 58362306a36Sopenharmony_ci } 58462306a36Sopenharmony_ci ddata->spi_suspended = 0; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci return 0; 58762306a36Sopenharmony_ci} 58862306a36Sopenharmony_ci#endif 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(tpo_td043_spi_pm, 59162306a36Sopenharmony_ci tpo_td043_spi_suspend, tpo_td043_spi_resume); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_cistatic const struct of_device_id tpo_td043_of_match[] = { 59462306a36Sopenharmony_ci { .compatible = "omapdss,tpo,td043mtea1", }, 59562306a36Sopenharmony_ci {}, 59662306a36Sopenharmony_ci}; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tpo_td043_of_match); 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_cistatic struct spi_driver tpo_td043_spi_driver = { 60162306a36Sopenharmony_ci .driver = { 60262306a36Sopenharmony_ci .name = "panel-tpo-td043mtea1", 60362306a36Sopenharmony_ci .pm = &tpo_td043_spi_pm, 60462306a36Sopenharmony_ci .of_match_table = tpo_td043_of_match, 60562306a36Sopenharmony_ci .suppress_bind_attrs = true, 60662306a36Sopenharmony_ci }, 60762306a36Sopenharmony_ci .probe = tpo_td043_probe, 60862306a36Sopenharmony_ci .remove = tpo_td043_remove, 60962306a36Sopenharmony_ci}; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_cimodule_spi_driver(tpo_td043_spi_driver); 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ciMODULE_ALIAS("spi:tpo,td043mtea1"); 61462306a36Sopenharmony_ciMODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>"); 61562306a36Sopenharmony_ciMODULE_DESCRIPTION("TPO TD043MTEA1 LCD Driver"); 61662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 617