|  | /* | 
|  | * Copyright © 2011 Intel Corporation | 
|  | * | 
|  | * Permission is hereby granted, free of charge, to any person obtaining a | 
|  | * copy of this software and associated documentation files (the "Software"), | 
|  | * to deal in the Software without restriction, including without limitation | 
|  | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | 
|  | * and/or sell copies of the Software, and to permit persons to whom the | 
|  | * Software is furnished to do so, subject to the following conditions: | 
|  | * | 
|  | * The above copyright notice and this permission notice (including the next | 
|  | * paragraph) shall be included in all copies or substantial portions of the | 
|  | * Software. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL | 
|  | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | 
|  | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | 
|  | * DEALINGS IN THE SOFTWARE. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include "mdfld_dsi_dpi.h" | 
|  | #include "mdfld_output.h" | 
|  | #include "mdfld_dsi_pkg_sender.h" | 
|  | #include "tc35876x-dsi-lvds.h" | 
|  | #include <linux/i2c/tc35876x.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <asm/intel_scu_ipc.h> | 
|  |  | 
|  | static struct i2c_client *tc35876x_client; | 
|  | static struct i2c_client *cmi_lcd_i2c_client; | 
|  |  | 
|  | #define FLD_MASK(start, end)	(((1 << ((start) - (end) + 1)) - 1) << (end)) | 
|  | #define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end)) | 
|  |  | 
|  | /* DSI D-PHY Layer Registers */ | 
|  | #define D0W_DPHYCONTTX		0x0004 | 
|  | #define CLW_DPHYCONTRX		0x0020 | 
|  | #define D0W_DPHYCONTRX		0x0024 | 
|  | #define D1W_DPHYCONTRX		0x0028 | 
|  | #define D2W_DPHYCONTRX		0x002C | 
|  | #define D3W_DPHYCONTRX		0x0030 | 
|  | #define COM_DPHYCONTRX		0x0038 | 
|  | #define CLW_CNTRL		0x0040 | 
|  | #define D0W_CNTRL		0x0044 | 
|  | #define D1W_CNTRL		0x0048 | 
|  | #define D2W_CNTRL		0x004C | 
|  | #define D3W_CNTRL		0x0050 | 
|  | #define DFTMODE_CNTRL		0x0054 | 
|  |  | 
|  | /* DSI PPI Layer Registers */ | 
|  | #define PPI_STARTPPI		0x0104 | 
|  | #define PPI_BUSYPPI		0x0108 | 
|  | #define PPI_LINEINITCNT		0x0110 | 
|  | #define PPI_LPTXTIMECNT		0x0114 | 
|  | #define PPI_LANEENABLE		0x0134 | 
|  | #define PPI_TX_RX_TA		0x013C | 
|  | #define PPI_CLS_ATMR		0x0140 | 
|  | #define PPI_D0S_ATMR		0x0144 | 
|  | #define PPI_D1S_ATMR		0x0148 | 
|  | #define PPI_D2S_ATMR		0x014C | 
|  | #define PPI_D3S_ATMR		0x0150 | 
|  | #define PPI_D0S_CLRSIPOCOUNT	0x0164 | 
|  | #define PPI_D1S_CLRSIPOCOUNT	0x0168 | 
|  | #define PPI_D2S_CLRSIPOCOUNT	0x016C | 
|  | #define PPI_D3S_CLRSIPOCOUNT	0x0170 | 
|  | #define CLS_PRE			0x0180 | 
|  | #define D0S_PRE			0x0184 | 
|  | #define D1S_PRE			0x0188 | 
|  | #define D2S_PRE			0x018C | 
|  | #define D3S_PRE			0x0190 | 
|  | #define CLS_PREP		0x01A0 | 
|  | #define D0S_PREP		0x01A4 | 
|  | #define D1S_PREP		0x01A8 | 
|  | #define D2S_PREP		0x01AC | 
|  | #define D3S_PREP		0x01B0 | 
|  | #define CLS_ZERO		0x01C0 | 
|  | #define D0S_ZERO		0x01C4 | 
|  | #define D1S_ZERO		0x01C8 | 
|  | #define D2S_ZERO		0x01CC | 
|  | #define D3S_ZERO		0x01D0 | 
|  | #define PPI_CLRFLG		0x01E0 | 
|  | #define PPI_CLRSIPO		0x01E4 | 
|  | #define HSTIMEOUT		0x01F0 | 
|  | #define HSTIMEOUTENABLE		0x01F4 | 
|  |  | 
|  | /* DSI Protocol Layer Registers */ | 
|  | #define DSI_STARTDSI		0x0204 | 
|  | #define DSI_BUSYDSI		0x0208 | 
|  | #define DSI_LANEENABLE		0x0210 | 
|  | #define DSI_LANESTATUS0		0x0214 | 
|  | #define DSI_LANESTATUS1		0x0218 | 
|  | #define DSI_INTSTATUS		0x0220 | 
|  | #define DSI_INTMASK		0x0224 | 
|  | #define DSI_INTCLR		0x0228 | 
|  | #define DSI_LPTXTO		0x0230 | 
|  |  | 
|  | /* DSI General Registers */ | 
|  | #define DSIERRCNT		0x0300 | 
|  |  | 
|  | /* DSI Application Layer Registers */ | 
|  | #define APLCTRL			0x0400 | 
|  | #define RDPKTLN			0x0404 | 
|  |  | 
|  | /* Video Path Registers */ | 
|  | #define VPCTRL			0x0450 | 
|  | #define HTIM1			0x0454 | 
|  | #define HTIM2			0x0458 | 
|  | #define VTIM1			0x045C | 
|  | #define VTIM2			0x0460 | 
|  | #define VFUEN			0x0464 | 
|  |  | 
|  | /* LVDS Registers */ | 
|  | #define LVMX0003		0x0480 | 
|  | #define LVMX0407		0x0484 | 
|  | #define LVMX0811		0x0488 | 
|  | #define LVMX1215		0x048C | 
|  | #define LVMX1619		0x0490 | 
|  | #define LVMX2023		0x0494 | 
|  | #define LVMX2427		0x0498 | 
|  | #define LVCFG			0x049C | 
|  | #define LVPHY0			0x04A0 | 
|  | #define LVPHY1			0x04A4 | 
|  |  | 
|  | /* System Registers */ | 
|  | #define SYSSTAT			0x0500 | 
|  | #define SYSRST			0x0504 | 
|  |  | 
|  | /* GPIO Registers */ | 
|  | /*#define GPIOC			0x0520*/ | 
|  | #define GPIOO			0x0524 | 
|  | #define GPIOI			0x0528 | 
|  |  | 
|  | /* I2C Registers */ | 
|  | #define I2CTIMCTRL		0x0540 | 
|  | #define I2CMADDR		0x0544 | 
|  | #define WDATAQ			0x0548 | 
|  | #define RDATAQ			0x054C | 
|  |  | 
|  | /* Chip/Rev Registers */ | 
|  | #define IDREG			0x0580 | 
|  |  | 
|  | /* Debug Registers */ | 
|  | #define DEBUG00			0x05A0 | 
|  | #define DEBUG01			0x05A4 | 
|  |  | 
|  | /* Panel CABC registers */ | 
|  | #define PANEL_PWM_CONTROL	0x90 | 
|  | #define PANEL_FREQ_DIVIDER_HI	0x91 | 
|  | #define PANEL_FREQ_DIVIDER_LO	0x92 | 
|  | #define PANEL_DUTY_CONTROL	0x93 | 
|  | #define PANEL_MODIFY_RGB	0x94 | 
|  | #define PANEL_FRAMERATE_CONTROL	0x96 | 
|  | #define PANEL_PWM_MIN		0x97 | 
|  | #define PANEL_PWM_REF		0x98 | 
|  | #define PANEL_PWM_MAX		0x99 | 
|  | #define PANEL_ALLOW_DISTORT	0x9A | 
|  | #define PANEL_BYPASS_PWMI	0x9B | 
|  |  | 
|  | /* Panel color management registers */ | 
|  | #define PANEL_CM_ENABLE		0x700 | 
|  | #define PANEL_CM_HUE		0x701 | 
|  | #define PANEL_CM_SATURATION	0x702 | 
|  | #define PANEL_CM_INTENSITY	0x703 | 
|  | #define PANEL_CM_BRIGHTNESS	0x704 | 
|  | #define PANEL_CM_CE_ENABLE	0x705 | 
|  | #define PANEL_CM_PEAK_EN	0x710 | 
|  | #define PANEL_CM_GAIN		0x711 | 
|  | #define PANEL_CM_HUETABLE_START	0x730 | 
|  | #define PANEL_CM_HUETABLE_END	0x747 /* inclusive */ | 
|  |  | 
|  | /* Input muxing for registers LVMX0003...LVMX2427 */ | 
|  | enum { | 
|  | INPUT_R0,	/* 0 */ | 
|  | INPUT_R1, | 
|  | INPUT_R2, | 
|  | INPUT_R3, | 
|  | INPUT_R4, | 
|  | INPUT_R5, | 
|  | INPUT_R6, | 
|  | INPUT_R7, | 
|  | INPUT_G0,	/* 8 */ | 
|  | INPUT_G1, | 
|  | INPUT_G2, | 
|  | INPUT_G3, | 
|  | INPUT_G4, | 
|  | INPUT_G5, | 
|  | INPUT_G6, | 
|  | INPUT_G7, | 
|  | INPUT_B0,	/* 16 */ | 
|  | INPUT_B1, | 
|  | INPUT_B2, | 
|  | INPUT_B3, | 
|  | INPUT_B4, | 
|  | INPUT_B5, | 
|  | INPUT_B6, | 
|  | INPUT_B7, | 
|  | INPUT_HSYNC,	/* 24 */ | 
|  | INPUT_VSYNC, | 
|  | INPUT_DE, | 
|  | LOGIC_0, | 
|  | /* 28...31 undefined */ | 
|  | }; | 
|  |  | 
|  | #define INPUT_MUX(lvmx03, lvmx02, lvmx01, lvmx00)		\ | 
|  | (FLD_VAL(lvmx03, 29, 24) | FLD_VAL(lvmx02, 20, 16) |	\ | 
|  | FLD_VAL(lvmx01, 12, 8) | FLD_VAL(lvmx00, 4, 0)) | 
|  |  | 
|  | /** | 
|  | * tc35876x_regw - Write DSI-LVDS bridge register using I2C | 
|  | * @client: struct i2c_client to use | 
|  | * @reg: register address | 
|  | * @value: value to write | 
|  | * | 
|  | * Returns 0 on success, or a negative error value. | 
|  | */ | 
|  | static int tc35876x_regw(struct i2c_client *client, u16 reg, u32 value) | 
|  | { | 
|  | int r; | 
|  | u8 tx_data[] = { | 
|  | /* NOTE: Register address big-endian, data little-endian. */ | 
|  | (reg >> 8) & 0xff, | 
|  | reg & 0xff, | 
|  | value & 0xff, | 
|  | (value >> 8) & 0xff, | 
|  | (value >> 16) & 0xff, | 
|  | (value >> 24) & 0xff, | 
|  | }; | 
|  | struct i2c_msg msgs[] = { | 
|  | { | 
|  | .addr = client->addr, | 
|  | .flags = 0, | 
|  | .buf = tx_data, | 
|  | .len = ARRAY_SIZE(tx_data), | 
|  | }, | 
|  | }; | 
|  |  | 
|  | r = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); | 
|  | if (r < 0) { | 
|  | dev_err(&client->dev, "%s: reg 0x%04x val 0x%08x error %d\n", | 
|  | __func__, reg, value, r); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | if (r < ARRAY_SIZE(msgs)) { | 
|  | dev_err(&client->dev, "%s: reg 0x%04x val 0x%08x msgs %d\n", | 
|  | __func__, reg, value, r); | 
|  | return -EAGAIN; | 
|  | } | 
|  |  | 
|  | dev_dbg(&client->dev, "%s: reg 0x%04x val 0x%08x\n", | 
|  | __func__, reg, value); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * tc35876x_regr - Read DSI-LVDS bridge register using I2C | 
|  | * @client: struct i2c_client to use | 
|  | * @reg: register address | 
|  | * @value: pointer for storing the value | 
|  | * | 
|  | * Returns 0 on success, or a negative error value. | 
|  | */ | 
|  | static int tc35876x_regr(struct i2c_client *client, u16 reg, u32 *value) | 
|  | { | 
|  | int r; | 
|  | u8 tx_data[] = { | 
|  | (reg >> 8) & 0xff, | 
|  | reg & 0xff, | 
|  | }; | 
|  | u8 rx_data[4]; | 
|  | struct i2c_msg msgs[] = { | 
|  | { | 
|  | .addr = client->addr, | 
|  | .flags = 0, | 
|  | .buf = tx_data, | 
|  | .len = ARRAY_SIZE(tx_data), | 
|  | }, | 
|  | { | 
|  | .addr = client->addr, | 
|  | .flags = I2C_M_RD, | 
|  | .buf = rx_data, | 
|  | .len = ARRAY_SIZE(rx_data), | 
|  | }, | 
|  | }; | 
|  |  | 
|  | r = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); | 
|  | if (r < 0) { | 
|  | dev_err(&client->dev, "%s: reg 0x%04x error %d\n", __func__, | 
|  | reg, r); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | if (r < ARRAY_SIZE(msgs)) { | 
|  | dev_err(&client->dev, "%s: reg 0x%04x msgs %d\n", __func__, | 
|  | reg, r); | 
|  | return -EAGAIN; | 
|  | } | 
|  |  | 
|  | *value = rx_data[0] << 24 | rx_data[1] << 16 | | 
|  | rx_data[2] << 8 | rx_data[3]; | 
|  |  | 
|  | dev_dbg(&client->dev, "%s: reg 0x%04x value 0x%08x\n", __func__, | 
|  | reg, *value); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void tc35876x_set_bridge_reset_state(struct drm_device *dev, int state) | 
|  | { | 
|  | struct tc35876x_platform_data *pdata; | 
|  |  | 
|  | if (WARN(!tc35876x_client, "%s called before probe", __func__)) | 
|  | return; | 
|  |  | 
|  | dev_dbg(&tc35876x_client->dev, "%s: state %d\n", __func__, state); | 
|  |  | 
|  | pdata = dev_get_platdata(&tc35876x_client->dev); | 
|  |  | 
|  | if (pdata->gpio_bridge_reset == -1) | 
|  | return; | 
|  |  | 
|  | if (state) { | 
|  | gpio_set_value_cansleep(pdata->gpio_bridge_reset, 0); | 
|  | mdelay(10); | 
|  | } else { | 
|  | /* Pull MIPI Bridge reset pin to Low */ | 
|  | gpio_set_value_cansleep(pdata->gpio_bridge_reset, 0); | 
|  | mdelay(20); | 
|  | /* Pull MIPI Bridge reset pin to High */ | 
|  | gpio_set_value_cansleep(pdata->gpio_bridge_reset, 1); | 
|  | mdelay(40); | 
|  | } | 
|  | } | 
|  |  | 
|  | void tc35876x_configure_lvds_bridge(struct drm_device *dev) | 
|  | { | 
|  | struct i2c_client *i2c = tc35876x_client; | 
|  | u32 ppi_lptxtimecnt; | 
|  | u32 txtagocnt; | 
|  | u32 txtasurecnt; | 
|  | u32 id; | 
|  |  | 
|  | if (WARN(!tc35876x_client, "%s called before probe", __func__)) | 
|  | return; | 
|  |  | 
|  | dev_dbg(&tc35876x_client->dev, "%s\n", __func__); | 
|  |  | 
|  | if (!tc35876x_regr(i2c, IDREG, &id)) | 
|  | dev_info(&tc35876x_client->dev, "tc35876x ID 0x%08x\n", id); | 
|  | else | 
|  | dev_err(&tc35876x_client->dev, "Cannot read ID\n"); | 
|  |  | 
|  | ppi_lptxtimecnt = 4; | 
|  | txtagocnt = (5 * ppi_lptxtimecnt - 3) / 4; | 
|  | txtasurecnt = 3 * ppi_lptxtimecnt / 2; | 
|  | tc35876x_regw(i2c, PPI_TX_RX_TA, FLD_VAL(txtagocnt, 26, 16) | | 
|  | FLD_VAL(txtasurecnt, 10, 0)); | 
|  | tc35876x_regw(i2c, PPI_LPTXTIMECNT, FLD_VAL(ppi_lptxtimecnt, 10, 0)); | 
|  |  | 
|  | tc35876x_regw(i2c, PPI_D0S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0)); | 
|  | tc35876x_regw(i2c, PPI_D1S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0)); | 
|  | tc35876x_regw(i2c, PPI_D2S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0)); | 
|  | tc35876x_regw(i2c, PPI_D3S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0)); | 
|  |  | 
|  | /* Enabling MIPI & PPI lanes, Enable 4 lanes */ | 
|  | tc35876x_regw(i2c, PPI_LANEENABLE, | 
|  | BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0)); | 
|  | tc35876x_regw(i2c, DSI_LANEENABLE, | 
|  | BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0)); | 
|  | tc35876x_regw(i2c, PPI_STARTPPI, BIT(0)); | 
|  | tc35876x_regw(i2c, DSI_STARTDSI, BIT(0)); | 
|  |  | 
|  | /* Setting LVDS output frequency */ | 
|  | tc35876x_regw(i2c, LVPHY0, FLD_VAL(1, 20, 16) | | 
|  | FLD_VAL(2, 15, 14) | FLD_VAL(6, 4, 0)); /* 0x00048006 */ | 
|  |  | 
|  | /* Setting video panel control register,0x00000120 VTGen=ON ?!?!? */ | 
|  | tc35876x_regw(i2c, VPCTRL, BIT(8) | BIT(5)); | 
|  |  | 
|  | /* Horizontal back porch and horizontal pulse width. 0x00280028 */ | 
|  | tc35876x_regw(i2c, HTIM1, FLD_VAL(40, 24, 16) | FLD_VAL(40, 8, 0)); | 
|  |  | 
|  | /* Horizontal front porch and horizontal active video size. 0x00500500*/ | 
|  | tc35876x_regw(i2c, HTIM2, FLD_VAL(80, 24, 16) | FLD_VAL(1280, 10, 0)); | 
|  |  | 
|  | /* Vertical back porch and vertical sync pulse width. 0x000e000a */ | 
|  | tc35876x_regw(i2c, VTIM1, FLD_VAL(14, 23, 16) | FLD_VAL(10, 7, 0)); | 
|  |  | 
|  | /* Vertical front porch and vertical display size. 0x000e0320 */ | 
|  | tc35876x_regw(i2c, VTIM2, FLD_VAL(14, 23, 16) | FLD_VAL(800, 10, 0)); | 
|  |  | 
|  | /* Set above HTIM1, HTIM2, VTIM1, and VTIM2 at next VSYNC. */ | 
|  | tc35876x_regw(i2c, VFUEN, BIT(0)); | 
|  |  | 
|  | /* Soft reset LCD controller. */ | 
|  | tc35876x_regw(i2c, SYSRST, BIT(2)); | 
|  |  | 
|  | /* LVDS-TX input muxing */ | 
|  | tc35876x_regw(i2c, LVMX0003, | 
|  | INPUT_MUX(INPUT_R5, INPUT_R4, INPUT_R3, INPUT_R2)); | 
|  | tc35876x_regw(i2c, LVMX0407, | 
|  | INPUT_MUX(INPUT_G2, INPUT_R7, INPUT_R1, INPUT_R6)); | 
|  | tc35876x_regw(i2c, LVMX0811, | 
|  | INPUT_MUX(INPUT_G1, INPUT_G0, INPUT_G4, INPUT_G3)); | 
|  | tc35876x_regw(i2c, LVMX1215, | 
|  | INPUT_MUX(INPUT_B2, INPUT_G7, INPUT_G6, INPUT_G5)); | 
|  | tc35876x_regw(i2c, LVMX1619, | 
|  | INPUT_MUX(INPUT_B4, INPUT_B3, INPUT_B1, INPUT_B0)); | 
|  | tc35876x_regw(i2c, LVMX2023, | 
|  | INPUT_MUX(LOGIC_0,  INPUT_B7, INPUT_B6, INPUT_B5)); | 
|  | tc35876x_regw(i2c, LVMX2427, | 
|  | INPUT_MUX(INPUT_R0, INPUT_DE, INPUT_VSYNC, INPUT_HSYNC)); | 
|  |  | 
|  | /* Enable LVDS transmitter. */ | 
|  | tc35876x_regw(i2c, LVCFG, BIT(0)); | 
|  |  | 
|  | /* Clear notifications. Don't write reserved bits. Was write 0xffffffff | 
|  | * to 0x0288, must be in error?! */ | 
|  | tc35876x_regw(i2c, DSI_INTCLR, FLD_MASK(31, 30) | FLD_MASK(22, 0)); | 
|  | } | 
|  |  | 
|  | #define GPIOPWMCTRL	0x38F | 
|  | #define PWM0CLKDIV0	0x62 /* low byte */ | 
|  | #define PWM0CLKDIV1	0x61 /* high byte */ | 
|  |  | 
|  | #define SYSTEMCLK	19200000UL /* 19.2 MHz */ | 
|  | #define PWM_FREQUENCY	9600 /* Hz */ | 
|  |  | 
|  | /* f = baseclk / (clkdiv + 1) => clkdiv = (baseclk - f) / f */ | 
|  | static inline u16 calc_clkdiv(unsigned long baseclk, unsigned int f) | 
|  | { | 
|  | return (baseclk - f) / f; | 
|  | } | 
|  |  | 
|  | static void tc35876x_brightness_init(struct drm_device *dev) | 
|  | { | 
|  | int ret; | 
|  | u8 pwmctrl; | 
|  | u16 clkdiv; | 
|  |  | 
|  | /* Make sure the PWM reference is the 19.2 MHz system clock. Read first | 
|  | * instead of setting directly to catch potential conflicts between PWM | 
|  | * users. */ | 
|  | ret = intel_scu_ipc_ioread8(GPIOPWMCTRL, &pwmctrl); | 
|  | if (ret || pwmctrl != 0x01) { | 
|  | if (ret) | 
|  | dev_err(&dev->pdev->dev, "GPIOPWMCTRL read failed\n"); | 
|  | else | 
|  | dev_warn(&dev->pdev->dev, "GPIOPWMCTRL was not set to system clock (pwmctrl = 0x%02x)\n", pwmctrl); | 
|  |  | 
|  | ret = intel_scu_ipc_iowrite8(GPIOPWMCTRL, 0x01); | 
|  | if (ret) | 
|  | dev_err(&dev->pdev->dev, "GPIOPWMCTRL set failed\n"); | 
|  | } | 
|  |  | 
|  | clkdiv = calc_clkdiv(SYSTEMCLK, PWM_FREQUENCY); | 
|  |  | 
|  | ret = intel_scu_ipc_iowrite8(PWM0CLKDIV1, (clkdiv >> 8) & 0xff); | 
|  | if (!ret) | 
|  | ret = intel_scu_ipc_iowrite8(PWM0CLKDIV0, clkdiv & 0xff); | 
|  |  | 
|  | if (ret) | 
|  | dev_err(&dev->pdev->dev, "PWM0CLKDIV set failed\n"); | 
|  | else | 
|  | dev_dbg(&dev->pdev->dev, "PWM0CLKDIV set to 0x%04x (%d Hz)\n", | 
|  | clkdiv, PWM_FREQUENCY); | 
|  | } | 
|  |  | 
|  | #define PWM0DUTYCYCLE			0x67 | 
|  |  | 
|  | void tc35876x_brightness_control(struct drm_device *dev, int level) | 
|  | { | 
|  | int ret; | 
|  | u8 duty_val; | 
|  | u8 panel_duty_val; | 
|  |  | 
|  | level = clamp(level, 0, MDFLD_DSI_BRIGHTNESS_MAX_LEVEL); | 
|  |  | 
|  | /* PWM duty cycle 0x00...0x63 corresponds to 0...99% */ | 
|  | duty_val = level * 0x63 / MDFLD_DSI_BRIGHTNESS_MAX_LEVEL; | 
|  |  | 
|  | /* I won't pretend to understand this formula. The panel spec is quite | 
|  | * bad engrish. | 
|  | */ | 
|  | panel_duty_val = (2 * level - 100) * 0xA9 / | 
|  | MDFLD_DSI_BRIGHTNESS_MAX_LEVEL + 0x56; | 
|  |  | 
|  | ret = intel_scu_ipc_iowrite8(PWM0DUTYCYCLE, duty_val); | 
|  | if (ret) | 
|  | dev_err(&tc35876x_client->dev, "%s: ipc write fail\n", | 
|  | __func__); | 
|  |  | 
|  | if (cmi_lcd_i2c_client) { | 
|  | ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client, | 
|  | PANEL_PWM_MAX, panel_duty_val); | 
|  | if (ret < 0) | 
|  | dev_err(&cmi_lcd_i2c_client->dev, "%s: i2c write failed\n", | 
|  | __func__); | 
|  | } | 
|  | } | 
|  |  | 
|  | void tc35876x_toshiba_bridge_panel_off(struct drm_device *dev) | 
|  | { | 
|  | struct tc35876x_platform_data *pdata; | 
|  |  | 
|  | if (WARN(!tc35876x_client, "%s called before probe", __func__)) | 
|  | return; | 
|  |  | 
|  | dev_dbg(&tc35876x_client->dev, "%s\n", __func__); | 
|  |  | 
|  | pdata = dev_get_platdata(&tc35876x_client->dev); | 
|  |  | 
|  | if (pdata->gpio_panel_bl_en != -1) | 
|  | gpio_set_value_cansleep(pdata->gpio_panel_bl_en, 0); | 
|  |  | 
|  | if (pdata->gpio_panel_vadd != -1) | 
|  | gpio_set_value_cansleep(pdata->gpio_panel_vadd, 0); | 
|  | } | 
|  |  | 
|  | void tc35876x_toshiba_bridge_panel_on(struct drm_device *dev) | 
|  | { | 
|  | struct tc35876x_platform_data *pdata; | 
|  | struct drm_psb_private *dev_priv = dev->dev_private; | 
|  |  | 
|  | if (WARN(!tc35876x_client, "%s called before probe", __func__)) | 
|  | return; | 
|  |  | 
|  | dev_dbg(&tc35876x_client->dev, "%s\n", __func__); | 
|  |  | 
|  | pdata = dev_get_platdata(&tc35876x_client->dev); | 
|  |  | 
|  | if (pdata->gpio_panel_vadd != -1) { | 
|  | gpio_set_value_cansleep(pdata->gpio_panel_vadd, 1); | 
|  | msleep(260); | 
|  | } | 
|  |  | 
|  | if (cmi_lcd_i2c_client) { | 
|  | int ret; | 
|  | dev_dbg(&cmi_lcd_i2c_client->dev, "setting TCON\n"); | 
|  | /* Bit 4 is average_saving. Setting it to 1, the brightness is | 
|  | * referenced to the average of the frame content. 0 means | 
|  | * reference to the maximum of frame contents. Bits 3:0 are | 
|  | * allow_distort. When set to a nonzero value, all color values | 
|  | * between 255-allow_distort*2 and 255 are mapped to the | 
|  | * 255-allow_distort*2 value. | 
|  | */ | 
|  | ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client, | 
|  | PANEL_ALLOW_DISTORT, 0x10); | 
|  | if (ret < 0) | 
|  | dev_err(&cmi_lcd_i2c_client->dev, | 
|  | "i2c write failed (%d)\n", ret); | 
|  | ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client, | 
|  | PANEL_BYPASS_PWMI, 0); | 
|  | if (ret < 0) | 
|  | dev_err(&cmi_lcd_i2c_client->dev, | 
|  | "i2c write failed (%d)\n", ret); | 
|  | /* Set minimum brightness value - this is tunable */ | 
|  | ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client, | 
|  | PANEL_PWM_MIN, 0x35); | 
|  | if (ret < 0) | 
|  | dev_err(&cmi_lcd_i2c_client->dev, | 
|  | "i2c write failed (%d)\n", ret); | 
|  | } | 
|  |  | 
|  | if (pdata->gpio_panel_bl_en != -1) | 
|  | gpio_set_value_cansleep(pdata->gpio_panel_bl_en, 1); | 
|  |  | 
|  | tc35876x_brightness_control(dev, dev_priv->brightness_adjusted); | 
|  | } | 
|  |  | 
|  | static struct drm_display_mode *tc35876x_get_config_mode(struct drm_device *dev) | 
|  | { | 
|  | struct drm_display_mode *mode; | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "%s\n", __func__); | 
|  |  | 
|  | mode = kzalloc(sizeof(*mode), GFP_KERNEL); | 
|  | if (!mode) | 
|  | return NULL; | 
|  |  | 
|  | /* FIXME: do this properly. */ | 
|  | mode->hdisplay = 1280; | 
|  | mode->vdisplay = 800; | 
|  | mode->hsync_start = 1360; | 
|  | mode->hsync_end = 1400; | 
|  | mode->htotal = 1440; | 
|  | mode->vsync_start = 814; | 
|  | mode->vsync_end = 824; | 
|  | mode->vtotal = 838; | 
|  | mode->clock = 33324 << 1; | 
|  |  | 
|  | dev_info(&dev->pdev->dev, "hdisplay(w) = %d\n", mode->hdisplay); | 
|  | dev_info(&dev->pdev->dev, "vdisplay(h) = %d\n", mode->vdisplay); | 
|  | dev_info(&dev->pdev->dev, "HSS = %d\n", mode->hsync_start); | 
|  | dev_info(&dev->pdev->dev, "HSE = %d\n", mode->hsync_end); | 
|  | dev_info(&dev->pdev->dev, "htotal = %d\n", mode->htotal); | 
|  | dev_info(&dev->pdev->dev, "VSS = %d\n", mode->vsync_start); | 
|  | dev_info(&dev->pdev->dev, "VSE = %d\n", mode->vsync_end); | 
|  | dev_info(&dev->pdev->dev, "vtotal = %d\n", mode->vtotal); | 
|  | dev_info(&dev->pdev->dev, "clock = %d\n", mode->clock); | 
|  |  | 
|  | drm_mode_set_name(mode); | 
|  | drm_mode_set_crtcinfo(mode, 0); | 
|  |  | 
|  | mode->type |= DRM_MODE_TYPE_PREFERRED; | 
|  |  | 
|  | return mode; | 
|  | } | 
|  |  | 
|  | /* DV1 Active area 216.96 x 135.6 mm */ | 
|  | #define DV1_PANEL_WIDTH 217 | 
|  | #define DV1_PANEL_HEIGHT 136 | 
|  |  | 
|  | static int tc35876x_get_panel_info(struct drm_device *dev, int pipe, | 
|  | struct panel_info *pi) | 
|  | { | 
|  | if (!dev || !pi) | 
|  | return -EINVAL; | 
|  |  | 
|  | pi->width_mm = DV1_PANEL_WIDTH; | 
|  | pi->height_mm = DV1_PANEL_HEIGHT; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int tc35876x_bridge_probe(struct i2c_client *client, | 
|  | const struct i2c_device_id *id) | 
|  | { | 
|  | struct tc35876x_platform_data *pdata; | 
|  |  | 
|  | dev_info(&client->dev, "%s\n", __func__); | 
|  |  | 
|  | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { | 
|  | dev_err(&client->dev, "%s: i2c_check_functionality() failed\n", | 
|  | __func__); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | pdata = dev_get_platdata(&client->dev); | 
|  | if (!pdata) { | 
|  | dev_err(&client->dev, "%s: no platform data\n", __func__); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (pdata->gpio_bridge_reset != -1) { | 
|  | gpio_request(pdata->gpio_bridge_reset, "tc35876x bridge reset"); | 
|  | gpio_direction_output(pdata->gpio_bridge_reset, 0); | 
|  | } | 
|  |  | 
|  | if (pdata->gpio_panel_bl_en != -1) { | 
|  | gpio_request(pdata->gpio_panel_bl_en, "tc35876x panel bl en"); | 
|  | gpio_direction_output(pdata->gpio_panel_bl_en, 0); | 
|  | } | 
|  |  | 
|  | if (pdata->gpio_panel_vadd != -1) { | 
|  | gpio_request(pdata->gpio_panel_vadd, "tc35876x panel vadd"); | 
|  | gpio_direction_output(pdata->gpio_panel_vadd, 0); | 
|  | } | 
|  |  | 
|  | tc35876x_client = client; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int tc35876x_bridge_remove(struct i2c_client *client) | 
|  | { | 
|  | struct tc35876x_platform_data *pdata = dev_get_platdata(&client->dev); | 
|  |  | 
|  | dev_dbg(&client->dev, "%s\n", __func__); | 
|  |  | 
|  | if (pdata->gpio_bridge_reset != -1) | 
|  | gpio_free(pdata->gpio_bridge_reset); | 
|  |  | 
|  | if (pdata->gpio_panel_bl_en != -1) | 
|  | gpio_free(pdata->gpio_panel_bl_en); | 
|  |  | 
|  | if (pdata->gpio_panel_vadd != -1) | 
|  | gpio_free(pdata->gpio_panel_vadd); | 
|  |  | 
|  | tc35876x_client = NULL; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct i2c_device_id tc35876x_bridge_id[] = { | 
|  | { "i2c_disp_brig", 0 }, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(i2c, tc35876x_bridge_id); | 
|  |  | 
|  | static struct i2c_driver tc35876x_bridge_i2c_driver = { | 
|  | .driver = { | 
|  | .name = "i2c_disp_brig", | 
|  | }, | 
|  | .id_table = tc35876x_bridge_id, | 
|  | .probe = tc35876x_bridge_probe, | 
|  | .remove = __devexit_p(tc35876x_bridge_remove), | 
|  | }; | 
|  |  | 
|  | /* LCD panel I2C */ | 
|  | static int cmi_lcd_i2c_probe(struct i2c_client *client, | 
|  | const struct i2c_device_id *id) | 
|  | { | 
|  | dev_info(&client->dev, "%s\n", __func__); | 
|  |  | 
|  | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { | 
|  | dev_err(&client->dev, "%s: i2c_check_functionality() failed\n", | 
|  | __func__); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | cmi_lcd_i2c_client = client; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int cmi_lcd_i2c_remove(struct i2c_client *client) | 
|  | { | 
|  | dev_dbg(&client->dev, "%s\n", __func__); | 
|  |  | 
|  | cmi_lcd_i2c_client = NULL; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct i2c_device_id cmi_lcd_i2c_id[] = { | 
|  | { "cmi-lcd", 0 }, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(i2c, cmi_lcd_i2c_id); | 
|  |  | 
|  | static struct i2c_driver cmi_lcd_i2c_driver = { | 
|  | .driver = { | 
|  | .name = "cmi-lcd", | 
|  | }, | 
|  | .id_table = cmi_lcd_i2c_id, | 
|  | .probe = cmi_lcd_i2c_probe, | 
|  | .remove = __devexit_p(cmi_lcd_i2c_remove), | 
|  | }; | 
|  |  | 
|  | /* HACK to create I2C device while it's not created by platform code */ | 
|  | #define CMI_LCD_I2C_ADAPTER	2 | 
|  | #define CMI_LCD_I2C_ADDR	0x60 | 
|  |  | 
|  | static int cmi_lcd_hack_create_device(void) | 
|  | { | 
|  | struct i2c_adapter *adapter; | 
|  | struct i2c_client *client; | 
|  | struct i2c_board_info info = { | 
|  | .type = "cmi-lcd", | 
|  | .addr = CMI_LCD_I2C_ADDR, | 
|  | }; | 
|  |  | 
|  | pr_debug("%s\n", __func__); | 
|  |  | 
|  | adapter = i2c_get_adapter(CMI_LCD_I2C_ADAPTER); | 
|  | if (!adapter) { | 
|  | pr_err("%s: i2c_get_adapter(%d) failed\n", __func__, | 
|  | CMI_LCD_I2C_ADAPTER); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | client = i2c_new_device(adapter, &info); | 
|  | if (!client) { | 
|  | pr_err("%s: i2c_new_device() failed\n", __func__); | 
|  | i2c_put_adapter(adapter); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct drm_encoder_helper_funcs tc35876x_encoder_helper_funcs = { | 
|  | .dpms = mdfld_dsi_dpi_dpms, | 
|  | .mode_fixup = mdfld_dsi_dpi_mode_fixup, | 
|  | .prepare = mdfld_dsi_dpi_prepare, | 
|  | .mode_set = mdfld_dsi_dpi_mode_set, | 
|  | .commit = mdfld_dsi_dpi_commit, | 
|  | }; | 
|  |  | 
|  | static const struct drm_encoder_funcs tc35876x_encoder_funcs = { | 
|  | .destroy = drm_encoder_cleanup, | 
|  | }; | 
|  |  | 
|  | const struct panel_funcs mdfld_tc35876x_funcs = { | 
|  | .encoder_funcs = &tc35876x_encoder_funcs, | 
|  | .encoder_helper_funcs = &tc35876x_encoder_helper_funcs, | 
|  | .get_config_mode = tc35876x_get_config_mode, | 
|  | .get_panel_info = tc35876x_get_panel_info, | 
|  | }; | 
|  |  | 
|  | void tc35876x_init(struct drm_device *dev) | 
|  | { | 
|  | int r; | 
|  |  | 
|  | dev_dbg(&dev->pdev->dev, "%s\n", __func__); | 
|  |  | 
|  | cmi_lcd_hack_create_device(); | 
|  |  | 
|  | r = i2c_add_driver(&cmi_lcd_i2c_driver); | 
|  | if (r < 0) | 
|  | dev_err(&dev->pdev->dev, | 
|  | "%s: i2c_add_driver() for %s failed (%d)\n", | 
|  | __func__, cmi_lcd_i2c_driver.driver.name, r); | 
|  |  | 
|  | r = i2c_add_driver(&tc35876x_bridge_i2c_driver); | 
|  | if (r < 0) | 
|  | dev_err(&dev->pdev->dev, | 
|  | "%s: i2c_add_driver() for %s failed (%d)\n", | 
|  | __func__, tc35876x_bridge_i2c_driver.driver.name, r); | 
|  |  | 
|  | tc35876x_brightness_init(dev); | 
|  | } | 
|  |  | 
|  | void tc35876x_exit(void) | 
|  | { | 
|  | pr_debug("%s\n", __func__); | 
|  |  | 
|  | i2c_del_driver(&tc35876x_bridge_i2c_driver); | 
|  |  | 
|  | if (cmi_lcd_i2c_client) | 
|  | i2c_del_driver(&cmi_lcd_i2c_driver); | 
|  | } |