| #include <linux/amba/bus.h> | |
| #include <linux/amba/clcd.h> | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/of.h> | |
| #include <linux/of_graph.h> | |
| #include <linux/delay.h> | |
| #include <linux/bitops.h> | |
| #include <linux/mfd/syscon.h> | |
| #include <linux/regmap.h> | |
| #include "amba-clcd-nomadik.h" | |
| static struct gpio_desc *grestb; | |
| static struct gpio_desc *scen; | |
| static struct gpio_desc *scl; | |
| static struct gpio_desc *sda; | |
| static u8 tpg110_readwrite_reg(bool write, u8 address, u8 outval) | |
| { | |
| int i; | |
| u8 inval = 0; | |
| /* Assert SCEN */ | |
| gpiod_set_value_cansleep(scen, 1); | |
| ndelay(150); | |
| /* Hammer out the address */ | |
| for (i = 5; i >= 0; i--) { | |
| if (address & BIT(i)) | |
| gpiod_set_value_cansleep(sda, 1); | |
| else | |
| gpiod_set_value_cansleep(sda, 0); | |
| ndelay(150); | |
| /* Send an SCL pulse */ | |
| gpiod_set_value_cansleep(scl, 1); | |
| ndelay(160); | |
| gpiod_set_value_cansleep(scl, 0); | |
| ndelay(160); | |
| } | |
| if (write) { | |
| /* WRITE */ | |
| gpiod_set_value_cansleep(sda, 0); | |
| } else { | |
| /* READ */ | |
| gpiod_set_value_cansleep(sda, 1); | |
| } | |
| ndelay(150); | |
| /* Send an SCL pulse */ | |
| gpiod_set_value_cansleep(scl, 1); | |
| ndelay(160); | |
| gpiod_set_value_cansleep(scl, 0); | |
| ndelay(160); | |
| if (!write) | |
| /* HiZ turn-around cycle */ | |
| gpiod_direction_input(sda); | |
| ndelay(150); | |
| /* Send an SCL pulse */ | |
| gpiod_set_value_cansleep(scl, 1); | |
| ndelay(160); | |
| gpiod_set_value_cansleep(scl, 0); | |
| ndelay(160); | |
| /* Hammer in/out the data */ | |
| for (i = 7; i >= 0; i--) { | |
| int value; | |
| if (write) { | |
| value = !!(outval & BIT(i)); | |
| gpiod_set_value_cansleep(sda, value); | |
| } else { | |
| value = gpiod_get_value(sda); | |
| if (value) | |
| inval |= BIT(i); | |
| } | |
| ndelay(150); | |
| /* Send an SCL pulse */ | |
| gpiod_set_value_cansleep(scl, 1); | |
| ndelay(160); | |
| gpiod_set_value_cansleep(scl, 0); | |
| ndelay(160); | |
| } | |
| gpiod_direction_output(sda, 0); | |
| /* Deassert SCEN */ | |
| gpiod_set_value_cansleep(scen, 0); | |
| /* Satisfies SCEN pulse width */ | |
| udelay(1); | |
| return inval; | |
| } | |
| static u8 tpg110_read_reg(u8 address) | |
| { | |
| return tpg110_readwrite_reg(false, address, 0); | |
| } | |
| static void tpg110_write_reg(u8 address, u8 outval) | |
| { | |
| tpg110_readwrite_reg(true, address, outval); | |
| } | |
| static void tpg110_startup(struct device *dev) | |
| { | |
| u8 val; | |
| dev_info(dev, "TPG110 display enable\n"); | |
| /* De-assert the reset signal */ | |
| gpiod_set_value_cansleep(grestb, 0); | |
| mdelay(1); | |
| dev_info(dev, "de-asserted GRESTB\n"); | |
| /* Test display communication */ | |
| tpg110_write_reg(0x00, 0x55); | |
| val = tpg110_read_reg(0x00); | |
| if (val == 0x55) | |
| dev_info(dev, "passed communication test\n"); | |
| val = tpg110_read_reg(0x01); | |
| dev_info(dev, "TPG110 chip ID: %d version: %d\n", | |
| val>>4, val&0x0f); | |
| /* Show display resolution */ | |
| val = tpg110_read_reg(0x02); | |
| val &= 7; | |
| switch (val) { | |
| case 0x0: | |
| dev_info(dev, "IN 400x240 RGB -> OUT 800x480 RGB (dual scan)"); | |
| break; | |
| case 0x1: | |
| dev_info(dev, "IN 480x272 RGB -> OUT 800x480 RGB (dual scan)"); | |
| break; | |
| case 0x4: | |
| dev_info(dev, "480x640 RGB"); | |
| break; | |
| case 0x5: | |
| dev_info(dev, "480x272 RGB"); | |
| break; | |
| case 0x6: | |
| dev_info(dev, "640x480 RGB"); | |
| break; | |
| case 0x7: | |
| dev_info(dev, "800x480 RGB"); | |
| break; | |
| default: | |
| dev_info(dev, "ILLEGAL RESOLUTION"); | |
| break; | |
| } | |
| val = tpg110_read_reg(0x03); | |
| dev_info(dev, "resolution is controlled by %s\n", | |
| (val & BIT(7)) ? "software" : "hardware"); | |
| } | |
| static void tpg110_enable(struct clcd_fb *fb) | |
| { | |
| struct device *dev = &fb->dev->dev; | |
| static bool startup; | |
| u8 val; | |
| if (!startup) { | |
| tpg110_startup(dev); | |
| startup = true; | |
| } | |
| /* Take chip out of standby */ | |
| val = tpg110_read_reg(0x03); | |
| val |= BIT(0); | |
| tpg110_write_reg(0x03, val); | |
| } | |
| static void tpg110_disable(struct clcd_fb *fb) | |
| { | |
| u8 val; | |
| dev_info(&fb->dev->dev, "TPG110 display disable\n"); | |
| val = tpg110_read_reg(0x03); | |
| /* Put into standby */ | |
| val &= ~BIT(0); | |
| tpg110_write_reg(0x03, val); | |
| } | |
| static void tpg110_init(struct device *dev, struct device_node *np, | |
| struct clcd_board *board) | |
| { | |
| dev_info(dev, "TPG110 display init\n"); | |
| /* This asserts the GRESTB signal, putting the display into reset */ | |
| grestb = devm_fwnode_get_gpiod_from_child(dev, "grestb", &np->fwnode, | |
| GPIOD_OUT_HIGH, "grestb"); | |
| if (IS_ERR(grestb)) { | |
| dev_err(dev, "no GRESTB GPIO\n"); | |
| return; | |
| } | |
| scen = devm_fwnode_get_gpiod_from_child(dev, "scen", &np->fwnode, | |
| GPIOD_OUT_LOW, "scen"); | |
| if (IS_ERR(scen)) { | |
| dev_err(dev, "no SCEN GPIO\n"); | |
| return; | |
| } | |
| scl = devm_fwnode_get_gpiod_from_child(dev, "scl", &np->fwnode, | |
| GPIOD_OUT_LOW, "scl"); | |
| if (IS_ERR(scl)) { | |
| dev_err(dev, "no SCL GPIO\n"); | |
| return; | |
| } | |
| sda = devm_fwnode_get_gpiod_from_child(dev, "sda", &np->fwnode, | |
| GPIOD_OUT_LOW, "sda"); | |
| if (IS_ERR(sda)) { | |
| dev_err(dev, "no SDA GPIO\n"); | |
| return; | |
| } | |
| board->enable = tpg110_enable; | |
| board->disable = tpg110_disable; | |
| } | |
| int nomadik_clcd_init_panel(struct clcd_fb *fb, struct device_node *panel) | |
| { | |
| if (of_device_is_compatible(panel, "tpo,tpg110")) | |
| tpg110_init(&fb->dev->dev, panel, fb->board); | |
| else | |
| dev_info(&fb->dev->dev, "unknown panel\n"); | |
| /* Unknown panel, fall through */ | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(nomadik_clcd_init_panel); | |
| #define PMU_CTRL_OFFSET 0x0000 | |
| #define PMU_CTRL_LCDNDIF BIT(26) | |
| int nomadik_clcd_init_board(struct amba_device *adev, | |
| struct clcd_board *board) | |
| { | |
| struct regmap *pmu_regmap; | |
| dev_info(&adev->dev, "Nomadik CLCD board init\n"); | |
| pmu_regmap = | |
| syscon_regmap_lookup_by_compatible("stericsson,nomadik-pmu"); | |
| if (IS_ERR(pmu_regmap)) { | |
| dev_err(&adev->dev, "could not find PMU syscon regmap\n"); | |
| return PTR_ERR(pmu_regmap); | |
| } | |
| regmap_update_bits(pmu_regmap, | |
| PMU_CTRL_OFFSET, | |
| PMU_CTRL_LCDNDIF, | |
| 0); | |
| dev_info(&adev->dev, "set PMU mux to CLCD mode\n"); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(nomadik_clcd_init_board); |