Linus Walleij | 1d3f0cb | 2016-06-16 11:36:17 +0200 | [diff] [blame^] | 1 | #include <linux/amba/bus.h> |
| 2 | #include <linux/amba/clcd.h> |
| 3 | #include <linux/gpio/consumer.h> |
| 4 | #include <linux/of.h> |
| 5 | #include <linux/of_graph.h> |
| 6 | #include <linux/delay.h> |
| 7 | #include <linux/bitops.h> |
| 8 | #include <linux/mfd/syscon.h> |
| 9 | #include <linux/regmap.h> |
| 10 | |
| 11 | #include "amba-clcd-nomadik.h" |
| 12 | |
| 13 | static struct gpio_desc *grestb; |
| 14 | static struct gpio_desc *scen; |
| 15 | static struct gpio_desc *scl; |
| 16 | static struct gpio_desc *sda; |
| 17 | |
| 18 | static u8 tpg110_readwrite_reg(bool write, u8 address, u8 outval) |
| 19 | { |
| 20 | int i; |
| 21 | u8 inval = 0; |
| 22 | |
| 23 | /* Assert SCEN */ |
| 24 | gpiod_set_value_cansleep(scen, 1); |
| 25 | ndelay(150); |
| 26 | /* Hammer out the address */ |
| 27 | for (i = 5; i >= 0; i--) { |
| 28 | if (address & BIT(i)) |
| 29 | gpiod_set_value_cansleep(sda, 1); |
| 30 | else |
| 31 | gpiod_set_value_cansleep(sda, 0); |
| 32 | ndelay(150); |
| 33 | /* Send an SCL pulse */ |
| 34 | gpiod_set_value_cansleep(scl, 1); |
| 35 | ndelay(160); |
| 36 | gpiod_set_value_cansleep(scl, 0); |
| 37 | ndelay(160); |
| 38 | } |
| 39 | |
| 40 | if (write) { |
| 41 | /* WRITE */ |
| 42 | gpiod_set_value_cansleep(sda, 0); |
| 43 | } else { |
| 44 | /* READ */ |
| 45 | gpiod_set_value_cansleep(sda, 1); |
| 46 | } |
| 47 | ndelay(150); |
| 48 | /* Send an SCL pulse */ |
| 49 | gpiod_set_value_cansleep(scl, 1); |
| 50 | ndelay(160); |
| 51 | gpiod_set_value_cansleep(scl, 0); |
| 52 | ndelay(160); |
| 53 | |
| 54 | if (!write) |
| 55 | /* HiZ turn-around cycle */ |
| 56 | gpiod_direction_input(sda); |
| 57 | ndelay(150); |
| 58 | /* Send an SCL pulse */ |
| 59 | gpiod_set_value_cansleep(scl, 1); |
| 60 | ndelay(160); |
| 61 | gpiod_set_value_cansleep(scl, 0); |
| 62 | ndelay(160); |
| 63 | |
| 64 | /* Hammer in/out the data */ |
| 65 | for (i = 7; i >= 0; i--) { |
| 66 | int value; |
| 67 | |
| 68 | if (write) { |
| 69 | value = !!(outval & BIT(i)); |
| 70 | gpiod_set_value_cansleep(sda, value); |
| 71 | } else { |
| 72 | value = gpiod_get_value(sda); |
| 73 | if (value) |
| 74 | inval |= BIT(i); |
| 75 | } |
| 76 | ndelay(150); |
| 77 | /* Send an SCL pulse */ |
| 78 | gpiod_set_value_cansleep(scl, 1); |
| 79 | ndelay(160); |
| 80 | gpiod_set_value_cansleep(scl, 0); |
| 81 | ndelay(160); |
| 82 | } |
| 83 | |
| 84 | gpiod_direction_output(sda, 0); |
| 85 | /* Deassert SCEN */ |
| 86 | gpiod_set_value_cansleep(scen, 0); |
| 87 | /* Satisfies SCEN pulse width */ |
| 88 | udelay(1); |
| 89 | |
| 90 | return inval; |
| 91 | } |
| 92 | |
| 93 | static u8 tpg110_read_reg(u8 address) |
| 94 | { |
| 95 | return tpg110_readwrite_reg(false, address, 0); |
| 96 | } |
| 97 | |
| 98 | static void tpg110_write_reg(u8 address, u8 outval) |
| 99 | { |
| 100 | tpg110_readwrite_reg(true, address, outval); |
| 101 | } |
| 102 | |
| 103 | static void tpg110_startup(struct device *dev) |
| 104 | { |
| 105 | u8 val; |
| 106 | |
| 107 | dev_info(dev, "TPG110 display enable\n"); |
| 108 | /* De-assert the reset signal */ |
| 109 | gpiod_set_value_cansleep(grestb, 0); |
| 110 | mdelay(1); |
| 111 | dev_info(dev, "de-asserted GRESTB\n"); |
| 112 | |
| 113 | /* Test display communication */ |
| 114 | tpg110_write_reg(0x00, 0x55); |
| 115 | val = tpg110_read_reg(0x00); |
| 116 | if (val == 0x55) |
| 117 | dev_info(dev, "passed communication test\n"); |
| 118 | val = tpg110_read_reg(0x01); |
| 119 | dev_info(dev, "TPG110 chip ID: %d version: %d\n", |
| 120 | val>>4, val&0x0f); |
| 121 | |
| 122 | /* Show display resolution */ |
| 123 | val = tpg110_read_reg(0x02); |
| 124 | val &= 7; |
| 125 | switch (val) { |
| 126 | case 0x0: |
| 127 | dev_info(dev, "IN 400x240 RGB -> OUT 800x480 RGB (dual scan)"); |
| 128 | break; |
| 129 | case 0x1: |
| 130 | dev_info(dev, "IN 480x272 RGB -> OUT 800x480 RGB (dual scan)"); |
| 131 | break; |
| 132 | case 0x4: |
| 133 | dev_info(dev, "480x640 RGB"); |
| 134 | break; |
| 135 | case 0x5: |
| 136 | dev_info(dev, "480x272 RGB"); |
| 137 | break; |
| 138 | case 0x6: |
| 139 | dev_info(dev, "640x480 RGB"); |
| 140 | break; |
| 141 | case 0x7: |
| 142 | dev_info(dev, "800x480 RGB"); |
| 143 | break; |
| 144 | default: |
| 145 | dev_info(dev, "ILLEGAL RESOLUTION"); |
| 146 | break; |
| 147 | } |
| 148 | |
| 149 | val = tpg110_read_reg(0x03); |
| 150 | dev_info(dev, "resolution is controlled by %s\n", |
| 151 | (val & BIT(7)) ? "software" : "hardware"); |
| 152 | } |
| 153 | |
| 154 | static void tpg110_enable(struct clcd_fb *fb) |
| 155 | { |
| 156 | struct device *dev = &fb->dev->dev; |
| 157 | static bool startup; |
| 158 | u8 val; |
| 159 | |
| 160 | if (!startup) { |
| 161 | tpg110_startup(dev); |
| 162 | startup = true; |
| 163 | } |
| 164 | |
| 165 | /* Take chip out of standby */ |
| 166 | val = tpg110_read_reg(0x03); |
| 167 | val |= BIT(0); |
| 168 | tpg110_write_reg(0x03, val); |
| 169 | } |
| 170 | |
| 171 | static void tpg110_disable(struct clcd_fb *fb) |
| 172 | { |
| 173 | u8 val; |
| 174 | |
| 175 | dev_info(&fb->dev->dev, "TPG110 display disable\n"); |
| 176 | val = tpg110_read_reg(0x03); |
| 177 | /* Put into standby */ |
| 178 | val &= ~BIT(0); |
| 179 | tpg110_write_reg(0x03, val); |
| 180 | } |
| 181 | |
| 182 | static void tpg110_init(struct device *dev, struct device_node *np, |
| 183 | struct clcd_board *board) |
| 184 | { |
| 185 | dev_info(dev, "TPG110 display init\n"); |
| 186 | |
| 187 | grestb = devm_get_gpiod_from_child(dev, "grestb", &np->fwnode); |
| 188 | if (IS_ERR(grestb)) { |
| 189 | dev_err(dev, "no GRESTB GPIO\n"); |
| 190 | return; |
| 191 | } |
| 192 | /* This asserts the GRESTB signal, putting the display into reset */ |
| 193 | gpiod_direction_output(grestb, 1); |
| 194 | |
| 195 | scen = devm_get_gpiod_from_child(dev, "scen", &np->fwnode); |
| 196 | if (IS_ERR(scen)) { |
| 197 | dev_err(dev, "no SCEN GPIO\n"); |
| 198 | return; |
| 199 | } |
| 200 | gpiod_direction_output(scen, 0); |
| 201 | scl = devm_get_gpiod_from_child(dev, "scl", &np->fwnode); |
| 202 | if (IS_ERR(scl)) { |
| 203 | dev_err(dev, "no SCL GPIO\n"); |
| 204 | return; |
| 205 | } |
| 206 | gpiod_direction_output(scl, 0); |
| 207 | sda = devm_get_gpiod_from_child(dev, "sda", &np->fwnode); |
| 208 | if (IS_ERR(sda)) { |
| 209 | dev_err(dev, "no SDA GPIO\n"); |
| 210 | return; |
| 211 | } |
| 212 | gpiod_direction_output(sda, 0); |
| 213 | board->enable = tpg110_enable; |
| 214 | board->disable = tpg110_disable; |
| 215 | } |
| 216 | |
| 217 | int nomadik_clcd_init_panel(struct clcd_fb *fb, |
| 218 | struct device_node *endpoint) |
| 219 | { |
| 220 | struct device_node *panel; |
| 221 | |
| 222 | panel = of_graph_get_remote_port_parent(endpoint); |
| 223 | if (!panel) |
| 224 | return -ENODEV; |
| 225 | |
| 226 | if (of_device_is_compatible(panel, "tpo,tpg110")) |
| 227 | tpg110_init(&fb->dev->dev, panel, fb->board); |
| 228 | else |
| 229 | dev_info(&fb->dev->dev, "unknown panel\n"); |
| 230 | |
| 231 | /* Unknown panel, fall through */ |
| 232 | return 0; |
| 233 | } |
| 234 | |
| 235 | #define PMU_CTRL_OFFSET 0x0000 |
| 236 | #define PMU_CTRL_LCDNDIF BIT(26) |
| 237 | |
| 238 | int nomadik_clcd_init_board(struct amba_device *adev, |
| 239 | struct clcd_board *board) |
| 240 | { |
| 241 | struct regmap *pmu_regmap; |
| 242 | |
| 243 | dev_info(&adev->dev, "Nomadik CLCD board init\n"); |
| 244 | pmu_regmap = |
| 245 | syscon_regmap_lookup_by_compatible("stericsson,nomadik-pmu"); |
| 246 | if (IS_ERR(pmu_regmap)) { |
| 247 | dev_err(&adev->dev, "could not find PMU syscon regmap\n"); |
| 248 | return PTR_ERR(pmu_regmap); |
| 249 | } |
| 250 | regmap_update_bits(pmu_regmap, |
| 251 | PMU_CTRL_OFFSET, |
| 252 | PMU_CTRL_LCDNDIF, |
| 253 | 0); |
| 254 | dev_info(&adev->dev, "set PMU mux to CLCD mode\n"); |
| 255 | |
| 256 | return 0; |
| 257 | } |