From cc35fdbc4d5b5ba85cae2568453957a62c3f1e4a Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 19 Apr 2012 04:33:09 +0000 Subject: serial: add LPC32X0 high-speed UART devices support This change adds an implementation of high-speed UART found on NXP LPC32X0 SoCs. Such UARTs are enumerated as UART1, UART2 and UART7. Signed-off-by: Vladimir Zapolskiy Cc: Albert ARIBAUD Acked-by: Marek Vasut --- drivers/serial/Makefile | 1 + drivers/serial/lpc32xx_hsuart.c | 112 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 drivers/serial/lpc32xx_hsuart.c (limited to 'drivers') diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 616b857..65d0f23 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -29,6 +29,7 @@ COBJS-$(CONFIG_ALTERA_UART) += altera_uart.o COBJS-$(CONFIG_ALTERA_JTAG_UART) += altera_jtag_uart.o COBJS-$(CONFIG_ARM_DCC) += arm_dcc.o COBJS-$(CONFIG_ATMEL_USART) += atmel_usart.o +COBJS-$(CONFIG_LPC32XX_HSUART) += lpc32xx_hsuart.o COBJS-$(CONFIG_MCFUART) += mcfuart.o COBJS-$(CONFIG_NS9750_UART) += ns9750_serial.o COBJS-$(CONFIG_OPENCORES_YANU) += opencores_yanu.o diff --git a/drivers/serial/lpc32xx_hsuart.c b/drivers/serial/lpc32xx_hsuart.c new file mode 100644 index 0000000..8ce3382 --- /dev/null +++ b/drivers/serial/lpc32xx_hsuart.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 Vladimir Zapolskiy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +static struct hsuart_regs *hsuart = (struct hsuart_regs *)HS_UART_BASE; + +static void lpc32xx_hsuart_set_baudrate(void) +{ + u32 div; + + /* UART rate = PERIPH_CLK / ((HSU_RATE + 1) x 14) */ + div = (get_serial_clock() / 14 + gd->baudrate / 2) / gd->baudrate - 1; + if (div > 255) + div = 255; + + writel(div, &hsuart->rate); +} + +static int lpc32xx_hsuart_getc(void) +{ + while (!(readl(&hsuart->level) & HSUART_LEVEL_RX)) + /* NOP */; + + return readl(&hsuart->rx) & HSUART_RX_DATA; +} + +static void lpc32xx_hsuart_putc(const char c) +{ + writel(c, &hsuart->tx); + + /* Wait for character to be sent */ + while (readl(&hsuart->level) & HSUART_LEVEL_TX) + /* NOP */; +} + +static int lpc32xx_hsuart_tstc(void) +{ + if (readl(&hsuart->level) & HSUART_LEVEL_RX) + return 1; + + return 0; +} + +static void lpc32xx_hsuart_init(void) +{ + lpc32xx_hsuart_set_baudrate(); + + /* Disable hardware RTS and CTS flow control, set up RX and TX FIFO */ + writel(HSUART_CTRL_TMO_16 | HSUART_CTRL_HSU_OFFSET(20) | + HSUART_CTRL_HSU_RX_TRIG_32 | HSUART_CTRL_HSU_TX_TRIG_0, + &hsuart->ctrl); +} + +void serial_setbrg(void) +{ + return lpc32xx_hsuart_set_baudrate(); +} + +void serial_putc(const char c) +{ + lpc32xx_hsuart_putc(c); + + /* If \n, also do \r */ + if (c == '\n') + lpc32xx_hsuart_putc('\r'); +} + +int serial_getc(void) +{ + return lpc32xx_hsuart_getc(); +} + +void serial_puts(const char *s) +{ + while (*s) + serial_putc(*s++); +} + +int serial_tstc(void) +{ + return lpc32xx_hsuart_tstc(); +} + +int serial_init(void) +{ + lpc32xx_hsuart_init(); + + return 0; +} -- cgit v1.1 From bbbc1ae9219ae8c8098c0af14060da5cb0d37e53 Mon Sep 17 00:00:00 2001 From: Jonathan Solnit Date: Fri, 24 Feb 2012 11:30:18 +0000 Subject: ARM:OMAP+:MMC: Add parameters to MMC init Add parameters to the OMAP MMC initialization function so the board can mask host capabilities and set the maximum clock frequency. While the OMAP supports a certain set of MMC host capabilities, individual boards may be more restricted and the OMAP may need to be configured to match the board. The PRG_SDMMC1_SPEEDCTRL bit in the OMAP3 is an example. Signed-off-by: Jonathan Solnit --- drivers/mmc/omap_hsmmc.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/omap_hsmmc.c b/drivers/mmc/omap_hsmmc.c index 2400db2..b8ab7bc 100644 --- a/drivers/mmc/omap_hsmmc.c +++ b/drivers/mmc/omap_hsmmc.c @@ -80,6 +80,11 @@ unsigned char mmc_board_init(struct mmc *mmc) writel(readl(&t2_base->devconf1) | MMCSDIO2ADPCLKISEL, &t2_base->devconf1); + /* Change from default of 52MHz to 26MHz if necessary */ + if (!(mmc->host_caps & MMC_MODE_HS_52MHz)) + writel(readl(&t2_base->ctl_prog_io1) & ~CTLPROGIO1SPEEDCTRL, + &t2_base->ctl_prog_io1); + writel(readl(&prcm_base->fclken1_core) | EN_MMC1 | EN_MMC2 | EN_MMC3, &prcm_base->fclken1_core); @@ -463,7 +468,7 @@ static void mmc_set_ios(struct mmc *mmc) writel(readl(&mmc_base->sysctl) | CEN_ENABLE, &mmc_base->sysctl); } -int omap_mmc_init(int dev_index) +int omap_mmc_init(int dev_index, uint host_caps_mask, uint f_max) { struct mmc *mmc; @@ -494,11 +499,22 @@ int omap_mmc_init(int dev_index) return 1; } mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; - mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS | - MMC_MODE_HC; + mmc->host_caps = (MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS | + MMC_MODE_HC) & ~host_caps_mask; mmc->f_min = 400000; - mmc->f_max = 52000000; + + if (f_max != 0) + mmc->f_max = f_max; + else { + if (mmc->host_caps & MMC_MODE_HS) { + if (mmc->host_caps & MMC_MODE_HS_52MHz) + mmc->f_max = 52000000; + else + mmc->f_max = 26000000; + } else + mmc->f_max = 20000000; + } mmc->b_max = 0; -- cgit v1.1 From 002a2c0c66d2e1757b89ad6824b4a472dcef1076 Mon Sep 17 00:00:00 2001 From: SRICHARAN R Date: Mon, 12 Mar 2012 02:25:42 +0000 Subject: OMAP4/5: Make the sysctrl structure common Make the sysctrl structure common, so that it can be used in generic functions across socs. Also change the base address of the system control module, to include all the registers and not simply the io regs. Signed-off-by: R Sricharan --- drivers/mmc/omap_hsmmc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/omap_hsmmc.c b/drivers/mmc/omap_hsmmc.c index b8ab7bc..f421ff9 100644 --- a/drivers/mmc/omap_hsmmc.c +++ b/drivers/mmc/omap_hsmmc.c @@ -45,8 +45,8 @@ static struct mmc hsmmc_dev[2]; static void omap4_vmmc_pbias_config(struct mmc *mmc) { u32 value = 0; - struct omap4_sys_ctrl_regs *const ctrl = - (struct omap4_sys_ctrl_regs *)SYSCTRL_GENERAL_CORE_BASE; + struct omap_sys_ctrl_regs *const ctrl = + (struct omap_sys_ctrl_regs *) SYSCTRL_GENERAL_CORE_BASE; value = readl(&ctrl->control_pbiaslite); -- cgit v1.1 From 211442989628d72fad1f2663a0bf5076e425fabd Mon Sep 17 00:00:00 2001 From: SRICHARAN R Date: Mon, 12 Mar 2012 02:25:48 +0000 Subject: power: twl6035: add palmas PMIC support palmas/TWL6035 is power IC for omap5 evm boards Signed-off-by: Balaji T K --- drivers/power/Makefile | 1 + drivers/power/twl6035.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 drivers/power/twl6035.c (limited to 'drivers') diff --git a/drivers/power/Makefile b/drivers/power/Makefile index ead00f8..f24b93c 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libpower.o COBJS-$(CONFIG_FTPMU010_POWER) += ftpmu010.o COBJS-$(CONFIG_TWL4030_POWER) += twl4030.o COBJS-$(CONFIG_TWL6030_POWER) += twl6030.o +COBJS-$(CONFIG_TWL6035_POWER) += twl6035.o COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/power/twl6035.c b/drivers/power/twl6035.c new file mode 100644 index 0000000..d75212c --- /dev/null +++ b/drivers/power/twl6035.c @@ -0,0 +1,40 @@ +/* + * (C) Copyright 2012 + * Texas Instruments, + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +#include +#include + +/* Functions to read and write from TWL6030 */ +int twl6035_i2c_write_u8(u8 chip_no, u8 val, u8 reg) +{ + return i2c_write(chip_no, reg, 1, &val, 1); +} + +int twl6035_i2c_read_u8(u8 chip_no, u8 *val, u8 reg) +{ + return i2c_read(chip_no, reg, 1, val, 1); +} + +void twl6035_init_settings(void) +{ + return; +} -- cgit v1.1 From dd23e59d595f02f6f79b003c372b22b467a005f2 Mon Sep 17 00:00:00 2001 From: Balaji T K Date: Mon, 12 Mar 2012 02:25:49 +0000 Subject: omap5: pbias ldo9 turn on Add omap5 pbias configuration for mmc1/sd lines and set voltage for sd data i/o lines Signed-off-by: Balaji T K --- drivers/mmc/omap_hsmmc.c | 33 +++++++++++++++++++++++++++++++++ drivers/power/twl6035.c | 25 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/omap_hsmmc.c b/drivers/mmc/omap_hsmmc.c index f421ff9..e3670d4 100644 --- a/drivers/mmc/omap_hsmmc.c +++ b/drivers/mmc/omap_hsmmc.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,34 @@ static void omap4_vmmc_pbias_config(struct mmc *mmc) } #endif +#if defined(CONFIG_OMAP54XX) && defined(CONFIG_TWL6035_POWER) +static void omap5_pbias_config(struct mmc *mmc) +{ + u32 value = 0; + struct omap_sys_ctrl_regs *const ctrl = + (struct omap_sys_ctrl_regs *) SYSCTRL_GENERAL_CORE_BASE; + + value = readl(&ctrl->control_pbias); + value &= ~(SDCARD_PWRDNZ | SDCARD_BIAS_PWRDNZ); + value |= SDCARD_BIAS_HIZ_MODE; + writel(value, &ctrl->control_pbias); + + twl6035_mmc1_poweron_ldo(); + + value = readl(&ctrl->control_pbias); + value &= ~SDCARD_BIAS_HIZ_MODE; + value |= SDCARD_PBIASLITE_VMODE | SDCARD_PWRDNZ | SDCARD_BIAS_PWRDNZ; + writel(value, &ctrl->control_pbias); + + value = readl(&ctrl->control_pbias); + if (value & (1 << 23)) { + value &= ~(SDCARD_PWRDNZ | SDCARD_BIAS_PWRDNZ); + value |= SDCARD_BIAS_HIZ_MODE; + writel(value, &ctrl->control_pbias); + } +} +#endif + unsigned char mmc_board_init(struct mmc *mmc) { #if defined(CONFIG_TWL4030_POWER) @@ -99,6 +128,10 @@ unsigned char mmc_board_init(struct mmc *mmc) if (mmc->block_dev.dev == 0) omap4_vmmc_pbias_config(mmc); #endif +#if defined(CONFIG_OMAP54XX) && defined(CONFIG_TWL6035_POWER) + if (mmc->block_dev.dev == 0) + omap5_pbias_config(mmc); +#endif return 0; } diff --git a/drivers/power/twl6035.c b/drivers/power/twl6035.c index d75212c..624c09e 100644 --- a/drivers/power/twl6035.c +++ b/drivers/power/twl6035.c @@ -34,7 +34,32 @@ int twl6035_i2c_read_u8(u8 chip_no, u8 *val, u8 reg) return i2c_read(chip_no, reg, 1, val, 1); } +/* To align with i2c mw/mr address, reg, val command syntax */ +static inline int palmas_write_u8(u8 chip_no, u8 reg, u8 val) +{ + return i2c_write(chip_no, reg, 1, &val, 1); +} + +static inline int palmas_read_u8(u8 chip_no, u8 reg, u8 *val) +{ + return i2c_read(chip_no, reg, 1, val, 1); +} + void twl6035_init_settings(void) { return; } + +void twl6035_mmc1_poweron_ldo(void) +{ + u8 val = 0; + + /* set LDO9 TWL6035 to 3V */ + val = 0x2b; /* (3 -.9)*28 +1 */ + palmas_write_u8(0x48, LDO9_VOLTAGE, val); + + /* TURN ON LDO9 */ + val = LDO_ON | LDO_MODE_SLEEP | LDO_MODE_ACTIVE; + palmas_write_u8(0x48, LDO9_CTRL, val); + return; +} -- cgit v1.1 From 8504296045f714430cd755434ccb094711ea95fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Majewski?= Date: Thu, 29 Mar 2012 01:29:17 +0000 Subject: misc:pmic:max8997 MAX8997 support for PMIC driver This commit adds support for MAX8997 PMIC driver. Signed-off-by: Lukasz Majewski Signed-off-by: Kyungmin Park Acked-by: Stefano Babic --- drivers/misc/Makefile | 1 + drivers/misc/pmic_max8997.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 drivers/misc/pmic_max8997.c (limited to 'drivers') diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index a709707..30c7f8d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -39,6 +39,7 @@ COBJS-$(CONFIG_PMIC_FSL) += pmic_fsl.o COBJS-$(CONFIG_PMIC_I2C) += pmic_i2c.o COBJS-$(CONFIG_PMIC_SPI) += pmic_spi.o COBJS-$(CONFIG_PMIC_MAX8998) += pmic_max8998.o +COBJS-$(CONFIG_PMIC_MAX8997) += pmic_max8997.o COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/misc/pmic_max8997.c b/drivers/misc/pmic_max8997.c new file mode 100644 index 0000000..62dbc05 --- /dev/null +++ b/drivers/misc/pmic_max8997.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * Lukasz Majewski + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include + +int pmic_init(void) +{ + struct pmic *p = get_pmic(); + static const char name[] = "MAX8997_PMIC"; + + puts("Board PMIC init\n"); + + p->name = name; + p->interface = PMIC_I2C; + p->number_of_regs = PMIC_NUM_OF_REGS; + p->hw.i2c.addr = MAX8997_I2C_ADDR; + p->hw.i2c.tx_num = 1; + p->bus = I2C_PMIC; + + return 0; +} -- cgit v1.1 From 6d4339f622ef378d903a0c35484581533a242d2b Mon Sep 17 00:00:00 2001 From: Donghwa Lee Date: Thu, 5 Apr 2012 19:36:17 +0000 Subject: EXYNOS: support EXYNOS framebuffer and FIMD display drivers. This patch support EXYNOS FB and FIMD display drivers. Signed-off-by: Donghwa Lee Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae Acked-by: Anatolij Gustschin --- drivers/video/Makefile | 1 + drivers/video/exynos_fb.c | 128 ++++++++++++++++ drivers/video/exynos_fb.h | 61 ++++++++ drivers/video/exynos_fimd.c | 354 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 544 insertions(+) create mode 100644 drivers/video/exynos_fb.c create mode 100644 drivers/video/exynos_fb.h create mode 100644 drivers/video/exynos_fimd.c (limited to 'drivers') diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 6252f6a..8896abe 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libvideo.o COBJS-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o COBJS-$(CONFIG_ATMEL_LCD) += atmel_lcdfb.o COBJS-$(CONFIG_CFB_CONSOLE) += cfb_console.o +COBJS-$(CONFIG_EXYNOS_FB) += exynos_fb.o exynos_fimd.o COBJS-$(CONFIG_FSL_DIU_FB) += fsl_diu_fb.o videomodes.o COBJS-$(CONFIG_S6E63D6) += s6e63d6.o COBJS-$(CONFIG_SED156X) += sed156x.o diff --git a/drivers/video/exynos_fb.c b/drivers/video/exynos_fb.c new file mode 100644 index 0000000..0eb7f58 --- /dev/null +++ b/drivers/video/exynos_fb.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: InKi Dae + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "exynos_fb.h" + +int lcd_line_length; +int lcd_color_fg; +int lcd_color_bg; + +void *lcd_base; +void *lcd_console_address; + +short console_col; +short console_row; + +static unsigned int panel_width, panel_height; + +/* LCD Panel data */ +vidinfo_t panel_info; + +static void exynos_lcd_init_mem(void *lcdbase, vidinfo_t *vid) +{ + unsigned long palette_size; + unsigned int fb_size; + + fb_size = vid->vl_row * vid->vl_col * (vid->vl_bpix >> 3); + + lcd_base = lcdbase; + + palette_size = NBITS(vid->vl_bpix) == 8 ? 256 : 16; + + exynos_fimd_lcd_init_mem((unsigned long)lcd_base, + (unsigned long)fb_size, palette_size); +} + +static void exynos_lcd_init(vidinfo_t *vid) +{ + exynos_fimd_lcd_init(vid); +} + +static void lcd_panel_on(vidinfo_t *vid) +{ + udelay(vid->init_delay); + + if (vid->backlight_reset) + vid->backlight_reset(); + + if (vid->cfg_gpio) + vid->cfg_gpio(); + + if (vid->lcd_power_on) + vid->lcd_power_on(); + + udelay(vid->power_on_delay); + + if (vid->reset_lcd) { + vid->reset_lcd(); + udelay(vid->reset_delay); + } + + if (vid->backlight_on) + vid->backlight_on(1); + + if (vid->cfg_ldo) + vid->cfg_ldo(); + + if (vid->enable_ldo) + vid->enable_ldo(1); + + if (vid->mipi_enabled) + exynos_mipi_dsi_init(); +} + +void lcd_ctrl_init(void *lcdbase) +{ + set_system_display_ctrl(); + set_lcd_clk(); + + /* initialize parameters which is specific to panel. */ + init_panel_info(&panel_info); + + panel_width = panel_info.vl_width; + panel_height = panel_info.vl_height; + + exynos_lcd_init_mem(lcdbase, &panel_info); + + exynos_lcd_init(&panel_info); +} + +void lcd_enable(void) +{ + lcd_panel_on(&panel_info); +} + +/* dummy function */ +void lcd_setcolreg(ushort regno, ushort red, ushort green, ushort blue) +{ + return; +} diff --git a/drivers/video/exynos_fb.h b/drivers/video/exynos_fb.h new file mode 100644 index 0000000..66f5da6 --- /dev/null +++ b/drivers/video/exynos_fb.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: InKi Dae + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef _EXYNOS_FB_H_ +#define _EXYNOS_FB_H_ + +#include + +#define MAX_CLOCK (86 * 1000000) + +enum exynos_fb_rgb_mode_t { + MODE_RGB_P = 0, + MODE_BGR_P = 1, + MODE_RGB_S = 2, + MODE_BGR_S = 3, +}; + +enum exynos_cpu_auto_cmd_rate { + DISABLE_AUTO_FRM, + PER_TWO_FRM, + PER_FOUR_FRM, + PER_SIX_FRM, + PER_EIGHT_FRM, + PER_TEN_FRM, + PER_TWELVE_FRM, + PER_FOURTEEN_FRM, + PER_SIXTEEN_FRM, + PER_EIGHTEEN_FRM, + PER_TWENTY_FRM, + PER_TWENTY_TWO_FRM, + PER_TWENTY_FOUR_FRM, + PER_TWENTY_SIX_FRM, + PER_TWENTY_EIGHT_FRM, + PER_THIRTY_FRM, +}; + +void exynos_fimd_lcd_init_mem(unsigned long screen_base, unsigned long fb_size, + unsigned long palette_size); +void exynos_fimd_lcd_init(vidinfo_t *vid); +unsigned long exynos_fimd_calc_fbsize(void); + +#endif diff --git a/drivers/video/exynos_fimd.c b/drivers/video/exynos_fimd.c new file mode 100644 index 0000000..2a60fcf --- /dev/null +++ b/drivers/video/exynos_fimd.c @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: InKi Dae + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "exynos_fb.h" + +static unsigned long *lcd_base_addr; +static vidinfo_t *pvid; + +void exynos_fimd_lcd_init_mem(u_long screen_base, u_long fb_size, + u_long palette_size) +{ + lcd_base_addr = (unsigned long *)screen_base; +} + +static void exynos_fimd_set_dualrgb(unsigned int enabled) +{ + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + unsigned int cfg = 0; + + if (enabled) { + cfg = EXYNOS_DUALRGB_BYPASS_DUAL | EXYNOS_DUALRGB_LINESPLIT | + EXYNOS_DUALRGB_VDEN_EN_ENABLE; + + /* in case of Line Split mode, MAIN_CNT doesn't neet to set. */ + cfg |= EXYNOS_DUALRGB_SUB_CNT(pvid->vl_col / 2) | + EXYNOS_DUALRGB_MAIN_CNT(0); + } + + writel(cfg, &fimd_ctrl->dualrgb); +} + +static void exynos_fimd_set_par(unsigned int win_id) +{ + unsigned int cfg = 0; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + /* set window control */ + cfg = readl((unsigned int)&fimd_ctrl->wincon0 + + EXYNOS_WINCON(win_id)); + + cfg &= ~(EXYNOS_WINCON_BITSWP_ENABLE | EXYNOS_WINCON_BYTESWP_ENABLE | + EXYNOS_WINCON_HAWSWP_ENABLE | EXYNOS_WINCON_WSWP_ENABLE | + EXYNOS_WINCON_BURSTLEN_MASK | EXYNOS_WINCON_BPPMODE_MASK | + EXYNOS_WINCON_INRGB_MASK | EXYNOS_WINCON_DATAPATH_MASK); + + /* DATAPATH is DMA */ + cfg |= EXYNOS_WINCON_DATAPATH_DMA; + + /* bpp is 32 */ + cfg |= EXYNOS_WINCON_WSWP_ENABLE; + + /* dma burst is 16 */ + cfg |= EXYNOS_WINCON_BURSTLEN_16WORD; + + /* pixel format is unpacked RGB888 */ + cfg |= EXYNOS_WINCON_BPPMODE_24BPP_888; + + writel(cfg, (unsigned int)&fimd_ctrl->wincon0 + + EXYNOS_WINCON(win_id)); + + /* set window position to x=0, y=0*/ + cfg = EXYNOS_VIDOSD_LEFT_X(0) | EXYNOS_VIDOSD_TOP_Y(0); + writel(cfg, (unsigned int)&fimd_ctrl->vidosd0a + + EXYNOS_VIDOSD(win_id)); + + cfg = EXYNOS_VIDOSD_RIGHT_X(pvid->vl_col - 1) | + EXYNOS_VIDOSD_BOTTOM_Y(pvid->vl_row - 1); + writel(cfg, (unsigned int)&fimd_ctrl->vidosd0b + + EXYNOS_VIDOSD(win_id)); + + /* set window size for window0*/ + cfg = EXYNOS_VIDOSD_SIZE(pvid->vl_col * pvid->vl_row); + writel(cfg, (unsigned int)&fimd_ctrl->vidosd0c + + EXYNOS_VIDOSD(win_id)); +} + +static void exynos_fimd_set_buffer_address(unsigned int win_id) +{ + unsigned long start_addr, end_addr; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + start_addr = (unsigned long)lcd_base_addr; + end_addr = start_addr + ((pvid->vl_col * (pvid->vl_bpix / 8)) * + pvid->vl_row); + + writel(start_addr, (unsigned int)&fimd_ctrl->vidw00add0b0 + + EXYNOS_BUFFER_OFFSET(win_id)); + writel(end_addr, (unsigned int)&fimd_ctrl->vidw00add1b0 + + EXYNOS_BUFFER_OFFSET(win_id)); +} + +static void exynos_fimd_set_clock(vidinfo_t *pvid) +{ + unsigned int cfg = 0, div = 0, remainder, remainder_div; + unsigned long pixel_clock; + unsigned long long src_clock; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + if (pvid->dual_lcd_enabled) { + pixel_clock = pvid->vl_freq * + (pvid->vl_hspw + pvid->vl_hfpd + + pvid->vl_hbpd + pvid->vl_col / 2) * + (pvid->vl_vspw + pvid->vl_vfpd + + pvid->vl_vbpd + pvid->vl_row); + } else if (pvid->interface_mode == FIMD_CPU_INTERFACE) { + pixel_clock = pvid->vl_freq * + pvid->vl_width * pvid->vl_height * + (pvid->cs_setup + pvid->wr_setup + + pvid->wr_act + pvid->wr_hold + 1); + } else { + pixel_clock = pvid->vl_freq * + (pvid->vl_hspw + pvid->vl_hfpd + + pvid->vl_hbpd + pvid->vl_col) * + (pvid->vl_vspw + pvid->vl_vfpd + + pvid->vl_vbpd + pvid->vl_row); + } + + cfg = readl(&fimd_ctrl->vidcon0); + cfg &= ~(EXYNOS_VIDCON0_CLKSEL_MASK | EXYNOS_VIDCON0_CLKVALUP_MASK | + EXYNOS_VIDCON0_CLKVAL_F(0xFF) | EXYNOS_VIDCON0_VCLKEN_MASK | + EXYNOS_VIDCON0_CLKDIR_MASK); + cfg |= (EXYNOS_VIDCON0_CLKSEL_SCLK | EXYNOS_VIDCON0_CLKVALUP_ALWAYS | + EXYNOS_VIDCON0_VCLKEN_NORMAL | EXYNOS_VIDCON0_CLKDIR_DIVIDED); + + if (pixel_clock > MAX_CLOCK) + pixel_clock = MAX_CLOCK; + + src_clock = (unsigned long long) get_lcd_clk(); + + /* get quotient and remainder. */ + remainder = do_div(src_clock, pixel_clock); + div = src_clock; + + remainder *= 10; + remainder_div = remainder / pixel_clock; + + /* round about one places of decimals. */ + if (remainder_div >= 5) + div++; + + /* in case of dual lcd mode. */ + if (pvid->dual_lcd_enabled) + div--; + + cfg |= EXYNOS_VIDCON0_CLKVAL_F(div - 1); + writel(cfg, &fimd_ctrl->vidcon0); +} + +void exynos_set_trigger(void) +{ + unsigned int cfg = 0; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + cfg = readl(&fimd_ctrl->trigcon); + + cfg |= (EXYNOS_I80SOFT_TRIG_EN | EXYNOS_I80START_TRIG); + + writel(cfg, &fimd_ctrl->trigcon); +} + +int exynos_is_i80_frame_done(void) +{ + unsigned int cfg = 0; + int status; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + cfg = readl(&fimd_ctrl->trigcon); + + /* frame done func is valid only when TRIMODE[0] is set to 1. */ + status = (cfg & EXYNOS_I80STATUS_TRIG_DONE) == + EXYNOS_I80STATUS_TRIG_DONE; + + return status; +} + +static void exynos_fimd_lcd_on(void) +{ + unsigned int cfg = 0; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + /* display on */ + cfg = readl(&fimd_ctrl->vidcon0); + cfg |= (EXYNOS_VIDCON0_ENVID_ENABLE | EXYNOS_VIDCON0_ENVID_F_ENABLE); + writel(cfg, &fimd_ctrl->vidcon0); +} + +static void exynos_fimd_window_on(unsigned int win_id) +{ + unsigned int cfg = 0; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + /* enable window */ + cfg = readl((unsigned int)&fimd_ctrl->wincon0 + + EXYNOS_WINCON(win_id)); + cfg |= EXYNOS_WINCON_ENWIN_ENABLE; + writel(cfg, (unsigned int)&fimd_ctrl->wincon0 + + EXYNOS_WINCON(win_id)); + + cfg = readl(&fimd_ctrl->winshmap); + cfg |= EXYNOS_WINSHMAP_CH_ENABLE(win_id); + writel(cfg, &fimd_ctrl->winshmap); +} + +void exynos_fimd_lcd_off(void) +{ + unsigned int cfg = 0; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + cfg = readl(&fimd_ctrl->vidcon0); + cfg &= (EXYNOS_VIDCON0_ENVID_DISABLE | EXYNOS_VIDCON0_ENVID_F_DISABLE); + writel(cfg, &fimd_ctrl->vidcon0); +} + +void exynos_fimd_window_off(unsigned int win_id) +{ + unsigned int cfg = 0; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + cfg = readl((unsigned int)&fimd_ctrl->wincon0 + + EXYNOS_WINCON(win_id)); + cfg &= EXYNOS_WINCON_ENWIN_DISABLE; + writel(cfg, (unsigned int)&fimd_ctrl->wincon0 + + EXYNOS_WINCON(win_id)); + + cfg = readl(&fimd_ctrl->winshmap); + cfg &= ~EXYNOS_WINSHMAP_CH_DISABLE(win_id); + writel(cfg, &fimd_ctrl->winshmap); +} + +void exynos_fimd_lcd_init(vidinfo_t *vid) +{ + unsigned int cfg = 0, rgb_mode; + struct exynos4_fb *fimd_ctrl = + (struct exynos4_fb *)samsung_get_base_fimd(); + + /* store panel info to global variable */ + pvid = vid; + + rgb_mode = MODE_RGB_P; + + if (vid->interface_mode == FIMD_RGB_INTERFACE) { + cfg |= EXYNOS_VIDCON0_VIDOUT_RGB; + writel(cfg, &fimd_ctrl->vidcon0); + + cfg = readl(&fimd_ctrl->vidcon2); + cfg &= ~(EXYNOS_VIDCON2_WB_MASK | + EXYNOS_VIDCON2_TVFORMATSEL_MASK | + EXYNOS_VIDCON2_TVFORMATSEL_YUV_MASK); + cfg |= EXYNOS_VIDCON2_WB_DISABLE; + writel(cfg, &fimd_ctrl->vidcon2); + + /* set polarity */ + cfg = 0; + if (!pvid->vl_clkp) + cfg |= EXYNOS_VIDCON1_IVCLK_RISING_EDGE; + if (!pvid->vl_hsp) + cfg |= EXYNOS_VIDCON1_IHSYNC_INVERT; + if (!pvid->vl_vsp) + cfg |= EXYNOS_VIDCON1_IVSYNC_INVERT; + if (!pvid->vl_dp) + cfg |= EXYNOS_VIDCON1_IVDEN_INVERT; + + writel(cfg, &fimd_ctrl->vidcon1); + + /* set timing */ + cfg = EXYNOS_VIDTCON0_VFPD(pvid->vl_vfpd - 1); + cfg |= EXYNOS_VIDTCON0_VBPD(pvid->vl_vbpd - 1); + cfg |= EXYNOS_VIDTCON0_VSPW(pvid->vl_vspw - 1); + writel(cfg, &fimd_ctrl->vidtcon0); + + cfg = EXYNOS_VIDTCON1_HFPD(pvid->vl_hfpd - 1); + cfg |= EXYNOS_VIDTCON1_HBPD(pvid->vl_hbpd - 1); + cfg |= EXYNOS_VIDTCON1_HSPW(pvid->vl_hspw - 1); + + writel(cfg, &fimd_ctrl->vidtcon1); + + /* set lcd size */ + cfg = EXYNOS_VIDTCON2_HOZVAL(pvid->vl_col - 1); + cfg |= EXYNOS_VIDTCON2_LINEVAL(pvid->vl_row - 1); + + writel(cfg, &fimd_ctrl->vidtcon2); + } + + /* set display mode */ + cfg = readl(&fimd_ctrl->vidcon0); + cfg &= ~EXYNOS_VIDCON0_PNRMODE_MASK; + cfg |= (rgb_mode << EXYNOS_VIDCON0_PNRMODE_SHIFT); + writel(cfg, &fimd_ctrl->vidcon0); + + /* set par */ + exynos_fimd_set_par(pvid->win_id); + + /* set memory address */ + exynos_fimd_set_buffer_address(pvid->win_id); + + /* set buffer size */ + cfg = EXYNOS_VIDADDR_PAGEWIDTH(pvid->vl_col * pvid->vl_bpix / 8); + writel(cfg, (unsigned int)&fimd_ctrl->vidw00add2 + + EXYNOS_BUFFER_SIZE(pvid->win_id)); + + /* set clock */ + exynos_fimd_set_clock(pvid); + + /* set rgb mode to dual lcd. */ + exynos_fimd_set_dualrgb(pvid->dual_lcd_enabled); + + /* display on */ + exynos_fimd_lcd_on(); + + /* window on */ + exynos_fimd_window_on(pvid->win_id); +} + +unsigned long exynos_fimd_calc_fbsize(void) +{ + return pvid->vl_col * pvid->vl_row * (pvid->vl_bpix / 8); +} -- cgit v1.1 From 2c7396cb1a1e05f0f69fe5a5baa79a122d086832 Mon Sep 17 00:00:00 2001 From: Donghwa Lee Date: Thu, 5 Apr 2012 19:36:21 +0000 Subject: EXYNOS: support EXYNOS MIPI DSI interface driver. EXYNOS SoC platform has MIPI-DSI controller and MIPI-DSI based LCD Panel could be used with it. This patch supports MIPI-DSI driver based Samsung SoC chip. LCD panel driver based MIPI-DSI should be registered to MIPI-DSI driver at board file and LCD panel driver specific function registered to mipi_dsim_ddi structure at lcd panel init function called system init. In the MIPI-DSI driver, find lcd panel driver by using registered lcd panel name, and then initialize lcd panel driver. Signed-off-by: Donghwa Lee Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae Acked-by: Anatolij Gustschin --- drivers/video/Makefile | 2 + drivers/video/exynos_mipi_dsi.c | 253 ++++++++++++ drivers/video/exynos_mipi_dsi_common.c | 637 ++++++++++++++++++++++++++++++ drivers/video/exynos_mipi_dsi_common.h | 48 +++ drivers/video/exynos_mipi_dsi_lowlevel.c | 652 +++++++++++++++++++++++++++++++ drivers/video/exynos_mipi_dsi_lowlevel.h | 111 ++++++ 6 files changed, 1703 insertions(+) create mode 100644 drivers/video/exynos_mipi_dsi.c create mode 100644 drivers/video/exynos_mipi_dsi_common.c create mode 100644 drivers/video/exynos_mipi_dsi_common.h create mode 100644 drivers/video/exynos_mipi_dsi_lowlevel.c create mode 100644 drivers/video/exynos_mipi_dsi_lowlevel.h (limited to 'drivers') diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 8896abe..43bc7b4 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -29,6 +29,8 @@ COBJS-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o COBJS-$(CONFIG_ATMEL_LCD) += atmel_lcdfb.o COBJS-$(CONFIG_CFB_CONSOLE) += cfb_console.o COBJS-$(CONFIG_EXYNOS_FB) += exynos_fb.o exynos_fimd.o +COBJS-$(CONFIG_EXYNOS_MIPI_DSIM) += exynos_mipi_dsi.o exynos_mipi_dsi_common.o \ + exynos_mipi_dsi_lowlevel.o COBJS-$(CONFIG_FSL_DIU_FB) += fsl_diu_fb.o videomodes.o COBJS-$(CONFIG_S6E63D6) += s6e63d6.o COBJS-$(CONFIG_SED156X) += sed156x.o diff --git a/drivers/video/exynos_mipi_dsi.c b/drivers/video/exynos_mipi_dsi.c new file mode 100644 index 0000000..aee248c --- /dev/null +++ b/drivers/video/exynos_mipi_dsi.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: InKi Dae + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "exynos_mipi_dsi_lowlevel.h" +#include "exynos_mipi_dsi_common.h" + +#define master_to_driver(a) (a->dsim_lcd_drv) +#define master_to_device(a) (a->dsim_lcd_dev) + +static struct exynos_platform_mipi_dsim *dsim_pd; + +struct mipi_dsim_ddi { + int bus_id; + struct list_head list; + struct mipi_dsim_lcd_device *dsim_lcd_dev; + struct mipi_dsim_lcd_driver *dsim_lcd_drv; +}; + +static LIST_HEAD(dsim_ddi_list); +static LIST_HEAD(dsim_lcd_dev_list); + +int exynos_mipi_dsi_register_lcd_device(struct mipi_dsim_lcd_device *lcd_dev) +{ + struct mipi_dsim_ddi *dsim_ddi; + + if (!lcd_dev) { + debug("mipi_dsim_lcd_device is NULL.\n"); + return -EFAULT; + } + + if (!lcd_dev->name) { + debug("dsim_lcd_device name is NULL.\n"); + return -EFAULT; + } + + dsim_ddi = kzalloc(sizeof(struct mipi_dsim_ddi), GFP_KERNEL); + if (!dsim_ddi) { + debug("failed to allocate dsim_ddi object.\n"); + return -EFAULT; + } + + dsim_ddi->dsim_lcd_dev = lcd_dev; + + list_add_tail(&dsim_ddi->list, &dsim_ddi_list); + + return 0; +} + +struct mipi_dsim_ddi + *exynos_mipi_dsi_find_lcd_device(struct mipi_dsim_lcd_driver *lcd_drv) +{ + struct mipi_dsim_ddi *dsim_ddi; + struct mipi_dsim_lcd_device *lcd_dev; + + list_for_each_entry(dsim_ddi, &dsim_ddi_list, list) { + lcd_dev = dsim_ddi->dsim_lcd_dev; + if (!lcd_dev) + continue; + + if (lcd_drv->id >= 0) { + if ((strcmp(lcd_drv->name, lcd_dev->name)) == 0 && + lcd_drv->id == lcd_dev->id) { + /** + * bus_id would be used to identify + * connected bus. + */ + dsim_ddi->bus_id = lcd_dev->bus_id; + + return dsim_ddi; + } + } else { + if ((strcmp(lcd_drv->name, lcd_dev->name)) == 0) { + /** + * bus_id would be used to identify + * connected bus. + */ + dsim_ddi->bus_id = lcd_dev->bus_id; + + return dsim_ddi; + } + } + + kfree(dsim_ddi); + list_del(&dsim_ddi_list); + } + + return NULL; +} + +int exynos_mipi_dsi_register_lcd_driver(struct mipi_dsim_lcd_driver *lcd_drv) +{ + struct mipi_dsim_ddi *dsim_ddi; + + if (!lcd_drv) { + debug("mipi_dsim_lcd_driver is NULL.\n"); + return -EFAULT; + } + + if (!lcd_drv->name) { + debug("dsim_lcd_driver name is NULL.\n"); + return -EFAULT; + } + + dsim_ddi = exynos_mipi_dsi_find_lcd_device(lcd_drv); + if (!dsim_ddi) { + debug("mipi_dsim_ddi object not found.\n"); + return -EFAULT; + } + + dsim_ddi->dsim_lcd_drv = lcd_drv; + + debug("registered panel driver(%s) to mipi-dsi driver.\n", + lcd_drv->name); + + return 0; + +} + +struct mipi_dsim_ddi + *exynos_mipi_dsi_bind_lcd_ddi(struct mipi_dsim_device *dsim, + const char *name) +{ + struct mipi_dsim_ddi *dsim_ddi; + struct mipi_dsim_lcd_driver *lcd_drv; + struct mipi_dsim_lcd_device *lcd_dev; + + list_for_each_entry(dsim_ddi, &dsim_ddi_list, list) { + lcd_drv = dsim_ddi->dsim_lcd_drv; + lcd_dev = dsim_ddi->dsim_lcd_dev; + if (!lcd_drv || !lcd_dev) + continue; + + debug("lcd_drv->id = %d, lcd_dev->id = %d\n", + lcd_drv->id, lcd_dev->id); + + if ((strcmp(lcd_drv->name, name) == 0)) { + lcd_dev->master = dsim; + + dsim->dsim_lcd_dev = lcd_dev; + dsim->dsim_lcd_drv = lcd_drv; + + return dsim_ddi; + } + } + + return NULL; +} + +/* define MIPI-DSI Master operations. */ +static struct mipi_dsim_master_ops master_ops = { + .cmd_write = exynos_mipi_dsi_wr_data, + .get_dsim_frame_done = exynos_mipi_dsi_get_frame_done_status, + .clear_dsim_frame_done = exynos_mipi_dsi_clear_frame_done, +}; + +int exynos_mipi_dsi_init(void) +{ + struct mipi_dsim_device *dsim; + struct mipi_dsim_config *dsim_config; + struct mipi_dsim_ddi *dsim_ddi; + + dsim = kzalloc(sizeof(struct mipi_dsim_device), GFP_KERNEL); + if (!dsim) { + debug("failed to allocate dsim object.\n"); + return -EFAULT; + } + + /* get mipi_dsim_config. */ + dsim_config = dsim_pd->dsim_config; + if (dsim_config == NULL) { + debug("failed to get dsim config data.\n"); + return -EFAULT; + } + + dsim->pd = dsim_pd; + dsim->dsim_config = dsim_config; + dsim->master_ops = &master_ops; + + /* bind lcd ddi matched with panel name. */ + dsim_ddi = exynos_mipi_dsi_bind_lcd_ddi(dsim, dsim_pd->lcd_panel_name); + if (!dsim_ddi) { + debug("mipi_dsim_ddi object not found.\n"); + return -ENOSYS; + } + if (dsim_pd->lcd_power) + dsim_pd->lcd_power(); + + if (dsim_pd->mipi_power) + dsim_pd->mipi_power(); + + /* phy_enable(unsigned int dev_index, unsigned int enable) */ + if (dsim_pd->phy_enable) + dsim_pd->phy_enable(0, 1); + + set_mipi_clk(); + + exynos_mipi_dsi_init_dsim(dsim); + exynos_mipi_dsi_init_link(dsim); + exynos_mipi_dsi_set_hs_enable(dsim); + + /* set display timing. */ + exynos_mipi_dsi_set_display_mode(dsim, dsim->dsim_config); + + /* initialize mipi-dsi client(lcd panel). */ + if (dsim_ddi->dsim_lcd_drv && dsim_ddi->dsim_lcd_drv->mipi_panel_init) { + dsim_ddi->dsim_lcd_drv->mipi_panel_init(dsim); + dsim_ddi->dsim_lcd_drv->mipi_display_on(dsim); + } + + debug("mipi-dsi driver(%s mode) has been probed.\n", + (dsim_config->e_interface == DSIM_COMMAND) ? + "CPU" : "RGB"); + + return 0; +} + +void exynos_set_dsim_platform_data(struct exynos_platform_mipi_dsim *pd) +{ + if (pd == NULL) { + debug("pd is NULL\n"); + return; + } + + dsim_pd = pd; +} diff --git a/drivers/video/exynos_mipi_dsi_common.c b/drivers/video/exynos_mipi_dsi_common.c new file mode 100644 index 0000000..6eeb464 --- /dev/null +++ b/drivers/video/exynos_mipi_dsi_common.c @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: InKi Dae + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include "exynos_mipi_dsi_lowlevel.h" + +#define MHZ (1000 * 1000) +#define FIN_HZ (24 * MHZ) + +#define DFIN_PLL_MIN_HZ (6 * MHZ) +#define DFIN_PLL_MAX_HZ (12 * MHZ) + +#define DFVCO_MIN_HZ (500 * MHZ) +#define DFVCO_MAX_HZ (1000 * MHZ) + +#define TRY_GET_FIFO_TIMEOUT (5000 * 2) + +/* MIPI-DSIM status types. */ +enum { + DSIM_STATE_INIT, /* should be initialized. */ + DSIM_STATE_STOP, /* CPU and LCDC are LP mode. */ + DSIM_STATE_HSCLKEN, /* HS clock was enabled. */ + DSIM_STATE_ULPS +}; + +/* define DSI lane types. */ +enum { + DSIM_LANE_CLOCK = (1 << 0), + DSIM_LANE_DATA0 = (1 << 1), + DSIM_LANE_DATA1 = (1 << 2), + DSIM_LANE_DATA2 = (1 << 3), + DSIM_LANE_DATA3 = (1 << 4) +}; + +static unsigned int dpll_table[15] = { + 100, 120, 170, 220, 270, + 320, 390, 450, 510, 560, + 640, 690, 770, 870, 950 +}; + +static void exynos_mipi_dsi_long_data_wr(struct mipi_dsim_device *dsim, + unsigned int data0, unsigned int data1) +{ + unsigned int data_cnt = 0, payload = 0; + + /* in case that data count is more then 4 */ + for (data_cnt = 0; data_cnt < data1; data_cnt += 4) { + /* + * after sending 4bytes per one time, + * send remainder data less then 4. + */ + if ((data1 - data_cnt) < 4) { + if ((data1 - data_cnt) == 3) { + payload = *(u8 *)(data0 + data_cnt) | + (*(u8 *)(data0 + (data_cnt + 1))) << 8 | + (*(u8 *)(data0 + (data_cnt + 2))) << 16; + debug("count = 3 payload = %x, %x %x %x\n", + payload, *(u8 *)(data0 + data_cnt), + *(u8 *)(data0 + (data_cnt + 1)), + *(u8 *)(data0 + (data_cnt + 2))); + } else if ((data1 - data_cnt) == 2) { + payload = *(u8 *)(data0 + data_cnt) | + (*(u8 *)(data0 + (data_cnt + 1))) << 8; + debug("count = 2 payload = %x, %x %x\n", payload, + *(u8 *)(data0 + data_cnt), + *(u8 *)(data0 + (data_cnt + 1))); + } else if ((data1 - data_cnt) == 1) { + payload = *(u8 *)(data0 + data_cnt); + } + } else { + /* send 4bytes per one time. */ + payload = *(u8 *)(data0 + data_cnt) | + (*(u8 *)(data0 + (data_cnt + 1))) << 8 | + (*(u8 *)(data0 + (data_cnt + 2))) << 16 | + (*(u8 *)(data0 + (data_cnt + 3))) << 24; + + debug("count = 4 payload = %x, %x %x %x %x\n", + payload, *(u8 *)(data0 + data_cnt), + *(u8 *)(data0 + (data_cnt + 1)), + *(u8 *)(data0 + (data_cnt + 2)), + *(u8 *)(data0 + (data_cnt + 3))); + + } + exynos_mipi_dsi_wr_tx_data(dsim, payload); + } +} + +int exynos_mipi_dsi_wr_data(struct mipi_dsim_device *dsim, unsigned int data_id, + unsigned int data0, unsigned int data1) +{ + unsigned int timeout = TRY_GET_FIFO_TIMEOUT; + unsigned long delay_val, delay; + unsigned int check_rx_ack = 0; + + if (dsim->state == DSIM_STATE_ULPS) { + debug("state is ULPS.\n"); + + return -EINVAL; + } + + delay_val = MHZ / dsim->dsim_config->esc_clk; + delay = 10 * delay_val; + + mdelay(delay); + + /* only if transfer mode is LPDT, wait SFR becomes empty. */ + if (dsim->state == DSIM_STATE_STOP) { + while (!(exynos_mipi_dsi_get_fifo_state(dsim) & + SFR_HEADER_EMPTY)) { + if ((timeout--) > 0) + mdelay(1); + else { + debug("SRF header fifo is not empty.\n"); + return -EINVAL; + } + } + } + + switch (data_id) { + /* short packet types of packet types for command. */ + case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM: + case MIPI_DSI_DCS_SHORT_WRITE: + case MIPI_DSI_DCS_SHORT_WRITE_PARAM: + case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE: + debug("data0 = %x data1 = %x\n", + data0, data1); + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0, data1); + if (check_rx_ack) { + /* process response func should be implemented */ + return 0; + } else { + return -EINVAL; + } + + /* general command */ + case MIPI_DSI_COLOR_MODE_OFF: + case MIPI_DSI_COLOR_MODE_ON: + case MIPI_DSI_SHUTDOWN_PERIPHERAL: + case MIPI_DSI_TURN_ON_PERIPHERAL: + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0, data1); + if (check_rx_ack) { + /* process response func should be implemented. */ + return 0; + } else { + return -EINVAL; + } + + /* packet types for video data */ + case MIPI_DSI_V_SYNC_START: + case MIPI_DSI_V_SYNC_END: + case MIPI_DSI_H_SYNC_START: + case MIPI_DSI_H_SYNC_END: + case MIPI_DSI_END_OF_TRANSMISSION: + return 0; + + /* short and response packet types for command */ + case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM: + case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM: + case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM: + case MIPI_DSI_DCS_READ: + exynos_mipi_dsi_clear_all_interrupt(dsim); + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0, data1); + /* process response func should be implemented. */ + return 0; + + /* long packet type and null packet */ + case MIPI_DSI_NULL_PACKET: + case MIPI_DSI_BLANKING_PACKET: + return 0; + case MIPI_DSI_GENERIC_LONG_WRITE: + case MIPI_DSI_DCS_LONG_WRITE: + { + unsigned int data_cnt = 0, payload = 0; + + /* if data count is less then 4, then send 3bytes data. */ + if (data1 < 4) { + payload = *(u8 *)(data0) | + *(u8 *)(data0 + 1) << 8 | + *(u8 *)(data0 + 2) << 16; + + exynos_mipi_dsi_wr_tx_data(dsim, payload); + + debug("count = %d payload = %x,%x %x %x\n", + data1, payload, + *(u8 *)(data0 + data_cnt), + *(u8 *)(data0 + (data_cnt + 1)), + *(u8 *)(data0 + (data_cnt + 2))); + } else { + /* in case that data count is more then 4 */ + exynos_mipi_dsi_long_data_wr(dsim, data0, data1); + } + + /* put data into header fifo */ + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data1 & 0xff, + (data1 & 0xff00) >> 8); + + } + if (check_rx_ack) + /* process response func should be implemented. */ + return 0; + else + return -EINVAL; + + /* packet typo for video data */ + case MIPI_DSI_PACKED_PIXEL_STREAM_16: + case MIPI_DSI_PACKED_PIXEL_STREAM_18: + case MIPI_DSI_PIXEL_STREAM_3BYTE_18: + case MIPI_DSI_PACKED_PIXEL_STREAM_24: + if (check_rx_ack) { + /* process response func should be implemented. */ + return 0; + } else { + return -EINVAL; + } + default: + debug("data id %x is not supported current DSI spec.\n", + data_id); + + return -EINVAL; + } + + return 0; +} + +int exynos_mipi_dsi_pll_on(struct mipi_dsim_device *dsim, unsigned int enable) +{ + int sw_timeout; + + if (enable) { + sw_timeout = 1000; + + exynos_mipi_dsi_clear_interrupt(dsim); + exynos_mipi_dsi_enable_pll(dsim, 1); + while (1) { + sw_timeout--; + if (exynos_mipi_dsi_is_pll_stable(dsim)) + return 0; + if (sw_timeout == 0) + return -EINVAL; + } + } else + exynos_mipi_dsi_enable_pll(dsim, 0); + + return 0; +} + +unsigned long exynos_mipi_dsi_change_pll(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler) +{ + unsigned long dfin_pll, dfvco, dpll_out; + unsigned int i, freq_band = 0xf; + + dfin_pll = (FIN_HZ / pre_divider); + + /****************************************************** + * Serial Clock(=ByteClk X 8) FreqBand[3:0] * + ****************************************************** + * ~ 99.99 MHz 0000 + * 100 ~ 119.99 MHz 0001 + * 120 ~ 159.99 MHz 0010 + * 160 ~ 199.99 MHz 0011 + * 200 ~ 239.99 MHz 0100 + * 140 ~ 319.99 MHz 0101 + * 320 ~ 389.99 MHz 0110 + * 390 ~ 449.99 MHz 0111 + * 450 ~ 509.99 MHz 1000 + * 510 ~ 559.99 MHz 1001 + * 560 ~ 639.99 MHz 1010 + * 640 ~ 689.99 MHz 1011 + * 690 ~ 769.99 MHz 1100 + * 770 ~ 869.99 MHz 1101 + * 870 ~ 949.99 MHz 1110 + * 950 ~ 1000 MHz 1111 + ******************************************************/ + if (dfin_pll < DFIN_PLL_MIN_HZ || dfin_pll > DFIN_PLL_MAX_HZ) { + debug("fin_pll range should be 6MHz ~ 12MHz\n"); + exynos_mipi_dsi_enable_afc(dsim, 0, 0); + } else { + if (dfin_pll < 7 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x1); + else if (dfin_pll < 8 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x0); + else if (dfin_pll < 9 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x3); + else if (dfin_pll < 10 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x2); + else if (dfin_pll < 11 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x5); + else + exynos_mipi_dsi_enable_afc(dsim, 1, 0x4); + } + + dfvco = dfin_pll * main_divider; + debug("dfvco = %lu, dfin_pll = %lu, main_divider = %d\n", + dfvco, dfin_pll, main_divider); + if (dfvco < DFVCO_MIN_HZ || dfvco > DFVCO_MAX_HZ) + debug("fvco range should be 500MHz ~ 1000MHz\n"); + + dpll_out = dfvco / (1 << scaler); + debug("dpll_out = %lu, dfvco = %lu, scaler = %d\n", + dpll_out, dfvco, scaler); + + for (i = 0; i < ARRAY_SIZE(dpll_table); i++) { + if (dpll_out < dpll_table[i] * MHZ) { + freq_band = i; + break; + } + } + + debug("freq_band = %d\n", freq_band); + + exynos_mipi_dsi_pll_freq(dsim, pre_divider, main_divider, scaler); + + exynos_mipi_dsi_hs_zero_ctrl(dsim, 0); + exynos_mipi_dsi_prep_ctrl(dsim, 0); + + /* Freq Band */ + exynos_mipi_dsi_pll_freq_band(dsim, freq_band); + + /* Stable time */ + exynos_mipi_dsi_pll_stable_time(dsim, + dsim->dsim_config->pll_stable_time); + + /* Enable PLL */ + debug("FOUT of mipi dphy pll is %luMHz\n", + (dpll_out / MHZ)); + + return dpll_out; +} + +int exynos_mipi_dsi_set_clock(struct mipi_dsim_device *dsim, + unsigned int byte_clk_sel, unsigned int enable) +{ + unsigned int esc_div; + unsigned long esc_clk_error_rate; + unsigned long hs_clk = 0, byte_clk = 0, escape_clk = 0; + + if (enable) { + dsim->e_clk_src = byte_clk_sel; + + /* Escape mode clock and byte clock source */ + exynos_mipi_dsi_set_byte_clock_src(dsim, byte_clk_sel); + + /* DPHY, DSIM Link : D-PHY clock out */ + if (byte_clk_sel == DSIM_PLL_OUT_DIV8) { + hs_clk = exynos_mipi_dsi_change_pll(dsim, + dsim->dsim_config->p, dsim->dsim_config->m, + dsim->dsim_config->s); + if (hs_clk == 0) { + debug("failed to get hs clock.\n"); + return -EINVAL; + } + + byte_clk = hs_clk / 8; + exynos_mipi_dsi_enable_pll_bypass(dsim, 0); + exynos_mipi_dsi_pll_on(dsim, 1); + /* DPHY : D-PHY clock out, DSIM link : external clock out */ + } else if (byte_clk_sel == DSIM_EXT_CLK_DIV8) + debug("not support EXT CLK source for MIPI DSIM\n"); + else if (byte_clk_sel == DSIM_EXT_CLK_BYPASS) + debug("not support EXT CLK source for MIPI DSIM\n"); + + /* escape clock divider */ + esc_div = byte_clk / (dsim->dsim_config->esc_clk); + debug("esc_div = %d, byte_clk = %lu, esc_clk = %lu\n", + esc_div, byte_clk, dsim->dsim_config->esc_clk); + if ((byte_clk / esc_div) >= (20 * MHZ) || + (byte_clk / esc_div) > dsim->dsim_config->esc_clk) + esc_div += 1; + + escape_clk = byte_clk / esc_div; + debug("escape_clk = %lu, byte_clk = %lu, esc_div = %d\n", + escape_clk, byte_clk, esc_div); + + /* enable escape clock. */ + exynos_mipi_dsi_enable_byte_clock(dsim, 1); + + /* enable byte clk and escape clock */ + exynos_mipi_dsi_set_esc_clk_prs(dsim, 1, esc_div); + /* escape clock on lane */ + exynos_mipi_dsi_enable_esc_clk_on_lane(dsim, + (DSIM_LANE_CLOCK | dsim->data_lane), 1); + + debug("byte clock is %luMHz\n", + (byte_clk / MHZ)); + debug("escape clock that user's need is %lu\n", + (dsim->dsim_config->esc_clk / MHZ)); + debug("escape clock divider is %x\n", esc_div); + debug("escape clock is %luMHz\n", + ((byte_clk / esc_div) / MHZ)); + + if ((byte_clk / esc_div) > escape_clk) { + esc_clk_error_rate = escape_clk / + (byte_clk / esc_div); + debug("error rate is %lu over.\n", + (esc_clk_error_rate / 100)); + } else if ((byte_clk / esc_div) < (escape_clk)) { + esc_clk_error_rate = (byte_clk / esc_div) / + escape_clk; + debug("error rate is %lu under.\n", + (esc_clk_error_rate / 100)); + } + } else { + exynos_mipi_dsi_enable_esc_clk_on_lane(dsim, + (DSIM_LANE_CLOCK | dsim->data_lane), 0); + exynos_mipi_dsi_set_esc_clk_prs(dsim, 0, 0); + + /* disable escape clock. */ + exynos_mipi_dsi_enable_byte_clock(dsim, 0); + + if (byte_clk_sel == DSIM_PLL_OUT_DIV8) + exynos_mipi_dsi_pll_on(dsim, 0); + } + + return 0; +} + +int exynos_mipi_dsi_init_dsim(struct mipi_dsim_device *dsim) +{ + dsim->state = DSIM_STATE_INIT; + + switch (dsim->dsim_config->e_no_data_lane) { + case DSIM_DATA_LANE_1: + dsim->data_lane = DSIM_LANE_DATA0; + break; + case DSIM_DATA_LANE_2: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1; + break; + case DSIM_DATA_LANE_3: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 | + DSIM_LANE_DATA2; + break; + case DSIM_DATA_LANE_4: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 | + DSIM_LANE_DATA2 | DSIM_LANE_DATA3; + break; + default: + debug("data lane is invalid.\n"); + return -EINVAL; + }; + + exynos_mipi_dsi_sw_reset(dsim); + exynos_mipi_dsi_dp_dn_swap(dsim, 0); + + return 0; +} + +int exynos_mipi_dsi_enable_frame_done_int(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + /* enable only frame done interrupt */ + exynos_mipi_dsi_set_interrupt_mask(dsim, INTMSK_FRAME_DONE, enable); + + return 0; +} + +static void convert_to_fb_videomode(struct fb_videomode *mode1, + vidinfo_t *mode2) +{ + mode1->xres = mode2->vl_width; + mode1->yres = mode2->vl_height; + mode1->upper_margin = mode2->vl_vfpd; + mode1->lower_margin = mode2->vl_vbpd; + mode1->left_margin = mode2->vl_hfpd; + mode1->right_margin = mode2->vl_hbpd; + mode1->vsync_len = mode2->vl_vspw; + mode1->hsync_len = mode2->vl_hspw; +} + +int exynos_mipi_dsi_set_display_mode(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config) +{ + struct exynos_platform_mipi_dsim *dsim_pd; + struct fb_videomode lcd_video; + vidinfo_t *vid; + + dsim_pd = (struct exynos_platform_mipi_dsim *)dsim->pd; + vid = (vidinfo_t *)dsim_pd->lcd_panel_info; + + convert_to_fb_videomode(&lcd_video, vid); + + /* in case of VIDEO MODE (RGB INTERFACE), it sets polarities. */ + if (dsim->dsim_config->e_interface == (u32) DSIM_VIDEO) { + if (dsim->dsim_config->auto_vertical_cnt == 0) { + exynos_mipi_dsi_set_main_disp_vporch(dsim, + vid->vl_cmd_allow_len, + lcd_video.upper_margin, + lcd_video.lower_margin); + exynos_mipi_dsi_set_main_disp_hporch(dsim, + lcd_video.left_margin, + lcd_video.right_margin); + exynos_mipi_dsi_set_main_disp_sync_area(dsim, + lcd_video.vsync_len, + lcd_video.hsync_len); + } + } + + exynos_mipi_dsi_set_main_disp_resol(dsim, lcd_video.xres, + lcd_video.yres); + + exynos_mipi_dsi_display_config(dsim, dsim->dsim_config); + + debug("lcd panel ==> width = %d, height = %d\n", + lcd_video.xres, lcd_video.yres); + + return 0; +} + +int exynos_mipi_dsi_init_link(struct mipi_dsim_device *dsim) +{ + unsigned int time_out = 100; + + switch (dsim->state) { + case DSIM_STATE_INIT: + exynos_mipi_dsi_init_fifo_pointer(dsim, 0x1f); + + /* dsi configuration */ + exynos_mipi_dsi_init_config(dsim); + exynos_mipi_dsi_enable_lane(dsim, DSIM_LANE_CLOCK, 1); + exynos_mipi_dsi_enable_lane(dsim, dsim->data_lane, 1); + + /* set clock configuration */ + exynos_mipi_dsi_set_clock(dsim, + dsim->dsim_config->e_byte_clk, 1); + + /* check clock and data lane state are stop state */ + while (!(exynos_mipi_dsi_is_lane_state(dsim))) { + time_out--; + if (time_out == 0) { + debug("DSI Master is not stop state.\n"); + debug("Check initialization process\n"); + + return -EINVAL; + } + } + + dsim->state = DSIM_STATE_STOP; + + /* BTA sequence counters */ + exynos_mipi_dsi_set_stop_state_counter(dsim, + dsim->dsim_config->stop_holding_cnt); + exynos_mipi_dsi_set_bta_timeout(dsim, + dsim->dsim_config->bta_timeout); + exynos_mipi_dsi_set_lpdr_timeout(dsim, + dsim->dsim_config->rx_timeout); + + return 0; + default: + debug("DSI Master is already init.\n"); + return 0; + } + + return 0; +} + +int exynos_mipi_dsi_set_hs_enable(struct mipi_dsim_device *dsim) +{ + if (dsim->state == DSIM_STATE_STOP) { + if (dsim->e_clk_src != DSIM_EXT_CLK_BYPASS) { + dsim->state = DSIM_STATE_HSCLKEN; + + /* set LCDC and CPU transfer mode to HS. */ + exynos_mipi_dsi_set_lcdc_transfer_mode(dsim, 0); + exynos_mipi_dsi_set_cpu_transfer_mode(dsim, 0); + + exynos_mipi_dsi_enable_hs_clock(dsim, 1); + + return 0; + } else + debug("clock source is external bypass.\n"); + } else + debug("DSIM is not stop state.\n"); + + return 0; +} + +int exynos_mipi_dsi_set_data_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int mode) +{ + if (mode) { + if (dsim->state != DSIM_STATE_HSCLKEN) { + debug("HS Clock lane is not enabled.\n"); + return -EINVAL; + } + + exynos_mipi_dsi_set_lcdc_transfer_mode(dsim, 0); + } else { + if (dsim->state == DSIM_STATE_INIT || dsim->state == + DSIM_STATE_ULPS) { + debug("DSI Master is not STOP or HSDT state.\n"); + return -EINVAL; + } + + exynos_mipi_dsi_set_cpu_transfer_mode(dsim, 0); + } + + return 0; +} + +int exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim) +{ + return _exynos_mipi_dsi_get_frame_done_status(dsim); +} + +int exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim) +{ + _exynos_mipi_dsi_clear_frame_done(dsim); + + return 0; +} diff --git a/drivers/video/exynos_mipi_dsi_common.h b/drivers/video/exynos_mipi_dsi_common.h new file mode 100644 index 0000000..4d80679 --- /dev/null +++ b/drivers/video/exynos_mipi_dsi_common.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: InKi Dae + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include + +#ifndef _EXYNOS_MIPI_DSI_COMMON_H +#define _EXYNOS_MIPI_DSI_COMMON_H + +int exynos_mipi_dsi_wr_data(struct mipi_dsim_device *dsim, unsigned int data_id, + unsigned int data0, unsigned int data1); +int exynos_mipi_dsi_pll_on(struct mipi_dsim_device *dsim, unsigned int enable); +unsigned long exynos_mipi_dsi_change_pll(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler); +int exynos_mipi_dsi_set_clock(struct mipi_dsim_device *dsim, + unsigned int byte_clk_sel, unsigned int enable); +int exynos_mipi_dsi_init_dsim(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_set_display_mode(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_info); +int exynos_mipi_dsi_init_link(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_set_hs_enable(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_set_data_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int mode); +int exynos_mipi_dsi_enable_frame_done_int(struct mipi_dsim_device *dsim, + unsigned int enable); +int exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim); + +#endif /* _EXYNOS_MIPI_DSI_COMMON_H */ diff --git a/drivers/video/exynos_mipi_dsi_lowlevel.c b/drivers/video/exynos_mipi_dsi_lowlevel.c new file mode 100644 index 0000000..d61b773 --- /dev/null +++ b/drivers/video/exynos_mipi_dsi_lowlevel.c @@ -0,0 +1,652 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: InKi Dae + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include "exynos_mipi_dsi_lowlevel.h" +#include "exynos_mipi_dsi_common.h" + +void exynos_mipi_dsi_func_reset(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = readl(&mipi_dsim->swrst); + + reg |= DSIM_FUNCRST; + + writel(reg, &mipi_dsim->swrst); +} + +void exynos_mipi_dsi_sw_reset(struct mipi_dsim_device *dsim) +{ + unsigned int reg = 0; + + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = readl(&mipi_dsim->swrst); + + reg |= DSIM_SWRST; + reg |= DSIM_FUNCRST; + + writel(reg, &mipi_dsim->swrst); +} + +void exynos_mipi_dsi_sw_release(struct mipi_dsim_device *dsim) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->intsrc); + + reg |= INTSRC_SWRST_RELEASE; + + writel(reg, &mipi_dsim->intsrc); +} + +void exynos_mipi_dsi_set_interrupt_mask(struct mipi_dsim_device *dsim, + unsigned int mode, unsigned int mask) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->intmsk); + + if (mask) + reg |= mode; + else + reg &= ~mode; + + writel(reg, &mipi_dsim->intmsk); +} + +void exynos_mipi_dsi_init_fifo_pointer(struct mipi_dsim_device *dsim, + unsigned int cfg) +{ + unsigned int reg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = readl(&mipi_dsim->fifoctrl); + + writel(reg & ~(cfg), &mipi_dsim->fifoctrl); + udelay(10 * 1000); + reg |= cfg; + + writel(reg, &mipi_dsim->fifoctrl); +} + +/* + * this function set PLL P, M and S value in D-PHY + */ +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + writel(DSIM_AFC_CTL(value), &mipi_dsim->phyacchr); +} + +void exynos_mipi_dsi_set_main_disp_resol(struct mipi_dsim_device *dsim, + unsigned int width_resol, unsigned int height_resol) +{ + unsigned int reg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + /* standby should be set after configuration so set to not ready*/ + reg = (readl(&mipi_dsim->mdresol)) & ~(DSIM_MAIN_STAND_BY); + writel(reg, &mipi_dsim->mdresol); + + /* reset resolution */ + reg &= ~(DSIM_MAIN_VRESOL(0x7ff) | DSIM_MAIN_HRESOL(0x7ff)); + reg |= DSIM_MAIN_VRESOL(height_resol) | DSIM_MAIN_HRESOL(width_resol); + + reg |= DSIM_MAIN_STAND_BY; + writel(reg, &mipi_dsim->mdresol); +} + +void exynos_mipi_dsi_set_main_disp_vporch(struct mipi_dsim_device *dsim, + unsigned int cmd_allow, unsigned int vfront, unsigned int vback) +{ + unsigned int reg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = (readl(&mipi_dsim->mvporch)) & + ~((DSIM_CMD_ALLOW_MASK) | (DSIM_STABLE_VFP_MASK) | + (DSIM_MAIN_VBP_MASK)); + + reg |= ((cmd_allow & 0xf) << DSIM_CMD_ALLOW_SHIFT) | + ((vfront & 0x7ff) << DSIM_STABLE_VFP_SHIFT) | + ((vback & 0x7ff) << DSIM_MAIN_VBP_SHIFT); + + writel(reg, &mipi_dsim->mvporch); +} + +void exynos_mipi_dsi_set_main_disp_hporch(struct mipi_dsim_device *dsim, + unsigned int front, unsigned int back) +{ + unsigned int reg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = (readl(&mipi_dsim->mhporch)) & + ~((DSIM_MAIN_HFP_MASK) | (DSIM_MAIN_HBP_MASK)); + + reg |= (front << DSIM_MAIN_HFP_SHIFT) | (back << DSIM_MAIN_HBP_SHIFT); + + writel(reg, &mipi_dsim->mhporch); +} + +void exynos_mipi_dsi_set_main_disp_sync_area(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori) +{ + unsigned int reg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = (readl(&mipi_dsim->msync)) & + ~((DSIM_MAIN_VSA_MASK) | (DSIM_MAIN_HSA_MASK)); + + reg |= ((vert & 0x3ff) << DSIM_MAIN_VSA_SHIFT) | + (hori << DSIM_MAIN_HSA_SHIFT); + + writel(reg, &mipi_dsim->msync); +} + +void exynos_mipi_dsi_set_sub_disp_resol(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori) +{ + unsigned int reg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = (readl(&mipi_dsim->sdresol)) & + ~(DSIM_SUB_STANDY_MASK); + + writel(reg, &mipi_dsim->sdresol); + + reg &= ~(DSIM_SUB_VRESOL_MASK) | ~(DSIM_SUB_HRESOL_MASK); + reg |= ((vert & 0x7ff) << DSIM_SUB_VRESOL_SHIFT) | + ((hori & 0x7ff) << DSIM_SUB_HRESOL_SHIFT); + writel(reg, &mipi_dsim->sdresol); + + /* DSIM STANDBY */ + reg |= (1 << DSIM_SUB_STANDY_SHIFT); + writel(reg, &mipi_dsim->sdresol); +} + +void exynos_mipi_dsi_init_config(struct mipi_dsim_device *dsim) +{ + struct mipi_dsim_config *dsim_config = dsim->dsim_config; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int cfg = (readl(&mipi_dsim->config)) & + ~((1 << DSIM_EOT_PACKET_SHIFT) | + (0x1f << DSIM_HSA_MODE_SHIFT) | + (0x3 << DSIM_NUM_OF_DATALANE_SHIFT)); + + cfg |= (dsim_config->auto_flush << DSIM_AUTO_FLUSH_SHIFT) | + (dsim_config->eot_disable << DSIM_EOT_PACKET_SHIFT) | + (dsim_config->auto_vertical_cnt << DSIM_AUTO_MODE_SHIFT) | + (dsim_config->hse << DSIM_HSE_MODE_SHIFT) | + (dsim_config->hfp << DSIM_HFP_MODE_SHIFT) | + (dsim_config->hbp << DSIM_HBP_MODE_SHIFT) | + (dsim_config->hsa << DSIM_HSA_MODE_SHIFT) | + (dsim_config->e_no_data_lane << DSIM_NUM_OF_DATALANE_SHIFT); + + writel(cfg, &mipi_dsim->config); +} + +void exynos_mipi_dsi_display_config(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + u32 reg = (readl(&mipi_dsim->config)) & + ~((0x3 << DSIM_BURST_MODE_SHIFT) | (1 << DSIM_VIDEO_MODE_SHIFT) + | (0x3 << DSIM_MAINVC_SHIFT) | (0x7 << DSIM_MAINPIX_SHIFT) + | (0x3 << DSIM_SUBVC_SHIFT) | (0x7 << DSIM_SUBPIX_SHIFT)); + + if (dsim_config->e_interface == DSIM_VIDEO) + reg |= (1 << DSIM_VIDEO_MODE_SHIFT); + else if (dsim_config->e_interface == DSIM_COMMAND) + reg &= ~(1 << DSIM_VIDEO_MODE_SHIFT); + else { + printf("unknown lcd type.\n"); + return; + } + + /* main lcd */ + reg |= ((u8) (dsim_config->e_burst_mode) & 0x3) << DSIM_BURST_MODE_SHIFT + | ((u8) (dsim_config->e_virtual_ch) & 0x3) << DSIM_MAINVC_SHIFT + | ((u8) (dsim_config->e_pixel_format) & 0x7) << DSIM_MAINPIX_SHIFT; + + writel(reg, &mipi_dsim->config); +} + +void exynos_mipi_dsi_enable_lane(struct mipi_dsim_device *dsim, + unsigned int lane, unsigned int enable) +{ + unsigned int reg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = readl(&mipi_dsim->config); + + if (enable) + reg |= DSIM_LANE_ENx(lane); + else + reg &= ~DSIM_LANE_ENx(lane); + + writel(reg, &mipi_dsim->config); +} + +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count) +{ + unsigned int cfg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + /* get the data lane number. */ + cfg = DSIM_NUM_OF_DATA_LANE(count); + + writel(cfg, &mipi_dsim->config); +} + +void exynos_mipi_dsi_enable_afc(struct mipi_dsim_device *dsim, + unsigned int enable, unsigned int afc_code) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->phyacchr); + + reg = 0; + + if (enable) { + reg |= DSIM_AFC_EN; + reg &= ~(0x7 << DSIM_AFC_CTL_SHIFT); + reg |= DSIM_AFC_CTL(afc_code); + } else + reg &= ~DSIM_AFC_EN; + + writel(reg, &mipi_dsim->phyacchr); +} + +void exynos_mipi_dsi_enable_pll_bypass(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->clkctrl)) & + ~(DSIM_PLL_BYPASS_EXTERNAL); + + reg |= enable << DSIM_PLL_BYPASS_SHIFT; + + writel(reg, &mipi_dsim->clkctrl); +} + +void exynos_mipi_dsi_pll_freq_band(struct mipi_dsim_device *dsim, + unsigned int freq_band) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->pllctrl)) & + ~(0x1f << DSIM_FREQ_BAND_SHIFT); + + reg |= ((freq_band & 0x1f) << DSIM_FREQ_BAND_SHIFT); + + writel(reg, &mipi_dsim->pllctrl); +} + +void exynos_mipi_dsi_pll_freq(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->pllctrl)) & + ~(0x7ffff << 1); + + reg |= ((pre_divider & 0x3f) << DSIM_PREDIV_SHIFT) | + ((main_divider & 0x1ff) << DSIM_MAIN_SHIFT) | + ((scaler & 0x7) << DSIM_SCALER_SHIFT); + + writel(reg, &mipi_dsim->pllctrl); +} + +void exynos_mipi_dsi_pll_stable_time(struct mipi_dsim_device *dsim, + unsigned int lock_time) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + writel(lock_time, &mipi_dsim->plltmr); +} + +void exynos_mipi_dsi_enable_pll(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->pllctrl)) & + ~(0x1 << DSIM_PLL_EN_SHIFT); + + reg |= ((enable & 0x1) << DSIM_PLL_EN_SHIFT); + + writel(reg, &mipi_dsim->pllctrl); +} + +void exynos_mipi_dsi_set_byte_clock_src(struct mipi_dsim_device *dsim, + unsigned int src) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->clkctrl)) & + ~(0x3 << DSIM_BYTE_CLK_SRC_SHIFT); + + reg |= ((unsigned int) src) << DSIM_BYTE_CLK_SRC_SHIFT; + + writel(reg, &mipi_dsim->clkctrl); +} + +void exynos_mipi_dsi_enable_byte_clock(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->clkctrl)) & + ~(1 << DSIM_BYTE_CLKEN_SHIFT); + + reg |= enable << DSIM_BYTE_CLKEN_SHIFT; + + writel(reg, &mipi_dsim->clkctrl); +} + +void exynos_mipi_dsi_set_esc_clk_prs(struct mipi_dsim_device *dsim, + unsigned int enable, unsigned int prs_val) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->clkctrl)) & + ~((1 << DSIM_ESC_CLKEN_SHIFT) | (0xffff)); + + reg |= enable << DSIM_ESC_CLKEN_SHIFT; + if (enable) + reg |= prs_val; + + writel(reg, &mipi_dsim->clkctrl); +} + +void exynos_mipi_dsi_enable_esc_clk_on_lane(struct mipi_dsim_device *dsim, + unsigned int lane_sel, unsigned int enable) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->clkctrl); + + if (enable) + reg |= DSIM_LANE_ESC_CLKEN(lane_sel); + else + reg &= ~DSIM_LANE_ESC_CLKEN(lane_sel); + + writel(reg, &mipi_dsim->clkctrl); +} + +void exynos_mipi_dsi_force_dphy_stop_state(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->escmode)) & + ~(0x1 << DSIM_FORCE_STOP_STATE_SHIFT); + + reg |= ((enable & 0x1) << DSIM_FORCE_STOP_STATE_SHIFT); + + writel(reg, &mipi_dsim->escmode); +} + +unsigned int exynos_mipi_dsi_is_lane_state(struct mipi_dsim_device *dsim) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->status); + + /** + * check clock and data lane states. + * if MIPI-DSI controller was enabled at bootloader then + * TX_READY_HS_CLK is enabled otherwise STOP_STATE_CLK. + * so it should be checked for two case. + */ + if ((reg & DSIM_STOP_STATE_DAT(0xf)) && + ((reg & DSIM_STOP_STATE_CLK) || + (reg & DSIM_TX_READY_HS_CLK))) + return 1; + else + return 0; +} + +void exynos_mipi_dsi_set_stop_state_counter(struct mipi_dsim_device *dsim, + unsigned int cnt_val) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->escmode)) & + ~(0x7ff << DSIM_STOP_STATE_CNT_SHIFT); + + reg |= ((cnt_val & 0x7ff) << DSIM_STOP_STATE_CNT_SHIFT); + + writel(reg, &mipi_dsim->escmode); +} + +void exynos_mipi_dsi_set_bta_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->timeout)) & + ~(0xff << DSIM_BTA_TOUT_SHIFT); + + reg |= (timeout << DSIM_BTA_TOUT_SHIFT); + + writel(reg, &mipi_dsim->timeout); +} + +void exynos_mipi_dsi_set_lpdr_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->timeout)) & + ~(0xffff << DSIM_LPDR_TOUT_SHIFT); + + reg |= (timeout << DSIM_LPDR_TOUT_SHIFT); + + writel(reg, &mipi_dsim->timeout); +} + +void exynos_mipi_dsi_set_cpu_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->escmode); + + reg &= ~DSIM_CMD_LPDT_LP; + + if (lp) + reg |= DSIM_CMD_LPDT_LP; + + writel(reg, &mipi_dsim->escmode); +} + +void exynos_mipi_dsi_set_lcdc_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->escmode); + + reg &= ~DSIM_TX_LPDT_LP; + + if (lp) + reg |= DSIM_TX_LPDT_LP; + + writel(reg, &mipi_dsim->escmode); +} + +void exynos_mipi_dsi_enable_hs_clock(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->clkctrl)) & + ~(1 << DSIM_TX_REQUEST_HSCLK_SHIFT); + + reg |= enable << DSIM_TX_REQUEST_HSCLK_SHIFT; + + writel(reg, &mipi_dsim->clkctrl); +} + +void exynos_mipi_dsi_dp_dn_swap(struct mipi_dsim_device *dsim, + unsigned int swap_en) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->phyacchr1); + + reg &= ~(0x3 << DSIM_DPDN_SWAP_DATA_SHIFT); + reg |= (swap_en & 0x3) << DSIM_DPDN_SWAP_DATA_SHIFT; + + writel(reg, &mipi_dsim->phyacchr1); +} + +void exynos_mipi_dsi_hs_zero_ctrl(struct mipi_dsim_device *dsim, + unsigned int hs_zero) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->pllctrl)) & + ~(0xf << DSIM_ZEROCTRL_SHIFT); + + reg |= ((hs_zero & 0xf) << DSIM_ZEROCTRL_SHIFT); + + writel(reg, &mipi_dsim->pllctrl); +} + +void exynos_mipi_dsi_prep_ctrl(struct mipi_dsim_device *dsim, unsigned int prep) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (readl(&mipi_dsim->pllctrl)) & + ~(0x7 << DSIM_PRECTRL_SHIFT); + + reg |= ((prep & 0x7) << DSIM_PRECTRL_SHIFT); + + writel(reg, &mipi_dsim->pllctrl); +} + +void exynos_mipi_dsi_clear_interrupt(struct mipi_dsim_device *dsim) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->intsrc); + + reg |= INTSRC_PLL_STABLE; + + writel(reg, &mipi_dsim->intsrc); +} + +void exynos_mipi_dsi_clear_all_interrupt(struct mipi_dsim_device *dsim) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + writel(0xffffffff, &mipi_dsim->intsrc); +} + +unsigned int exynos_mipi_dsi_is_pll_stable(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + reg = readl(&mipi_dsim->status); + + return reg & DSIM_PLL_STABLE ? 1 : 0; +} + +unsigned int exynos_mipi_dsi_get_fifo_state(struct mipi_dsim_device *dsim) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + return readl(&mipi_dsim->fifoctrl) & ~(0x1f); +} + +void exynos_mipi_dsi_wr_tx_header(struct mipi_dsim_device *dsim, + unsigned int di, unsigned int data0, unsigned int data1) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = (DSIM_PKTHDR_DAT1(data1) | DSIM_PKTHDR_DAT0(data0) | + DSIM_PKTHDR_DI(di)); + + writel(reg, &mipi_dsim->pkthdr); +} + +unsigned int _exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device + *dsim) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->intsrc); + + return (reg & INTSRC_FRAME_DONE) ? 1 : 0; +} + +void _exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + unsigned int reg = readl(&mipi_dsim->intsrc); + + writel(reg | INTSRC_FRAME_DONE, &mipi_dsim->intsrc); +} + +void exynos_mipi_dsi_wr_tx_data(struct mipi_dsim_device *dsim, + unsigned int tx_data) +{ + struct exynos_mipi_dsim *mipi_dsim = + (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim(); + + writel(tx_data, &mipi_dsim->payload); +} diff --git a/drivers/video/exynos_mipi_dsi_lowlevel.h b/drivers/video/exynos_mipi_dsi_lowlevel.h new file mode 100644 index 0000000..4b8c441 --- /dev/null +++ b/drivers/video/exynos_mipi_dsi_lowlevel.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: InKi Dae + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef _EXYNOS_MIPI_DSI_LOWLEVEL_H +#define _EXYNOS_MIPI_DSI_LOWLEVEL_H + +void exynos_mipi_dsi_register(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_func_reset(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_sw_reset(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_sw_release(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_set_interrupt_mask(struct mipi_dsim_device *dsim, + unsigned int mode, unsigned int mask); +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count); +void exynos_mipi_dsi_init_fifo_pointer(struct mipi_dsim_device *dsim, + unsigned int cfg); +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value); +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value); +void exynos_mipi_dsi_set_main_disp_resol(struct mipi_dsim_device *dsim, + unsigned int width_resol, unsigned int height_resol); +void exynos_mipi_dsi_set_main_disp_vporch(struct mipi_dsim_device *dsim, + unsigned int cmd_allow, unsigned int vfront, unsigned int vback); +void exynos_mipi_dsi_set_main_disp_hporch(struct mipi_dsim_device *dsim, + unsigned int front, unsigned int back); +void exynos_mipi_dsi_set_main_disp_sync_area(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori); +void exynos_mipi_dsi_set_sub_disp_resol(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori); +void exynos_mipi_dsi_init_config(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_display_config(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config); +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count); +void exynos_mipi_dsi_enable_lane(struct mipi_dsim_device *dsim, + unsigned int lane, unsigned int enable); +void exynos_mipi_dsi_enable_afc(struct mipi_dsim_device *dsim, + unsigned int enable, unsigned int afc_code); +void exynos_mipi_dsi_enable_pll_bypass(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_pll_freq_band(struct mipi_dsim_device *dsim, + unsigned int freq_band); +void exynos_mipi_dsi_pll_freq(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler); +void exynos_mipi_dsi_pll_stable_time(struct mipi_dsim_device *dsim, + unsigned int lock_time); +void exynos_mipi_dsi_enable_pll(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_byte_clock_src(struct mipi_dsim_device *dsim, + unsigned int src); +void exynos_mipi_dsi_enable_byte_clock(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_esc_clk_prs(struct mipi_dsim_device *dsim, + unsigned int enable, unsigned int prs_val); +void exynos_mipi_dsi_enable_esc_clk_on_lane(struct mipi_dsim_device *dsim, + unsigned int lane_sel, unsigned int enable); +void exynos_mipi_dsi_force_dphy_stop_state(struct mipi_dsim_device *dsim, + unsigned int enable); +unsigned int exynos_mipi_dsi_is_lane_state(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_set_stop_state_counter(struct mipi_dsim_device *dsim, + unsigned int cnt_val); +void exynos_mipi_dsi_set_bta_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout); +void exynos_mipi_dsi_set_lpdr_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout); +void exynos_mipi_dsi_set_lcdc_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp); +void exynos_mipi_dsi_set_cpu_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp); +void exynos_mipi_dsi_enable_hs_clock(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_dp_dn_swap(struct mipi_dsim_device *dsim, + unsigned int swap_en); +void exynos_mipi_dsi_hs_zero_ctrl(struct mipi_dsim_device *dsim, + unsigned int hs_zero); +void exynos_mipi_dsi_prep_ctrl(struct mipi_dsim_device *dsim, + unsigned int prep); +void exynos_mipi_dsi_clear_interrupt(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_clear_all_interrupt(struct mipi_dsim_device *dsim); +unsigned int exynos_mipi_dsi_is_pll_stable(struct mipi_dsim_device *dsim); +unsigned int exynos_mipi_dsi_get_fifo_state(struct mipi_dsim_device *dsim); +unsigned int _exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device + *dsim); +void _exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_wr_tx_header(struct mipi_dsim_device *dsim, + unsigned int di, unsigned int data0, unsigned int data1); +void exynos_mipi_dsi_wr_tx_data(struct mipi_dsim_device *dsim, + unsigned int tx_data); + +#endif /* _EXYNOS_MIPI_DSI_LOWLEVEL_H */ -- cgit v1.1 From bba09e9f56d70b5bd96b4fdf29f5f093fc483dd6 Mon Sep 17 00:00:00 2001 From: Donghwa Lee Date: Fri, 6 Apr 2012 14:24:01 +0900 Subject: LCD: support S6E8AX0 amoled driver based on EXYNOS MIPI DSI This patch support S6E8AX0 amoled driver based on EXYNOS MIPI DSI interface. Signed-off-by: Donghwa Lee Signed-off-by: Kyungmin Park Signed-off-by: Inki Dae Acked-by: Anatolij Gustschin --- drivers/video/Makefile | 1 + drivers/video/s6e8ax0.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 drivers/video/s6e8ax0.c (limited to 'drivers') diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 43bc7b4..4fad20d 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -32,6 +32,7 @@ COBJS-$(CONFIG_EXYNOS_FB) += exynos_fb.o exynos_fimd.o COBJS-$(CONFIG_EXYNOS_MIPI_DSIM) += exynos_mipi_dsi.o exynos_mipi_dsi_common.o \ exynos_mipi_dsi_lowlevel.o COBJS-$(CONFIG_FSL_DIU_FB) += fsl_diu_fb.o videomodes.o +COBJS-$(CONFIG_S6E8AX0) += s6e8ax0.o COBJS-$(CONFIG_S6E63D6) += s6e63d6.o COBJS-$(CONFIG_SED156X) += sed156x.o COBJS-$(CONFIG_VIDEO_AMBA) += amba.o diff --git a/drivers/video/s6e8ax0.c b/drivers/video/s6e8ax0.c new file mode 100644 index 0000000..1ec7fd6 --- /dev/null +++ b/drivers/video/s6e8ax0.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include + +#include "exynos_mipi_dsi_lowlevel.h" +#include "exynos_mipi_dsi_common.h" + +static void s6e8ax0_panel_cond(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xf8, 0x3d, 0x35, 0x00, 0x00, 0x00, 0x8d, 0x00, 0x4c, + 0x6e, 0x10, 0x27, 0x7d, 0x3f, 0x10, 0x00, 0x00, 0x20, + 0x04, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x08, 0x08, + 0x23, 0x23, 0xc0, 0xc8, 0x08, 0x48, 0xc1, 0x00, 0xc3, + 0xff, 0xff, 0xc8 + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_display_cond(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xf2, 0x80, 0x03, 0x0d + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_gamma_cond(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + /* 7500K 2.2 Set (M3, 300cd) */ + const unsigned char data_to_send[] = { + 0xfa, 0x01, 0x0f, 0x00, 0x0f, 0xda, 0xc0, 0xe4, 0xc8, + 0xc8, 0xc6, 0xd3, 0xd6, 0xd0, 0xab, 0xb2, 0xa6, 0xbf, + 0xc2, 0xb9, 0x00, 0x93, 0x00, 0x86, 0x00, 0xd1 + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_gamma_update(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0xf7, 0x3); +} + +static void s6e8ax0_etc_source_control(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xf6, 0x00, 0x02, 0x00 + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_pentile_control(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xff, 0x44, 0x44, 0xc0, + 0x00 + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_mipi_control1(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xe1, 0x10, 0x1c, 0x17, 0x08, 0x1d + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_mipi_control2(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xe2, 0xed, 0x07, 0xc3, 0x13, 0x0d, 0x03 + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_power_control(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xf4, 0xcf, 0x0a, 0x12, 0x10, 0x19, 0x33, 0x02 + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_mipi_control3(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_SHORT_WRITE_PARAM, 0xe3, 0x40); +} + +static void s6e8ax0_etc_mipi_control4(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xe4, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00 + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_elvss_set(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xb1, 0x04, 0x00 + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_display_on(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + + ops->cmd_write(dsim_dev, + MIPI_DSI_DCS_SHORT_WRITE, 0x29, 0x00); +} + +static void s6e8ax0_sleep_out(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + + ops->cmd_write(dsim_dev, + MIPI_DSI_DCS_SHORT_WRITE, 0x11, 0x00); +} + +static void s6e8ax0_apply_level1_key(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xf0, 0x5a, 0x5a + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_apply_mtp_key(struct mipi_dsim_device *dsim_dev) +{ + struct mipi_dsim_master_ops *ops = dsim_dev->master_ops; + const unsigned char data_to_send[] = { + 0xf1, 0x5a, 0x5a + }; + + ops->cmd_write(dsim_dev, MIPI_DSI_DCS_LONG_WRITE, + (unsigned int)data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_panel_init(struct mipi_dsim_device *dsim_dev) +{ + /* + * in case of setting gamma and panel condition at first, + * it shuold be setting like below. + * set_gamma() -> set_panel_condition() + */ + + s6e8ax0_apply_level1_key(dsim_dev); + s6e8ax0_apply_mtp_key(dsim_dev); + + s6e8ax0_sleep_out(dsim_dev); + mdelay(5); + s6e8ax0_panel_cond(dsim_dev); + s6e8ax0_display_cond(dsim_dev); + s6e8ax0_gamma_cond(dsim_dev); + s6e8ax0_gamma_update(dsim_dev); + + s6e8ax0_etc_source_control(dsim_dev); + s6e8ax0_elvss_set(dsim_dev); + s6e8ax0_etc_pentile_control(dsim_dev); + s6e8ax0_etc_mipi_control1(dsim_dev); + s6e8ax0_etc_mipi_control2(dsim_dev); + s6e8ax0_etc_power_control(dsim_dev); + s6e8ax0_etc_mipi_control3(dsim_dev); + s6e8ax0_etc_mipi_control4(dsim_dev); +} + +static int s6e8ax0_panel_set(struct mipi_dsim_device *dsim_dev) +{ + s6e8ax0_panel_init(dsim_dev); + + return 0; +} + +static void s6e8ax0_display_enable(struct mipi_dsim_device *dsim_dev) +{ + s6e8ax0_display_on(dsim_dev); +} + +static struct mipi_dsim_lcd_driver s6e8ax0_dsim_ddi_driver = { + .name = "s6e8ax0", + .id = -1, + + .mipi_panel_init = s6e8ax0_panel_set, + .mipi_display_on = s6e8ax0_display_enable, +}; + +void s6e8ax0_init(void) +{ + exynos_mipi_dsi_register_lcd_driver(&s6e8ax0_dsim_ddi_driver); +} -- cgit v1.1 From f78095e40b5d9f0c03985e296289be5a84a40f8a Mon Sep 17 00:00:00 2001 From: Donghwa Lee Date: Mon, 23 Apr 2012 15:37:05 +0000 Subject: EXYNOS: Change bits per pixel value proper for u-boot. vl_bpix of vidinfo_t was changed proper value for u-boot. It is used to multiple of 2 by using NBITS() macro. Signed-off-by: Donghwa Lee Signed-off-by: Kyungmin Park Acked-by: Anatolij Gustschin Signed-off-by: Minkyu Kang --- drivers/video/exynos_fb.c | 2 +- drivers/video/exynos_fimd.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/video/exynos_fb.c b/drivers/video/exynos_fb.c index 0eb7f58..a1cf449 100644 --- a/drivers/video/exynos_fb.c +++ b/drivers/video/exynos_fb.c @@ -52,7 +52,7 @@ static void exynos_lcd_init_mem(void *lcdbase, vidinfo_t *vid) unsigned long palette_size; unsigned int fb_size; - fb_size = vid->vl_row * vid->vl_col * (vid->vl_bpix >> 3); + fb_size = vid->vl_row * vid->vl_col * (NBITS(vid->vl_bpix) >> 3); lcd_base = lcdbase; diff --git a/drivers/video/exynos_fimd.c b/drivers/video/exynos_fimd.c index 2a60fcf..6416b90 100644 --- a/drivers/video/exynos_fimd.c +++ b/drivers/video/exynos_fimd.c @@ -110,7 +110,7 @@ static void exynos_fimd_set_buffer_address(unsigned int win_id) (struct exynos4_fb *)samsung_get_base_fimd(); start_addr = (unsigned long)lcd_base_addr; - end_addr = start_addr + ((pvid->vl_col * (pvid->vl_bpix / 8)) * + end_addr = start_addr + ((pvid->vl_col * (NBITS(pvid->vl_bpix) / 8)) * pvid->vl_row); writel(start_addr, (unsigned int)&fimd_ctrl->vidw00add0b0 + @@ -331,7 +331,7 @@ void exynos_fimd_lcd_init(vidinfo_t *vid) exynos_fimd_set_buffer_address(pvid->win_id); /* set buffer size */ - cfg = EXYNOS_VIDADDR_PAGEWIDTH(pvid->vl_col * pvid->vl_bpix / 8); + cfg = EXYNOS_VIDADDR_PAGEWIDTH(pvid->vl_col * NBITS(pvid->vl_bpix) / 8); writel(cfg, (unsigned int)&fimd_ctrl->vidw00add2 + EXYNOS_BUFFER_SIZE(pvid->win_id)); @@ -350,5 +350,5 @@ void exynos_fimd_lcd_init(vidinfo_t *vid) unsigned long exynos_fimd_calc_fbsize(void) { - return pvid->vl_col * pvid->vl_row * (pvid->vl_bpix / 8); + return pvid->vl_col * pvid->vl_row * (NBITS(pvid->vl_bpix) / 8); } -- cgit v1.1 From b774fe9d8a3047293867267ca60355cf14f8268d Mon Sep 17 00:00:00 2001 From: Stefano Babic Date: Wed, 22 Feb 2012 00:24:35 +0000 Subject: NET: fec_mxc.c: Add a way to disable auto negotiation Signed-off-by: Stefano Babic CC: Fabio Estevam --- drivers/net/fec_mxc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/fec_mxc.c b/drivers/net/fec_mxc.c index d8db9f0..5c3b90f 100644 --- a/drivers/net/fec_mxc.c +++ b/drivers/net/fec_mxc.c @@ -187,9 +187,10 @@ int fec_phy_write(struct mii_dev *bus, int phyAddr, int dev_addr, int regAddr, #ifndef CONFIG_PHYLIB static int miiphy_restart_aneg(struct eth_device *dev) { + int ret = 0; +#if !defined(CONFIG_FEC_MXC_NO_ANEG) struct fec_priv *fec = (struct fec_priv *)dev->priv; struct ethernet_regs *eth = fec->bus->priv; - int ret = 0; /* * Wake up from sleep if necessary @@ -213,6 +214,7 @@ static int miiphy_restart_aneg(struct eth_device *dev) if (fec->mii_postcall) ret = fec->mii_postcall(fec->phy_id); +#endif return ret; } -- cgit v1.1 From 9f472e6540f5632ce27b779d841a006d1c1b3d55 Mon Sep 17 00:00:00 2001 From: Stefano Babic Date: Wed, 22 Feb 2012 00:24:39 +0000 Subject: SATA: add driver for MX5 / MX6 SOCs This driver is part of Freescale's LTIB for MX5 / MX6. Signed-off-by: Stefano Babic Signed-off-by: Terry Lv CC: Fabio Estevam CC: Dirk Behme --- drivers/block/Makefile | 1 + drivers/block/dwc_ahsata.c | 969 +++++++++++++++++++++++++++++++++++++++++++++ drivers/block/dwc_ahsata.h | 335 ++++++++++++++++ 3 files changed, 1305 insertions(+) create mode 100644 drivers/block/dwc_ahsata.c create mode 100644 drivers/block/dwc_ahsata.h (limited to 'drivers') diff --git a/drivers/block/Makefile b/drivers/block/Makefile index 98560ef..b9c2047 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -27,6 +27,7 @@ LIB := $(obj)libblock.o COBJS-$(CONFIG_SCSI_AHCI) += ahci.o COBJS-$(CONFIG_ATA_PIIX) += ata_piix.o +COBJS-$(CONFIG_DWC_AHSATA) += dwc_ahsata.o COBJS-$(CONFIG_FSL_SATA) += fsl_sata.o COBJS-$(CONFIG_IDE_FTIDE020) += ftide020.o COBJS-$(CONFIG_LIBATA) += libata.o diff --git a/drivers/block/dwc_ahsata.c b/drivers/block/dwc_ahsata.c new file mode 100644 index 0000000..2703d3d --- /dev/null +++ b/drivers/block/dwc_ahsata.c @@ -0,0 +1,969 @@ +/* + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Terry Lv + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "dwc_ahsata.h" + +struct sata_port_regs { + u32 clb; + u32 clbu; + u32 fb; + u32 fbu; + u32 is; + u32 ie; + u32 cmd; + u32 res1[1]; + u32 tfd; + u32 sig; + u32 ssts; + u32 sctl; + u32 serr; + u32 sact; + u32 ci; + u32 sntf; + u32 res2[1]; + u32 dmacr; + u32 res3[1]; + u32 phycr; + u32 physr; +}; + +struct sata_host_regs { + u32 cap; + u32 ghc; + u32 is; + u32 pi; + u32 vs; + u32 ccc_ctl; + u32 ccc_ports; + u32 res1[2]; + u32 cap2; + u32 res2[30]; + u32 bistafr; + u32 bistcr; + u32 bistfctr; + u32 bistsr; + u32 bistdecr; + u32 res3[2]; + u32 oobr; + u32 res4[8]; + u32 timer1ms; + u32 res5[1]; + u32 gparam1r; + u32 gparam2r; + u32 pparamr; + u32 testr; + u32 versionr; + u32 idr; +}; + +#define MAX_DATA_BYTES_PER_SG (4 * 1024 * 1024) +#define MAX_BYTES_PER_TRANS (AHCI_MAX_SG * MAX_DATA_BYTES_PER_SG) + +#define writel_with_flush(a, b) do { writel(a, b); readl(b); } while (0) + +static int is_ready; + +static inline u32 ahci_port_base(u32 base, u32 port) +{ + return base + 0x100 + (port * 0x80); +} + +static int waiting_for_cmd_completed(u8 *offset, + int timeout_msec, + u32 sign) +{ + int i; + u32 status; + + for (i = 0; + ((status = readl(offset)) & sign) && i < timeout_msec; + ++i) + mdelay(1); + + return (i < timeout_msec) ? 0 : -1; +} + +static int ahci_setup_oobr(struct ahci_probe_ent *probe_ent, + int clk) +{ + struct sata_host_regs *host_mmio = + (struct sata_host_regs *)probe_ent->mmio_base; + + writel(SATA_HOST_OOBR_WE, &(host_mmio->oobr)); + writel(0x02060b14, &(host_mmio->oobr)); + + return 0; +} + +static int ahci_host_init(struct ahci_probe_ent *probe_ent) +{ + u32 tmp, cap_save, num_ports; + int i, j, timeout = 1000; + struct sata_port_regs *port_mmio = NULL; + struct sata_host_regs *host_mmio = + (struct sata_host_regs *)probe_ent->mmio_base; + int clk = mxc_get_clock(MXC_SATA_CLK); + + cap_save = readl(&(host_mmio->cap)); + cap_save |= SATA_HOST_CAP_SSS; + + /* global controller reset */ + tmp = readl(&(host_mmio->ghc)); + if ((tmp & SATA_HOST_GHC_HR) == 0) + writel_with_flush(tmp | SATA_HOST_GHC_HR, &(host_mmio->ghc)); + + while ((readl(&(host_mmio->ghc)) & SATA_HOST_GHC_HR) + && --timeout) + ; + + if (timeout <= 0) { + debug("controller reset failed (0x%x)\n", tmp); + return -1; + } + + /* Set timer 1ms */ + writel(clk / 1000, &(host_mmio->timer1ms)); + + ahci_setup_oobr(probe_ent, 0); + + writel_with_flush(SATA_HOST_GHC_AE, &(host_mmio->ghc)); + writel(cap_save, &(host_mmio->cap)); + num_ports = (cap_save & SATA_HOST_CAP_NP_MASK) + 1; + writel_with_flush((1 << num_ports) - 1, + &(host_mmio->pi)); + + /* + * Determine which Ports are implemented by the DWC_ahsata, + * by reading the PI register. This bit map value aids the + * software to determine how many Ports are available and + * which Port registers need to be initialized. + */ + probe_ent->cap = readl(&(host_mmio->cap)); + probe_ent->port_map = readl(&(host_mmio->pi)); + + /* Determine how many command slots the HBA supports */ + probe_ent->n_ports = + (probe_ent->cap & SATA_HOST_CAP_NP_MASK) + 1; + + debug("cap 0x%x port_map 0x%x n_ports %d\n", + probe_ent->cap, probe_ent->port_map, probe_ent->n_ports); + + for (i = 0; i < probe_ent->n_ports; i++) { + probe_ent->port[i].port_mmio = + ahci_port_base((u32)host_mmio, i); + port_mmio = + (struct sata_port_regs *)probe_ent->port[i].port_mmio; + + /* Ensure that the DWC_ahsata is in idle state */ + tmp = readl(&(port_mmio->cmd)); + + /* + * When P#CMD.ST, P#CMD.CR, P#CMD.FRE and P#CMD.FR + * are all cleared, the Port is in an idle state. + */ + if (tmp & (SATA_PORT_CMD_CR | SATA_PORT_CMD_FR | + SATA_PORT_CMD_FRE | SATA_PORT_CMD_ST)) { + + /* + * System software places a Port into the idle state by + * clearing P#CMD.ST and waiting for P#CMD.CR to return + * 0 when read. + */ + tmp &= ~SATA_PORT_CMD_ST; + writel_with_flush(tmp, &(port_mmio->cmd)); + + /* + * spec says 500 msecs for each bit, so + * this is slightly incorrect. + */ + mdelay(500); + + timeout = 1000; + while ((readl(&(port_mmio->cmd)) & SATA_PORT_CMD_CR) + && --timeout) + ; + + if (timeout <= 0) { + debug("port reset failed (0x%x)\n", tmp); + return -1; + } + } + + /* Spin-up device */ + tmp = readl(&(port_mmio->cmd)); + writel((tmp | SATA_PORT_CMD_SUD), &(port_mmio->cmd)); + + /* Wait for spin-up to finish */ + timeout = 1000; + while (!(readl(&(port_mmio->cmd)) | SATA_PORT_CMD_SUD) + && --timeout) + ; + if (timeout <= 0) { + debug("Spin-Up can't finish!\n"); + return -1; + } + + for (j = 0; j < 100; ++j) { + mdelay(10); + tmp = readl(&(port_mmio->ssts)); + if (((tmp & SATA_PORT_SSTS_DET_MASK) == 0x3) || + ((tmp & SATA_PORT_SSTS_DET_MASK) == 0x1)) + break; + } + + /* Wait for COMINIT bit 26 (DIAG_X) in SERR */ + timeout = 1000; + while (!(readl(&(port_mmio->serr)) | SATA_PORT_SERR_DIAG_X) + && --timeout) + ; + if (timeout <= 0) { + debug("Can't find DIAG_X set!\n"); + return -1; + } + + /* + * For each implemented Port, clear the P#SERR + * register, by writing ones to each implemented\ + * bit location. + */ + tmp = readl(&(port_mmio->serr)); + debug("P#SERR 0x%x\n", + tmp); + writel(tmp, &(port_mmio->serr)); + + /* Ack any pending irq events for this port */ + tmp = readl(&(host_mmio->is)); + debug("IS 0x%x\n", tmp); + if (tmp) + writel(tmp, &(host_mmio->is)); + + writel(1 << i, &(host_mmio->is)); + + /* set irq mask (enables interrupts) */ + writel(DEF_PORT_IRQ, &(port_mmio->ie)); + + /* register linkup ports */ + tmp = readl(&(port_mmio->ssts)); + debug("Port %d status: 0x%x\n", i, tmp); + if ((tmp & SATA_PORT_SSTS_DET_MASK) == 0x03) + probe_ent->link_port_map |= (0x01 << i); + } + + tmp = readl(&(host_mmio->ghc)); + debug("GHC 0x%x\n", tmp); + writel(tmp | SATA_HOST_GHC_IE, &(host_mmio->ghc)); + tmp = readl(&(host_mmio->ghc)); + debug("GHC 0x%x\n", tmp); + + return 0; +} + +static void ahci_print_info(struct ahci_probe_ent *probe_ent) +{ + struct sata_host_regs *host_mmio = + (struct sata_host_regs *)probe_ent->mmio_base; + u32 vers, cap, impl, speed; + const char *speed_s; + const char *scc_s; + + vers = readl(&(host_mmio->vs)); + cap = probe_ent->cap; + impl = probe_ent->port_map; + + speed = (cap & SATA_HOST_CAP_ISS_MASK) + >> SATA_HOST_CAP_ISS_OFFSET; + if (speed == 1) + speed_s = "1.5"; + else if (speed == 2) + speed_s = "3"; + else + speed_s = "?"; + + scc_s = "SATA"; + + printf("AHCI %02x%02x.%02x%02x " + "%u slots %u ports %s Gbps 0x%x impl %s mode\n", + (vers >> 24) & 0xff, + (vers >> 16) & 0xff, + (vers >> 8) & 0xff, + vers & 0xff, + ((cap >> 8) & 0x1f) + 1, + (cap & 0x1f) + 1, + speed_s, + impl, + scc_s); + + printf("flags: " + "%s%s%s%s%s%s" + "%s%s%s%s%s%s%s\n", + cap & (1 << 31) ? "64bit " : "", + cap & (1 << 30) ? "ncq " : "", + cap & (1 << 28) ? "ilck " : "", + cap & (1 << 27) ? "stag " : "", + cap & (1 << 26) ? "pm " : "", + cap & (1 << 25) ? "led " : "", + cap & (1 << 24) ? "clo " : "", + cap & (1 << 19) ? "nz " : "", + cap & (1 << 18) ? "only " : "", + cap & (1 << 17) ? "pmp " : "", + cap & (1 << 15) ? "pio " : "", + cap & (1 << 14) ? "slum " : "", + cap & (1 << 13) ? "part " : ""); +} + +static int ahci_init_one(int pdev) +{ + int rc; + struct ahci_probe_ent *probe_ent = NULL; + + probe_ent = malloc(sizeof(struct ahci_probe_ent)); + memset(probe_ent, 0, sizeof(struct ahci_probe_ent)); + probe_ent->dev = pdev; + + probe_ent->host_flags = ATA_FLAG_SATA + | ATA_FLAG_NO_LEGACY + | ATA_FLAG_MMIO + | ATA_FLAG_PIO_DMA + | ATA_FLAG_NO_ATAPI; + + probe_ent->mmio_base = CONFIG_DWC_AHSATA_BASE_ADDR; + + /* initialize adapter */ + rc = ahci_host_init(probe_ent); + if (rc) + goto err_out; + + ahci_print_info(probe_ent); + + /* Save the private struct to block device struct */ + sata_dev_desc[pdev].priv = (void *)probe_ent; + + return 0; + +err_out: + return rc; +} + +static int ahci_fill_sg(struct ahci_probe_ent *probe_ent, + u8 port, unsigned char *buf, int buf_len) +{ + struct ahci_ioports *pp = &(probe_ent->port[port]); + struct ahci_sg *ahci_sg = pp->cmd_tbl_sg; + u32 sg_count, max_bytes; + int i; + + max_bytes = MAX_DATA_BYTES_PER_SG; + sg_count = ((buf_len - 1) / max_bytes) + 1; + if (sg_count > AHCI_MAX_SG) { + printf("Error:Too much sg!\n"); + return -1; + } + + for (i = 0; i < sg_count; i++) { + ahci_sg->addr = + cpu_to_le32((u32)buf + i * max_bytes); + ahci_sg->addr_hi = 0; + ahci_sg->flags_size = cpu_to_le32(0x3fffff & + (buf_len < max_bytes + ? (buf_len - 1) + : (max_bytes - 1))); + ahci_sg++; + buf_len -= max_bytes; + } + + return sg_count; +} + +static void ahci_fill_cmd_slot(struct ahci_ioports *pp, u32 cmd_slot, u32 opts) +{ + struct ahci_cmd_hdr *cmd_hdr = (struct ahci_cmd_hdr *)(pp->cmd_slot + + AHCI_CMD_SLOT_SZ * cmd_slot); + + memset(cmd_hdr, 0, AHCI_CMD_SLOT_SZ); + cmd_hdr->opts = cpu_to_le32(opts); + cmd_hdr->status = 0; + cmd_hdr->tbl_addr = cpu_to_le32(pp->cmd_tbl & 0xffffffff); + cmd_hdr->tbl_addr_hi = 0; +} + +#define AHCI_GET_CMD_SLOT(c) ((c) ? ffs(c) : 0) + +static int ahci_exec_ata_cmd(struct ahci_probe_ent *probe_ent, + u8 port, struct sata_fis_h2d *cfis, + u8 *buf, u32 buf_len, s32 is_write) +{ + struct ahci_ioports *pp = &(probe_ent->port[port]); + struct sata_port_regs *port_mmio = + (struct sata_port_regs *)pp->port_mmio; + u32 opts; + int sg_count = 0, cmd_slot = 0; + + cmd_slot = AHCI_GET_CMD_SLOT(readl(&(port_mmio->ci))); + if (32 == cmd_slot) { + printf("Can't find empty command slot!\n"); + return 0; + } + + /* Check xfer length */ + if (buf_len > MAX_BYTES_PER_TRANS) { + printf("Max transfer length is %dB\n\r", + MAX_BYTES_PER_TRANS); + return 0; + } + + memcpy((u8 *)(pp->cmd_tbl), cfis, sizeof(struct sata_fis_h2d)); + if (buf && buf_len) + sg_count = ahci_fill_sg(probe_ent, port, buf, buf_len); + opts = (sizeof(struct sata_fis_h2d) >> 2) | (sg_count << 16); + if (is_write) + opts |= 0x40; + ahci_fill_cmd_slot(pp, cmd_slot, opts); + + writel_with_flush(1 << cmd_slot, &(port_mmio->ci)); + + if (waiting_for_cmd_completed((u8 *)&(port_mmio->ci), + 10000, 0x1 << cmd_slot)) { + printf("timeout exit!\n"); + return -1; + } + debug("ahci_exec_ata_cmd: %d byte transferred.\n", + pp->cmd_slot->status); + + return buf_len; +} + +static void ahci_set_feature(u8 dev, u8 port) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + struct sata_fis_h2d h2d, *cfis = &h2d; + + memset(cfis, 0, sizeof(struct sata_fis_h2d)); + cfis->fis_type = SATA_FIS_TYPE_REGISTER_H2D; + cfis->pm_port_c = 1 << 7; + cfis->command = ATA_CMD_SET_FEATURES; + cfis->features = SETFEATURES_XFER; + cfis->sector_count = ffs(probe_ent->udma_mask + 1) + 0x3e; + + ahci_exec_ata_cmd(probe_ent, port, cfis, NULL, 0, READ_CMD); +} + +static int ahci_port_start(struct ahci_probe_ent *probe_ent, + u8 port) +{ + struct ahci_ioports *pp = &(probe_ent->port[port]); + struct sata_port_regs *port_mmio = + (struct sata_port_regs *)pp->port_mmio; + u32 port_status; + u32 mem; + int timeout = 10000000; + + debug("Enter start port: %d\n", port); + port_status = readl(&(port_mmio->ssts)); + debug("Port %d status: %x\n", port, port_status); + if ((port_status & 0xf) != 0x03) { + printf("No Link on this port!\n"); + return -1; + } + + mem = (u32)malloc(AHCI_PORT_PRIV_DMA_SZ + 1024); + if (!mem) { + free(pp); + printf("No mem for table!\n"); + return -ENOMEM; + } + + mem = (mem + 0x400) & (~0x3ff); /* Aligned to 1024-bytes */ + memset((u8 *)mem, 0, AHCI_PORT_PRIV_DMA_SZ); + + /* + * First item in chunk of DMA memory: 32-slot command table, + * 32 bytes each in size + */ + pp->cmd_slot = (struct ahci_cmd_hdr *)mem; + debug("cmd_slot = 0x%x\n", (unsigned int) pp->cmd_slot); + mem += (AHCI_CMD_SLOT_SZ * DWC_AHSATA_MAX_CMD_SLOTS); + + /* + * Second item: Received-FIS area, 256-Byte aligned + */ + pp->rx_fis = mem; + mem += AHCI_RX_FIS_SZ; + + /* + * Third item: data area for storing a single command + * and its scatter-gather table + */ + pp->cmd_tbl = mem; + debug("cmd_tbl_dma = 0x%x\n", pp->cmd_tbl); + + mem += AHCI_CMD_TBL_HDR; + + writel_with_flush(0x00004444, &(port_mmio->dmacr)); + pp->cmd_tbl_sg = (struct ahci_sg *)mem; + writel_with_flush((u32)pp->cmd_slot, &(port_mmio->clb)); + writel_with_flush(pp->rx_fis, &(port_mmio->fb)); + + /* Enable FRE */ + writel_with_flush((SATA_PORT_CMD_FRE | readl(&(port_mmio->cmd))), + &(port_mmio->cmd)); + + /* Wait device ready */ + while ((readl(&(port_mmio->tfd)) & (SATA_PORT_TFD_STS_ERR | + SATA_PORT_TFD_STS_DRQ | SATA_PORT_TFD_STS_BSY)) + && --timeout) + ; + if (timeout <= 0) { + debug("Device not ready for BSY, DRQ and" + "ERR in TFD!\n"); + return -1; + } + + writel_with_flush(PORT_CMD_ICC_ACTIVE | PORT_CMD_FIS_RX | + PORT_CMD_POWER_ON | PORT_CMD_SPIN_UP | + PORT_CMD_START, &(port_mmio->cmd)); + + debug("Exit start port %d\n", port); + + return 0; +} + +int init_sata(int dev) +{ + int i; + u32 linkmap; + struct ahci_probe_ent *probe_ent = NULL; + + if (dev < 0 || dev > (CONFIG_SYS_SATA_MAX_DEVICE - 1)) { + printf("The sata index %d is out of ranges\n\r", dev); + return -1; + } + + ahci_init_one(dev); + + probe_ent = (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + linkmap = probe_ent->link_port_map; + + if (0 == linkmap) { + printf("No port device detected!\n"); + return 1; + } + + for (i = 0; i < probe_ent->n_ports; i++) { + if ((linkmap >> i) && ((linkmap >> i) & 0x01)) { + if (ahci_port_start(probe_ent, (u8)i)) { + printf("Can not start port %d\n", i); + return 1; + } + probe_ent->hard_port_no = i; + break; + } + } + + return 0; +} + +static void dwc_ahsata_print_info(int dev) +{ + block_dev_desc_t *pdev = &(sata_dev_desc[dev]); + + printf("SATA Device Info:\n\r"); +#ifdef CONFIG_SYS_64BIT_LBA + printf("S/N: %s\n\rProduct model number: %s\n\r" + "Firmware version: %s\n\rCapacity: %lld sectors\n\r", + pdev->product, pdev->vendor, pdev->revision, pdev->lba); +#else + printf("S/N: %s\n\rProduct model number: %s\n\r" + "Firmware version: %s\n\rCapacity: %ld sectors\n\r", + pdev->product, pdev->vendor, pdev->revision, pdev->lba); +#endif +} + +static void dwc_ahsata_identify(int dev, u16 *id) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + struct sata_fis_h2d h2d, *cfis = &h2d; + u8 port = probe_ent->hard_port_no; + + memset(cfis, 0, sizeof(struct sata_fis_h2d)); + + cfis->fis_type = SATA_FIS_TYPE_REGISTER_H2D; + cfis->pm_port_c = 0x80; /* is command */ + cfis->command = ATA_CMD_ID_ATA; + + ahci_exec_ata_cmd(probe_ent, port, cfis, + (u8 *)id, ATA_ID_WORDS * 2, READ_CMD); + ata_swap_buf_le16(id, ATA_ID_WORDS); +} + +static void dwc_ahsata_xfer_mode(int dev, u16 *id) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + + probe_ent->pio_mask = id[ATA_ID_PIO_MODES]; + probe_ent->udma_mask = id[ATA_ID_UDMA_MODES]; + debug("pio %04x, udma %04x\n\r", + probe_ent->pio_mask, probe_ent->udma_mask); +} + +static u32 dwc_ahsata_rw_cmd(int dev, u32 start, u32 blkcnt, + u8 *buffer, int is_write) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + struct sata_fis_h2d h2d, *cfis = &h2d; + u8 port = probe_ent->hard_port_no; + u32 block; + + block = start; + + memset(cfis, 0, sizeof(struct sata_fis_h2d)); + + cfis->fis_type = SATA_FIS_TYPE_REGISTER_H2D; + cfis->pm_port_c = 0x80; /* is command */ + cfis->command = (is_write) ? ATA_CMD_WRITE : ATA_CMD_READ; + cfis->device = ATA_LBA; + + cfis->device |= (block >> 24) & 0xf; + cfis->lba_high = (block >> 16) & 0xff; + cfis->lba_mid = (block >> 8) & 0xff; + cfis->lba_low = block & 0xff; + cfis->sector_count = (u8)(blkcnt & 0xff); + + if (ahci_exec_ata_cmd(probe_ent, port, cfis, + buffer, ATA_SECT_SIZE * blkcnt, is_write) > 0) + return blkcnt; + else + return 0; +} + +void dwc_ahsata_flush_cache(int dev) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + struct sata_fis_h2d h2d, *cfis = &h2d; + u8 port = probe_ent->hard_port_no; + + memset(cfis, 0, sizeof(struct sata_fis_h2d)); + + cfis->fis_type = SATA_FIS_TYPE_REGISTER_H2D; + cfis->pm_port_c = 0x80; /* is command */ + cfis->command = ATA_CMD_FLUSH; + + ahci_exec_ata_cmd(probe_ent, port, cfis, NULL, 0, 0); +} + +static u32 dwc_ahsata_rw_cmd_ext(int dev, u32 start, lbaint_t blkcnt, + u8 *buffer, int is_write) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + struct sata_fis_h2d h2d, *cfis = &h2d; + u8 port = probe_ent->hard_port_no; + u64 block; + + block = (u64)start; + + memset(cfis, 0, sizeof(struct sata_fis_h2d)); + + cfis->fis_type = SATA_FIS_TYPE_REGISTER_H2D; + cfis->pm_port_c = 0x80; /* is command */ + + cfis->command = (is_write) ? ATA_CMD_WRITE_EXT + : ATA_CMD_READ_EXT; + + cfis->lba_high_exp = (block >> 40) & 0xff; + cfis->lba_mid_exp = (block >> 32) & 0xff; + cfis->lba_low_exp = (block >> 24) & 0xff; + cfis->lba_high = (block >> 16) & 0xff; + cfis->lba_mid = (block >> 8) & 0xff; + cfis->lba_low = block & 0xff; + cfis->device = ATA_LBA; + cfis->sector_count_exp = (blkcnt >> 8) & 0xff; + cfis->sector_count = blkcnt & 0xff; + + if (ahci_exec_ata_cmd(probe_ent, port, cfis, buffer, + ATA_SECT_SIZE * blkcnt, is_write) > 0) + return blkcnt; + else + return 0; +} + +u32 dwc_ahsata_rw_ncq_cmd(int dev, u32 start, lbaint_t blkcnt, + u8 *buffer, int is_write) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + struct sata_fis_h2d h2d, *cfis = &h2d; + u8 port = probe_ent->hard_port_no; + u64 block; + + if (sata_dev_desc[dev].lba48 != 1) { + printf("execute FPDMA command on non-LBA48 hard disk\n\r"); + return -1; + } + + block = (u64)start; + + memset(cfis, 0, sizeof(struct sata_fis_h2d)); + + cfis->fis_type = SATA_FIS_TYPE_REGISTER_H2D; + cfis->pm_port_c = 0x80; /* is command */ + + cfis->command = (is_write) ? ATA_CMD_FPDMA_WRITE + : ATA_CMD_FPDMA_READ; + + cfis->lba_high_exp = (block >> 40) & 0xff; + cfis->lba_mid_exp = (block >> 32) & 0xff; + cfis->lba_low_exp = (block >> 24) & 0xff; + cfis->lba_high = (block >> 16) & 0xff; + cfis->lba_mid = (block >> 8) & 0xff; + cfis->lba_low = block & 0xff; + + cfis->device = ATA_LBA; + cfis->features_exp = (blkcnt >> 8) & 0xff; + cfis->features = blkcnt & 0xff; + + /* Use the latest queue */ + ahci_exec_ata_cmd(probe_ent, port, cfis, + buffer, ATA_SECT_SIZE * blkcnt, is_write); + + return blkcnt; +} + +void dwc_ahsata_flush_cache_ext(int dev) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + struct sata_fis_h2d h2d, *cfis = &h2d; + u8 port = probe_ent->hard_port_no; + + memset(cfis, 0, sizeof(struct sata_fis_h2d)); + + cfis->fis_type = SATA_FIS_TYPE_REGISTER_H2D; + cfis->pm_port_c = 0x80; /* is command */ + cfis->command = ATA_CMD_FLUSH_EXT; + + ahci_exec_ata_cmd(probe_ent, port, cfis, NULL, 0, 0); +} + +static void dwc_ahsata_init_wcache(int dev, u16 *id) +{ + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + + if (ata_id_has_wcache(id) && ata_id_wcache_enabled(id)) + probe_ent->flags |= SATA_FLAG_WCACHE; + if (ata_id_has_flush(id)) + probe_ent->flags |= SATA_FLAG_FLUSH; + if (ata_id_has_flush_ext(id)) + probe_ent->flags |= SATA_FLAG_FLUSH_EXT; +} + +u32 ata_low_level_rw_lba48(int dev, u32 blknr, lbaint_t blkcnt, + void *buffer, int is_write) +{ + u32 start, blks; + u8 *addr; + int max_blks; + + start = blknr; + blks = blkcnt; + addr = (u8 *)buffer; + + max_blks = ATA_MAX_SECTORS_LBA48; + + do { + if (blks > max_blks) { + if (max_blks != dwc_ahsata_rw_cmd_ext(dev, start, + max_blks, addr, is_write)) + return 0; + start += max_blks; + blks -= max_blks; + addr += ATA_SECT_SIZE * max_blks; + } else { + if (blks != dwc_ahsata_rw_cmd_ext(dev, start, + blks, addr, is_write)) + return 0; + start += blks; + blks = 0; + addr += ATA_SECT_SIZE * blks; + } + } while (blks != 0); + + return blkcnt; +} + +u32 ata_low_level_rw_lba28(int dev, u32 blknr, lbaint_t blkcnt, + void *buffer, int is_write) +{ + u32 start, blks; + u8 *addr; + int max_blks; + + start = blknr; + blks = blkcnt; + addr = (u8 *)buffer; + + max_blks = ATA_MAX_SECTORS; + do { + if (blks > max_blks) { + if (max_blks != dwc_ahsata_rw_cmd(dev, start, + max_blks, addr, is_write)) + return 0; + start += max_blks; + blks -= max_blks; + addr += ATA_SECT_SIZE * max_blks; + } else { + if (blks != dwc_ahsata_rw_cmd(dev, start, + blks, addr, is_write)) + return 0; + start += blks; + blks = 0; + addr += ATA_SECT_SIZE * blks; + } + } while (blks != 0); + + return blkcnt; +} + +/* + * SATA interface between low level driver and command layer + */ +ulong sata_read(int dev, unsigned long blknr, lbaint_t blkcnt, void *buffer) +{ + u32 rc; + + if (sata_dev_desc[dev].lba48) + rc = ata_low_level_rw_lba48(dev, blknr, blkcnt, + buffer, READ_CMD); + else + rc = ata_low_level_rw_lba28(dev, blknr, blkcnt, + buffer, READ_CMD); + return rc; +} + +ulong sata_write(int dev, unsigned long blknr, lbaint_t blkcnt, void *buffer) +{ + u32 rc; + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + u32 flags = probe_ent->flags; + + if (sata_dev_desc[dev].lba48) { + rc = ata_low_level_rw_lba48(dev, blknr, blkcnt, + buffer, WRITE_CMD); + if ((flags & SATA_FLAG_WCACHE) && + (flags & SATA_FLAG_FLUSH_EXT)) + dwc_ahsata_flush_cache_ext(dev); + } else { + rc = ata_low_level_rw_lba28(dev, blknr, blkcnt, + buffer, WRITE_CMD); + if ((flags & SATA_FLAG_WCACHE) && + (flags & SATA_FLAG_FLUSH)) + dwc_ahsata_flush_cache(dev); + } + return rc; +} + +int scan_sata(int dev) +{ + u8 serial[ATA_ID_SERNO_LEN + 1] = { 0 }; + u8 firmware[ATA_ID_FW_REV_LEN + 1] = { 0 }; + u8 product[ATA_ID_PROD_LEN + 1] = { 0 }; + u16 *id; + u64 n_sectors; + struct ahci_probe_ent *probe_ent = + (struct ahci_probe_ent *)sata_dev_desc[dev].priv; + u8 port = probe_ent->hard_port_no; + block_dev_desc_t *pdev = &(sata_dev_desc[dev]); + + id = (u16 *)malloc(ATA_ID_WORDS * 2); + if (!id) { + printf("id malloc failed\n\r"); + return -1; + } + + /* Identify device to get information */ + dwc_ahsata_identify(dev, id); + + /* Serial number */ + ata_id_c_string(id, serial, ATA_ID_SERNO, sizeof(serial)); + memcpy(pdev->product, serial, sizeof(serial)); + + /* Firmware version */ + ata_id_c_string(id, firmware, ATA_ID_FW_REV, sizeof(firmware)); + memcpy(pdev->revision, firmware, sizeof(firmware)); + + /* Product model */ + ata_id_c_string(id, product, ATA_ID_PROD, sizeof(product)); + memcpy(pdev->vendor, product, sizeof(product)); + + /* Totoal sectors */ + n_sectors = ata_id_n_sectors(id); + pdev->lba = (u32)n_sectors; + + pdev->type = DEV_TYPE_HARDDISK; + pdev->blksz = ATA_SECT_SIZE; + pdev->lun = 0 ; + + /* Check if support LBA48 */ + if (ata_id_has_lba48(id)) { + pdev->lba48 = 1; + debug("Device support LBA48\n\r"); + } + + /* Get the NCQ queue depth from device */ + probe_ent->flags &= (~SATA_FLAG_Q_DEP_MASK); + probe_ent->flags |= ata_id_queue_depth(id); + + /* Get the xfer mode from device */ + dwc_ahsata_xfer_mode(dev, id); + + /* Get the write cache status from device */ + dwc_ahsata_init_wcache(dev, id); + + /* Set the xfer mode to highest speed */ + ahci_set_feature(dev, port); + + free((void *)id); + + dwc_ahsata_print_info(dev); + + is_ready = 1; + + return 0; +} diff --git a/drivers/block/dwc_ahsata.h b/drivers/block/dwc_ahsata.h new file mode 100644 index 0000000..84860ea --- /dev/null +++ b/drivers/block/dwc_ahsata.h @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2010 Freescale Semiconductor, Inc. + * Terry Lv + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __FSL_SATA_H__ +#define __FSL_SATA_H__ + +#define DWC_AHSATA_MAX_CMD_SLOTS 32 + +/* Max host controller numbers */ +#define SATA_HC_MAX_NUM 4 +/* Max command queue depth per host controller */ +#define DWC_AHSATA_HC_MAX_CMD 32 +/* Max port number per host controller */ +#define SATA_HC_MAX_PORT 16 + +/* Generic Host Register */ + +/* HBA Capabilities Register */ +#define SATA_HOST_CAP_S64A 0x80000000 +#define SATA_HOST_CAP_SNCQ 0x40000000 +#define SATA_HOST_CAP_SSNTF 0x20000000 +#define SATA_HOST_CAP_SMPS 0x10000000 +#define SATA_HOST_CAP_SSS 0x08000000 +#define SATA_HOST_CAP_SALP 0x04000000 +#define SATA_HOST_CAP_SAL 0x02000000 +#define SATA_HOST_CAP_SCLO 0x01000000 +#define SATA_HOST_CAP_ISS_MASK 0x00f00000 +#define SATA_HOST_CAP_ISS_OFFSET 20 +#define SATA_HOST_CAP_SNZO 0x00080000 +#define SATA_HOST_CAP_SAM 0x00040000 +#define SATA_HOST_CAP_SPM 0x00020000 +#define SATA_HOST_CAP_PMD 0x00008000 +#define SATA_HOST_CAP_SSC 0x00004000 +#define SATA_HOST_CAP_PSC 0x00002000 +#define SATA_HOST_CAP_NCS 0x00001f00 +#define SATA_HOST_CAP_CCCS 0x00000080 +#define SATA_HOST_CAP_EMS 0x00000040 +#define SATA_HOST_CAP_SXS 0x00000020 +#define SATA_HOST_CAP_NP_MASK 0x0000001f + +/* Global HBA Control Register */ +#define SATA_HOST_GHC_AE 0x80000000 +#define SATA_HOST_GHC_IE 0x00000002 +#define SATA_HOST_GHC_HR 0x00000001 + +/* Interrupt Status Register */ + +/* Ports Implemented Register */ + +/* AHCI Version Register */ +#define SATA_HOST_VS_MJR_MASK 0xffff0000 +#define SATA_HOST_VS_MJR_OFFSET 16 +#define SATA_HOST_VS_MJR_MNR 0x0000ffff + +/* Command Completion Coalescing Control */ +#define SATA_HOST_CCC_CTL_TV_MASK 0xffff0000 +#define SATA_HOST_CCC_CTL_TV_OFFSET 16 +#define SATA_HOST_CCC_CTL_CC_MASK 0x0000ff00 +#define SATA_HOST_CCC_CTL_CC_OFFSET 8 +#define SATA_HOST_CCC_CTL_INT_MASK 0x000000f8 +#define SATA_HOST_CCC_CTL_INT_OFFSET 3 +#define SATA_HOST_CCC_CTL_EN 0x00000001 + +/* Command Completion Coalescing Ports */ + +/* HBA Capabilities Extended Register */ +#define SATA_HOST_CAP2_APST 0x00000004 + +/* BIST Activate FIS Register */ +#define SATA_HOST_BISTAFR_NCP_MASK 0x0000ff00 +#define SATA_HOST_BISTAFR_NCP_OFFSET 8 +#define SATA_HOST_BISTAFR_PD_MASK 0x000000ff +#define SATA_HOST_BISTAFR_PD_OFFSET 0 + +/* BIST Control Register */ +#define SATA_HOST_BISTCR_FERLB 0x00100000 +#define SATA_HOST_BISTCR_TXO 0x00040000 +#define SATA_HOST_BISTCR_CNTCLR 0x00020000 +#define SATA_HOST_BISTCR_NEALB 0x00010000 +#define SATA_HOST_BISTCR_LLC_MASK 0x00000700 +#define SATA_HOST_BISTCR_LLC_OFFSET 8 +#define SATA_HOST_BISTCR_ERREN 0x00000040 +#define SATA_HOST_BISTCR_FLIP 0x00000020 +#define SATA_HOST_BISTCR_PV 0x00000010 +#define SATA_HOST_BISTCR_PATTERN_MASK 0x0000000f +#define SATA_HOST_BISTCR_PATTERN_OFFSET 0 + +/* BIST FIS Count Register */ + +/* BIST Status Register */ +#define SATA_HOST_BISTSR_FRAMERR_MASK 0x0000ffff +#define SATA_HOST_BISTSR_FRAMERR_OFFSET 0 +#define SATA_HOST_BISTSR_BRSTERR_MASK 0x00ff0000 +#define SATA_HOST_BISTSR_BRSTERR_OFFSET 16 + +/* BIST DWORD Error Count Register */ + +/* OOB Register*/ +#define SATA_HOST_OOBR_WE 0x80000000 +#define SATA_HOST_OOBR_cwMin_MASK 0x7f000000 +#define SATA_HOST_OOBR_cwMAX_MASK 0x00ff0000 +#define SATA_HOST_OOBR_ciMin_MASK 0x0000ff00 +#define SATA_HOST_OOBR_ciMax_MASK 0x000000ff + +/* Timer 1-ms Register */ + +/* Global Parameter 1 Register */ +#define SATA_HOST_GPARAM1R_ALIGN_M 0x80000000 +#define SATA_HOST_GPARAM1R_RX_BUFFER 0x40000000 +#define SATA_HOST_GPARAM1R_PHY_DATA_MASK 0x30000000 +#define SATA_HOST_GPARAM1R_PHY_RST 0x08000000 +#define SATA_HOST_GPARAM1R_PHY_CTRL_MASK 0x07e00000 +#define SATA_HOST_GPARAM1R_PHY_STAT_MASK 0x001f8000 +#define SATA_HOST_GPARAM1R_LATCH_M 0x00004000 +#define SATA_HOST_GPARAM1R_BIST_M 0x00002000 +#define SATA_HOST_GPARAM1R_PHY_TYPE 0x00001000 +#define SATA_HOST_GPARAM1R_RETURN_ERR 0x00000400 +#define SATA_HOST_GPARAM1R_AHB_ENDIAN_MASK 0x00000300 +#define SATA_HOST_GPARAM1R_S_HADDR 0X00000080 +#define SATA_HOST_GPARAM1R_M_HADDR 0X00000040 + +/* Global Parameter 2 Register */ +#define SATA_HOST_GPARAM2R_DEV_CP 0x00004000 +#define SATA_HOST_GPARAM2R_DEV_MP 0x00002000 +#define SATA_HOST_GPARAM2R_DEV_ENCODE_M 0x00001000 +#define SATA_HOST_GPARAM2R_RXOOB_CLK_M 0x00000800 +#define SATA_HOST_GPARAM2R_RXOOB_M 0x00000400 +#define SATA_HOST_GPARAM2R_TX_OOB_M 0x00000200 +#define SATA_HOST_GPARAM2R_RXOOB_CLK_MASK 0x000001ff + +/* Port Parameter Register */ +#define SATA_HOST_PPARAMR_TX_MEM_M 0x00000200 +#define SATA_HOST_PPARAMR_TX_MEM_S 0x00000100 +#define SATA_HOST_PPARAMR_RX_MEM_M 0x00000080 +#define SATA_HOST_PPARAMR_RX_MEM_S 0x00000040 +#define SATA_HOST_PPARAMR_TXFIFO_DEPTH_MASK 0x00000038 +#define SATA_HOST_PPARAMR_RXFIFO_DEPTH_MASK 0x00000007 + +/* Test Register */ +#define SATA_HOST_TESTR_PSEL_MASK 0x00070000 +#define SATA_HOST_TESTR_TEST_IF 0x00000001 + +/* Port Register Descriptions */ +/* Port# Command List Base Address Register */ +#define SATA_PORT_CLB_CLB_MASK 0xfffffc00 + +/* Port# Command List Base Address Upper 32-Bits Register */ + +/* Port# FIS Base Address Register */ +#define SATA_PORT_FB_FB_MASK 0xfffffff0 + +/* Port# FIS Base Address Upper 32-Bits Register */ + +/* Port# Interrupt Status Register */ +#define SATA_PORT_IS_CPDS 0x80000000 +#define SATA_PORT_IS_TFES 0x40000000 +#define SATA_PORT_IS_HBFS 0x20000000 +#define SATA_PORT_IS_HBDS 0x10000000 +#define SATA_PORT_IS_IFS 0x08000000 +#define SATA_PORT_IS_INFS 0x04000000 +#define SATA_PORT_IS_OFS 0x01000000 +#define SATA_PORT_IS_IPMS 0x00800000 +#define SATA_PORT_IS_PRCS 0x00400000 +#define SATA_PORT_IS_DMPS 0x00000080 +#define SATA_PORT_IS_PCS 0x00000040 +#define SATA_PORT_IS_DPS 0x00000020 +#define SATA_PORT_IS_UFS 0x00000010 +#define SATA_PORT_IS_SDBS 0x00000008 +#define SATA_PORT_IS_DSS 0x00000004 +#define SATA_PORT_IS_PSS 0x00000002 +#define SATA_PORT_IS_DHRS 0x00000001 + +/* Port# Interrupt Enable Register */ +#define SATA_PORT_IE_CPDE 0x80000000 +#define SATA_PORT_IE_TFEE 0x40000000 +#define SATA_PORT_IE_HBFE 0x20000000 +#define SATA_PORT_IE_HBDE 0x10000000 +#define SATA_PORT_IE_IFE 0x08000000 +#define SATA_PORT_IE_INFE 0x04000000 +#define SATA_PORT_IE_OFE 0x01000000 +#define SATA_PORT_IE_IPME 0x00800000 +#define SATA_PORT_IE_PRCE 0x00400000 +#define SATA_PORT_IE_DMPE 0x00000080 +#define SATA_PORT_IE_PCE 0x00000040 +#define SATA_PORT_IE_DPE 0x00000020 +#define SATA_PORT_IE_UFE 0x00000010 +#define SATA_PORT_IE_SDBE 0x00000008 +#define SATA_PORT_IE_DSE 0x00000004 +#define SATA_PORT_IE_PSE 0x00000002 +#define SATA_PORT_IE_DHRE 0x00000001 + +/* Port# Command Register */ +#define SATA_PORT_CMD_ICC_MASK 0xf0000000 +#define SATA_PORT_CMD_ASP 0x08000000 +#define SATA_PORT_CMD_ALPE 0x04000000 +#define SATA_PORT_CMD_DLAE 0x02000000 +#define SATA_PORT_CMD_ATAPI 0x01000000 +#define SATA_PORT_CMD_APSTE 0x00800000 +#define SATA_PORT_CMD_ESP 0x00200000 +#define SATA_PORT_CMD_CPD 0x00100000 +#define SATA_PORT_CMD_MPSP 0x00080000 +#define SATA_PORT_CMD_HPCP 0x00040000 +#define SATA_PORT_CMD_PMA 0x00020000 +#define SATA_PORT_CMD_CPS 0x00010000 +#define SATA_PORT_CMD_CR 0x00008000 +#define SATA_PORT_CMD_FR 0x00004000 +#define SATA_PORT_CMD_MPSS 0x00002000 +#define SATA_PORT_CMD_CCS_MASK 0x00001f00 +#define SATA_PORT_CMD_FRE 0x00000010 +#define SATA_PORT_CMD_CLO 0x00000008 +#define SATA_PORT_CMD_POD 0x00000004 +#define SATA_PORT_CMD_SUD 0x00000002 +#define SATA_PORT_CMD_ST 0x00000001 + +/* Port# Task File Data Register */ +#define SATA_PORT_TFD_ERR_MASK 0x0000ff00 +#define SATA_PORT_TFD_STS_MASK 0x000000ff +#define SATA_PORT_TFD_STS_ERR 0x00000001 +#define SATA_PORT_TFD_STS_DRQ 0x00000008 +#define SATA_PORT_TFD_STS_BSY 0x00000080 + +/* Port# Signature Register */ + +/* Port# Serial ATA Status {SStatus} Register */ +#define SATA_PORT_SSTS_IPM_MASK 0x00000f00 +#define SATA_PORT_SSTS_SPD_MASK 0x000000f0 +#define SATA_PORT_SSTS_DET_MASK 0x0000000f + +/* Port# Serial ATA Control {SControl} Register */ +#define SATA_PORT_SCTL_IPM_MASK 0x00000f00 +#define SATA_PORT_SCTL_SPD_MASK 0x000000f0 +#define SATA_PORT_SCTL_DET_MASK 0x0000000f + +/* Port# Serial ATA Error {SError} Register */ +#define SATA_PORT_SERR_DIAG_X 0x04000000 +#define SATA_PORT_SERR_DIAG_F 0x02000000 +#define SATA_PORT_SERR_DIAG_T 0x01000000 +#define SATA_PORT_SERR_DIAG_S 0x00800000 +#define SATA_PORT_SERR_DIAG_H 0x00400000 +#define SATA_PORT_SERR_DIAG_C 0x00200000 +#define SATA_PORT_SERR_DIAG_D 0x00100000 +#define SATA_PORT_SERR_DIAG_B 0x00080000 +#define SATA_PORT_SERR_DIAG_W 0x00040000 +#define SATA_PORT_SERR_DIAG_I 0x00020000 +#define SATA_PORT_SERR_DIAG_N 0x00010000 +#define SATA_PORT_SERR_ERR_E 0x00000800 +#define SATA_PORT_SERR_ERR_P 0x00000400 +#define SATA_PORT_SERR_ERR_C 0x00000200 +#define SATA_PORT_SERR_ERR_T 0x00000100 +#define SATA_PORT_SERR_ERR_M 0x00000002 +#define SATA_PORT_SERR_ERR_I 0x00000001 + +/* Port# Serial ATA Active {SActive} Register */ + +/* Port# Command Issue Register */ + +/* Port# Serial ATA Notification Register */ + +/* Port# DMA Control Register */ +#define SATA_PORT_DMACR_RXABL_MASK 0x0000f000 +#define SATA_PORT_DMACR_TXABL_MASK 0x00000f00 +#define SATA_PORT_DMACR_RXTS_MASK 0x000000f0 +#define SATA_PORT_DMACR_TXTS_MASK 0x0000000f + +/* Port# PHY Control Register */ + +/* Port# PHY Status Register */ + +#define SATA_HC_CMD_HDR_ENTRY_SIZE sizeof(struct cmd_hdr_entry) + +/* DW0 +*/ +#define CMD_HDR_DI_CFL_MASK 0x0000001f +#define CMD_HDR_DI_CFL_OFFSET 0 +#define CMD_HDR_DI_A 0x00000020 +#define CMD_HDR_DI_W 0x00000040 +#define CMD_HDR_DI_P 0x00000080 +#define CMD_HDR_DI_R 0x00000100 +#define CMD_HDR_DI_B 0x00000200 +#define CMD_HDR_DI_C 0x00000400 +#define CMD_HDR_DI_PMP_MASK 0x0000f000 +#define CMD_HDR_DI_PMP_OFFSET 12 +#define CMD_HDR_DI_PRDTL 0xffff0000 +#define CMD_HDR_DI_PRDTL_OFFSET 16 + +/* prde_fis_len +*/ +#define CMD_HDR_PRD_ENTRY_SHIFT 16 +#define CMD_HDR_PRD_ENTRY_MASK 0x003f0000 +#define CMD_HDR_FIS_LEN_SHIFT 2 + +/* attribute +*/ +#define CMD_HDR_ATTR_RES 0x00000800 /* Reserved bit, should be 1 */ +#define CMD_HDR_ATTR_VBIST 0x00000400 /* Vendor BIST */ +/* Snoop enable for all descriptor */ +#define CMD_HDR_ATTR_SNOOP 0x00000200 +#define CMD_HDR_ATTR_FPDMA 0x00000100 /* FPDMA queued command */ +#define CMD_HDR_ATTR_RESET 0x00000080 /* Reset - a SRST or device reset */ +/* BIST - require the host to enter BIST mode */ +#define CMD_HDR_ATTR_BIST 0x00000040 +#define CMD_HDR_ATTR_ATAPI 0x00000020 /* ATAPI command */ +#define CMD_HDR_ATTR_TAG 0x0000001f /* TAG mask */ + +#define FLAGS_DMA 0x00000000 +#define FLAGS_FPDMA 0x00000001 + +#define SATA_FLAG_Q_DEP_MASK 0x0000000f +#define SATA_FLAG_WCACHE 0x00000100 +#define SATA_FLAG_FLUSH 0x00000200 +#define SATA_FLAG_FLUSH_EXT 0x00000400 + +#define READ_CMD 0 +#define WRITE_CMD 1 + +extern block_dev_desc_t sata_dev_desc[CONFIG_SYS_SATA_MAX_DEVICE]; + +#endif /* __FSL_SATA_H__ */ -- cgit v1.1 From 20831061d8cec4ec537fada6b9abe263e208b4ae Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Tue, 20 Mar 2012 11:40:06 +0000 Subject: pmic: Add support for the Dialog DA9053 PMIC Add support for the Dialog DA9053 PMIC. Signed-off-by: Fabio Estevam --- drivers/misc/Makefile | 1 + drivers/misc/pmic_dialog.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 drivers/misc/pmic_dialog.c (limited to 'drivers') diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 30c7f8d..271463c 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -35,6 +35,7 @@ COBJS-$(CONFIG_PDSP188x) += pdsp188x.o COBJS-$(CONFIG_STATUS_LED) += status_led.o COBJS-$(CONFIG_TWL4030_LED) += twl4030_led.o COBJS-$(CONFIG_PMIC) += pmic_core.o +COBJS-$(CONFIG_DIALOG_PMIC) += pmic_dialog.o COBJS-$(CONFIG_PMIC_FSL) += pmic_fsl.o COBJS-$(CONFIG_PMIC_I2C) += pmic_i2c.o COBJS-$(CONFIG_PMIC_SPI) += pmic_spi.o diff --git a/drivers/misc/pmic_dialog.c b/drivers/misc/pmic_dialog.c new file mode 100644 index 0000000..7242073 --- /dev/null +++ b/drivers/misc/pmic_dialog.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011 Samsung Electronics + * Lukasz Majewski + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +int pmic_init(void) +{ + struct pmic *p = get_pmic(); + static const char name[] = "DIALOG_PMIC"; + + p->name = name; + p->number_of_regs = PMIC_NUM_OF_REGS; + + p->interface = PMIC_I2C; + p->hw.i2c.addr = CONFIG_SYS_DIALOG_PMIC_I2C_ADDR; + p->hw.i2c.tx_num = 1; + p->bus = I2C_PMIC; + + return 0; +} -- cgit v1.1 From 3f29d96218d985268c46a6821ba893d1ea2cbc8c Mon Sep 17 00:00:00 2001 From: Wolfgang Grandegger Date: Wed, 2 May 2012 04:36:39 +0000 Subject: USB: ehci-mx6: Fix broken IO access To get USB working again on the i.MX6, this patch fixes a bug introduced with commit 522b2a0 "Add proper IO accessors for mx6 usb registers.". At that occasion, I also added the missing __iomem directive. Cc: Marek Vasut CC: Fabio Estevam Signed-off-by: Wolfgang Grandegger --- drivers/usb/host/ehci-mx6.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-mx6.c b/drivers/usb/host/ehci-mx6.c index 5dec673..42c77fe 100644 --- a/drivers/usb/host/ehci-mx6.c +++ b/drivers/usb/host/ehci-mx6.c @@ -73,7 +73,8 @@ static void usbh1_internal_phy_clock_gate(int on) static void usbh1_power_config(void) { - struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR; + struct anatop_regs __iomem *anatop = + (struct anatop_regs __iomem *)ANATOP_BASE_ADDR; /* * Some phy and power's special controls for host1 * 1. The external charger detector needs to be disabled @@ -87,7 +88,7 @@ static void usbh1_power_config(void) &anatop->usb2_chrg_detect); __raw_writel(ANADIG_USB2_PLL_480_CTRL_BYPASS, - &anatop->usb2_pll_480_ctrl); + &anatop->usb2_pll_480_ctrl_clr); __raw_writel(ANADIG_USB2_PLL_480_CTRL_ENABLE | ANADIG_USB2_PLL_480_CTRL_POWER | -- cgit v1.1 From eba1868be44a272a2c2b38189c86fbad0a9a0804 Mon Sep 17 00:00:00 2001 From: Timo Ketola Date: Wed, 18 Apr 2012 22:55:29 +0000 Subject: i.MX25: This architecture has a GPIO4 too Signed-off-by: Timo Ketola Acked-by: Stefano Babic --- drivers/gpio/mxc_gpio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpio/mxc_gpio.c b/drivers/gpio/mxc_gpio.c index f1b1c16..6615535 100644 --- a/drivers/gpio/mxc_gpio.c +++ b/drivers/gpio/mxc_gpio.c @@ -41,7 +41,8 @@ static unsigned long gpio_ports[] = { [0] = GPIO1_BASE_ADDR, [1] = GPIO2_BASE_ADDR, [2] = GPIO3_BASE_ADDR, -#if defined(CONFIG_MX51) || defined(CONFIG_MX53) || defined(CONFIG_MX6Q) +#if defined(CONFIG_MX25) || defined(CONFIG_MX51) || defined(CONFIG_MX53) || \ + defined(CONFIG_MX6Q) [3] = GPIO4_BASE_ADDR, #endif #if defined(CONFIG_MX53) || defined(CONFIG_MX6Q) -- cgit v1.1 From a1028730b5a5f8266fc3a198cd626bf73e157e6c Mon Sep 17 00:00:00 2001 From: Timo Ketola Date: Wed, 18 Apr 2012 22:55:31 +0000 Subject: imx: nand: Support flash based BBT Signed-off-by: Timo Ketola Acked-by: Scott Wood --- drivers/mtd/nand/mxc_nand.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'drivers') diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index 35e89a0..936186f 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -1302,12 +1302,45 @@ static void mxc_setup_config1(void) #define mxc_setup_config1() #endif +#ifdef CONFIG_SYS_NAND_USE_FLASH_BBT + +static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = {'1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = mirror_pattern, +}; + +#endif + int board_nand_init(struct nand_chip *this) { struct mtd_info *mtd; uint16_t tmp; int err = 0; +#ifdef CONFIG_SYS_NAND_USE_FLASH_BBT + this->options |= NAND_USE_FLASH_BBT; + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; +#endif + /* structures must be linked */ mtd = &host->mtd; mtd->priv = this; -- cgit v1.1 From 45b460dab3b47d03392ed46d42652103666503b7 Mon Sep 17 00:00:00 2001 From: Timo Ketola Date: Wed, 18 Apr 2012 22:55:32 +0000 Subject: i.MX25: usb: Set PORTSCx register The USB controller in i.MX25 has a PORTSCx registers which should be set. In this regard it is similar to the controller in i.MX31. As this file is compiled only with i.MX25 and -31, #ifdef check can be removed. Signed-off-by: Timo Ketola Acked-by: Stefano Babic --- drivers/usb/host/ehci-mxc.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c index 61dbccd..7384580 100644 --- a/drivers/usb/host/ehci-mxc.c +++ b/drivers/usb/host/ehci-mxc.c @@ -125,11 +125,9 @@ int ehci_hcd_init(void) hcor = (struct ehci_hcor *)((uint32_t) hccr + HC_LENGTH(ehci_readl(&hccr->cr_capbase))); setbits_le32(&ehci->usbmode, CM_HOST); -#ifdef CONFIG_MX31 setbits_le32(&ehci->control, USB_EN); __raw_writel(CONFIG_MXC_USB_PORTSC, &ehci->portsc); -#endif mxc_set_usbcontrol(CONFIG_MXC_USB_PORT, CONFIG_MXC_USB_FLAGS); udelay(10000); -- cgit v1.1 From 5614553d564e1c66684c207e120557ef81e416dc Mon Sep 17 00:00:00 2001 From: Timo Ketola Date: Wed, 18 Apr 2012 22:55:33 +0000 Subject: imx: usb: There is no such register The reference manual of i.MX25 (nor i.MX31) does not define such register. This seems to access read only UH2_CAPLENGTH register (if CONFIG_MXC_USB_PORT is zero). Signed-off-by: Timo Ketola Acked-by: Stefano Babic --- drivers/usb/host/ehci-mxc.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c index 7384580..45cbd18 100644 --- a/drivers/usb/host/ehci-mxc.c +++ b/drivers/usb/host/ehci-mxc.c @@ -125,8 +125,6 @@ int ehci_hcd_init(void) hcor = (struct ehci_hcor *)((uint32_t) hccr + HC_LENGTH(ehci_readl(&hccr->cr_capbase))); setbits_le32(&ehci->usbmode, CM_HOST); - setbits_le32(&ehci->control, USB_EN); - __raw_writel(CONFIG_MXC_USB_PORTSC, &ehci->portsc); mxc_set_usbcontrol(CONFIG_MXC_USB_PORT, CONFIG_MXC_USB_FLAGS); -- cgit v1.1 From ed5157e8895a6bf59a8163329639e34db0702331 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 7 May 2012 10:25:58 +0000 Subject: pmic: dialog: Avoid name conflicts As mx53loco board has two variants: one with Dialog PMIC and another with FSL MC34708 PMIC, we need to be able to build both drivers. Change pmic_init() and PMIC_NUM_OF_REGS names to avoid build conflicts when both drivers are present. Signed-off-by: Fabio Estevam Acked-by: Stefano Babic --- drivers/misc/pmic_dialog.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/misc/pmic_dialog.c b/drivers/misc/pmic_dialog.c index 7242073..e97af1d 100644 --- a/drivers/misc/pmic_dialog.c +++ b/drivers/misc/pmic_dialog.c @@ -20,13 +20,13 @@ #include #include -int pmic_init(void) +int pmic_dialog_init(void) { struct pmic *p = get_pmic(); static const char name[] = "DIALOG_PMIC"; p->name = name; - p->number_of_regs = PMIC_NUM_OF_REGS; + p->number_of_regs = DIALOG_NUM_OF_REGS; p->interface = PMIC_I2C; p->hw.i2c.addr = CONFIG_SYS_DIALOG_PMIC_I2C_ADDR; -- cgit v1.1 From 79cb14ab39f970b85204ab1b2f796b02bb65335d Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 23 Apr 2012 08:30:49 +0000 Subject: spi: mxs: Introduce spi_cs_is_valid() Introduce spi_cs_is_valid() for validating spi bus and chip select numbers. Signed-off-by: Fabio Estevam Acked-by: Marek Vasut Acked-by: Mike Frysinger --- drivers/spi/mxs_spi.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/spi/mxs_spi.c b/drivers/spi/mxs_spi.c index 4e6f14e..e7237e7 100644 --- a/drivers/spi/mxs_spi.c +++ b/drivers/spi/mxs_spi.c @@ -51,14 +51,23 @@ void spi_init(void) { } +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ + /* MXS SPI: 4 ports and 3 chip selects maximum */ + if (bus > 3 || cs > 2) + return 0; + else + return 1; +} + struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int mode) { struct mxs_spi_slave *mxs_slave; uint32_t addr; - if (bus > 3) { - printf("MXS SPI: Max bus number is 3\n"); + if (!spi_cs_is_valid(bus, cs)) { + printf("mxs_spi: invalid bus %d / chip select %d\n", bus, cs); return NULL; } -- cgit v1.1 From 148ca64f327a89ef77e84756f5d351af33e59b64 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 23 Apr 2012 08:30:50 +0000 Subject: spi: mxs: Allow other chip selects to work MXS SSP controller may have up to three chip selects per port: SS0, SS1 and SS2. Currently only SS0 is supported in the mxs_spi driver. Allow all the three chip select to work by selecting the desired one in bits 20 and 21 of the HW_SSP_CTRL0 register. Signed-off-by: Fabio Estevam Acked-by: Marek Vasut --- drivers/spi/mxs_spi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/spi/mxs_spi.c b/drivers/spi/mxs_spi.c index e7237e7..7859536 100644 --- a/drivers/spi/mxs_spi.c +++ b/drivers/spi/mxs_spi.c @@ -34,6 +34,8 @@ #define MXS_SPI_MAX_TIMEOUT 1000000 #define MXS_SPI_PORT_OFFSET 0x2000 +#define MXS_SSP_CHIPSELECT_MASK 0x00300000 +#define MXS_SSP_CHIPSELECT_SHIFT 20 struct mxs_spi_slave { struct spi_slave slave; @@ -65,6 +67,8 @@ struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, { struct mxs_spi_slave *mxs_slave; uint32_t addr; + struct mx28_ssp_regs *ssp_regs; + int reg; if (!spi_cs_is_valid(bus, cs)) { printf("mxs_spi: invalid bus %d / chip select %d\n", bus, cs); @@ -82,7 +86,13 @@ struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, mxs_slave->max_khz = max_hz / 1000; mxs_slave->mode = mode; mxs_slave->regs = (struct mx28_ssp_regs *)addr; + ssp_regs = mxs_slave->regs; + reg = readl(&ssp_regs->hw_ssp_ctrl0); + reg &= ~(MXS_SSP_CHIPSELECT_MASK); + reg |= cs << MXS_SSP_CHIPSELECT_SHIFT; + + writel(reg, &ssp_regs->hw_ssp_ctrl0); return &mxs_slave->slave; } -- cgit v1.1 From a5990b267449882733f3bd3d13493e0a78bfdc75 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 1 May 2012 11:09:41 +0000 Subject: FEC: Abstract out register setup Abstract out common register setup. This also configured r_cntrl to correct value at registration time. Signed-off-by: Marek Vasut Cc: Detlev Zundel Cc: Fabio Estevam Cc: Stefano Babic Cc: Wolfgang Denk --- drivers/net/fec_mxc.c | 84 +++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 46 deletions(-) (limited to 'drivers') diff --git a/drivers/net/fec_mxc.c b/drivers/net/fec_mxc.c index 5c3b90f..156fa8f 100644 --- a/drivers/net/fec_mxc.c +++ b/drivers/net/fec_mxc.c @@ -400,6 +400,42 @@ static void fec_eth_phy_config(struct eth_device *dev) #endif } +/* + * Do initial configuration of the FEC registers + */ +static void fec_reg_setup(struct fec_priv *fec) +{ + uint32_t rcntrl; + + /* + * Set interrupt mask register + */ + writel(0x00000000, &fec->eth->imask); + + /* + * Clear FEC-Lite interrupt event register(IEVENT) + */ + writel(0xffffffff, &fec->eth->ievent); + + + /* + * Set FEC-Lite receive control register(R_CNTRL): + */ + + /* Start with frame length = 1518, common for all modes. */ + rcntrl = PKTSIZE << FEC_RCNTRL_MAX_FL_SHIFT; + if (fec->xcv_type == SEVENWIRE) + rcntrl |= FEC_RCNTRL_FCE; + else if (fec->xcv_type == RGMII) + rcntrl |= FEC_RCNTRL_RGMII; + else if (fec->xcv_type == RMII) + rcntrl |= FEC_RCNTRL_RMII; + else /* MII mode */ + rcntrl |= FEC_RCNTRL_FCE | FEC_RCNTRL_MII_MODE; + + writel(rcntrl, &fec->eth->r_cntrl); +} + /** * Start the FEC engine * @param[in] dev Our device to handle @@ -514,7 +550,6 @@ static int fec_init(struct eth_device *dev, bd_t* bd) { struct fec_priv *fec = (struct fec_priv *)dev->priv; uint32_t mib_ptr = (uint32_t)&fec->eth->rmon_t_drop; - uint32_t rcntrl; uint32_t size; int i, ret; @@ -562,33 +597,7 @@ static int fec_init(struct eth_device *dev, bd_t* bd) (unsigned)fec->rbd_base + size); } - /* - * Set interrupt mask register - */ - writel(0x00000000, &fec->eth->imask); - - /* - * Clear FEC-Lite interrupt event register(IEVENT) - */ - writel(0xffffffff, &fec->eth->ievent); - - - /* - * Set FEC-Lite receive control register(R_CNTRL): - */ - - /* Start with frame length = 1518, common for all modes. */ - rcntrl = PKTSIZE << FEC_RCNTRL_MAX_FL_SHIFT; - if (fec->xcv_type == SEVENWIRE) - rcntrl |= FEC_RCNTRL_FCE; - else if (fec->xcv_type == RGMII) - rcntrl |= FEC_RCNTRL_RGMII; - else if (fec->xcv_type == RMII) - rcntrl |= FEC_RCNTRL_RMII; - else /* MII mode */ - rcntrl |= FEC_RCNTRL_FCE | FEC_RCNTRL_MII_MODE; - - writel(rcntrl, &fec->eth->r_cntrl); + fec_reg_setup(fec); if (fec->xcv_type == MII10 || fec->xcv_type == MII100) fec_mii_setspeed(fec); @@ -935,24 +944,7 @@ static int fec_probe(bd_t *bd, int dev_id, int phy_id, uint32_t base_addr) udelay(10); } - /* - * Set interrupt mask register - */ - writel(0x00000000, &fec->eth->imask); - - /* - * Clear FEC-Lite interrupt event register(IEVENT) - */ - writel(0xffffffff, &fec->eth->ievent); - - /* - * Set FEC-Lite receive control register(R_CNTRL): - */ - /* - * Frame length=1518; MII mode; - */ - writel((PKTSIZE << FEC_RCNTRL_MAX_FL_SHIFT) | FEC_RCNTRL_FCE | - FEC_RCNTRL_MII_MODE, &fec->eth->r_cntrl); + fec_reg_setup(fec); fec_mii_setspeed(fec); if (dev_id == -1) { -- cgit v1.1 From c7527b70f96791c1ee746c870f4e728ed73f2bb9 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 1 May 2012 11:09:52 +0000 Subject: i.MX28: Check if WP detection is implemented at all If the WP function is NULL, simply assume the card is always RW. Signed-off-by: Marek Vasut Cc: Stefano Babic Cc: Wolfgang Denk Cc: Detlev Zundel Cc: Fabio Estevam Acked-by: Stefano Babic --- drivers/mmc/mxsmmc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/mxsmmc.c b/drivers/mmc/mxsmmc.c index 35c6bda..c7200ee 100644 --- a/drivers/mmc/mxsmmc.c +++ b/drivers/mmc/mxsmmc.c @@ -133,7 +133,8 @@ mxsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) /* READ or WRITE */ if (data->flags & MMC_DATA_READ) { ctrl0 |= SSP_CTRL0_READ; - } else if (priv->mmc_is_wp(mmc->block_dev.dev)) { + } else if (priv->mmc_is_wp && + priv->mmc_is_wp(mmc->block_dev.dev)) { printf("MMC%d: Can not write a locked card!\n", mmc->block_dev.dev); return UNUSABLE_ERR; -- cgit v1.1 From a35925b8c10c99a7020bfcda74c8a6c72ed90cf5 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 10 May 2012 11:37:35 +0000 Subject: Add abs() macro to return absolute value This macro is generally useful to make it available in common. Signed-off-by: Simon Glass Signed-off-by: Tom Warren Acked-by: Tom Rini Acked-by: Mike Frysinger --- drivers/bios_emulator/x86emu/prim_ops.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers') diff --git a/drivers/bios_emulator/x86emu/prim_ops.c b/drivers/bios_emulator/x86emu/prim_ops.c index 7553087..5f6c795 100644 --- a/drivers/bios_emulator/x86emu/prim_ops.c +++ b/drivers/bios_emulator/x86emu/prim_ops.c @@ -118,11 +118,6 @@ static u32 x86emu_parity_tab[8] = #define PARITY(x) (((x86emu_parity_tab[(x) / 32] >> ((x) % 32)) & 1) == 0) #define XOR2(x) (((x) ^ ((x)>>1)) & 0x1) -/*----------------------------- Implementation ----------------------------*/ -int abs(int v) -{ - return (v>0)?v:-v; -} /*----------------------------- Implementation ----------------------------*/ -- cgit v1.1 From 1aa7e23e2ba41c94d61bcd9199afb23817400f3c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 2 Apr 2012 13:18:44 +0000 Subject: i2c: Add TPS6586X driver This power management chip supports battery charging and a large number of power supplies. This initial driver only provides the ability to adjust the two synchronous buck converters SM0 and SM1 in a stepwise manner. Signed-off-by: Simon Glass Signed-off-by: Tom Warren --- drivers/power/Makefile | 1 + drivers/power/tps6586x.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 drivers/power/tps6586x.c (limited to 'drivers') diff --git a/drivers/power/Makefile b/drivers/power/Makefile index f24b93c..6bf388c 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -26,6 +26,7 @@ include $(TOPDIR)/config.mk LIB := $(obj)libpower.o COBJS-$(CONFIG_FTPMU010_POWER) += ftpmu010.o +COBJS-$(CONFIG_TPS6586X_POWER) += tps6586x.o COBJS-$(CONFIG_TWL4030_POWER) += twl4030.o COBJS-$(CONFIG_TWL6030_POWER) += twl6030.o COBJS-$(CONFIG_TWL6035_POWER) += twl6035.o diff --git a/drivers/power/tps6586x.c b/drivers/power/tps6586x.c new file mode 100644 index 0000000..f3f2ec6 --- /dev/null +++ b/drivers/power/tps6586x.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * (C) Copyright 2010,2011 NVIDIA Corporation + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include + +static int bus_num; /* I2C bus we are on */ +#define I2C_ADDRESS 0x34 /* chip requires this address */ +static char inited; /* 1 if we have been inited */ + +enum { + /* Registers that we access */ + SUPPLY_CONTROL1 = 0x20, + SUPPLY_CONTROL2, + SM1_VOLTAGE_V1 = 0x23, + SM1_VOLTAGE_V2, + SM0_VOLTAGE_V1 = 0x26, + SM0_VOLTAGE_V2, + PFM_MODE = 0x47, + + /* Bits in the supply control registers */ + CTRL_SM1_RAMP = 0x01, + CTRL_SM1_SUPPLY2 = 0x02, + CTRL_SM0_RAMP = 0x04, + CTRL_SM0_SUPPLY2 = 0x08, +}; + +#define MAX_I2C_RETRY 3 +int tps6586x_read(int reg) +{ + int i; + uchar data; + int retval = -1; + int old_bus_num; + + old_bus_num = i2c_get_bus_num(); + i2c_set_bus_num(bus_num); + + for (i = 0; i < MAX_I2C_RETRY; ++i) { + if (!i2c_read(I2C_ADDRESS, reg, 1, &data, 1)) { + retval = (int)data; + goto exit; + } + + /* i2c access failed, retry */ + udelay(100); + } + +exit: + i2c_set_bus_num(old_bus_num); + debug("pmu_read %x=%x\n", reg, retval); + if (retval < 0) + debug("%s: failed to read register %#x: %d\n", __func__, reg, + retval); + return retval; +} + +int tps6586x_write(int reg, uchar *data, uint len) +{ + int i; + int retval = -1; + int old_bus_num; + + old_bus_num = i2c_get_bus_num(); + i2c_set_bus_num(bus_num); + + for (i = 0; i < MAX_I2C_RETRY; ++i) { + if (!i2c_write(I2C_ADDRESS, reg, 1, data, len)) { + retval = 0; + goto exit; + } + + /* i2c access failed, retry */ + udelay(100); + } + +exit: + i2c_set_bus_num(old_bus_num); + debug("pmu_write %x=%x: ", reg, retval); + for (i = 0; i < len; i++) + debug("%x ", data[i]); + if (retval) + debug("%s: failed to write register %#x\n", __func__, reg); + return retval; +} + +/* + * Get current voltage of SM0 and SM1 + * + * @param sm0 Place to put SM0 voltage + * @param sm1 Place to put SM1 voltage + * @return 0 if ok, -1 on error + */ +static int read_voltages(int *sm0, int *sm1) +{ + int ctrl1, ctrl2; + int is_v2; + + /* + * Each vdd has two supply sources, ie, v1 and v2. + * The supply control reg1 and reg2 determine the current selection. + */ + ctrl1 = tps6586x_read(SUPPLY_CONTROL1); + ctrl2 = tps6586x_read(SUPPLY_CONTROL2); + if (ctrl1 == -1 || ctrl2 == -1) + return -1; + + /* Figure out whether V1 or V2 is selected */ + is_v2 = (ctrl1 | ctrl2) & CTRL_SM0_SUPPLY2; + *sm0 = tps6586x_read(is_v2 ? SM0_VOLTAGE_V2 : SM0_VOLTAGE_V1); + *sm1 = tps6586x_read(is_v2 ? SM1_VOLTAGE_V2 : SM1_VOLTAGE_V1); + if (*sm0 == -1 || *sm1 == -1) + return -1; + + return 0; +} + +static int set_voltage(int reg, int data, int rate) +{ + uchar control_bit; + uchar buff[3]; + + control_bit = (reg == SM0_VOLTAGE_V1 ? CTRL_SM0_RAMP : CTRL_SM1_RAMP); + + /* + * Only one supply is needed in u-boot. set both v1 and v2 to + * same value. + * + * When both v1 and v2 are set to same value, we just need to set + * control1 reg to trigger the supply selection. + */ + buff[0] = buff[1] = (uchar)data; + buff[2] = rate; + + /* write v1, v2 and rate, then trigger */ + if (tps6586x_write(reg, buff, 3) || + tps6586x_write(SUPPLY_CONTROL1, &control_bit, 1)) + return -1; + + return 0; +} + +static int calculate_next_voltage(int voltage, int target, int step) +{ + int diff = voltage < target ? step : -step; + + if (abs(target - voltage) > step) + voltage += diff; + else + voltage = target; + + return voltage; +} + +int tps6586x_set_pwm_mode(int mask) +{ + uchar val; + int ret; + + assert(inited); + ret = tps6586x_read(PFM_MODE); + if (ret != -1) { + val = (uchar)ret; + val |= mask; + + ret = tps6586x_write(PFM_MODE, &val, 1); + } + + if (ret == -1) + debug("%s: Failed to read/write PWM mode reg\n", __func__); + + return ret; +} + +int tps6586x_adjust_sm0_sm1(int sm0_target, int sm1_target, int step, int rate, + int min_sm0_over_sm1) +{ + int sm0, sm1; + int bad; + + assert(inited); + + /* get current voltage settings */ + if (read_voltages(&sm0, &sm1)) { + debug("%s: Cannot read voltage settings\n", __func__); + return -1; + } + + /* + * if vdd_core < vdd_cpu + rel + * skip + * + * This condition may happen when system reboots due to kernel crash. + */ + if (min_sm0_over_sm1 != -1 && sm0 < sm1 + min_sm0_over_sm1) { + debug("%s: SM0 is %d, SM1 is %d, but min_sm0_over_sm1 is %d\n", + __func__, sm0, sm1, min_sm0_over_sm1); + return -1; + } + + /* + * Since vdd_core and vdd_cpu may both stand at either greater or less + * than their nominal voltage, the adjustment may go either directions. + * + * Make sure vdd_core is always higher than vdd_cpu with certain margin. + * So, find out which vdd to adjust first in each step. + * + * case 1: both sm0 and sm1 need to move up + * adjust sm0 before sm1 + * + * case 2: both sm0 and sm1 need to move down + * adjust sm1 before sm0 + * + * case 3: sm0 moves down and sm1 moves up + * adjusting either one first is fine. + * + * Adjust vdd_core and vdd_cpu one step at a time until they reach + * their nominal values. + */ + bad = 0; + while (!bad && (sm0 != sm0_target || sm1 != sm1_target)) { + int adjust_sm0_late = 0; /* flag to adjust vdd_core later */ + + debug("%d-%d %d-%d ", sm0, sm0_target, sm1, sm1_target); + + if (sm0 != sm0_target) { + /* + * if case 1 and case 3, set new sm0 first. + * otherwise, hold down until new sm1 is set. + */ + sm0 = calculate_next_voltage(sm0, sm0_target, step); + if (sm1 < sm1_target) + bad |= set_voltage(SM0_VOLTAGE_V1, sm0, rate); + else + adjust_sm0_late = 1; + } + + if (sm1 != sm1_target) { + sm1 = calculate_next_voltage(sm1, sm1_target, step); + bad |= set_voltage(SM1_VOLTAGE_V1, sm1, rate); + } + + if (adjust_sm0_late) + bad |= set_voltage(SM0_VOLTAGE_V1, sm0, rate); + debug("%d\n", adjust_sm0_late); + } + debug("%d-%d %d-%d done\n", sm0, sm0_target, sm1, sm1_target); + + return bad ? -1 : 0; +} + +int tps6586x_init(int bus) +{ + bus_num = bus; + inited = 1; + + return 0; +} -- cgit v1.1 From e31c1e50ac8d85ede3f62c438034397ba54734d7 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 2 Apr 2012 13:19:01 +0000 Subject: tegra: i2c: Add function to find DVC bus Add tegra_i2c_get_dvc_bus_num() to obtain the I2C bus number of DVC bus. This allows us to talk to the PMU. Signed-off-by: Simon Glass Signed-off-by: Tom Warren --- drivers/i2c/tegra_i2c.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers') diff --git a/drivers/i2c/tegra_i2c.c b/drivers/i2c/tegra_i2c.c index 21f6897..5b6ea0e 100644 --- a/drivers/i2c/tegra_i2c.c +++ b/drivers/i2c/tegra_i2c.c @@ -567,3 +567,17 @@ int i2c_set_bus_num(unsigned int bus) return 0; } #endif + +int tegra_i2c_get_dvc_bus_num(void) +{ + int i; + + for (i = 0; i < CONFIG_SYS_MAX_I2C_BUS; i++) { + struct i2c_bus *bus = &i2c_controllers[i]; + + if (bus->inited && bus->is_dvc) + return i; + } + + return -1; +} -- cgit v1.1 From 9bc590e5119f38fd822dedb16e3e0e2363f09756 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 17 Apr 2012 09:01:30 +0000 Subject: input: Add generic keyboard input handler Add a module which understands converting key codes (or scan codes) to ASCII characters. It includes FIFO support and can call back to drivers to read new characters when its FIFO is empty. Keycode maps are provided for un-modified, shift and ctrl keys. The plan is to use this module where such mapping is required. Signed-off-by: Simon Glass Signed-off-by: Tom Warren --- drivers/input/Makefile | 1 + drivers/input/input.c | 430 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 431 insertions(+) create mode 100644 drivers/input/input.c (limited to 'drivers') diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 1f4dad3..d06acb9 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -30,6 +30,7 @@ ifdef CONFIG_PS2KBD COBJS-y += keyboard.o pc_keyb.o COBJS-$(CONFIG_PS2MULT) += ps2mult.o ps2ser.o endif +COBJS-y += input.o COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/input/input.c b/drivers/input/input.c new file mode 100644 index 0000000..4eadd77 --- /dev/null +++ b/drivers/input/input.c @@ -0,0 +1,430 @@ +/* + * Translate key codes into ASCII + * + * Copyright (c) 2011 The Chromium OS Authors. + * (C) Copyright 2004 DENX Software Engineering, Wolfgang Denk, wd@denx.de + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include + +enum { + /* These correspond to the lights on the keyboard */ + FLAG_NUM_LOCK = 1 << 0, + FLAG_CAPS_LOCK = 1 << 1, + FLAG_SCROLL_LOCK = 1 << 2, + + /* Special flag ORed with key code to indicate release */ + KEY_RELEASE = 1 << 15, + KEY_MASK = 0xfff, +}; + +/* + * These takes map key codes to ASCII. 0xff means no key, or special key. + * Three tables are provided - one for plain keys, one for when the shift + * 'modifier' key is pressed and one for when the ctrl modifier key is + * pressed. + */ +static const uchar kbd_plain_xlate[] = { + 0xff, 0x1b, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', '\b', '\t', /* 0x00 - 0x0f */ + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', '\r', 0xff, 'a', 's', /* 0x10 - 0x1f */ + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + '\'', '`', 0xff, '\\', 'z', 'x', 'c', 'v', /* 0x20 - 0x2f */ + 'b', 'n', 'm', ',' , '.', '/', 0xff, 0xff, 0xff, + ' ', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x30 - 0x3f */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, '7', + '8', '9', '-', '4', '5', '6', '+', '1', /* 0x40 - 0x4f */ + '2', '3', '0', '.', 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x50 - 0x5F */ + '\r', 0xff, 0xff +}; + +static unsigned char kbd_shift_xlate[] = { + 0xff, 0x1b, '!', '@', '#', '$', '%', '^', + '&', '*', '(', ')', '_', '+', '\b', '\t', /* 0x00 - 0x0f */ + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', + 'O', 'P', '{', '}', '\r', 0xff, 'A', 'S', /* 0x10 - 0x1f */ + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', + '"', '~', 0xff, '|', 'Z', 'X', 'C', 'V', /* 0x20 - 0x2f */ + 'B', 'N', 'M', '<', '>', '?', 0xff, 0xff, 0xff, + ' ', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x30 - 0x3f */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, '7', + '8', '9', '-', '4', '5', '6', '+', '1', /* 0x40 - 0x4f */ + '2', '3', '0', '.', 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x50 - 0x5F */ + '\r', 0xff, 0xff +}; + +static unsigned char kbd_ctrl_xlate[] = { + 0xff, 0x1b, '1', 0x00, '3', '4', '5', 0x1E, + '7', '8', '9', '0', 0x1F, '=', '\b', '\t', /* 0x00 - 0x0f */ + 0x11, 0x17, 0x05, 0x12, 0x14, 0x18, 0x15, 0x09, + 0x0f, 0x10, 0x1b, 0x1d, '\n', 0xff, 0x01, 0x13, /* 0x10 - 0x1f */ + 0x04, 0x06, 0x08, 0x09, 0x0a, 0x0b, 0x0c, ';', + '\'', '~', 0x00, 0x1c, 0x1a, 0x18, 0x03, 0x16, /* 0x20 - 0x2f */ + 0x02, 0x0e, 0x0d, '<', '>', '?', 0xff, 0xff, + 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x30 - 0x3f */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, '7', + '8', '9', '-', '4', '5', '6', '+', '1', /* 0x40 - 0x4f */ + '2', '3', '0', '.', 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x50 - 0x5F */ + '\r', 0xff, 0xff +}; + + +int input_queue_ascii(struct input_config *config, int ch) +{ + if (config->fifo_in + 1 == INPUT_BUFFER_LEN) { + if (!config->fifo_out) + return -1; /* buffer full */ + else + config->fifo_in = 0; + } else { + if (config->fifo_in + 1 == config->fifo_out) + return -1; /* buffer full */ + config->fifo_in++; + } + config->fifo[config->fifo_in] = (uchar)ch; + + return 0; +} + +int input_tstc(struct input_config *config) +{ + if (config->fifo_in == config->fifo_out && config->read_keys) { + if (!(*config->read_keys)(config)) + return 0; + } + return config->fifo_in != config->fifo_out; +} + +int input_getc(struct input_config *config) +{ + int err = 0; + + while (config->fifo_in == config->fifo_out) { + if (config->read_keys) + err = (*config->read_keys)(config); + if (err) + return -1; + } + + if (++config->fifo_out == INPUT_BUFFER_LEN) + config->fifo_out = 0; + + return config->fifo[config->fifo_out]; +} + +/** + * Process a modifier/special key press or release and decide which key + * translation array should be used as a result. + * + * TODO: Should keep track of modifier press/release + * + * @param config Input state + * @param key Key code to process + * @param release 0 if a press, 1 if a release + * @return pointer to keycode->ascii translation table that should be used + */ +static struct input_key_xlate *process_modifier(struct input_config *config, + int key, int release) +{ + struct input_key_xlate *table; + int flip = -1; + int i; + + /* Start with the main table, and see what modifiers change it */ + assert(config->num_tables > 0); + table = &config->table[0]; + for (i = 1; i < config->num_tables; i++) { + struct input_key_xlate *tab = &config->table[i]; + + if (key == tab->left_keycode || key == tab->right_keycode) + table = tab; + } + + /* Handle the lighted keys */ + if (!release) { + switch (key) { + case KEY_SCROLLLOCK: + flip = FLAG_SCROLL_LOCK; + break; + case KEY_NUMLOCK: + flip = FLAG_NUM_LOCK; + break; + case KEY_CAPSLOCK: + flip = FLAG_CAPS_LOCK; + break; + } + } + + if (flip != -1) { + int leds = 0; + + config->leds ^= flip; + if (config->flags & FLAG_NUM_LOCK) + leds |= INPUT_LED_NUM; + if (config->flags & FLAG_CAPS_LOCK) + leds |= INPUT_LED_CAPS; + if (config->flags & FLAG_SCROLL_LOCK) + leds |= INPUT_LED_SCROLL; + config->leds = leds; + } + + return table; +} + +/** + * Search an int array for a key value + * + * @param array Array to search + * @param count Number of elements in array + * @param key Key value to find + * @return element where value was first found, -1 if none + */ +static int array_search(int *array, int count, int key) +{ + int i; + + for (i = 0; i < count; i++) { + if (array[i] == key) + return i; + } + + return -1; +} + +/** + * Sort an array so that those elements that exist in the ordering are + * first in the array, and in the same order as the ordering. The algorithm + * is O(count * ocount) and designed for small arrays. + * + * TODO: Move this to common / lib? + * + * @param dest Array with elements to sort, also destination array + * @param count Number of elements to sort + * @param order Array containing ordering elements + * @param ocount Number of ordering elements + * @return number of elements in dest that are in order (these will be at the + * start of dest). + */ +static int sort_array_by_ordering(int *dest, int count, int *order, + int ocount) +{ + int temp[count]; + int dest_count; + int same; /* number of elements which are the same */ + int i; + + /* setup output items, copy items to be sorted into our temp area */ + memcpy(temp, dest, count * sizeof(*dest)); + dest_count = 0; + + /* work through the ordering, move over the elements we agree on */ + for (i = 0; i < ocount; i++) { + if (array_search(temp, count, order[i]) != -1) + dest[dest_count++] = order[i]; + } + same = dest_count; + + /* now move over the elements that are not in the ordering */ + for (i = 0; i < count; i++) { + if (array_search(order, ocount, temp[i]) == -1) + dest[dest_count++] = temp[i]; + } + assert(dest_count == count); + return same; +} + +/** + * Check a list of key codes against the previous key scan + * + * Given a list of new key codes, we check how many of these are the same + * as last time. + * + * @param config Input state + * @param keycode List of key codes to examine + * @param num_keycodes Number of key codes + * @param same Returns number of key codes which are the same + */ +static int input_check_keycodes(struct input_config *config, + int keycode[], int num_keycodes, int *same) +{ + /* Select the 'plain' xlate table to start with */ + if (!config->num_tables) { + debug("%s: No xlate tables: cannot decode keys\n", __func__); + return -1; + } + + /* sort the keycodes into the same order as the previous ones */ + *same = sort_array_by_ordering(keycode, num_keycodes, + config->prev_keycodes, config->num_prev_keycodes); + + memcpy(config->prev_keycodes, keycode, num_keycodes * sizeof(int)); + config->num_prev_keycodes = num_keycodes; + + return *same != num_keycodes; +} + +/** + * Convert a list of key codes into ASCII + * + * You must call input_check_keycodes() before this. It turns the keycode + * list into a list of ASCII characters which are ready to send to the + * input layer. + * + * Characters which were seen last time do not generate fresh ASCII output. + * + * @param config Input state + * @param keycode List of key codes to examine + * @param num_keycodes Number of key codes + * @param same Number of key codes which are the same + */ +static int input_keycodes_to_ascii(struct input_config *config, + int keycode[], int num_keycodes, char output_ch[], int same) +{ + struct input_key_xlate *table; + int ch_count; + int i; + + table = &config->table[0]; + + /* deal with modifiers first */ + for (i = 0; i < num_keycodes; i++) { + int key = keycode[i] & KEY_MASK; + + if (key >= table->num_entries || table->xlate[key] == 0xff) { + table = process_modifier(config, key, + keycode[i] & KEY_RELEASE); + } + } + + /* now find normal keys */ + for (i = ch_count = 0; i < num_keycodes; i++) { + int key = keycode[i]; + + if (key < table->num_entries && i >= same) { + int ch = table->xlate[key]; + + /* If a normal key with an ASCII value, add it! */ + if (ch != 0xff) + output_ch[ch_count++] = (uchar)ch; + } + } + + /* ok, so return keys */ + return ch_count; +} + +int input_send_keycodes(struct input_config *config, + int keycode[], int num_keycodes) +{ + char ch[num_keycodes]; + int count, i, same = 0; + int is_repeat = 0; + unsigned delay_ms; + + config->modifiers = 0; + if (!input_check_keycodes(config, keycode, num_keycodes, &same)) { + /* + * Same as last time - is it time for another repeat? + * TODO(sjg@chromium.org) We drop repeats here and since + * the caller may not call in again for a while, our + * auto-repeat speed is not quite correct. We should + * insert another character if we later realise that we + * have missed a repeat slot. + */ + is_repeat = (int)get_timer(config->next_repeat_ms) >= 0; + if (!is_repeat) + return 0; + } + + count = input_keycodes_to_ascii(config, keycode, num_keycodes, + ch, is_repeat ? 0 : same); + for (i = 0; i < count; i++) + input_queue_ascii(config, ch[i]); + delay_ms = is_repeat ? + config->repeat_rate_ms : + config->repeat_delay_ms; + + config->next_repeat_ms = get_timer(0) + delay_ms; + return 0; +} + +int input_add_table(struct input_config *config, int left_keycode, + int right_keycode, const uchar *xlate, int num_entries) +{ + struct input_key_xlate *table; + + if (config->num_tables == INPUT_MAX_MODIFIERS) { + debug("%s: Too many modifier tables\n", __func__); + return -1; + } + + table = &config->table[config->num_tables++]; + table->left_keycode = left_keycode; + table->right_keycode = right_keycode; + table->xlate = xlate; + table->num_entries = num_entries; + + return 0; +} + +int input_init(struct input_config *config, int leds, int repeat_delay_ms, + int repeat_rate_ms) +{ + memset(config, '\0', sizeof(*config)); + config->leds = leds; + config->repeat_delay_ms = repeat_delay_ms; + config->repeat_rate_ms = repeat_rate_ms; + if (input_add_table(config, -1, -1, + kbd_plain_xlate, ARRAY_SIZE(kbd_plain_xlate)) || + input_add_table(config, KEY_LEFTSHIFT, KEY_RIGHTSHIFT, + kbd_shift_xlate, ARRAY_SIZE(kbd_shift_xlate)) || + input_add_table(config, KEY_LEFTCTRL, KEY_RIGHTCTRL, + kbd_ctrl_xlate, ARRAY_SIZE(kbd_ctrl_xlate))) { + debug("%s: Could not add modifier tables\n", __func__); + return -1; + } + + return 0; +} + +int input_stdio_register(struct stdio_dev *dev) +{ + int error; + + error = stdio_register(dev); + + /* check if this is the standard input device */ + if (!error && strcmp(getenv("stdin"), dev->name) == 0) { + /* reassign the console */ + if (OVERWRITE_CONSOLE || + console_assign(stdin, dev->name)) + return -1; + } + + return 0; +} -- cgit v1.1 From 92c27c5193154465df03a4713b1a5323bb18506b Mon Sep 17 00:00:00 2001 From: Bernie Thompson Date: Tue, 17 Apr 2012 09:01:31 +0000 Subject: input: Add support for keyboard matrix decoding from an fdt Matrix keyboards require a key map to be set up, and must also deal with key ghosting. Create a keyboard matrix management implementation which can be leveraged by various keyboard drivers. This includes code to read the keymap from the FDT and perform debouncing. Signed-off-by: Bernie Thompson Signed-off-by: Simon Glass Signed-off-by: Tom Warren --- drivers/input/Makefile | 2 + drivers/input/key_matrix.c | 208 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 drivers/input/key_matrix.c (limited to 'drivers') diff --git a/drivers/input/Makefile b/drivers/input/Makefile index d06acb9..5c831b2 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -26,11 +26,13 @@ include $(TOPDIR)/config.mk LIB := $(obj)libinput.o COBJS-$(CONFIG_I8042_KBD) += i8042.o +COBJS-$(CONFIG_TEGRA2_KEYBOARD) += tegra-kbc.o ifdef CONFIG_PS2KBD COBJS-y += keyboard.o pc_keyb.o COBJS-$(CONFIG_PS2MULT) += ps2mult.o ps2ser.o endif COBJS-y += input.o +COBJS-$(CONFIG_OF_CONTROL) += key_matrix.o COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/input/key_matrix.c b/drivers/input/key_matrix.c new file mode 100644 index 0000000..84b898f --- /dev/null +++ b/drivers/input/key_matrix.c @@ -0,0 +1,208 @@ +/* + * Manage Keyboard Matrices + * + * Copyright (c) 2012 The Chromium OS Authors. + * (C) Copyright 2004 DENX Software Engineering, Wolfgang Denk, wd@denx.de + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include + +/** + * Determine if the current keypress configuration can cause key ghosting + * + * We figure this out by seeing if we have two or more keys in the same + * column, as well as two or more keys in the same row. + * + * @param config Keyboard matrix config + * @param keys List of keys to check + * @param valid Number of valid keypresses to check + * @return 0 if no ghosting is possible, 1 if it is + */ +static int has_ghosting(struct key_matrix *config, struct key_matrix_key *keys, + int valid) +{ + int key_in_same_col = 0, key_in_same_row = 0; + int i, j; + + for (i = 0; i < valid; i++) { + /* + * Find 2 keys such that one key is in the same row + * and the other is in the same column as the i-th key. + */ + for (j = i + 1; j < valid; j++) { + if (keys[j].col == keys[i].col) + key_in_same_col = 1; + if (keys[j].row == keys[i].row) + key_in_same_row = 1; + } + } + + if (key_in_same_col && key_in_same_row) + return 1; + else + return 0; +} + +int key_matrix_decode(struct key_matrix *config, struct key_matrix_key keys[], + int num_keys, int keycode[], int max_keycodes) +{ + const u8 *keymap; + int valid, upto; + int pos; + + debug("%s: num_keys = %d\n", __func__, num_keys); + keymap = config->plain_keycode; + for (valid = upto = 0; upto < num_keys; upto++) { + struct key_matrix_key *key = &keys[upto]; + + debug(" valid=%d, row=%d, col=%d\n", key->valid, key->row, + key->col); + if (!key->valid) + continue; + pos = key->row * config->num_cols + key->col; + if (config->fn_keycode && pos == config->fn_pos) + keymap = config->fn_keycode; + + /* Convert the (row, col) values into a keycode */ + if (valid < max_keycodes) + keycode[valid++] = keymap[pos]; + debug(" keycode=%d\n", keymap[pos]); + } + + /* For a ghost key config, ignore the keypresses for this iteration. */ + if (valid >= 3 && has_ghosting(config, keys, valid)) { + valid = 0; + debug(" ghosting detected!\n"); + } + debug(" %d valid keycodes found\n", valid); + + return valid; +} + +/** + * Create a new keycode map from some provided data + * + * This decodes a keycode map in the format used by the fdt, which is one + * word per entry, with the row, col and keycode encoded in that word. + * + * We create a (row x col) size byte array with each entry containing the + * keycode for that (row, col). We also search for map_keycode and return + * its position if found (this is used for finding the Fn key). + * + * @param config Key matrix dimensions structure + * @param data Keycode data + * @param len Number of entries in keycode table + * @param map_keycode Key code to find in the map + * @param pos Returns position of map_keycode, if found, else -1 + * @return map Pointer to allocated map + */ +static uchar *create_keymap(struct key_matrix *config, u32 *data, int len, + int map_keycode, int *pos) +{ + uchar *map; + + if (pos) + *pos = -1; + map = (uchar *)calloc(1, config->key_count); + if (!map) { + debug("%s: failed to malloc %d bytes\n", __func__, + config->key_count); + return NULL; + } + + for (; len >= sizeof(u32); data++, len -= 4) { + u32 tmp = fdt32_to_cpu(*data); + int key_code, row, col; + int entry; + + row = (tmp >> 24) & 0xff; + col = (tmp >> 16) & 0xff; + key_code = tmp & 0xffff; + entry = row * config->num_cols + col; + map[entry] = key_code; + if (pos && map_keycode == key_code) + *pos = entry; + } + + return map; +} + +int key_matrix_decode_fdt(struct key_matrix *config, const void *blob, + int node) +{ + const struct fdt_property *prop; + int offset; + + /* Check each property name for ones that we understand */ + for (offset = fdt_first_property_offset(blob, node); + offset > 0; + offset = fdt_next_property_offset(blob, offset)) { + const char *name; + int len; + + prop = fdt_get_property_by_offset(blob, offset, NULL); + name = fdt_string(blob, fdt32_to_cpu(prop->nameoff)); + len = strlen(name); + + /* Name needs to match "1,keymap" */ + debug("%s: property '%s'\n", __func__, name); + if (strncmp(name, "1,", 2) || len < 8 || + strcmp(name + len - 6, "keymap")) + continue; + + len -= 8; + if (len == 0) { + config->plain_keycode = create_keymap(config, + (u32 *)prop->data, fdt32_to_cpu(prop->len), + KEY_FN, &config->fn_pos); + } else if (0 == strncmp(name + 2, "fn-", len)) { + config->fn_keycode = create_keymap(config, + (u32 *)prop->data, fdt32_to_cpu(prop->len), + -1, NULL); + } else { + debug("%s: unrecognised property '%s'\n", __func__, + name); + } + } + debug("%s: Decoded key maps %p, %p from fdt\n", __func__, + config->plain_keycode, config->fn_keycode); + + if (!config->plain_keycode) { + debug("%s: cannot find keycode-plain map\n", __func__); + return -1; + } + + return 0; +} + +int key_matrix_init(struct key_matrix *config, int rows, int cols) +{ + memset(config, '\0', sizeof(*config)); + config->num_rows = rows; + config->num_cols = cols; + config->key_count = rows * cols; + assert(config->key_count > 0); + + return 0; +} -- cgit v1.1 From 6642a681e410c1b3d767742d74146cb624dca287 Mon Sep 17 00:00:00 2001 From: Rakesh Iyer Date: Tue, 17 Apr 2012 09:01:35 +0000 Subject: tegra: Add tegra keyboard driver Add support for internal matrix keyboard controller for Nvidia Tegra platforms. This driver uses the fdt decode function to obtain its key codes. Support for the Ctrl modifier is provided. The left and right ctrl keys are dealt with in the same way. This uses the new keyboard input library (drivers/input/input.c) to decode keys and handle most of the common input logic. The new key matrix library is also used to decode (row, column) key positions into key codes. The intent is to make this driver purely about dealing with the hardware. Key detection before the driver is loaded is supported. This key will be picked up when the keyboard driver is initialized. Modified by Bernie Thompson and Simon Glass for device tree, input layer, key matrix and various other things. Signed-off-by: Simon Glass Signed-off-by: Tom Warren --- drivers/input/tegra-kbc.c | 375 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 drivers/input/tegra-kbc.c (limited to 'drivers') diff --git a/drivers/input/tegra-kbc.c b/drivers/input/tegra-kbc.c new file mode 100644 index 0000000..f164791 --- /dev/null +++ b/drivers/input/tegra-kbc.c @@ -0,0 +1,375 @@ +/* + * (C) Copyright 2011 + * NVIDIA Corporation + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +enum { + KBC_MAX_GPIO = 24, + KBC_MAX_KPENT = 8, /* size of keypress entry queue */ +}; + +#define KBC_FIFO_TH_CNT_SHIFT 14 +#define KBC_DEBOUNCE_CNT_SHIFT 4 +#define KBC_CONTROL_FIFO_CNT_INT_EN (1 << 3) +#define KBC_CONTROL_KBC_EN (1 << 0) +#define KBC_INT_FIFO_CNT_INT_STATUS (1 << 2) +#define KBC_KPENT_VALID (1 << 7) +#define KBC_ST_STATUS (1 << 3) + +enum { + KBC_DEBOUNCE_COUNT = 2, + KBC_REPEAT_RATE_MS = 30, + KBC_REPEAT_DELAY_MS = 240, + KBC_CLOCK_KHZ = 32, /* Keyboard uses a 32KHz clock */ +}; + +/* keyboard controller config and state */ +static struct keyb { + struct input_config input; /* The input layer */ + struct key_matrix matrix; /* The key matrix layer */ + + struct kbc_tegra *kbc; /* tegra keyboard controller */ + unsigned char inited; /* 1 if keyboard has been inited */ + unsigned char first_scan; /* 1 if this is our first key scan */ + + /* + * After init we must wait a short time before polling the keyboard. + * This gives the tegra keyboard controller time to react after reset + * and lets us grab keys pressed during reset. + */ + unsigned int init_dly_ms; /* Delay before we can read keyboard */ + unsigned int start_time_ms; /* Time that we inited (in ms) */ + unsigned int last_poll_ms; /* Time we should last polled */ + unsigned int next_repeat_ms; /* Next time we repeat a key */ +} config; + +/** + * reads the keyboard fifo for current keypresses + * + * @param config Keyboard config + * @param fifo Place to put fifo results + * @param max_keycodes Maximum number of key codes to put in the fifo + * @return number of items put into fifo + */ +static int tegra_kbc_find_keys(struct keyb *config, int *fifo, + int max_keycodes) +{ + struct key_matrix_key keys[KBC_MAX_KPENT], *key; + u32 kp_ent = 0; + int i; + + for (key = keys, i = 0; i < KBC_MAX_KPENT; i++, key++) { + /* Get next word */ + if (!(i & 3)) + kp_ent = readl(&config->kbc->kp_ent[i / 4]); + + key->valid = (kp_ent & KBC_KPENT_VALID) != 0; + key->row = (kp_ent >> 3) & 0xf; + key->col = kp_ent & 0x7; + + /* Shift to get next entry */ + kp_ent >>= 8; + } + return key_matrix_decode(&config->matrix, keys, KBC_MAX_KPENT, fifo, + max_keycodes); +} + +/** + * Process all the keypress sequences in fifo and send key codes + * + * The fifo contains zero or more keypress sets. Each set + * consists of from 1-8 keycodes, representing the keycodes which + * were simultaneously pressed during that scan. + * + * This function works through each set and generates ASCII characters + * for each. Not that one set may produce more than one ASCII characters - + * for example holding down 'd' and 'f' at the same time will generate + * two ASCII characters. + * + * Note: if fifo_cnt is 0, we will tell the input layer that no keys are + * pressed. + * + * @param config Keyboard config + * @param fifo_cnt Number of entries in the keyboard fifo + */ +static void process_fifo(struct keyb *config, int fifo_cnt) +{ + int fifo[KBC_MAX_KPENT]; + int cnt = 0; + + /* Always call input_send_keycodes() at least once */ + do { + if (fifo_cnt) + cnt = tegra_kbc_find_keys(config, fifo, KBC_MAX_KPENT); + + input_send_keycodes(&config->input, fifo, cnt); + } while (--fifo_cnt > 0); +} + +/** + * Check the keyboard controller and emit ASCII characters for any keys that + * are pressed. + * + * @param config Keyboard config + */ +static void check_for_keys(struct keyb *config) +{ + int fifo_cnt; + + if (!config->first_scan && + get_timer(config->last_poll_ms) < KBC_REPEAT_RATE_MS) + return; + config->last_poll_ms = get_timer(0); + config->first_scan = 0; + + /* + * Once we get here we know the keyboard has been scanned. So if there + * scan waiting for us, we know that nothing is held down. + */ + fifo_cnt = (readl(&config->kbc->interrupt) >> 4) & 0xf; + process_fifo(config, fifo_cnt); +} + +/** + * In order to detect keys pressed on boot, wait for the hardware to + * complete scanning the keys. This includes time to transition from + * Wkup mode to Continous polling mode and the repoll time. We can + * deduct the time that's already elapsed. + * + * @param config Keyboard config + */ +static void kbd_wait_for_fifo_init(struct keyb *config) +{ + if (!config->inited) { + unsigned long elapsed_time; + long delay_ms; + + elapsed_time = get_timer(config->start_time_ms); + delay_ms = config->init_dly_ms - elapsed_time; + if (delay_ms > 0) { + udelay(delay_ms * 1000); + debug("%s: delay %ldms\n", __func__, delay_ms); + } + + config->inited = 1; + } +} + +/** + * Check the tegra keyboard, and send any keys that are pressed. + * + * This is called by input_tstc() and input_getc() when they need more + * characters + * + * @param input Input configuration + * @return 1, to indicate that we have something to look at + */ +int tegra_kbc_check(struct input_config *input) +{ + kbd_wait_for_fifo_init(&config); + check_for_keys(&config); + + return 1; +} + +/** + * Test if keys are available to be read + * + * @return 0 if no keys available, 1 if keys are available + */ +static int kbd_tstc(void) +{ + /* Just get input to do this for us */ + return input_tstc(&config.input); +} + +/** + * Read a key + * + * TODO: U-Boot wants 0 for no key, but Ctrl-@ is a valid key... + * + * @return ASCII key code, or 0 if no key, or -1 if error + */ +static int kbd_getc(void) +{ + /* Just get input to do this for us */ + return input_getc(&config.input); +} + +/* configures keyboard GPIO registers to use the rows and columns */ +static void config_kbc_gpio(struct kbc_tegra *kbc) +{ + int i; + + for (i = 0; i < KBC_MAX_GPIO; i++) { + u32 row_cfg, col_cfg; + u32 r_shift = 5 * (i % 6); + u32 c_shift = 4 * (i % 8); + u32 r_mask = 0x1f << r_shift; + u32 c_mask = 0xf << c_shift; + u32 r_offs = i / 6; + u32 c_offs = i / 8; + + row_cfg = readl(&kbc->row_cfg[r_offs]); + col_cfg = readl(&kbc->col_cfg[c_offs]); + + row_cfg &= ~r_mask; + col_cfg &= ~c_mask; + + if (i < config.matrix.num_rows) { + row_cfg |= ((i << 1) | 1) << r_shift; + } else { + col_cfg |= (((i - config.matrix.num_rows) << 1) | 1) + << c_shift; + } + + writel(row_cfg, &kbc->row_cfg[r_offs]); + writel(col_cfg, &kbc->col_cfg[c_offs]); + } +} + +/** + * Start up the keyboard device + */ +static void tegra_kbc_open(void) +{ + struct kbc_tegra *kbc = config.kbc; + unsigned int scan_period; + u32 val; + + /* + * We will scan at twice the keyboard repeat rate, so that there is + * always a scan ready when we check it in check_for_keys(). + */ + scan_period = KBC_REPEAT_RATE_MS / 2; + writel(scan_period * KBC_CLOCK_KHZ, &kbc->rpt_dly); + writel(scan_period * KBC_CLOCK_KHZ, &kbc->init_dly); + /* + * Before reading from the keyboard we must wait for the init_dly + * plus the rpt_delay, plus 2ms for the row scan time. + */ + config.init_dly_ms = scan_period * 2 + 2; + + val = KBC_DEBOUNCE_COUNT << KBC_DEBOUNCE_CNT_SHIFT; + val |= 1 << KBC_FIFO_TH_CNT_SHIFT; /* fifo interrupt threshold */ + val |= KBC_CONTROL_KBC_EN; /* enable */ + writel(val, &kbc->control); + + config.start_time_ms = get_timer(0); + config.last_poll_ms = config.next_repeat_ms = get_timer(0); + config.first_scan = 1; +} + +/** + * Set up the tegra keyboard. This is called by the stdio device handler + * + * We want to do this init when the keyboard is actually used rather than + * at start-up, since keyboard input may not currently be selected. + * + * Once the keyboard starts there will be a period during which we must + * wait for the keyboard to init. We do this only when a key is first + * read - see kbd_wait_for_fifo_init(). + * + * @return 0 if ok, -ve on error + */ +static int init_tegra_keyboard(void) +{ +#ifdef CONFIG_OF_CONTROL + int node; + + node = fdtdec_next_compatible(gd->fdt_blob, 0, + COMPAT_NVIDIA_TEGRA20_KBC); + if (node < 0) { + debug("%s: cannot locate keyboard node\n", __func__); + return node; + } + config.kbc = (struct kbc_tegra *)fdtdec_get_addr(gd->fdt_blob, + node, "reg"); + if ((fdt_addr_t)config.kbc == FDT_ADDR_T_NONE) { + debug("%s: No keyboard register found\n", __func__); + return -1; + } + + /* Decode the keyboard matrix information (16 rows, 8 columns) */ + if (key_matrix_init(&config.matrix, 16, 8)) { + debug("%s: Could not init key matrix\n", __func__); + return -1; + } + if (key_matrix_decode_fdt(&config.matrix, gd->fdt_blob, node)) { + debug("%s: Could not decode key matrix from fdt\n", __func__); + return -1; + } + if (config.matrix.fn_keycode) { + if (input_add_table(&config.input, KEY_FN, -1, + config.matrix.fn_keycode, + config.matrix.key_count)) + return -1; + } +#else +#error "Tegra keyboard driver requires FDT definitions" +#endif + + /* Set up pin mux and enable the clock */ + funcmux_select(PERIPH_ID_KBC, FUNCMUX_DEFAULT); + clock_enable(PERIPH_ID_KBC); + config_kbc_gpio(config.kbc); + + tegra_kbc_open(); + debug("%s: Tegra keyboard ready\n", __func__); + + return 0; +} + +int drv_keyboard_init(void) +{ + struct stdio_dev dev; + + if (input_init(&config.input, 0, KBC_REPEAT_DELAY_MS, + KBC_REPEAT_RATE_MS)) { + debug("%s: Cannot set up input\n", __func__); + return -1; + } + config.input.read_keys = tegra_kbc_check; + + memset(&dev, '\0', sizeof(dev)); + strcpy(dev.name, "tegra-kbc"); + dev.flags = DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM; + dev.getc = kbd_getc; + dev.tstc = kbd_tstc; + dev.start = init_tegra_keyboard; + + /* Register the device. init_tegra_keyboard() will be called soon */ + return input_stdio_register(&dev); +} -- cgit v1.1