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