amba-clcd-nomadik.c 5.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
#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");

	grestb = devm_get_gpiod_from_child(dev, "grestb", &np->fwnode);
	if (IS_ERR(grestb)) {
		dev_err(dev, "no GRESTB GPIO\n");
		return;
	}
	/* This asserts the GRESTB signal, putting the display into reset */
	gpiod_direction_output(grestb, 1);

	scen = devm_get_gpiod_from_child(dev, "scen", &np->fwnode);
	if (IS_ERR(scen)) {
		dev_err(dev, "no SCEN GPIO\n");
		return;
	}
	gpiod_direction_output(scen, 0);
	scl = devm_get_gpiod_from_child(dev, "scl", &np->fwnode);
	if (IS_ERR(scl)) {
		dev_err(dev, "no SCL GPIO\n");
		return;
	}
	gpiod_direction_output(scl, 0);
	sda = devm_get_gpiod_from_child(dev, "sda", &np->fwnode);
	if (IS_ERR(sda)) {
		dev_err(dev, "no SDA GPIO\n");
		return;
	}
	gpiod_direction_output(sda, 0);
	board->enable = tpg110_enable;
	board->disable = tpg110_disable;
}

int nomadik_clcd_init_panel(struct clcd_fb *fb,
			    struct device_node *endpoint)
{
	struct device_node *panel;

	panel = of_graph_get_remote_port_parent(endpoint);
	if (!panel)
		return -ENODEV;

	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;
}

#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;
}