423 lines
13 KiB
C
423 lines
13 KiB
C
|
/*
|
||
|
* * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are
|
||
|
* met:
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above
|
||
|
* copyright notice, this list of conditions and the following
|
||
|
* disclaimer in the documentation and/or other materials provided
|
||
|
* with the distribution.
|
||
|
* * Neither the name of The Linux Foundation nor the names of its
|
||
|
* contributors may be used to endorse or promote products derived
|
||
|
* from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||
|
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
#include <debug.h>
|
||
|
#include <kernel/thread.h>
|
||
|
#include <i2c_qup.h>
|
||
|
#include <platform.h>
|
||
|
#include <platform/iomap.h>
|
||
|
#include <platform/gpio.h>
|
||
|
#include <platform/clock.h>
|
||
|
#include <platform/pmic.h>
|
||
|
#include <platform/pmic_pwm.h>
|
||
|
#include <platform/machtype.h>
|
||
|
#include <platform/timer.h>
|
||
|
#include <gsbi.h>
|
||
|
#include <dev/lcdc.h>
|
||
|
|
||
|
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||
|
|
||
|
static struct qup_i2c_dev *dev = NULL;
|
||
|
void gpio_tlmm_config(uint32_t gpio, uint8_t func,
|
||
|
uint8_t dir, uint8_t pull,
|
||
|
uint8_t drvstr, uint32_t enable);
|
||
|
|
||
|
uint8_t expander_read(uint8_t addr)
|
||
|
{
|
||
|
uint8_t ret = 0;
|
||
|
/* Create a i2c_msg buffer, that is used to put the controller into read
|
||
|
mode and then to read some data. */
|
||
|
struct i2c_msg msg_buf[] = {
|
||
|
{CORE_GPIO_EXPANDER_I2C_ADDRESS, I2C_M_WR, 1, &addr},
|
||
|
{CORE_GPIO_EXPANDER_I2C_ADDRESS, I2C_M_RD, 1, &ret}
|
||
|
};
|
||
|
|
||
|
qup_i2c_xfer(dev, msg_buf, 2);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
uint8_t expander_write(uint8_t addr, uint8_t val)
|
||
|
{
|
||
|
uint8_t data_buf[] = { addr, val };
|
||
|
|
||
|
/* Create a i2c_msg buffer, that is used to put the controller into write
|
||
|
mode and then to write some data. */
|
||
|
struct i2c_msg msg_buf[] = { {CORE_GPIO_EXPANDER_I2C_ADDRESS,
|
||
|
I2C_M_WR, 2, data_buf}
|
||
|
};
|
||
|
|
||
|
qup_i2c_xfer(dev, msg_buf, 1);
|
||
|
|
||
|
/* Double check that the write worked. */
|
||
|
if (val != expander_read(addr)) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void panel_backlight(int on)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
static int display_common_power(int on)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
static int lcd_power_on()
|
||
|
{
|
||
|
uint8_t buffer = 0x0, mask = 0x0, prev_val = 0x0;
|
||
|
int ret = 0;
|
||
|
|
||
|
/* Configure LDO L2 TEST Bank 2, to Range Select 0 */
|
||
|
/* Not updating reference voltage */
|
||
|
buffer = (0x80); /* Write mode */
|
||
|
buffer |= (PM8901_LDO_TEST_BANK(2)); /* Test Bank 2 */
|
||
|
mask = buffer | LDO_TEST_RANGE_SELECT_MASK;
|
||
|
|
||
|
if ((ret = pm8901_test_bank_read(&prev_val,
|
||
|
PM8901_LDO_TEST_BANK(2),
|
||
|
PM8901_LDO_L2_TEST_BANK))) {
|
||
|
return ret;
|
||
|
}
|
||
|
if ((ret = pm8901_vreg_write(&buffer, mask, PM8901_LDO_L2_TEST_BANK,
|
||
|
prev_val))) {
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Enable LDO L2 at Max Voltage (should be around 3.3v) */
|
||
|
buffer = (0x0 << PM8901_LDO_CTL_ENABLE__S);
|
||
|
/* Disable Pull Down */
|
||
|
buffer |= (0x1 << PM8901_LDO_CTL_PULL_DOWN__S);
|
||
|
/* Put LDO into normal mode instead of low power mode */
|
||
|
buffer |= (0x0 << PM8901_LDO_CTL_MODE__S);
|
||
|
|
||
|
/* Set voltage programming to 3.3V or 2.85V(8660 fluid) */
|
||
|
if (board_machtype() == LINUX_MACHTYPE_8660_FLUID)
|
||
|
buffer |= (0xB);
|
||
|
else
|
||
|
buffer |= (0xF);
|
||
|
|
||
|
mask = buffer | LDO_CTL_ENABLE_MASK |
|
||
|
LDO_CTL_PULL_DOWN_MASK |
|
||
|
LDO_CTL_NORMAL_POWER_MODE_MASK | LDO_CTL_VOLTAGE_SET_MASK;
|
||
|
|
||
|
/* Do a normal read here, as to not destroy the value in LDO control */
|
||
|
if ((ret = pm8901_read(&prev_val, 1, PM8901_LDO_L2))) {
|
||
|
return ret;
|
||
|
}
|
||
|
/* Configure the LDO2 for 3.3V or 2.85V(8660 fluid) */
|
||
|
ret = pm8901_vreg_write(&buffer, mask, PM8901_LDO_L2, prev_val);
|
||
|
|
||
|
/* Configure LDO L2 TEST Bank 4, for High Range Mode */
|
||
|
buffer = (0x80); /* Write mode */
|
||
|
buffer |= (PM8901_LDO_TEST_BANK(4)); /* Test Bank 4 */
|
||
|
buffer |= (0x01); /* Put into High Range Mode */
|
||
|
mask = buffer | LDO_TEST_OUTPUT_RANGE_MASK;
|
||
|
|
||
|
if ((ret = pm8901_test_bank_read(&prev_val,
|
||
|
PM8901_LDO_TEST_BANK(4),
|
||
|
PM8901_LDO_L2_TEST_BANK))) {
|
||
|
return ret;
|
||
|
}
|
||
|
if ((ret = pm8901_vreg_write(&buffer, mask, PM8901_LDO_L2_TEST_BANK,
|
||
|
prev_val))) {
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Configure LDO L2 TEST Bank 2, to Range Select 0 */
|
||
|
buffer = (0x80); /* Write mode */
|
||
|
buffer |= (PM8901_LDO_TEST_BANK(2)); /* Test Bank 2 */
|
||
|
buffer |= (1 << 1); /* For fine step 50 mV */
|
||
|
buffer |= (1 << 3); /* to update reference voltage */
|
||
|
mask = buffer | LDO_TEST_RANGE_SELECT_MASK;
|
||
|
mask |= (1 << 2); /* Setting mask to make ref voltage as 1.25 V */
|
||
|
|
||
|
if ((ret = pm8901_test_bank_read(&prev_val,
|
||
|
PM8901_LDO_TEST_BANK(2),
|
||
|
PM8901_LDO_L2_TEST_BANK))) {
|
||
|
return ret;
|
||
|
}
|
||
|
if ((ret = pm8901_vreg_write(&buffer, mask, PM8901_LDO_L2_TEST_BANK,
|
||
|
prev_val))) {
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Enable PMR for LDO L2 */
|
||
|
buffer = 0x7F;
|
||
|
mask = 0x7F;
|
||
|
if ((ret = pm8901_read(&prev_val, 1, PM8901_PMR_REG(LDO_L2)))) {
|
||
|
return ret;
|
||
|
}
|
||
|
ret = pm8901_vreg_write(&buffer, mask, PM8901_PMR_REG(LDO_L2), prev_val);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Configures the GPIO that are needed to enable LCD.
|
||
|
* This function also configures the PMIC for PWM control of the LCD backlight.
|
||
|
*/
|
||
|
static void lcd_gpio_cfg(uint8_t on)
|
||
|
{
|
||
|
uint32_t func;
|
||
|
uint32_t pull;
|
||
|
uint32_t dir;
|
||
|
uint32_t enable = 0; /* not used in gpio_tlmm_config */
|
||
|
uint32_t drv;
|
||
|
if (on) {
|
||
|
func = 1; /* Configure GPIO for LCDC function */
|
||
|
pull = GPIO_NO_PULL;
|
||
|
dir = 1; /* doesn't matter since it is not configured as
|
||
|
GPIO */
|
||
|
drv = GPIO_16MA;
|
||
|
} else {
|
||
|
/* As discussed in the MSM8660 FFA HW SW Control Doc configure these
|
||
|
GPIO as input and pull down. */
|
||
|
func = 0; /* GPIO */
|
||
|
pull = GPIO_PULL_DOWN;
|
||
|
dir = 0; /* Input */
|
||
|
drv = 0; /* does not matter configured as input */
|
||
|
}
|
||
|
|
||
|
gpio_tlmm_config(0, func, dir, pull, drv, enable); /* lcdc_pclk */
|
||
|
gpio_tlmm_config(1, func, dir, pull, drv, enable); /* lcdc_hsync */
|
||
|
gpio_tlmm_config(2, func, dir, pull, drv, enable); /* lcdc_vsync */
|
||
|
gpio_tlmm_config(3, func, dir, pull, drv, enable); /* lcdc_den */
|
||
|
gpio_tlmm_config(4, func, dir, pull, drv, enable); /* lcdc_red7 */
|
||
|
gpio_tlmm_config(5, func, dir, pull, drv, enable); /* lcdc_red6 */
|
||
|
gpio_tlmm_config(6, func, dir, pull, drv, enable); /* lcdc_red5 */
|
||
|
gpio_tlmm_config(7, func, dir, pull, drv, enable); /* lcdc_red4 */
|
||
|
gpio_tlmm_config(8, func, dir, pull, drv, enable); /* lcdc_red3 */
|
||
|
gpio_tlmm_config(9, func, dir, pull, drv, enable); /* lcdc_red2 */
|
||
|
gpio_tlmm_config(10, func, dir, pull, drv, enable); /* lcdc_red1 */
|
||
|
gpio_tlmm_config(11, func, dir, pull, drv, enable); /* lcdc_red0 */
|
||
|
gpio_tlmm_config(12, func, dir, pull, drv, enable); /* lcdc_rgn7 */
|
||
|
gpio_tlmm_config(13, func, dir, pull, drv, enable); /* lcdc_rgn6 */
|
||
|
gpio_tlmm_config(14, func, dir, pull, drv, enable); /* lcdc_rgn5 */
|
||
|
gpio_tlmm_config(15, func, dir, pull, drv, enable); /* lcdc_rgn4 */
|
||
|
gpio_tlmm_config(16, func, dir, pull, drv, enable); /* lcdc_rgn3 */
|
||
|
gpio_tlmm_config(17, func, dir, pull, drv, enable); /* lcdc_rgn2 */
|
||
|
gpio_tlmm_config(18, func, dir, pull, drv, enable); /* lcdc_rgn1 */
|
||
|
gpio_tlmm_config(19, func, dir, pull, drv, enable); /* lcdc_rgn0 */
|
||
|
gpio_tlmm_config(20, func, dir, pull, drv, enable); /* lcdc_blu7 */
|
||
|
gpio_tlmm_config(21, func, dir, pull, drv, enable); /* lcdc_blu6 */
|
||
|
gpio_tlmm_config(22, func, dir, pull, drv, enable); /* lcdc_blu5 */
|
||
|
gpio_tlmm_config(23, func, dir, pull, drv, enable); /* lcdc_blu4 */
|
||
|
gpio_tlmm_config(24, func, dir, pull, drv, enable); /* lcdc_blu3 */
|
||
|
gpio_tlmm_config(25, func, dir, pull, drv, enable); /* lcdc_blu2 */
|
||
|
gpio_tlmm_config(26, func, dir, pull, drv, enable); /* lcdc_blu1 */
|
||
|
gpio_tlmm_config(27, func, dir, pull, drv, enable); /* lcdc_blu0 */
|
||
|
}
|
||
|
|
||
|
/* API to set backlight level configuring PWM in PM8058 */
|
||
|
|
||
|
int panel_set_backlight(uint8_t bt_level)
|
||
|
{
|
||
|
int rc = -1;
|
||
|
uint32_t duty_us, period_us;
|
||
|
|
||
|
if ((bt_level < 0) || (bt_level > 15)) {
|
||
|
dprintf(CRITICAL, "Error in brightness level (1-15 allowed)\n");
|
||
|
goto bail_out;
|
||
|
}
|
||
|
|
||
|
duty_us = bt_level * PWM_DUTY_LEVEL;
|
||
|
period_us = PWM_PERIOD_USEC;
|
||
|
rc = pm_pwm_config(0, duty_us, period_us);
|
||
|
if (rc) {
|
||
|
dprintf(CRITICAL, "Error in pwm_config0\n");
|
||
|
goto bail_out;
|
||
|
}
|
||
|
|
||
|
duty_us = PWM_PERIOD_USEC - (bt_level * PWM_DUTY_LEVEL);
|
||
|
period_us = PWM_PERIOD_USEC;
|
||
|
rc = pm_pwm_config(1, duty_us, period_us);
|
||
|
if (rc) {
|
||
|
dprintf(CRITICAL, "Error in pwm_config1\n");
|
||
|
goto bail_out;
|
||
|
}
|
||
|
|
||
|
rc = pm_pwm_enable(0);
|
||
|
if (rc) {
|
||
|
dprintf(CRITICAL, "Error in pwm_enable0\n");
|
||
|
goto bail_out;
|
||
|
}
|
||
|
|
||
|
rc = pm_pwm_enable(1);
|
||
|
if (rc)
|
||
|
dprintf(CRITICAL, "Error in pwm_enable1\n");
|
||
|
|
||
|
bail_out:
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
void bl_gpio_init(void)
|
||
|
{
|
||
|
/* Configure PM8058 GPIO24 as a PWM driver (LPG ch0) for chain 1 of 6 LEDs */
|
||
|
pm8058_write_one(0x81, GPIO24_GPIO_CNTRL); /* Write, Bank0, VIN0, Mode
|
||
|
selection enabled */
|
||
|
pm8058_write_one(0x98, GPIO24_GPIO_CNTRL); /* Write, Bank1, OutOn/InOff,
|
||
|
CMOS, Don't Invert Output */
|
||
|
pm8058_write_one(0xAA, GPIO24_GPIO_CNTRL); /* Write, Bank2, GPIO no pull */
|
||
|
pm8058_write_one(0xB4, GPIO24_GPIO_CNTRL); /* Write, Bank3, high drv
|
||
|
strength */
|
||
|
pm8058_write_one(0xC6, GPIO24_GPIO_CNTRL); /* Write, Bank4, Src: LPG_DRV1
|
||
|
(Spec. Fnc 2) */
|
||
|
pm8058_write_one(0xD8, GPIO24_GPIO_CNTRL); /* Write, Bank5, Interrupt
|
||
|
polarity noninversion */
|
||
|
|
||
|
/* Configure PM8058 GPIO25 as a PWM driver (LPG ch1) for chain 2 of 5 LEDs */
|
||
|
pm8058_write_one(0x81, GPIO25_GPIO_CNTRL); /* Write, Bank0, VIN0, Mode
|
||
|
selection enabled */
|
||
|
pm8058_write_one(0x98, GPIO25_GPIO_CNTRL); /* Write, Bank1, OutOn/InOff,
|
||
|
CMOS, Don't Invert Output */
|
||
|
pm8058_write_one(0xAA, GPIO25_GPIO_CNTRL); /* Write, Bank2, GPIO no pull */
|
||
|
pm8058_write_one(0xB4, GPIO25_GPIO_CNTRL); /* Write, Bank3, high drv
|
||
|
strength */
|
||
|
pm8058_write_one(0xC6, GPIO25_GPIO_CNTRL); /* Write, Bank4, Src: LPG_DRV2
|
||
|
(Spec. Fnc 2) */
|
||
|
pm8058_write_one(0xD8, GPIO25_GPIO_CNTRL); /* Write, Bank5, Interrupt
|
||
|
polarity noninversion */
|
||
|
}
|
||
|
|
||
|
void board_lcd_enable(void)
|
||
|
{
|
||
|
int rc = -1;
|
||
|
dev = qup_i2c_init(GSBI_ID_8, 100000, 24000000);
|
||
|
|
||
|
/* Make sure dev is created and initialized properly */
|
||
|
if (!dev) {
|
||
|
while (1) ;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Store current value of these registers as to not destroy their previous
|
||
|
state. */
|
||
|
uint8_t open_drain_a = expander_read(GPIO_EXPANDER_REG_OPEN_DRAIN_A);
|
||
|
uint8_t dir_b = expander_read(GPIO_EXPANDER_REG_DIR_B);
|
||
|
uint8_t dir_a = expander_read(GPIO_EXPANDER_REG_DIR_A);
|
||
|
uint8_t data_b = expander_read(GPIO_EXPANDER_REG_DATA_B);
|
||
|
uint8_t data_a = expander_read(GPIO_EXPANDER_REG_DATA_A);
|
||
|
|
||
|
/* Set the LVDS_SHUTDOWN_N to open drain and output low. */
|
||
|
dprintf(INFO, "Enable lvds_shutdown_n line for Open Drain.\n");
|
||
|
expander_write(GPIO_EXPANDER_REG_OPEN_DRAIN_A, 0x04 | open_drain_a);
|
||
|
|
||
|
dprintf(INFO, "Enable lvds_shutdown_n line for output.\n");
|
||
|
expander_write(GPIO_EXPANDER_REG_DIR_A, ~0x04 & dir_a);
|
||
|
|
||
|
dprintf(INFO, "Drive the LVDS_SHUTDOWN_N pin high here.\n");
|
||
|
expander_write(GPIO_EXPANDER_REG_DATA_A, 0x04 | data_a);
|
||
|
|
||
|
/* Turn on the VREG_L2B to 3.3V. */
|
||
|
|
||
|
/* Power on the appropiate PMIC LDO power rails */
|
||
|
if (lcd_power_on())
|
||
|
return;
|
||
|
|
||
|
/* Enable the GPIO as LCDC mode LCD. */
|
||
|
lcd_gpio_cfg(1);
|
||
|
|
||
|
/* Arbitrary delay */
|
||
|
udelay(20000);
|
||
|
|
||
|
/* Set the GPIOs needed for backlight */
|
||
|
bl_gpio_init();
|
||
|
/* Set backlight level with API (to 8 by default) */
|
||
|
rc = panel_set_backlight(8);
|
||
|
if (rc)
|
||
|
dprintf(CRITICAL, "Error in setting panel backlight\n");
|
||
|
|
||
|
dprintf(INFO, "Enable BACKLIGHT_EN line for output.\n");
|
||
|
expander_write(GPIO_EXPANDER_REG_DIR_B, ~0x10 & dir_b);
|
||
|
|
||
|
dprintf(INFO, "Drive BACKLIGHT_EN to high\n");
|
||
|
expander_write(GPIO_EXPANDER_REG_DATA_B, 0x10 | data_b);
|
||
|
|
||
|
}
|
||
|
|
||
|
void lcdc_on(void)
|
||
|
{
|
||
|
board_lcd_enable();
|
||
|
}
|
||
|
|
||
|
void auo_board_lcd_enable(void)
|
||
|
{
|
||
|
/* Make sure dev is created and initialized properly */
|
||
|
dev = qup_i2c_init(GSBI_ID_8, 100000, 24000000);
|
||
|
if (!dev) {
|
||
|
dprintf(CRITICAL, "Error in qup_i2c_init\n");
|
||
|
while (1) ;
|
||
|
}
|
||
|
|
||
|
/* Setup RESX_N */
|
||
|
uint8_t open_drain_a = expander_read(GPIO_EXPANDER_REG_OPEN_DRAIN_A);
|
||
|
uint8_t dir_a = expander_read(GPIO_EXPANDER_REG_DIR_A);
|
||
|
uint8_t data_a = expander_read(GPIO_EXPANDER_REG_DATA_A);
|
||
|
|
||
|
expander_write(GPIO_EXPANDER_REG_DIR_A, ~0x04 & dir_a);
|
||
|
expander_write(GPIO_EXPANDER_REG_DATA_A, ~0x04 & data_a);
|
||
|
|
||
|
/* Power on the appropiate PMIC LDO power rails */
|
||
|
if (lcd_power_on())
|
||
|
return;
|
||
|
|
||
|
/* Toggle RESX_N */
|
||
|
mdelay(20);
|
||
|
expander_write(GPIO_EXPANDER_REG_DATA_A, 0x04 | data_a);
|
||
|
mdelay(1);
|
||
|
expander_write(GPIO_EXPANDER_REG_DATA_A, ~0x04 & data_a);
|
||
|
mdelay(1);
|
||
|
expander_write(GPIO_EXPANDER_REG_DATA_A, 0x04 | data_a);
|
||
|
mdelay(25);
|
||
|
|
||
|
/* Enable the gpios for LCD */
|
||
|
lcd_gpio_cfg(1);
|
||
|
|
||
|
auo_lcdc_init();
|
||
|
}
|
||
|
|
||
|
void panel_poweron(void)
|
||
|
{
|
||
|
if (board_machtype() == LINUX_MACHTYPE_8660_FLUID) {
|
||
|
auo_board_lcd_enable();
|
||
|
} else {
|
||
|
panel_backlight(1);
|
||
|
lcdc_on();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct lcdc_timing_parameters *get_lcd_timing(void)
|
||
|
{
|
||
|
if (board_machtype() == LINUX_MACHTYPE_8660_FLUID)
|
||
|
return auo_timing_param();
|
||
|
else
|
||
|
return DEFAULT_LCD_TIMING;
|
||
|
}
|