| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 1 | /* | 
|  | 2 | * LCD panel driver for TPO TD043MTEA1 | 
|  | 3 | * | 
|  | 4 | * Author: Gražvydas Ignotas <notasas@gmail.com> | 
|  | 5 | * | 
|  | 6 | * This program is free software; you can redistribute it and/or modify | 
|  | 7 | * it under the terms of the GNU General Public License as published by | 
|  | 8 | * the Free Software Foundation; either version 2 of the License, or | 
|  | 9 | * (at your option) any later version. | 
|  | 10 | */ | 
|  | 11 |  | 
|  | 12 | #include <linux/module.h> | 
|  | 13 | #include <linux/delay.h> | 
|  | 14 | #include <linux/spi/spi.h> | 
|  | 15 | #include <linux/regulator/consumer.h> | 
|  | 16 | #include <linux/gpio.h> | 
|  | 17 | #include <linux/err.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 18 | #include <linux/slab.h> | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 19 |  | 
| Tomi Valkeinen | a0b38cc | 2011-05-11 14:05:07 +0300 | [diff] [blame] | 20 | #include <video/omapdss.h> | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 21 |  | 
|  | 22 | #define TPO_R02_MODE(x)		((x) & 7) | 
|  | 23 | #define TPO_R02_MODE_800x480	7 | 
|  | 24 | #define TPO_R02_NCLK_RISING	BIT(3) | 
|  | 25 | #define TPO_R02_HSYNC_HIGH	BIT(4) | 
|  | 26 | #define TPO_R02_VSYNC_HIGH	BIT(5) | 
|  | 27 |  | 
|  | 28 | #define TPO_R03_NSTANDBY	BIT(0) | 
|  | 29 | #define TPO_R03_EN_CP_CLK	BIT(1) | 
|  | 30 | #define TPO_R03_EN_VGL_PUMP	BIT(2) | 
|  | 31 | #define TPO_R03_EN_PWM		BIT(3) | 
|  | 32 | #define TPO_R03_DRIVING_CAP_100	BIT(4) | 
|  | 33 | #define TPO_R03_EN_PRE_CHARGE	BIT(6) | 
|  | 34 | #define TPO_R03_SOFTWARE_CTL	BIT(7) | 
|  | 35 |  | 
|  | 36 | #define TPO_R04_NFLIP_H		BIT(0) | 
|  | 37 | #define TPO_R04_NFLIP_V		BIT(1) | 
|  | 38 | #define TPO_R04_CP_CLK_FREQ_1H	BIT(2) | 
|  | 39 | #define TPO_R04_VGL_FREQ_1H	BIT(4) | 
|  | 40 |  | 
|  | 41 | #define TPO_R03_VAL_NORMAL (TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | \ | 
|  | 42 | TPO_R03_EN_VGL_PUMP |  TPO_R03_EN_PWM | \ | 
|  | 43 | TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \ | 
|  | 44 | TPO_R03_SOFTWARE_CTL) | 
|  | 45 |  | 
|  | 46 | #define TPO_R03_VAL_STANDBY (TPO_R03_DRIVING_CAP_100 | \ | 
|  | 47 | TPO_R03_EN_PRE_CHARGE | TPO_R03_SOFTWARE_CTL) | 
|  | 48 |  | 
|  | 49 | static const u16 tpo_td043_def_gamma[12] = { | 
| Grazvydas Ignotas | 4306b72 | 2012-02-07 02:13:50 +0200 | [diff] [blame] | 50 | 105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023 | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 51 | }; | 
|  | 52 |  | 
|  | 53 | struct tpo_td043_device { | 
|  | 54 | struct spi_device *spi; | 
|  | 55 | struct regulator *vcc_reg; | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 56 | int nreset_gpio; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 57 | u16 gamma[12]; | 
|  | 58 | u32 mode; | 
|  | 59 | u32 hmirror:1; | 
|  | 60 | u32 vmirror:1; | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 61 | u32 powered_on:1; | 
|  | 62 | u32 spi_suspended:1; | 
|  | 63 | u32 power_on_resume:1; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 64 | }; | 
|  | 65 |  | 
|  | 66 | static int tpo_td043_write(struct spi_device *spi, u8 addr, u8 data) | 
|  | 67 | { | 
|  | 68 | struct spi_message	m; | 
|  | 69 | struct spi_transfer	xfer; | 
|  | 70 | u16			w; | 
|  | 71 | int			r; | 
|  | 72 |  | 
|  | 73 | spi_message_init(&m); | 
|  | 74 |  | 
|  | 75 | memset(&xfer, 0, sizeof(xfer)); | 
|  | 76 |  | 
|  | 77 | w = ((u16)addr << 10) | (1 << 8) | data; | 
|  | 78 | xfer.tx_buf = &w; | 
|  | 79 | xfer.bits_per_word = 16; | 
|  | 80 | xfer.len = 2; | 
|  | 81 | spi_message_add_tail(&xfer, &m); | 
|  | 82 |  | 
|  | 83 | r = spi_sync(spi, &m); | 
|  | 84 | if (r < 0) | 
|  | 85 | dev_warn(&spi->dev, "failed to write to LCD reg (%d)\n", r); | 
|  | 86 | return r; | 
|  | 87 | } | 
|  | 88 |  | 
|  | 89 | static void tpo_td043_write_gamma(struct spi_device *spi, u16 gamma[12]) | 
|  | 90 | { | 
|  | 91 | u8 i, val; | 
|  | 92 |  | 
|  | 93 | /* gamma bits [9:8] */ | 
|  | 94 | for (val = i = 0; i < 4; i++) | 
|  | 95 | val |= (gamma[i] & 0x300) >> ((i + 1) * 2); | 
|  | 96 | tpo_td043_write(spi, 0x11, val); | 
|  | 97 |  | 
|  | 98 | for (val = i = 0; i < 4; i++) | 
|  | 99 | val |= (gamma[i+4] & 0x300) >> ((i + 1) * 2); | 
|  | 100 | tpo_td043_write(spi, 0x12, val); | 
|  | 101 |  | 
|  | 102 | for (val = i = 0; i < 4; i++) | 
|  | 103 | val |= (gamma[i+8] & 0x300) >> ((i + 1) * 2); | 
|  | 104 | tpo_td043_write(spi, 0x13, val); | 
|  | 105 |  | 
|  | 106 | /* gamma bits [7:0] */ | 
|  | 107 | for (val = i = 0; i < 12; i++) | 
|  | 108 | tpo_td043_write(spi, 0x14 + i, gamma[i] & 0xff); | 
|  | 109 | } | 
|  | 110 |  | 
|  | 111 | static int tpo_td043_write_mirror(struct spi_device *spi, bool h, bool v) | 
|  | 112 | { | 
|  | 113 | u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V | \ | 
|  | 114 | TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H; | 
|  | 115 | if (h) | 
|  | 116 | reg4 &= ~TPO_R04_NFLIP_H; | 
|  | 117 | if (v) | 
|  | 118 | reg4 &= ~TPO_R04_NFLIP_V; | 
|  | 119 |  | 
|  | 120 | return tpo_td043_write(spi, 4, reg4); | 
|  | 121 | } | 
|  | 122 |  | 
|  | 123 | static int tpo_td043_set_hmirror(struct omap_dss_device *dssdev, bool enable) | 
|  | 124 | { | 
|  | 125 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&dssdev->dev); | 
|  | 126 |  | 
|  | 127 | tpo_td043->hmirror = enable; | 
|  | 128 | return tpo_td043_write_mirror(tpo_td043->spi, tpo_td043->hmirror, | 
|  | 129 | tpo_td043->vmirror); | 
|  | 130 | } | 
|  | 131 |  | 
|  | 132 | static bool tpo_td043_get_hmirror(struct omap_dss_device *dssdev) | 
|  | 133 | { | 
|  | 134 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&dssdev->dev); | 
|  | 135 |  | 
|  | 136 | return tpo_td043->hmirror; | 
|  | 137 | } | 
|  | 138 |  | 
|  | 139 | static ssize_t tpo_td043_vmirror_show(struct device *dev, | 
|  | 140 | struct device_attribute *attr, char *buf) | 
|  | 141 | { | 
|  | 142 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev); | 
|  | 143 |  | 
|  | 144 | return snprintf(buf, PAGE_SIZE, "%d\n", tpo_td043->vmirror); | 
|  | 145 | } | 
|  | 146 |  | 
|  | 147 | static ssize_t tpo_td043_vmirror_store(struct device *dev, | 
|  | 148 | struct device_attribute *attr, const char *buf, size_t count) | 
|  | 149 | { | 
|  | 150 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev); | 
| Tomi Valkeinen | e3502ce | 2011-04-04 15:40:23 +0300 | [diff] [blame] | 151 | int val; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 152 | int ret; | 
|  | 153 |  | 
| Tomi Valkeinen | e3502ce | 2011-04-04 15:40:23 +0300 | [diff] [blame] | 154 | ret = kstrtoint(buf, 0, &val); | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 155 | if (ret < 0) | 
|  | 156 | return ret; | 
|  | 157 |  | 
| Tomi Valkeinen | e3502ce | 2011-04-04 15:40:23 +0300 | [diff] [blame] | 158 | val = !!val; | 
|  | 159 |  | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 160 | ret = tpo_td043_write_mirror(tpo_td043->spi, tpo_td043->hmirror, val); | 
|  | 161 | if (ret < 0) | 
|  | 162 | return ret; | 
|  | 163 |  | 
|  | 164 | tpo_td043->vmirror = val; | 
|  | 165 |  | 
|  | 166 | return count; | 
|  | 167 | } | 
|  | 168 |  | 
|  | 169 | static ssize_t tpo_td043_mode_show(struct device *dev, | 
|  | 170 | struct device_attribute *attr, char *buf) | 
|  | 171 | { | 
|  | 172 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev); | 
|  | 173 |  | 
|  | 174 | return snprintf(buf, PAGE_SIZE, "%d\n", tpo_td043->mode); | 
|  | 175 | } | 
|  | 176 |  | 
|  | 177 | static ssize_t tpo_td043_mode_store(struct device *dev, | 
|  | 178 | struct device_attribute *attr, const char *buf, size_t count) | 
|  | 179 | { | 
|  | 180 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev); | 
|  | 181 | long val; | 
|  | 182 | int ret; | 
|  | 183 |  | 
| Tomi Valkeinen | e3502ce | 2011-04-04 15:40:23 +0300 | [diff] [blame] | 184 | ret = kstrtol(buf, 0, &val); | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 185 | if (ret != 0 || val & ~7) | 
|  | 186 | return -EINVAL; | 
|  | 187 |  | 
|  | 188 | tpo_td043->mode = val; | 
|  | 189 |  | 
|  | 190 | val |= TPO_R02_NCLK_RISING; | 
|  | 191 | tpo_td043_write(tpo_td043->spi, 2, val); | 
|  | 192 |  | 
|  | 193 | return count; | 
|  | 194 | } | 
|  | 195 |  | 
|  | 196 | static ssize_t tpo_td043_gamma_show(struct device *dev, | 
|  | 197 | struct device_attribute *attr, char *buf) | 
|  | 198 | { | 
|  | 199 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev); | 
|  | 200 | ssize_t len = 0; | 
|  | 201 | int ret; | 
|  | 202 | int i; | 
|  | 203 |  | 
|  | 204 | for (i = 0; i < ARRAY_SIZE(tpo_td043->gamma); i++) { | 
|  | 205 | ret = snprintf(buf + len, PAGE_SIZE - len, "%u ", | 
|  | 206 | tpo_td043->gamma[i]); | 
|  | 207 | if (ret < 0) | 
|  | 208 | return ret; | 
|  | 209 | len += ret; | 
|  | 210 | } | 
|  | 211 | buf[len - 1] = '\n'; | 
|  | 212 |  | 
|  | 213 | return len; | 
|  | 214 | } | 
|  | 215 |  | 
|  | 216 | static ssize_t tpo_td043_gamma_store(struct device *dev, | 
|  | 217 | struct device_attribute *attr, const char *buf, size_t count) | 
|  | 218 | { | 
|  | 219 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev); | 
|  | 220 | unsigned int g[12]; | 
|  | 221 | int ret; | 
|  | 222 | int i; | 
|  | 223 |  | 
|  | 224 | ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u", | 
|  | 225 | &g[0], &g[1], &g[2], &g[3], &g[4], &g[5], | 
|  | 226 | &g[6], &g[7], &g[8], &g[9], &g[10], &g[11]); | 
|  | 227 |  | 
|  | 228 | if (ret != 12) | 
|  | 229 | return -EINVAL; | 
|  | 230 |  | 
|  | 231 | for (i = 0; i < 12; i++) | 
|  | 232 | tpo_td043->gamma[i] = g[i]; | 
|  | 233 |  | 
|  | 234 | tpo_td043_write_gamma(tpo_td043->spi, tpo_td043->gamma); | 
|  | 235 |  | 
|  | 236 | return count; | 
|  | 237 | } | 
|  | 238 |  | 
|  | 239 | static DEVICE_ATTR(vmirror, S_IRUGO | S_IWUSR, | 
|  | 240 | tpo_td043_vmirror_show, tpo_td043_vmirror_store); | 
|  | 241 | static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, | 
|  | 242 | tpo_td043_mode_show, tpo_td043_mode_store); | 
|  | 243 | static DEVICE_ATTR(gamma, S_IRUGO | S_IWUSR, | 
|  | 244 | tpo_td043_gamma_show, tpo_td043_gamma_store); | 
|  | 245 |  | 
|  | 246 | static struct attribute *tpo_td043_attrs[] = { | 
|  | 247 | &dev_attr_vmirror.attr, | 
|  | 248 | &dev_attr_mode.attr, | 
|  | 249 | &dev_attr_gamma.attr, | 
|  | 250 | NULL, | 
|  | 251 | }; | 
|  | 252 |  | 
|  | 253 | static struct attribute_group tpo_td043_attr_group = { | 
|  | 254 | .attrs = tpo_td043_attrs, | 
|  | 255 | }; | 
|  | 256 |  | 
|  | 257 | static const struct omap_video_timings tpo_td043_timings = { | 
|  | 258 | .x_res		= 800, | 
|  | 259 | .y_res		= 480, | 
|  | 260 |  | 
|  | 261 | .pixel_clock	= 36000, | 
|  | 262 |  | 
|  | 263 | .hsw		= 1, | 
|  | 264 | .hfp		= 68, | 
|  | 265 | .hbp		= 214, | 
|  | 266 |  | 
|  | 267 | .vsw		= 1, | 
|  | 268 | .vfp		= 39, | 
|  | 269 | .vbp		= 34, | 
| Archit Taneja | a8d5e41 | 2012-06-25 12:26:38 +0530 | [diff] [blame] | 270 |  | 
|  | 271 | .vsync_level	= OMAPDSS_SIG_ACTIVE_LOW, | 
|  | 272 | .hsync_level	= OMAPDSS_SIG_ACTIVE_LOW, | 
|  | 273 | .data_pclk_edge	= OMAPDSS_DRIVE_SIG_FALLING_EDGE, | 
|  | 274 | .de_level	= OMAPDSS_SIG_ACTIVE_HIGH, | 
|  | 275 | .sync_pclk_edge	= OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES, | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 276 | }; | 
|  | 277 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 278 | static int tpo_td043_power_on(struct tpo_td043_device *tpo_td043) | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 279 | { | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 280 | int nreset_gpio = tpo_td043->nreset_gpio; | 
| Mark Brown | 956107e | 2012-03-19 15:02:31 +0000 | [diff] [blame] | 281 | int r; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 282 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 283 | if (tpo_td043->powered_on) | 
| Stanley.Miao | 18016e3 | 2010-09-03 05:03:48 +0200 | [diff] [blame] | 284 | return 0; | 
|  | 285 |  | 
| Mark Brown | 956107e | 2012-03-19 15:02:31 +0000 | [diff] [blame] | 286 | r = regulator_enable(tpo_td043->vcc_reg); | 
|  | 287 | if (r != 0) | 
|  | 288 | return r; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 289 |  | 
| Mark Brown | 2c83af4 | 2012-03-19 15:02:32 +0000 | [diff] [blame] | 290 | /* wait for panel to stabilize */ | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 291 | msleep(160); | 
|  | 292 |  | 
|  | 293 | if (gpio_is_valid(nreset_gpio)) | 
|  | 294 | gpio_set_value(nreset_gpio, 1); | 
|  | 295 |  | 
|  | 296 | tpo_td043_write(tpo_td043->spi, 2, | 
|  | 297 | TPO_R02_MODE(tpo_td043->mode) | TPO_R02_NCLK_RISING); | 
|  | 298 | tpo_td043_write(tpo_td043->spi, 3, TPO_R03_VAL_NORMAL); | 
|  | 299 | tpo_td043_write(tpo_td043->spi, 0x20, 0xf0); | 
|  | 300 | tpo_td043_write(tpo_td043->spi, 0x21, 0xf0); | 
|  | 301 | tpo_td043_write_mirror(tpo_td043->spi, tpo_td043->hmirror, | 
|  | 302 | tpo_td043->vmirror); | 
|  | 303 | tpo_td043_write_gamma(tpo_td043->spi, tpo_td043->gamma); | 
|  | 304 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 305 | tpo_td043->powered_on = 1; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 306 | return 0; | 
|  | 307 | } | 
|  | 308 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 309 | static void tpo_td043_power_off(struct tpo_td043_device *tpo_td043) | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 310 | { | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 311 | int nreset_gpio = tpo_td043->nreset_gpio; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 312 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 313 | if (!tpo_td043->powered_on) | 
| Stanley.Miao | 18016e3 | 2010-09-03 05:03:48 +0200 | [diff] [blame] | 314 | return; | 
|  | 315 |  | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 316 | tpo_td043_write(tpo_td043->spi, 3, | 
|  | 317 | TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM); | 
|  | 318 |  | 
|  | 319 | if (gpio_is_valid(nreset_gpio)) | 
|  | 320 | gpio_set_value(nreset_gpio, 0); | 
|  | 321 |  | 
|  | 322 | /* wait for at least 2 vsyncs before cutting off power */ | 
|  | 323 | msleep(50); | 
|  | 324 |  | 
|  | 325 | tpo_td043_write(tpo_td043->spi, 3, TPO_R03_VAL_STANDBY); | 
|  | 326 |  | 
|  | 327 | regulator_disable(tpo_td043->vcc_reg); | 
|  | 328 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 329 | tpo_td043->powered_on = 0; | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 330 | } | 
|  | 331 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 332 | static int tpo_td043_enable_dss(struct omap_dss_device *dssdev) | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 333 | { | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 334 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&dssdev->dev); | 
|  | 335 | int r; | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 336 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 337 | if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) | 
|  | 338 | return 0; | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 339 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 340 | r = omapdss_dpi_display_enable(dssdev); | 
|  | 341 | if (r) | 
|  | 342 | goto err0; | 
|  | 343 |  | 
|  | 344 | if (dssdev->platform_enable) { | 
|  | 345 | r = dssdev->platform_enable(dssdev); | 
|  | 346 | if (r) | 
|  | 347 | goto err1; | 
|  | 348 | } | 
|  | 349 |  | 
|  | 350 | /* | 
|  | 351 | * If we are resuming from system suspend, SPI clocks might not be | 
|  | 352 | * enabled yet, so we'll program the LCD from SPI PM resume callback. | 
|  | 353 | */ | 
|  | 354 | if (!tpo_td043->spi_suspended) { | 
|  | 355 | r = tpo_td043_power_on(tpo_td043); | 
|  | 356 | if (r) | 
|  | 357 | goto err1; | 
|  | 358 | } | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 359 |  | 
|  | 360 | dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; | 
|  | 361 |  | 
|  | 362 | return 0; | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 363 | err1: | 
|  | 364 | omapdss_dpi_display_disable(dssdev); | 
|  | 365 | err0: | 
|  | 366 | return r; | 
|  | 367 | } | 
|  | 368 |  | 
|  | 369 | static void tpo_td043_disable_dss(struct omap_dss_device *dssdev) | 
|  | 370 | { | 
|  | 371 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&dssdev->dev); | 
|  | 372 |  | 
|  | 373 | if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE) | 
|  | 374 | return; | 
|  | 375 |  | 
|  | 376 | if (dssdev->platform_disable) | 
|  | 377 | dssdev->platform_disable(dssdev); | 
|  | 378 |  | 
|  | 379 | omapdss_dpi_display_disable(dssdev); | 
|  | 380 |  | 
|  | 381 | if (!tpo_td043->spi_suspended) | 
|  | 382 | tpo_td043_power_off(tpo_td043); | 
|  | 383 | } | 
|  | 384 |  | 
|  | 385 | static int tpo_td043_enable(struct omap_dss_device *dssdev) | 
|  | 386 | { | 
|  | 387 | dev_dbg(&dssdev->dev, "enable\n"); | 
|  | 388 |  | 
|  | 389 | return tpo_td043_enable_dss(dssdev); | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 390 | } | 
|  | 391 |  | 
|  | 392 | static void tpo_td043_disable(struct omap_dss_device *dssdev) | 
|  | 393 | { | 
|  | 394 | dev_dbg(&dssdev->dev, "disable\n"); | 
|  | 395 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 396 | tpo_td043_disable_dss(dssdev); | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 397 |  | 
|  | 398 | dssdev->state = OMAP_DSS_DISPLAY_DISABLED; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 399 | } | 
|  | 400 |  | 
|  | 401 | static int tpo_td043_suspend(struct omap_dss_device *dssdev) | 
|  | 402 | { | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 403 | dev_dbg(&dssdev->dev, "suspend\n"); | 
|  | 404 |  | 
|  | 405 | tpo_td043_disable_dss(dssdev); | 
|  | 406 |  | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 407 | dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED; | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 408 |  | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 409 | return 0; | 
|  | 410 | } | 
|  | 411 |  | 
|  | 412 | static int tpo_td043_resume(struct omap_dss_device *dssdev) | 
|  | 413 | { | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 414 | dev_dbg(&dssdev->dev, "resume\n"); | 
| Tomi Valkeinen | 37ac60e | 2010-01-12 15:12:07 +0200 | [diff] [blame] | 415 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 416 | return tpo_td043_enable_dss(dssdev); | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 417 | } | 
|  | 418 |  | 
|  | 419 | static int tpo_td043_probe(struct omap_dss_device *dssdev) | 
|  | 420 | { | 
|  | 421 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&dssdev->dev); | 
|  | 422 | int nreset_gpio = dssdev->reset_gpio; | 
|  | 423 | int ret = 0; | 
|  | 424 |  | 
|  | 425 | dev_dbg(&dssdev->dev, "probe\n"); | 
|  | 426 |  | 
|  | 427 | if (tpo_td043 == NULL) { | 
|  | 428 | dev_err(&dssdev->dev, "missing tpo_td043_device\n"); | 
|  | 429 | return -ENODEV; | 
|  | 430 | } | 
|  | 431 |  | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 432 | dssdev->panel.timings = tpo_td043_timings; | 
|  | 433 | dssdev->ctrl.pixel_size = 24; | 
|  | 434 |  | 
|  | 435 | tpo_td043->mode = TPO_R02_MODE_800x480; | 
|  | 436 | memcpy(tpo_td043->gamma, tpo_td043_def_gamma, sizeof(tpo_td043->gamma)); | 
|  | 437 |  | 
|  | 438 | tpo_td043->vcc_reg = regulator_get(&dssdev->dev, "vcc"); | 
|  | 439 | if (IS_ERR(tpo_td043->vcc_reg)) { | 
|  | 440 | dev_err(&dssdev->dev, "failed to get LCD VCC regulator\n"); | 
|  | 441 | ret = PTR_ERR(tpo_td043->vcc_reg); | 
|  | 442 | goto fail_regulator; | 
|  | 443 | } | 
|  | 444 |  | 
|  | 445 | if (gpio_is_valid(nreset_gpio)) { | 
| Jingoo Han | f8bd493 | 2012-01-26 19:32:34 +0900 | [diff] [blame] | 446 | ret = gpio_request_one(nreset_gpio, GPIOF_OUT_INIT_LOW, | 
|  | 447 | "lcd reset"); | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 448 | if (ret < 0) { | 
|  | 449 | dev_err(&dssdev->dev, "couldn't request reset GPIO\n"); | 
|  | 450 | goto fail_gpio_req; | 
|  | 451 | } | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 452 | } | 
|  | 453 |  | 
|  | 454 | ret = sysfs_create_group(&dssdev->dev.kobj, &tpo_td043_attr_group); | 
|  | 455 | if (ret) | 
|  | 456 | dev_warn(&dssdev->dev, "failed to create sysfs files\n"); | 
|  | 457 |  | 
|  | 458 | return 0; | 
|  | 459 |  | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 460 | fail_gpio_req: | 
|  | 461 | regulator_put(tpo_td043->vcc_reg); | 
|  | 462 | fail_regulator: | 
|  | 463 | kfree(tpo_td043); | 
|  | 464 | return ret; | 
|  | 465 | } | 
|  | 466 |  | 
|  | 467 | static void tpo_td043_remove(struct omap_dss_device *dssdev) | 
|  | 468 | { | 
|  | 469 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&dssdev->dev); | 
|  | 470 | int nreset_gpio = dssdev->reset_gpio; | 
|  | 471 |  | 
|  | 472 | dev_dbg(&dssdev->dev, "remove\n"); | 
|  | 473 |  | 
|  | 474 | sysfs_remove_group(&dssdev->dev.kobj, &tpo_td043_attr_group); | 
|  | 475 | regulator_put(tpo_td043->vcc_reg); | 
|  | 476 | if (gpio_is_valid(nreset_gpio)) | 
|  | 477 | gpio_free(nreset_gpio); | 
|  | 478 | } | 
|  | 479 |  | 
| Grazvydas Ignotas | 31e8dfe | 2012-03-15 20:00:24 +0200 | [diff] [blame] | 480 | static void tpo_td043_set_timings(struct omap_dss_device *dssdev, | 
|  | 481 | struct omap_video_timings *timings) | 
|  | 482 | { | 
|  | 483 | dpi_set_timings(dssdev, timings); | 
|  | 484 | } | 
|  | 485 |  | 
|  | 486 | static int tpo_td043_check_timings(struct omap_dss_device *dssdev, | 
|  | 487 | struct omap_video_timings *timings) | 
|  | 488 | { | 
|  | 489 | return dpi_check_timings(dssdev, timings); | 
|  | 490 | } | 
|  | 491 |  | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 492 | static struct omap_dss_driver tpo_td043_driver = { | 
|  | 493 | .probe		= tpo_td043_probe, | 
|  | 494 | .remove		= tpo_td043_remove, | 
|  | 495 |  | 
|  | 496 | .enable		= tpo_td043_enable, | 
|  | 497 | .disable	= tpo_td043_disable, | 
|  | 498 | .suspend	= tpo_td043_suspend, | 
|  | 499 | .resume		= tpo_td043_resume, | 
|  | 500 | .set_mirror	= tpo_td043_set_hmirror, | 
|  | 501 | .get_mirror	= tpo_td043_get_hmirror, | 
|  | 502 |  | 
| Grazvydas Ignotas | 31e8dfe | 2012-03-15 20:00:24 +0200 | [diff] [blame] | 503 | .set_timings	= tpo_td043_set_timings, | 
|  | 504 | .check_timings	= tpo_td043_check_timings, | 
|  | 505 |  | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 506 | .driver         = { | 
|  | 507 | .name	= "tpo_td043mtea1_panel", | 
|  | 508 | .owner  = THIS_MODULE, | 
|  | 509 | }, | 
|  | 510 | }; | 
|  | 511 |  | 
|  | 512 | static int tpo_td043_spi_probe(struct spi_device *spi) | 
|  | 513 | { | 
|  | 514 | struct omap_dss_device *dssdev = spi->dev.platform_data; | 
|  | 515 | struct tpo_td043_device *tpo_td043; | 
|  | 516 | int ret; | 
|  | 517 |  | 
|  | 518 | if (dssdev == NULL) { | 
|  | 519 | dev_err(&spi->dev, "missing dssdev\n"); | 
|  | 520 | return -ENODEV; | 
|  | 521 | } | 
|  | 522 |  | 
|  | 523 | spi->bits_per_word = 16; | 
|  | 524 | spi->mode = SPI_MODE_0; | 
|  | 525 |  | 
|  | 526 | ret = spi_setup(spi); | 
|  | 527 | if (ret < 0) { | 
|  | 528 | dev_err(&spi->dev, "spi_setup failed: %d\n", ret); | 
|  | 529 | return ret; | 
|  | 530 | } | 
|  | 531 |  | 
|  | 532 | tpo_td043 = kzalloc(sizeof(*tpo_td043), GFP_KERNEL); | 
|  | 533 | if (tpo_td043 == NULL) | 
|  | 534 | return -ENOMEM; | 
|  | 535 |  | 
|  | 536 | tpo_td043->spi = spi; | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 537 | tpo_td043->nreset_gpio = dssdev->reset_gpio; | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 538 | dev_set_drvdata(&spi->dev, tpo_td043); | 
|  | 539 | dev_set_drvdata(&dssdev->dev, tpo_td043); | 
|  | 540 |  | 
|  | 541 | omap_dss_register_driver(&tpo_td043_driver); | 
|  | 542 |  | 
|  | 543 | return 0; | 
|  | 544 | } | 
|  | 545 |  | 
|  | 546 | static int __devexit tpo_td043_spi_remove(struct spi_device *spi) | 
|  | 547 | { | 
|  | 548 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&spi->dev); | 
|  | 549 |  | 
|  | 550 | omap_dss_unregister_driver(&tpo_td043_driver); | 
|  | 551 | kfree(tpo_td043); | 
|  | 552 |  | 
|  | 553 | return 0; | 
|  | 554 | } | 
|  | 555 |  | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 556 | #ifdef CONFIG_PM_SLEEP | 
|  | 557 | static int tpo_td043_spi_suspend(struct device *dev) | 
|  | 558 | { | 
|  | 559 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev); | 
|  | 560 |  | 
|  | 561 | dev_dbg(dev, "tpo_td043_spi_suspend, tpo %p\n", tpo_td043); | 
|  | 562 |  | 
|  | 563 | tpo_td043->power_on_resume = tpo_td043->powered_on; | 
|  | 564 | tpo_td043_power_off(tpo_td043); | 
|  | 565 | tpo_td043->spi_suspended = 1; | 
|  | 566 |  | 
|  | 567 | return 0; | 
|  | 568 | } | 
|  | 569 |  | 
|  | 570 | static int tpo_td043_spi_resume(struct device *dev) | 
|  | 571 | { | 
|  | 572 | struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev); | 
|  | 573 | int ret; | 
|  | 574 |  | 
|  | 575 | dev_dbg(dev, "tpo_td043_spi_resume\n"); | 
|  | 576 |  | 
|  | 577 | if (tpo_td043->power_on_resume) { | 
|  | 578 | ret = tpo_td043_power_on(tpo_td043); | 
|  | 579 | if (ret) | 
|  | 580 | return ret; | 
|  | 581 | } | 
|  | 582 | tpo_td043->spi_suspended = 0; | 
|  | 583 |  | 
|  | 584 | return 0; | 
|  | 585 | } | 
|  | 586 | #endif | 
|  | 587 |  | 
|  | 588 | static SIMPLE_DEV_PM_OPS(tpo_td043_spi_pm, | 
|  | 589 | tpo_td043_spi_suspend, tpo_td043_spi_resume); | 
|  | 590 |  | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 591 | static struct spi_driver tpo_td043_spi_driver = { | 
|  | 592 | .driver = { | 
|  | 593 | .name	= "tpo_td043mtea1_panel_spi", | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 594 | .owner	= THIS_MODULE, | 
| Grazvydas Ignotas | 8df4f5c | 2012-02-07 02:13:49 +0200 | [diff] [blame] | 595 | .pm	= &tpo_td043_spi_pm, | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 596 | }, | 
|  | 597 | .probe	= tpo_td043_spi_probe, | 
|  | 598 | .remove	= __devexit_p(tpo_td043_spi_remove), | 
|  | 599 | }; | 
|  | 600 |  | 
| Axel Lin | c6d242a | 2012-01-27 16:02:54 +0800 | [diff] [blame] | 601 | module_spi_driver(tpo_td043_spi_driver); | 
| Grazvydas Ignotas | 9ce4ad0 | 2009-12-11 21:30:14 +0200 | [diff] [blame] | 602 |  | 
|  | 603 | MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>"); | 
|  | 604 | MODULE_DESCRIPTION("TPO TD043MTEA1 LCD Driver"); | 
|  | 605 | MODULE_LICENSE("GPL"); |