diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpio/s5p_gpio.c | 6 | ||||
-rw-r--r-- | drivers/input/tegra-kbc.c | 18 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/pmic_max77686.c | 42 | ||||
-rw-r--r-- | drivers/mmc/tegra_mmc.c | 7 | ||||
-rw-r--r-- | drivers/serial/s3c64xx.c | 3 | ||||
-rw-r--r-- | drivers/sound/Makefile | 48 | ||||
-rw-r--r-- | drivers/sound/samsung-i2s.c | 358 | ||||
-rw-r--r-- | drivers/sound/sound.c | 228 | ||||
-rw-r--r-- | drivers/sound/wm8994.c | 792 | ||||
-rw-r--r-- | drivers/sound/wm8994.h | 87 | ||||
-rw-r--r-- | drivers/sound/wm8994_registers.h | 299 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/exynos_spi.c | 367 | ||||
-rw-r--r-- | drivers/video/Makefile | 2 | ||||
-rw-r--r-- | drivers/video/exynos_fb.c | 15 | ||||
-rw-r--r-- | drivers/video/ld9040.c | 144 | ||||
-rw-r--r-- | drivers/video/tegra.c | 379 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/s5p_wdt.c | 59 |
20 files changed, 2848 insertions, 9 deletions
diff --git a/drivers/gpio/s5p_gpio.c b/drivers/gpio/s5p_gpio.c index 47f3213927..656bf4a06c 100644 --- a/drivers/gpio/s5p_gpio.c +++ b/drivers/gpio/s5p_gpio.c @@ -144,9 +144,11 @@ void s5p_gpio_set_rate(struct s5p_gpio_bank *bank, int gpio, int mode) struct s5p_gpio_bank *s5p_gpio_get_bank(unsigned gpio) { - int bank = gpio / GPIO_PER_BANK; - bank *= sizeof(struct s5p_gpio_bank); + int bank; + unsigned g = gpio - s5p_gpio_part_max(gpio); + bank = g / GPIO_PER_BANK; + bank *= sizeof(struct s5p_gpio_bank); return (struct s5p_gpio_bank *) (s5p_gpio_base(gpio) + bank); } diff --git a/drivers/input/tegra-kbc.c b/drivers/input/tegra-kbc.c index ab7a9e33ee..88471d3edf 100644 --- a/drivers/input/tegra-kbc.c +++ b/drivers/input/tegra-kbc.c @@ -63,6 +63,7 @@ static struct keyb { 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 */ + unsigned char created; /* 1 if driver has been created */ /* * After init we must wait a short time before polling the keyboard. @@ -306,6 +307,10 @@ static void tegra_kbc_open(void) */ static int init_tegra_keyboard(void) { + /* check if already created */ + if (config.created) + return 0; + #ifdef CONFIG_OF_CONTROL int node; @@ -349,6 +354,7 @@ static int init_tegra_keyboard(void) config_kbc_gpio(config.kbc); tegra_kbc_open(); + config.created = 1; debug("%s: Tegra keyboard ready\n", __func__); return 0; @@ -357,6 +363,8 @@ static int init_tegra_keyboard(void) int drv_keyboard_init(void) { struct stdio_dev dev; + char *stdinname = getenv("stdin"); + int error; if (input_init(&config.input, 0)) { debug("%s: Cannot set up input\n", __func__); @@ -372,5 +380,13 @@ int drv_keyboard_init(void) dev.start = init_tegra_keyboard; /* Register the device. init_tegra_keyboard() will be called soon */ - return input_stdio_register(&dev); + error = input_stdio_register(&dev); + if (error) + return error; +#ifdef CONFIG_CONSOLE_MUX + error = iomux_doenv(stdin, stdinname); + if (error) + return error; +#endif + return 0; } diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 271463cf14..9fac190a6a 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -39,6 +39,7 @@ 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 +COBJS-$(CONFIG_PMIC_MAX77686) += pmic_max77686.o COBJS-$(CONFIG_PMIC_MAX8998) += pmic_max8998.o COBJS-$(CONFIG_PMIC_MAX8997) += pmic_max8997.o diff --git a/drivers/misc/pmic_max77686.c b/drivers/misc/pmic_max77686.c new file mode 100644 index 0000000000..36f7f4dde7 --- /dev/null +++ b/drivers/misc/pmic_max77686.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * Rajeshwari Shinde <rajeshwari.s@samsung.com> + * + * 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 <common.h> +#include <pmic.h> +#include <max77686_pmic.h> + +int pmic_init(void) +{ + struct pmic *p = get_pmic(); + static const char name[] = "MAX77686_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 = MAX77686_I2C_ADDR; + p->hw.i2c.tx_num = 1; + p->bus = I2C_PMIC; + + return 0; +} diff --git a/drivers/mmc/tegra_mmc.c b/drivers/mmc/tegra_mmc.c index 8fea6a6bfb..1fd5592f2d 100644 --- a/drivers/mmc/tegra_mmc.c +++ b/drivers/mmc/tegra_mmc.c @@ -547,10 +547,11 @@ int tegra_mmc_init(int dev_index, int bus_width, int pwr_gpio, int cd_gpio) mmc->getcd = tegra_mmc_getcd; mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + mmc->host_caps = 0; if (bus_width == 8) - mmc->host_caps = MMC_MODE_8BIT; - else - mmc->host_caps = MMC_MODE_4BIT; + mmc->host_caps |= MMC_MODE_8BIT; + if (bus_width >= 4) + mmc->host_caps |= MMC_MODE_4BIT; mmc->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS | MMC_MODE_HC; /* diff --git a/drivers/serial/s3c64xx.c b/drivers/serial/s3c64xx.c index f53c2bf003..b590992dc8 100644 --- a/drivers/serial/s3c64xx.c +++ b/drivers/serial/s3c64xx.c @@ -22,7 +22,8 @@ */ #include <common.h> - +#include <linux/compiler.h> +#include <serial.h> #include <asm/arch/s3c6400.h> DECLARE_GLOBAL_DATA_PTR; diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile new file mode 100644 index 0000000000..8fdffb10ef --- /dev/null +++ b/drivers/sound/Makefile @@ -0,0 +1,48 @@ +# +# Copyright (C) 2012 Samsung Electronics +# R. Chandrasekar <rcsekar@samsung.com> +# +# 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 $(TOPDIR)/config.mk + +LIB := $(obj)libsound.o + +COBJS-$(CONFIG_SOUND) += sound.o +COBJS-$(CONFIG_I2S) += samsung-i2s.o +COBJS-$(CONFIG_SOUND_WM8994) += wm8994.o + +COBJS := $(COBJS-y) +SRCS := $(COBJS:.o=.c) +OBJS := $(addprefix $(obj),$(COBJS)) + +all: $(LIB) + +$(LIB): $(obj).depend $(OBJS) + $(call cmd_link_o_target, $(OBJS)) + +######################################################################### + +# defines $(obj).depend target +include $(SRCTREE)/rules.mk + +sinclude $(obj).depend + +# diff --git a/drivers/sound/samsung-i2s.c b/drivers/sound/samsung-i2s.c new file mode 100644 index 0000000000..9f3117dd94 --- /dev/null +++ b/drivers/sound/samsung-i2s.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * R. Chandrasekar <rcsekar@samsung.com> + * + * 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 <asm/arch/clk.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/i2s-regs.h> +#include <asm/io.h> +#include <common.h> +#include <sound.h> +#include <i2s.h> + +#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf) +#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf) +#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf) +#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf) +#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f) + +#define TIMEOUT_I2S_TX 100 /* i2s transfer timeout */ + +/* + * Sets the frame size for I2S LR clock + * + * @param i2s_reg i2s regiter address + * @param rfs Frame Size + */ +static void i2s_set_lr_framesize(struct i2s_reg *i2s_reg, unsigned int rfs) +{ + unsigned int mod = readl(&i2s_reg->mod); + + mod &= ~MOD_RCLK_MASK; + + switch (rfs) { + case 768: + mod |= MOD_RCLK_768FS; + break; + case 512: + mod |= MOD_RCLK_512FS; + break; + case 384: + mod |= MOD_RCLK_384FS; + break; + default: + mod |= MOD_RCLK_256FS; + break; + } + + writel(mod, &i2s_reg->mod); +} + +/* + * Sets the i2s transfer control + * + * @param i2s_reg i2s regiter address + * @param on 1 enable tx , 0 disable tx transfer + */ +static void i2s_txctrl(struct i2s_reg *i2s_reg, int on) +{ + unsigned int con = readl(&i2s_reg->con); + unsigned int mod = readl(&i2s_reg->mod) & ~MOD_MASK; + + if (on) { + con |= CON_ACTIVE; + con &= ~CON_TXCH_PAUSE; + + } else { + + con |= CON_TXCH_PAUSE; + con &= ~CON_ACTIVE; + } + + writel(mod, &i2s_reg->mod); + writel(con, &i2s_reg->con); +} + +/* + * set the bit clock frame size (in multiples of LRCLK) + * + * @param i2s_reg i2s regiter address + * @param bfs bit Frame Size + */ +static void i2s_set_bitclk_framesize(struct i2s_reg *i2s_reg, unsigned bfs) +{ + unsigned int mod = readl(&i2s_reg->mod); + + mod &= ~MOD_BCLK_MASK; + + switch (bfs) { + case 48: + mod |= MOD_BCLK_48FS; + break; + case 32: + mod |= MOD_BCLK_32FS; + break; + case 24: + mod |= MOD_BCLK_24FS; + break; + case 16: + mod |= MOD_BCLK_16FS; + break; + default: + return; + } + writel(mod, &i2s_reg->mod); +} + +/* + * flushes the i2stx fifo + * + * @param i2s_reg i2s regiter address + * @param flush Tx fifo flush command (0x00 - do not flush + * 0x80 - flush tx fifo) + */ +void i2s_fifo(struct i2s_reg *i2s_reg, unsigned int flush) +{ + /* Flush the FIFO */ + setbits_le32(&i2s_reg->fic, flush); + clrbits_le32(&i2s_reg->fic, flush); +} + +/* + * Set System Clock direction + * + * @param i2s_reg i2s regiter address + * @param dir Clock direction + * + * @return int value 0 for success, -1 in case of error + */ +int i2s_set_sysclk_dir(struct i2s_reg *i2s_reg, int dir) +{ + unsigned int mod = readl(&i2s_reg->mod); + + if (dir == SND_SOC_CLOCK_IN) + mod |= MOD_CDCLKCON; + else + mod &= ~MOD_CDCLKCON; + + writel(mod, &i2s_reg->mod); + + return 0; +} + +/* + * Sets I2S Clcok format + * + * @param fmt i2s clock properties + * @param i2s_reg i2s regiter address + * + * @return int value 0 for success, -1 in case of error + */ +int i2s_set_fmt(struct i2s_reg *i2s_reg, unsigned int fmt) +{ + unsigned int mod = readl(&i2s_reg->mod); + unsigned int tmp = 0; + unsigned int ret = 0; + + /* Format is priority */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + tmp |= MOD_LR_RLOW; + tmp |= MOD_SDF_MSB; + break; + case SND_SOC_DAIFMT_LEFT_J: + tmp |= MOD_LR_RLOW; + tmp |= MOD_SDF_LSB; + break; + case SND_SOC_DAIFMT_I2S: + tmp |= MOD_SDF_IIS; + break; + default: + debug("%s: Invalid format priority [0x%x]\n", __func__, + (fmt & SND_SOC_DAIFMT_FORMAT_MASK)); + return -1; + } + + /* + * INV flag is relative to the FORMAT flag - if set it simply + * flips the polarity specified by the Standard + */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + if (tmp & MOD_LR_RLOW) + tmp &= ~MOD_LR_RLOW; + else + tmp |= MOD_LR_RLOW; + break; + default: + debug("%s: Invalid clock ploarity input [0x%x]\n", __func__, + (fmt & SND_SOC_DAIFMT_INV_MASK)); + return -1; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + tmp |= MOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Set default source clock in Master mode */ + ret = i2s_set_sysclk_dir(i2s_reg, SND_SOC_CLOCK_OUT); + if (ret != 0) { + debug("%s:set i2s clock direction failed\n", __func__); + return -1; + } + break; + default: + debug("%s: Invalid master selection [0x%x]\n", __func__, + (fmt & SND_SOC_DAIFMT_MASTER_MASK)); + return -1; + } + + mod &= ~(MOD_SDF_MASK | MOD_LR_RLOW | MOD_SLAVE); + mod |= tmp; + writel(mod, &i2s_reg->mod); + + return 0; +} + +/* + * Sets the sample width in bits + * + * @param blc samplewidth (size of sample in bits) + * @param i2s_reg i2s regiter address + * + * @return int value 0 for success, -1 in case of error + */ +int i2s_set_samplesize(struct i2s_reg *i2s_reg, unsigned int blc) +{ + unsigned int mod = readl(&i2s_reg->mod); + + mod &= ~MOD_BLCP_MASK; + mod &= ~MOD_BLC_MASK; + + switch (blc) { + case 8: + mod |= MOD_BLCP_8BIT; + mod |= MOD_BLC_8BIT; + break; + case 16: + mod |= MOD_BLCP_16BIT; + mod |= MOD_BLC_16BIT; + break; + case 24: + mod |= MOD_BLCP_24BIT; + mod |= MOD_BLC_24BIT; + break; + default: + debug("%s: Invalid sample size input [0x%x]\n", + __func__, blc); + return -1; + } + writel(mod, &i2s_reg->mod); + + return 0; +} + +int i2s_transfer_tx_data(struct i2stx_info *pi2s_tx, unsigned int *data, + unsigned long data_size) +{ + int i; + int start; + struct i2s_reg *i2s_reg = + (struct i2s_reg *)pi2s_tx->base_address; + + if (data_size < FIFO_LENGTH) { + debug("%s : Invalid data size\n", __func__); + return -1; /* invalid pcm data size */ + } + + /* fill the tx buffer before stating the tx transmit */ + for (i = 0; i < FIFO_LENGTH; i++) + writel(*data++, &i2s_reg->txd); + + data_size -= FIFO_LENGTH; + i2s_txctrl(i2s_reg, I2S_TX_ON); + + while (data_size > 0) { + start = get_timer(0); + if (!(CON_TXFIFO_FULL & (readl(&i2s_reg->con)))) { + writel(*data++, &i2s_reg->txd); + data_size--; + } else { + if (get_timer(start) > TIMEOUT_I2S_TX) { + i2s_txctrl(i2s_reg, I2S_TX_OFF); + debug("%s: I2S Transfer Timeout\n", __func__); + return -1; + } + } + } + i2s_txctrl(i2s_reg, I2S_TX_OFF); + + return 0; +} + +int i2s_tx_init(struct i2stx_info *pi2s_tx) +{ + int ret; + struct i2s_reg *i2s_reg = + (struct i2s_reg *)pi2s_tx->base_address; + + /* Initialize GPIO for I2s */ + exynos_pinmux_config(PERIPH_ID_I2S1, 0); + + /* Set EPLL Clock */ + ret = set_epll_clk(pi2s_tx->audio_pll_clk); + if (ret != 0) { + debug("%s: epll clock set rate falied\n", __func__); + return -1; + } + + /* Select Clk Source for Audio1 */ + set_i2s_clk_source(); + + /* Set Prescaler to get MCLK */ + set_i2s_clk_prescaler(pi2s_tx->audio_pll_clk, + (pi2s_tx->samplingrate * (pi2s_tx->rfs))); + + /* Configure I2s format */ + ret = i2s_set_fmt(i2s_reg, (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM)); + if (ret == 0) { + i2s_set_lr_framesize(i2s_reg, pi2s_tx->rfs); + ret = i2s_set_samplesize(i2s_reg, pi2s_tx->bitspersample); + if (ret != 0) { + debug("%s:set sample rate failed\n", __func__); + return -1; + } + + i2s_set_bitclk_framesize(i2s_reg, pi2s_tx->bfs); + /* disable i2s transfer flag and flush the fifo */ + i2s_txctrl(i2s_reg, I2S_TX_OFF); + i2s_fifo(i2s_reg, FIC_TXFLUSH); + } else { + debug("%s: failed\n", __func__); + } + + return ret; +} diff --git a/drivers/sound/sound.c b/drivers/sound/sound.c new file mode 100644 index 0000000000..4c74534c95 --- /dev/null +++ b/drivers/sound/sound.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * R. Chandrasekar <rcsekar@samsung.com> + * + * 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 <malloc.h> +#include <common.h> +#include <asm/io.h> +#include <i2c.h> +#include <i2s.h> +#include <sound.h> +#include "wm8994.h" +#include <asm/arch/sound.h> + +/* defines */ +#define SOUND_400_HZ 400 +#define SOUND_BITS_IN_BYTE 8 + +static struct i2stx_info g_i2stx_pri; +static struct sound_codec_info g_codec_info; + +/* + * get_sound_fdt_values gets fdt values for i2s parameters + * + * @param i2stx_info i2s transmitter transfer param structure + * @param blob FDT blob + */ +static void get_sound_i2s_values(struct i2stx_info *i2s) +{ + i2s->base_address = samsung_get_base_i2s(); + i2s->audio_pll_clk = I2S_PLL_CLK; + i2s->samplingrate = I2S_SAMPLING_RATE; + i2s->bitspersample = I2S_BITS_PER_SAMPLE; + i2s->channels = I2S_CHANNELS; + i2s->rfs = I2S_RFS; + i2s->bfs = I2S_BFS; +} + +/* + * Gets fdt values for wm8994 config parameters + * + * @param pcodec_info codec information structure + * @param blob FDT blob + * @return int value, 0 for success + */ +static int get_sound_wm8994_values(struct sound_codec_info *pcodec_info) +{ + int error = 0; + + switch (AUDIO_COMPAT) { + case AUDIO_COMPAT_SPI: + debug("%s: Support not added for SPI interface\n", __func__); + return -1; + break; + case AUDIO_COMPAT_I2C: + pcodec_info->i2c_bus = AUDIO_I2C_BUS; + pcodec_info->i2c_dev_addr = AUDIO_I2C_REG; + debug("i2c dev addr = %d\n", pcodec_info->i2c_dev_addr); + break; + default: + debug("%s: Unknown compat id %d\n", __func__, AUDIO_COMPAT); + return -1; + } + + if (error == -1) { + debug("fail to get wm8994 codec node properties\n"); + return -1; + } + + return 0; +} + +/* + * Gets fdt values for codec config parameters + * + * @param pcodec_info codec information structure + * @param blob FDT blob + * @return int value, 0 for success + */ +static int get_sound_codec_values(struct sound_codec_info *pcodec_info) +{ + int error = 0; + const char *codectype; + + codectype = AUDIO_CODEC; + + if (!strcmp(codectype, "wm8994")) { + pcodec_info->codec_type = CODEC_WM_8994; + error = get_sound_wm8994_values(pcodec_info); + } else { + error = -1; + } + + if (error == -1) { + debug("fail to get sound codec node properties\n"); + return -1; + } + + return 0; +} + +int sound_init(void) +{ + int ret; + struct i2stx_info *pi2s_tx = &g_i2stx_pri; + struct sound_codec_info *pcodec_info = &g_codec_info; + + /* Get the I2S Values */ + get_sound_i2s_values(pi2s_tx); + + /* Get the codec Values */ + if (get_sound_codec_values(pcodec_info) < 0) + return -1; + + ret = i2s_tx_init(pi2s_tx); + if (ret) { + debug("%s: Failed to init i2c transmit: ret=%d\n", __func__, + ret); + return ret; + } + + /* Check the codec type and initialise the same */ + if (pcodec_info->codec_type == CODEC_WM_8994) { + ret = wm8994_init(pcodec_info, WM8994_AIF2, + pi2s_tx->samplingrate, + (pi2s_tx->samplingrate * (pi2s_tx->rfs)), + pi2s_tx->bitspersample, pi2s_tx->channels); + } else { + debug("%s: Unknown code type %d\n", __func__, + pcodec_info->codec_type); + return -1; + } + if (ret) { + debug("%s: Codec init failed\n", __func__); + return -1; + } + + return ret; +} + +/* + * Generates square wave sound data for 1 second + * + * @param data data buffer pointer + * @param size size of the buffer + * @param freq frequency of the wave + */ +static void sound_prepare_buffer(unsigned short *data, int size, uint32_t freq) +{ + const int sample = 48000; + const unsigned short amplitude = 16000; /* between 1 and 32767 */ + const int period = freq ? sample / freq : 0; + const int half = period / 2; + + assert(freq); + + /* Make sure we don't overflow our buffer */ + if (size % 2) + size--; + + while (size) { + int i; + for (i = 0; size && i < half; i++) { + size -= 2; + *data++ = amplitude; + *data++ = amplitude; + } + for (i = 0; size && i < period - half; i++) { + size -= 2; + *data++ = -amplitude; + *data++ = -amplitude; + } + } +} + +int sound_play(uint32_t msec, uint32_t frequency) +{ + unsigned int *data; + unsigned long data_size; + unsigned int ret = 0; + + /*Buffer length computation */ + data_size = g_i2stx_pri.samplingrate * g_i2stx_pri.channels; + data_size *= (g_i2stx_pri.bitspersample / SOUND_BITS_IN_BYTE); + data = malloc(data_size); + + if (data == NULL) { + debug("%s: malloc failed\n", __func__); + return -1; + } + + sound_prepare_buffer((unsigned short *)data, + data_size / sizeof(unsigned short), frequency); + + while (msec >= 1000) { + ret = i2s_transfer_tx_data(&g_i2stx_pri, data, + (data_size / sizeof(int))); + msec -= 1000; + } + if (msec) { + unsigned long size = + (data_size * msec) / (sizeof(int) * 1000); + + ret = i2s_transfer_tx_data(&g_i2stx_pri, data, size); + } + + free(data); + + return ret; +} diff --git a/drivers/sound/wm8994.c b/drivers/sound/wm8994.c new file mode 100644 index 0000000000..293903ada2 --- /dev/null +++ b/drivers/sound/wm8994.c @@ -0,0 +1,792 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * R. Chandrasekar <rcsekar@samsung.com> + * + * 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 <asm/arch/clk.h> +#include <asm/arch/cpu.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <common.h> +#include <div64.h> +#include <i2c.h> +#include <i2s.h> +#include <sound.h> +#include "wm8994.h" +#include "wm8994_registers.h" + +/* defines for wm8994 system clock selection */ +#define SEL_MCLK1 0x00 +#define SEL_MCLK2 0x08 +#define SEL_FLL1 0x10 +#define SEL_FLL2 0x18 + +/* fll config to configure fll */ +struct wm8994_fll_config { + int src; /* Source */ + int in; /* Input frequency in Hz */ + int out; /* output frequency in Hz */ +}; + +/* codec private data */ +struct wm8994_priv { + enum wm8994_type type; /* codec type of wolfson */ + int revision; /* Revision */ + int sysclk[WM8994_MAX_AIF]; /* System clock frequency in Hz */ + int mclk[WM8994_MAX_AIF]; /* master clock frequency in Hz */ + int aifclk[WM8994_MAX_AIF]; /* audio interface clock in Hz */ + struct wm8994_fll_config fll[2]; /* fll config to configure fll */ +}; + +/* wm 8994 supported sampling rate values */ +static unsigned int src_rate[] = { + 8000, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 88200, 96000 +}; + +/* op clock divisions */ +static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 }; + +/* lr clock frame size ratio */ +static int fs_ratios[] = { + 64, 128, 192, 256, 348, 512, 768, 1024, 1408, 1536 +}; + +/* bit clock divisors */ +static int bclk_divs[] = { + 10, 15, 20, 30, 40, 50, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480, + 640, 880, 960, 1280, 1760, 1920 +}; + +static struct wm8994_priv g_wm8994_info; +static unsigned char g_wm8994_i2c_dev_addr; + +/* + * Initialise I2C for wm 8994 + * + * @param bus no i2c bus number in which wm8994 is connected + */ +static void wm8994_i2c_init(int bus_no) +{ + i2c_set_bus_num(bus_no); +} + +/* + * Writes value to a device register through i2c + * + * @param reg reg number to be write + * @param data data to be writen to the above registor + * + * @return int value 1 for change, 0 for no change or negative error code. + */ +static int wm8994_i2c_write(unsigned int reg, unsigned short data) +{ + unsigned char val[2]; + + val[0] = (unsigned char)((data >> 8) & 0xff); + val[1] = (unsigned char)(data & 0xff); + debug("Write Addr : 0x%04X, Data : 0x%04X\n", reg, data); + + return i2c_write(g_wm8994_i2c_dev_addr, reg, 2, val, 2); +} + +/* + * Read a value from a device register through i2c + * + * @param reg reg number to be read + * @param data address of read data to be stored + * + * @return int value 0 for success, -1 in case of error. + */ +static unsigned int wm8994_i2c_read(unsigned int reg , unsigned short *data) +{ + unsigned char val[2]; + int ret; + + ret = i2c_read(g_wm8994_i2c_dev_addr, reg, 2, val, 2); + if (ret != 0) { + debug("%s: Error while reading register %#04x\n", + __func__, reg); + return -1; + } + + *data = val[0]; + *data <<= 8; + *data |= val[1]; + + return 0; +} + +/* + * update device register bits through i2c + * + * @param reg codec register + * @param mask register mask + * @param value new value + * + * @return int value 1 if change in the register value, + * 0 for no change or negative error code. + */ +static int wm8994_update_bits(unsigned int reg, unsigned short mask, + unsigned short value) +{ + int change , ret = 0; + unsigned short old, new; + + if (wm8994_i2c_read(reg, &old) != 0) + return -1; + new = (old & ~mask) | (value & mask); + change = (old != new) ? 1 : 0; + if (change) + ret = wm8994_i2c_write(reg, new); + if (ret < 0) + return ret; + + return change; +} + +/* + * Sets i2s set format + * + * @param aif_id Interface ID + * @param fmt i2S format + * + * @return -1 for error and 0 Success. + */ +int wm8994_set_fmt(int aif_id, unsigned int fmt) +{ + int ms_reg; + int aif_reg; + int ms = 0; + int aif = 0; + int aif_clk = 0; + int error = 0; + + switch (aif_id) { + case 1: + ms_reg = WM8994_AIF1_MASTER_SLAVE; + aif_reg = WM8994_AIF1_CONTROL_1; + aif_clk = WM8994_AIF1_CLOCKING_1; + break; + case 2: + ms_reg = WM8994_AIF2_MASTER_SLAVE; + aif_reg = WM8994_AIF2_CONTROL_1; + aif_clk = WM8994_AIF2_CLOCKING_1; + break; + default: + debug("%s: Invalid audio interface selection\n", __func__); + return -1; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ms = WM8994_AIF1_MSTR; + break; + default: + debug("%s: Invalid i2s master selection\n", __func__); + return -1; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif |= WM8994_AIF1_LRCLK_INV; + case SND_SOC_DAIFMT_DSP_A: + aif |= 0x18; + break; + case SND_SOC_DAIFMT_I2S: + aif |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif |= 0x8; + break; + default: + debug("%s: Invalid i2s format selection\n", __func__); + return -1; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8994_AIF1_BCLK_INV; + break; + default: + debug("%s: Invalid i2s frame inverse selection\n", + __func__); + return -1; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif |= WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8994_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif |= WM8994_AIF1_LRCLK_INV; + break; + default: + debug("%s: Invalid i2s clock polarity selection\n", + __func__); + return -1; + } + break; + default: + debug("%s: Invalid i2s format selection\n", __func__); + return -1; + } + + error = wm8994_update_bits(aif_reg, WM8994_AIF1_BCLK_INV | + WM8994_AIF1_LRCLK_INV_MASK | WM8994_AIF1_FMT_MASK, aif); + + error |= wm8994_update_bits(ms_reg, WM8994_AIF1_MSTR_MASK, ms); + error |= wm8994_update_bits(aif_clk, WM8994_AIF1CLK_ENA_MASK, + WM8994_AIF1CLK_ENA); + if (error < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Sets hw params FOR WM8994 + * + * @param wm8994 wm8994 information pointer + * @param aif_id Audio interface ID + * @param sampling_rate Sampling rate + * @param bits_per_sample Bits per sample + * @param Channels Channels in the given audio input + * + * @return -1 for error and 0 Success. + */ +static int wm8994_hw_params(struct wm8994_priv *wm8994, int aif_id, + unsigned int sampling_rate, unsigned int bits_per_sample, + unsigned int channels) +{ + int aif1_reg; + int aif2_reg; + int bclk_reg; + int bclk = 0; + int rate_reg; + int aif1 = 0; + int aif2 = 0; + int rate_val = 0; + int id = aif_id - 1; + int i, cur_val, best_val, bclk_rate, best; + unsigned short reg_data; + int ret = 0; + + switch (aif_id) { + case 1: + aif1_reg = WM8994_AIF1_CONTROL_1; + aif2_reg = WM8994_AIF1_CONTROL_2; + bclk_reg = WM8994_AIF1_BCLK; + rate_reg = WM8994_AIF1_RATE; + break; + case 2: + aif1_reg = WM8994_AIF2_CONTROL_1; + aif2_reg = WM8994_AIF2_CONTROL_2; + bclk_reg = WM8994_AIF2_BCLK; + rate_reg = WM8994_AIF2_RATE; + break; + default: + return -1; + } + + bclk_rate = sampling_rate * 32; + switch (bits_per_sample) { + case 16: + bclk_rate *= 16; + break; + case 20: + bclk_rate *= 20; + aif1 |= 0x20; + break; + case 24: + bclk_rate *= 24; + aif1 |= 0x40; + break; + case 32: + bclk_rate *= 32; + aif1 |= 0x60; + break; + default: + return -1; + } + + /* Try to find an appropriate sample rate; look for an exact match. */ + for (i = 0; i < ARRAY_SIZE(src_rate); i++) + if (src_rate[i] == sampling_rate) + break; + + if (i == ARRAY_SIZE(src_rate)) { + debug("%s: Could not get the best matching samplingrate\n", + __func__); + return -1; + } + + rate_val |= i << WM8994_AIF1_SR_SHIFT; + + /* AIFCLK/fs ratio; look for a close match in either direction */ + best = 0; + best_val = abs((fs_ratios[0] * sampling_rate) + - wm8994->aifclk[id]); + + for (i = 1; i < ARRAY_SIZE(fs_ratios); i++) { + cur_val = abs((fs_ratios[i] * sampling_rate) + - wm8994->aifclk[id]); + if (cur_val >= best_val) + continue; + best = i; + best_val = cur_val; + } + + rate_val |= best; + + /* + * We may not get quite the right frequency if using + * approximate clocks so look for the closest match that is + * higher than the target (we need to ensure that there enough + * BCLKs to clock out the samples). + */ + best = 0; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = (wm8994->aifclk[id] * 10 / bclk_divs[i]) - bclk_rate; + if (cur_val < 0) /* BCLK table is sorted */ + break; + best = i; + } + + if (i == ARRAY_SIZE(bclk_divs)) { + debug("%s: Could not get the best matching bclk division\n", + __func__); + return -1; + } + + bclk_rate = wm8994->aifclk[id] * 10 / bclk_divs[best]; + bclk |= best << WM8994_AIF1_BCLK_DIV_SHIFT; + + if (wm8994_i2c_read(aif1_reg, ®_data) != 0) { + debug("%s: AIF1 register read Failed\n", __func__); + return -1; + } + + if ((channels == 1) && ((reg_data & 0x18) == 0x18)) + aif2 |= WM8994_AIF1_MONO; + + if (wm8994->aifclk[id] == 0) { + debug("%s:Audio interface clock not set\n", __func__); + return -1; + } + + ret = wm8994_update_bits(aif1_reg, WM8994_AIF1_WL_MASK, aif1); + ret |= wm8994_update_bits(aif2_reg, WM8994_AIF1_MONO, aif2); + ret |= wm8994_update_bits(bclk_reg, WM8994_AIF1_BCLK_DIV_MASK, bclk); + ret |= wm8994_update_bits(rate_reg, WM8994_AIF1_SR_MASK | + WM8994_AIF1CLK_RATE_MASK, rate_val); + + debug("rate vale = %x , bclk val= %x\n", rate_val, bclk); + + if (ret < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Configures Audio interface Clock + * + * @param wm8994 wm8994 information pointer + * @param aif Audio Interface ID + * + * @return -1 for error and 0 Success. + */ +static int configure_aif_clock(struct wm8994_priv *wm8994, int aif) +{ + int rate; + int reg1 = 0; + int offset; + int ret; + + /* AIF(1/0) register adress offset calculated */ + if (aif) + offset = 4; + else + offset = 0; + + switch (wm8994->sysclk[aif]) { + case WM8994_SYSCLK_MCLK1: + reg1 |= SEL_MCLK1; + rate = wm8994->mclk[0]; + break; + + case WM8994_SYSCLK_MCLK2: + reg1 |= SEL_MCLK2; + rate = wm8994->mclk[1]; + break; + + case WM8994_SYSCLK_FLL1: + reg1 |= SEL_FLL1; + rate = wm8994->fll[0].out; + break; + + case WM8994_SYSCLK_FLL2: + reg1 |= SEL_FLL2; + rate = wm8994->fll[1].out; + break; + + default: + debug("%s: Invalid input clock selection [%d]\n", + __func__, wm8994->sysclk[aif]); + return -1; + } + + /* if input clock frequenct is more than 135Mhz then divide */ + if (rate >= WM8994_MAX_INPUT_CLK_FREQ) { + rate /= 2; + reg1 |= WM8994_AIF1CLK_DIV; + } + + wm8994->aifclk[aif] = rate; + + ret = wm8994_update_bits(WM8994_AIF1_CLOCKING_1 + offset, + WM8994_AIF1CLK_SRC_MASK | WM8994_AIF1CLK_DIV, + reg1); + + ret |= wm8994_update_bits(WM8994_CLOCKING_1, + WM8994_SYSCLK_SRC | WM8994_AIF2DSPCLK_ENA_MASK | + WM8994_SYSDSPCLK_ENA_MASK, WM8994_SYSCLK_SRC | + WM8994_AIF2DSPCLK_ENA | WM8994_SYSDSPCLK_ENA); + + if (ret < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Configures Audio interface for the given frequency + * + * @param wm8994 wm8994 information + * @param aif_id Audio Interface + * @param clk_id Input Clock ID + * @param freq Sampling frequency in Hz + * + * @return -1 for error and 0 success. + */ +static int wm8994_set_sysclk(struct wm8994_priv *wm8994, int aif_id, + int clk_id, unsigned int freq) +{ + int i; + int ret = 0; + + wm8994->sysclk[aif_id - 1] = clk_id; + + switch (clk_id) { + case WM8994_SYSCLK_MCLK1: + wm8994->mclk[0] = freq; + if (aif_id == 2) { + ret = wm8994_update_bits(WM8994_AIF1_CLOCKING_2 , + WM8994_AIF2DAC_DIV_MASK , 0); + } + break; + + case WM8994_SYSCLK_MCLK2: + /* TODO: Set GPIO AF */ + wm8994->mclk[1] = freq; + break; + + case WM8994_SYSCLK_FLL1: + case WM8994_SYSCLK_FLL2: + break; + + case WM8994_SYSCLK_OPCLK: + /* + * Special case - a division (times 10) is given and + * no effect on main clocking. + */ + if (freq) { + for (i = 0; i < ARRAY_SIZE(opclk_divs); i++) + if (opclk_divs[i] == freq) + break; + if (i == ARRAY_SIZE(opclk_divs)) { + debug("%s frequency divisor not found\n", + __func__); + return -1; + } + ret = wm8994_update_bits(WM8994_CLOCKING_2, + WM8994_OPCLK_DIV_MASK, i); + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_2, + WM8994_OPCLK_ENA, WM8994_OPCLK_ENA); + } else { + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_2, + WM8994_OPCLK_ENA, 0); + } + + default: + debug("%s Invalid input clock selection [%d]\n", + __func__, clk_id); + return -1; + } + + ret |= configure_aif_clock(wm8994, aif_id - 1); + + if (ret < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Initializes Volume for AIF2 to HP path + * + * @returns -1 for error and 0 Success. + * + */ +static int wm8994_init_volume_aif2_dac1(void) +{ + int ret; + + /* Unmute AIF2DAC */ + ret = wm8994_update_bits(WM8994_AIF2_DAC_FILTERS_1, + WM8994_AIF2DAC_MUTE_MASK, 0); + + + ret |= wm8994_update_bits(WM8994_AIF2_DAC_LEFT_VOLUME, + WM8994_AIF2DAC_VU_MASK | WM8994_AIF2DACL_VOL_MASK, + WM8994_AIF2DAC_VU | 0xff); + + ret |= wm8994_update_bits(WM8994_AIF2_DAC_RIGHT_VOLUME, + WM8994_AIF2DAC_VU_MASK | WM8994_AIF2DACR_VOL_MASK, + WM8994_AIF2DAC_VU | 0xff); + + + ret |= wm8994_update_bits(WM8994_DAC1_LEFT_VOLUME, + WM8994_DAC1_VU_MASK | WM8994_DAC1L_VOL_MASK | + WM8994_DAC1L_MUTE_MASK, WM8994_DAC1_VU | 0xc0); + + ret |= wm8994_update_bits(WM8994_DAC1_RIGHT_VOLUME, + WM8994_DAC1_VU_MASK | WM8994_DAC1R_VOL_MASK | + WM8994_DAC1R_MUTE_MASK, WM8994_DAC1_VU | 0xc0); + /* Head Phone Volume */ + ret |= wm8994_i2c_write(WM8994_LEFT_OUTPUT_VOLUME, 0x12D); + ret |= wm8994_i2c_write(WM8994_RIGHT_OUTPUT_VOLUME, 0x12D); + + if (ret < 0) { + debug("%s: codec register access error\n", __func__); + return -1; + } + + return 0; +} + +/* + * Intialise wm8994 codec device + * + * @param wm8994 wm8994 information + * + * @returns -1 for error and 0 Success. + */ +static int wm8994_device_init(struct wm8994_priv *wm8994) +{ + const char *devname; + unsigned short reg_data; + int ret; + + wm8994_i2c_write(WM8994_SOFTWARE_RESET, WM8994_SW_RESET);/* Reset */ + + ret = wm8994_i2c_read(WM8994_SOFTWARE_RESET, ®_data); + if (ret < 0) { + debug("Failed to read ID register\n"); + goto err; + } + + if (reg_data == WM8994_ID) { + devname = "WM8994"; + debug("Device registered as type %d\n", wm8994->type); + wm8994->type = WM8994; + } else { + debug("Device is not a WM8994, ID is %x\n", ret); + ret = -1; + goto err; + } + + ret = wm8994_i2c_read(WM8994_CHIP_REVISION, ®_data); + if (ret < 0) { + debug("Failed to read revision register: %d\n", ret); + goto err; + } + wm8994->revision = reg_data; + debug("%s revision %c\n", devname, 'A' + wm8994->revision); + + /* VMID Selection */ + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_1, + WM8994_VMID_SEL_MASK | WM8994_BIAS_ENA_MASK, 0x3); + + /* Charge Pump Enable */ + ret |= wm8994_update_bits(WM8994_CHARGE_PUMP_1, WM8994_CP_ENA_MASK, + WM8994_CP_ENA); + + /* Head Phone Power Enable */ + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_1, + WM8994_HPOUT1L_ENA_MASK, WM8994_HPOUT1L_ENA); + + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_1, + WM8994_HPOUT1R_ENA_MASK, WM8994_HPOUT1R_ENA); + + /* Power enable for AIF2 and DAC1 */ + ret |= wm8994_update_bits(WM8994_POWER_MANAGEMENT_5, + WM8994_AIF2DACL_ENA_MASK | WM8994_AIF2DACR_ENA_MASK | + WM8994_DAC1L_ENA_MASK | WM8994_DAC1R_ENA_MASK, + WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA | WM8994_DAC1L_ENA | + WM8994_DAC1R_ENA); + + /* Head Phone Initialisation */ + ret |= wm8994_update_bits(WM8994_ANALOGUE_HP_1, + WM8994_HPOUT1L_DLY_MASK | WM8994_HPOUT1R_DLY_MASK, + WM8994_HPOUT1L_DLY | WM8994_HPOUT1R_DLY); + + ret |= wm8994_update_bits(WM8994_DC_SERVO_1, + WM8994_DCS_ENA_CHAN_0_MASK | + WM8994_DCS_ENA_CHAN_1_MASK , WM8994_DCS_ENA_CHAN_0 | + WM8994_DCS_ENA_CHAN_1); + + ret |= wm8994_update_bits(WM8994_ANALOGUE_HP_1, + WM8994_HPOUT1L_DLY_MASK | + WM8994_HPOUT1R_DLY_MASK | WM8994_HPOUT1L_OUTP_MASK | + WM8994_HPOUT1R_OUTP_MASK | + WM8994_HPOUT1L_RMV_SHORT_MASK | + WM8994_HPOUT1R_RMV_SHORT_MASK, WM8994_HPOUT1L_DLY | + WM8994_HPOUT1R_DLY | WM8994_HPOUT1L_OUTP | + WM8994_HPOUT1R_OUTP | WM8994_HPOUT1L_RMV_SHORT | + WM8994_HPOUT1R_RMV_SHORT); + + /* MIXER Config DAC1 to HP */ + ret |= wm8994_update_bits(WM8994_OUTPUT_MIXER_1, + WM8994_DAC1L_TO_HPOUT1L_MASK, WM8994_DAC1L_TO_HPOUT1L); + + ret |= wm8994_update_bits(WM8994_OUTPUT_MIXER_2, + WM8994_DAC1R_TO_HPOUT1R_MASK, WM8994_DAC1R_TO_HPOUT1R); + + /* Routing AIF2 to DAC1 */ + ret |= wm8994_update_bits(WM8994_DAC1_LEFT_MIXER_ROUTING, + WM8994_AIF2DACL_TO_DAC1L_MASK, + WM8994_AIF2DACL_TO_DAC1L); + + ret |= wm8994_update_bits(WM8994_DAC1_RIGHT_MIXER_ROUTING, + WM8994_AIF2DACR_TO_DAC1R_MASK, + WM8994_AIF2DACR_TO_DAC1R); + + /* GPIO Settings for AIF2 */ + /* B CLK */ + ret |= wm8994_update_bits(WM8994_GPIO_3, WM8994_GPIO_DIR_MASK | + WM8994_GPIO_FUNCTION_MASK , + WM8994_GPIO_DIR_OUTPUT | + WM8994_GPIO_FUNCTION_I2S_CLK); + + /* LR CLK */ + ret |= wm8994_update_bits(WM8994_GPIO_4, WM8994_GPIO_DIR_MASK | + WM8994_GPIO_FUNCTION_MASK, + WM8994_GPIO_DIR_OUTPUT | + WM8994_GPIO_FUNCTION_I2S_CLK); + + /* DATA */ + ret |= wm8994_update_bits(WM8994_GPIO_5, WM8994_GPIO_DIR_MASK | + WM8994_GPIO_FUNCTION_MASK, + WM8994_GPIO_DIR_OUTPUT | + WM8994_GPIO_FUNCTION_I2S_CLK); + + ret |= wm8994_init_volume_aif2_dac1(); + if (ret < 0) + goto err; + + debug("%s: Codec chip init ok\n", __func__); + return 0; +err: + debug("%s: Codec chip init error\n", __func__); + return -1; +} + +/*wm8994 Device Initialisation */ +int wm8994_init(struct sound_codec_info *pcodec_info, + enum en_audio_interface aif_id, + int sampling_rate, int mclk_freq, + int bits_per_sample, unsigned int channels) +{ + int ret = 0; + + /* shift the device address by 1 for 7 bit addressing */ + g_wm8994_i2c_dev_addr = pcodec_info->i2c_dev_addr; + wm8994_i2c_init(pcodec_info->i2c_bus); + + if (pcodec_info->codec_type == CODEC_WM_8994) + g_wm8994_info.type = WM8994; + else { + debug("%s: Codec id [%d] not defined\n", __func__, + pcodec_info->codec_type); + return -1; + } + + ret = wm8994_device_init(&g_wm8994_info); + if (ret < 0) { + debug("%s: wm8994 codec chip init failed\n", __func__); + return ret; + } + + ret = wm8994_set_sysclk(&g_wm8994_info, aif_id, WM8994_SYSCLK_MCLK1, + mclk_freq); + if (ret < 0) { + debug("%s: wm8994 codec set sys clock failed\n", __func__); + return ret; + } + + ret = wm8994_hw_params(&g_wm8994_info, aif_id, sampling_rate, + bits_per_sample, channels); + + if (ret == 0) { + ret = wm8994_set_fmt(aif_id, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + } + return ret; +} diff --git a/drivers/sound/wm8994.h b/drivers/sound/wm8994.h new file mode 100644 index 0000000000..a8f0de18c5 --- /dev/null +++ b/drivers/sound/wm8994.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * R. Chadrasekar <rcsekar@samsung.com> + * + * 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 + */ + +#ifndef __WM8994_H__ +#define __WM8994_H__ + +/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */ +#define WM8994_SYSCLK_MCLK1 1 +#define WM8994_SYSCLK_MCLK2 2 +#define WM8994_SYSCLK_FLL1 3 +#define WM8994_SYSCLK_FLL2 4 + +/* Avilable audi interface ports in wm8994 codec */ +enum en_audio_interface { + WM8994_AIF1 = 1, + WM8994_AIF2, + WM8994_AIF3 +}; + +/* OPCLK is also configured with set_dai_sysclk, specify division*10 as rate. */ +#define WM8994_SYSCLK_OPCLK 5 + +#define WM8994_FLL1 1 +#define WM8994_FLL2 2 + +#define WM8994_FLL_SRC_MCLK1 1 +#define WM8994_FLL_SRC_MCLK2 2 +#define WM8994_FLL_SRC_LRCLK 3 +#define WM8994_FLL_SRC_BCLK 4 + +/* maximum available digital interfac in the dac to configure */ +#define WM8994_MAX_AIF 2 + +#define WM8994_MAX_INPUT_CLK_FREQ 13500000 +#define WM8994_ID 0x8994 + +enum wm8994_vmid_mode { + WM8994_VMID_NORMAL, + WM8994_VMID_FORCE, +}; + +/* wm 8994 family devices */ +enum wm8994_type { + WM8994 = 0, + WM8958 = 1, + WM1811 = 2, +}; + +/* + * intialise wm8994 sound codec device for the given configuration + * + * @param pcodec_info pointer value of the sound codec info structure + * parsed from device tree + * @param aif_id enum value of codec interface port in which + * soc i2s is connected + * @param sampling_rate Sampling rate ranges between from 8khz to 96khz + * @param mclk_freq Master clock frequency. + * @param bits_per_sample bits per Sample can be 16 or 24 + * @param channels Number of channnels, maximum 2 + * + * @returns -1 for error and 0 Success. + */ +int wm8994_init(struct sound_codec_info *pcodec_info, + enum en_audio_interface aif_id, + int sampling_rate, int mclk_freq, + int bits_per_sample, unsigned int channels); +#endif /*__WM8994_H__ */ diff --git a/drivers/sound/wm8994_registers.h b/drivers/sound/wm8994_registers.h new file mode 100644 index 0000000000..f455b112f0 --- /dev/null +++ b/drivers/sound/wm8994_registers.h @@ -0,0 +1,299 @@ +/* + * (C) Copyright 2012 Samsung Electronics + * + * 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. + * + */ + +#ifndef __WM8994_REGISTERS_H__ +#define __WM8994_REGISTERS_H__ + +/* + * Register values. + */ +#define WM8994_SOFTWARE_RESET 0x00 +#define WM8994_POWER_MANAGEMENT_1 0x01 +#define WM8994_POWER_MANAGEMENT_2 0x02 +#define WM8994_POWER_MANAGEMENT_5 0x05 +#define WM8994_LEFT_OUTPUT_VOLUME 0x1C +#define WM8994_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8994_OUTPUT_MIXER_1 0x2D +#define WM8994_OUTPUT_MIXER_2 0x2E +#define WM8994_CHARGE_PUMP_1 0x4C +#define WM8994_DC_SERVO_1 0x54 +#define WM8994_ANALOGUE_HP_1 0x60 +#define WM8994_CHIP_REVISION 0x100 +#define WM8994_AIF1_CLOCKING_1 0x200 +#define WM8994_AIF1_CLOCKING_2 0x201 +#define WM8994_AIF2_CLOCKING_1 0x204 +#define WM8994_CLOCKING_1 0x208 +#define WM8994_CLOCKING_2 0x209 +#define WM8994_AIF1_RATE 0x210 +#define WM8994_AIF2_RATE 0x211 +#define WM8994_RATE_STATUS 0x212 +#define WM8994_AIF1_CONTROL_1 0x300 +#define WM8994_AIF1_CONTROL_2 0x301 +#define WM8994_AIF1_MASTER_SLAVE 0x302 +#define WM8994_AIF1_BCLK 0x303 +#define WM8994_AIF2_CONTROL_1 0x310 +#define WM8994_AIF2_CONTROL_2 0x311 +#define WM8994_AIF2_MASTER_SLAVE 0x312 +#define WM8994_AIF2_BCLK 0x313 +#define WM8994_AIF2_DAC_LEFT_VOLUME 0x502 +#define WM8994_AIF2_DAC_RIGHT_VOLUME 0x503 +#define WM8994_AIF2_DAC_FILTERS_1 0x520 +#define WM8994_DAC1_LEFT_MIXER_ROUTING 0x601 +#define WM8994_DAC1_RIGHT_MIXER_ROUTING 0x602 +#define WM8994_DAC1_LEFT_VOLUME 0x610 +#define WM8994_DAC1_RIGHT_VOLUME 0x611 +#define WM8994_GPIO_3 0x702 +#define WM8994_GPIO_4 0x703 +#define WM8994_GPIO_5 0x704 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +/* SW_RESET */ +#define WM8994_SW_RESET 1 +/* + * R1 (0x01) - Power Management (1) + */ +/* HPOUT1L_ENA */ +#define WM8994_HPOUT1L_ENA 0x0200 +/* HPOUT1L_ENA */ +#define WM8994_HPOUT1L_ENA_MASK 0x0200 +/* HPOUT1R_ENA */ +#define WM8994_HPOUT1R_ENA 0x0100 +/* HPOUT1R_ENA */ +#define WM8994_HPOUT1R_ENA_MASK 0x0100 +/* VMID_SEL - [2:1] */ +#define WM8994_VMID_SEL_MASK 0x0006 +/* BIAS_ENA */ +#define WM8994_BIAS_ENA 0x0001 +/* BIAS_ENA */ +#define WM8994_BIAS_ENA_MASK 0x0001 + +/* + * R2 (0x02) - Power Management (2) + */ +/* OPCLK_ENA */ +#define WM8994_OPCLK_ENA 0x0800 + +/* + * R5 (0x05) - Power Management (5) + */ +/* AIF2DACL_ENA */ +#define WM8994_AIF2DACL_ENA 0x2000 +#define WM8994_AIF2DACL_ENA_MASK 0x2000 +/* AIF2DACR_ENA */ +#define WM8994_AIF2DACR_ENA 0x1000 +#define WM8994_AIF2DACR_ENA_MASK 0x1000 +/* DAC1L_ENA */ +#define WM8994_DAC1L_ENA 0x0002 +#define WM8994_DAC1L_ENA_MASK 0x0002 +/* DAC1R_ENA */ +#define WM8994_DAC1R_ENA 0x0001 +#define WM8994_DAC1R_ENA_MASK 0x0001 + +/* + * R45 (0x2D) - Output Mixer (1) + */ +/* DAC1L_TO_HPOUT1L */ +#define WM8994_DAC1L_TO_HPOUT1L 0x0100 +#define WM8994_DAC1L_TO_HPOUT1L_MASK 0x0100 + +/* + * R46 (0x2E) - Output Mixer (2) + */ +/* DAC1R_TO_HPOUT1R */ +#define WM8994_DAC1R_TO_HPOUT1R 0x0100 +#define WM8994_DAC1R_TO_HPOUT1R_MASK 0x0100 + +/* + * R76 (0x4C) - Charge Pump (1) + */ +/* CP_ENA */ +#define WM8994_CP_ENA 0x8000 +#define WM8994_CP_ENA_MASK 0x8000 +/* + * R84 (0x54) - DC Servo (1) + */ +/* DCS_ENA_CHAN_1 */ +#define WM8994_DCS_ENA_CHAN_1 0x0002 +#define WM8994_DCS_ENA_CHAN_1_MASK 0x0002 +/* DCS_ENA_CHAN_0 */ +#define WM8994_DCS_ENA_CHAN_0 0x0001 +#define WM8994_DCS_ENA_CHAN_0_MASK 0x0001 + +/* + * R96 (0x60) - Analogue HP (1) + */ +/* HPOUT1L_RMV_SHORT */ +#define WM8994_HPOUT1L_RMV_SHORT 0x0080 +#define WM8994_HPOUT1L_RMV_SHORT_MASK 0x0080 +/* HPOUT1L_OUTP */ +#define WM8994_HPOUT1L_OUTP 0x0040 +#define WM8994_HPOUT1L_OUTP_MASK 0x0040 +/* HPOUT1L_DLY */ +#define WM8994_HPOUT1L_DLY 0x0020 +#define WM8994_HPOUT1L_DLY_MASK 0x0020 +/* HPOUT1R_RMV_SHORT */ +#define WM8994_HPOUT1R_RMV_SHORT 0x0008 +#define WM8994_HPOUT1R_RMV_SHORT_MASK 0x0008 +/* HPOUT1R_OUTP */ +#define WM8994_HPOUT1R_OUTP 0x0004 +#define WM8994_HPOUT1R_OUTP_MASK 0x0004 +/* HPOUT1R_DLY */ +#define WM8994_HPOUT1R_DLY 0x0002 +#define WM8994_HPOUT1R_DLY_MASK 0x0002 + +/* + * R512 (0x200) - AIF1 Clocking (1) + */ +/* AIF1CLK_SRC - [4:3] */ +#define WM8994_AIF1CLK_SRC_MASK 0x0018 +/* AIF1CLK_DIV */ +#define WM8994_AIF1CLK_DIV 0x0002 +/* AIF1CLK_ENA */ +#define WM8994_AIF1CLK_ENA 0x0001 +#define WM8994_AIF1CLK_ENA_MASK 0x0001 + +/* + * R517 (0x205) - AIF2 Clocking (2) + */ +/* AIF2DAC_DIV - [5:3] */ +#define WM8994_AIF2DAC_DIV_MASK 0x0038 + +/* + * R520 (0x208) - Clocking (1) + */ +/* AIF2DSPCLK_ENA */ +#define WM8994_AIF2DSPCLK_ENA 0x0004 +#define WM8994_AIF2DSPCLK_ENA_MASK 0x0004 +/* SYSDSPCLK_ENA */ +#define WM8994_SYSDSPCLK_ENA 0x0002 +#define WM8994_SYSDSPCLK_ENA_MASK 0x0002 +/* SYSCLK_SRC */ +#define WM8994_SYSCLK_SRC 0x0001 + +/* + * R521 (0x209) - Clocking (2) + */ +/* OPCLK_DIV - [2:0] */ +#define WM8994_OPCLK_DIV_MASK 0x0007 + +/* + * R528 (0x210) - AIF1 Rate + */ +/* AIF1_SR - [7:4] */ +#define WM8994_AIF1_SR_MASK 0x00F0 +#define WM8994_AIF1_SR_SHIFT 4 +/* AIF1CLK_RATE - [3:0] */ +#define WM8994_AIF1CLK_RATE_MASK 0x000F + +/* + * R768 (0x300) - AIF1 Control (1) + */ +/* AIF1_BCLK_INV */ +#define WM8994_AIF1_BCLK_INV 0x0100 +/* AIF1_LRCLK_INV */ +#define WM8994_AIF1_LRCLK_INV 0x0080 +#define WM8994_AIF1_LRCLK_INV_MASK 0x0080 +/* AIF1_WL - [6:5] */ +#define WM8994_AIF1_WL_MASK 0x0060 +/* AIF1_FMT - [4:3] */ +#define WM8994_AIF1_FMT_MASK 0x0018 + +/* + * R769 (0x301) - AIF1 Control (2) + */ +/* AIF1_MONO */ +#define WM8994_AIF1_MONO 0x0100 + +/* + * R770 (0x302) - AIF1 Master/Slave + */ +/* AIF1_MSTR */ +#define WM8994_AIF1_MSTR 0x4000 +#define WM8994_AIF1_MSTR_MASK 0x4000 + +/* + * R771 (0x303) - AIF1 BCLK + */ +/* AIF1_BCLK_DIV - [8:4] */ +#define WM8994_AIF1_BCLK_DIV_MASK 0x01F0 +#define WM8994_AIF1_BCLK_DIV_SHIFT 4 + +/* + * R1282 (0x502) - AIF2 DAC Left Volume + */ +/* AIF2DAC_VU */ +#define WM8994_AIF2DAC_VU 0x0100 +#define WM8994_AIF2DAC_VU_MASK 0x0100 +/* AIF2DACL_VOL - [7:0] */ +#define WM8994_AIF2DACL_VOL_MASK 0x00FF + +/* + * R1283 (0x503) - AIF2 DAC Right Volume + */ +/* AIF2DACR_VOL - [7:0] */ +#define WM8994_AIF2DACR_VOL_MASK 0x00FF + +/* + * R1312 (0x520) - AIF2 DAC Filters (1) + */ +/* AIF2DAC_MUTE */ +#define WM8994_AIF2DAC_MUTE_MASK 0x0200 + +/* + * R1537 (0x601) - DAC1 Left Mixer Routing + */ +/* AIF2DACL_TO_DAC1L */ +#define WM8994_AIF2DACL_TO_DAC1L 0x0004 +#define WM8994_AIF2DACL_TO_DAC1L_MASK 0x0004 + +/* + * R1538 (0x602) - DAC1 Right Mixer Routing + */ +/* AIF2DACR_TO_DAC1R */ +#define WM8994_AIF2DACR_TO_DAC1R 0x0004 +#define WM8994_AIF2DACR_TO_DAC1R_MASK 0x0004 + +/* + * R1552 (0x610) - DAC1 Left Volume + */ +/* DAC1L_MUTE */ +#define WM8994_DAC1L_MUTE_MASK 0x0200 +/* DAC1_VU */ +#define WM8994_DAC1_VU 0x0100 +#define WM8994_DAC1_VU_MASK 0x0100 +/* DAC1L_VOL - [7:0] */ +#define WM8994_DAC1L_VOL_MASK 0x00FF + +/* + * R1553 (0x611) - DAC1 Right Volume + */ +/* DAC1R_MUTE */ +#define WM8994_DAC1R_MUTE_MASK 0x0200 +/* DAC1R_VOL - [7:0] */ +#define WM8994_DAC1R_VOL_MASK 0x00FF + +/* + * GPIO + */ +/* OUTPUT PIN */ +#define WM8994_GPIO_DIR_OUTPUT 0x8000 +/* GPIO PIN MASK */ +#define WM8994_GPIO_DIR_MASK 0xFFE0 +/* I2S CLK */ +#define WM8994_GPIO_FUNCTION_I2S_CLK 0x0000 +/* GPn FN */ +#define WM8994_GPIO_FUNCTION_MASK 0x001F +#endif diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index f0b82c67f5..824d357d94 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -34,6 +34,7 @@ COBJS-$(CONFIG_BFIN_SPI) += bfin_spi.o COBJS-$(CONFIG_CF_SPI) += cf_spi.o COBJS-$(CONFIG_CF_QSPI) += cf_qspi.o COBJS-$(CONFIG_DAVINCI_SPI) += davinci_spi.o +COBJS-$(CONFIG_EXYNOS_SPI) += exynos_spi.o COBJS-$(CONFIG_KIRKWOOD_SPI) += kirkwood_spi.o COBJS-$(CONFIG_MPC52XX_SPI) += mpc52xx_spi.o COBJS-$(CONFIG_MPC8XXX_SPI) += mpc8xxx_spi.o diff --git a/drivers/spi/exynos_spi.c b/drivers/spi/exynos_spi.c new file mode 100644 index 0000000000..3e6c18f87f --- /dev/null +++ b/drivers/spi/exynos_spi.c @@ -0,0 +1,367 @@ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Padmavathi Venna <padma.v@samsung.com> + * + * 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 <common.h> +#include <malloc.h> +#include <spi.h> +#include <asm/arch/clk.h> +#include <asm/arch/clock.h> +#include <asm/arch/cpu.h> +#include <asm/arch/gpio.h> +#include <asm/arch/pinmux.h> +#include <asm/arch-exynos/spi.h> +#include <asm/io.h> + +/* Information about each SPI controller */ +struct spi_bus { + enum periph_id periph_id; + s32 frequency; /* Default clock frequency, -1 for none */ + struct exynos_spi *regs; + int inited; /* 1 if this bus is ready for use */ +}; + +/* A list of spi buses that we know about */ +static struct spi_bus spi_bus[EXYNOS5_SPI_NUM_CONTROLLERS]; + +struct exynos_spi_slave { + struct spi_slave slave; + struct exynos_spi *regs; + unsigned int freq; /* Default frequency */ + unsigned int mode; + enum periph_id periph_id; /* Peripheral ID for this device */ + unsigned int fifo_size; +}; + +static struct spi_bus *spi_get_bus(unsigned dev_index) +{ + if (dev_index < EXYNOS5_SPI_NUM_CONTROLLERS) + return &spi_bus[dev_index]; + debug("%s: invalid bus %d", __func__, dev_index); + + return NULL; +} + +static inline struct exynos_spi_slave *to_exynos_spi(struct spi_slave *slave) +{ + return container_of(slave, struct exynos_spi_slave, slave); +} + +/** + * Setup the driver private data + * + * @param bus ID of the bus that the slave is attached to + * @param cs ID of the chip select connected to the slave + * @param max_hz Required spi frequency + * @param mode Required spi mode (clk polarity, clk phase and + * master or slave) + * @return new device or NULL + */ +struct spi_slave *spi_setup_slave(unsigned int busnum, unsigned int cs, + unsigned int max_hz, unsigned int mode) +{ + struct exynos_spi_slave *spi_slave; + struct spi_bus *bus; + + if (!spi_cs_is_valid(busnum, cs)) { + debug("%s: Invalid bus/chip select %d, %d\n", __func__, + busnum, cs); + return NULL; + } + + spi_slave = malloc(sizeof(*spi_slave)); + if (!spi_slave) { + debug("%s: Could not allocate spi_slave\n", __func__); + return NULL; + } + + bus = &spi_bus[busnum]; + spi_slave->slave.bus = busnum; + spi_slave->slave.cs = cs; + spi_slave->regs = bus->regs; + spi_slave->mode = mode; + spi_slave->periph_id = bus->periph_id; + if (bus->periph_id == PERIPH_ID_SPI1 || + bus->periph_id == PERIPH_ID_SPI2) + spi_slave->fifo_size = 64; + else + spi_slave->fifo_size = 256; + + spi_slave->freq = bus->frequency; + if (max_hz) + spi_slave->freq = min(max_hz, spi_slave->freq); + + return &spi_slave->slave; +} + +/** + * Free spi controller + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_free_slave(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + + free(spi_slave); +} + +/** + * Flush spi tx, rx fifos and reset the SPI controller + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +static void spi_flush_fifo(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + struct exynos_spi *regs = spi_slave->regs; + + clrsetbits_le32(®s->ch_cfg, SPI_CH_HS_EN, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_CH_RST); + setbits_le32(®s->ch_cfg, SPI_TX_CH_ON | SPI_RX_CH_ON); +} + +/** + * Initialize the spi base registers, set the required clock frequency and + * initialize the gpios + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + * @return zero on success else a negative value + */ +int spi_claim_bus(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + struct exynos_spi *regs = spi_slave->regs; + u32 reg = 0; + int ret; + + ret = set_spi_clk(spi_slave->periph_id, + spi_slave->freq); + if (ret < 0) { + debug("%s: Failed to setup spi clock\n", __func__); + return ret; + } + + exynos_pinmux_config(spi_slave->periph_id, PINMUX_FLAG_NONE); + + spi_flush_fifo(slave); + + reg = readl(®s->ch_cfg); + reg &= ~(SPI_CH_CPHA_B | SPI_CH_CPOL_L); + + if (spi_slave->mode & SPI_CPHA) + reg |= SPI_CH_CPHA_B; + + if (spi_slave->mode & SPI_CPOL) + reg |= SPI_CH_CPOL_L; + + writel(reg, ®s->ch_cfg); + writel(SPI_FB_DELAY_180, ®s->fb_clk); + + return 0; +} + +/** + * Reset the spi H/W and flush the tx and rx fifos + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_release_bus(struct spi_slave *slave) +{ + spi_flush_fifo(slave); +} + +static void spi_get_fifo_levels(struct exynos_spi *regs, + int *rx_lvl, int *tx_lvl) +{ + uint32_t spi_sts = readl(®s->spi_sts); + + *rx_lvl = (spi_sts >> SPI_RX_LVL_OFFSET) & SPI_FIFO_LVL_MASK; + *tx_lvl = (spi_sts >> SPI_TX_LVL_OFFSET) & SPI_FIFO_LVL_MASK; +} + +/** + * If there's something to transfer, do a software reset and set a + * transaction size. + * + * @param regs SPI peripheral registers + * @param count Number of bytes to transfer + */ +static void spi_request_bytes(struct exynos_spi *regs, int count) +{ + assert(count && count < (1 << 16)); + setbits_le32(®s->ch_cfg, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_CH_RST); + writel(count | SPI_PACKET_CNT_EN, ®s->pkt_cnt); +} + +static void spi_rx_tx(struct exynos_spi_slave *spi_slave, int todo, + void **dinp, void const **doutp) +{ + struct exynos_spi *regs = spi_slave->regs; + uchar *rxp = *dinp; + const uchar *txp = *doutp; + int rx_lvl, tx_lvl; + uint out_bytes, in_bytes; + + out_bytes = in_bytes = todo; + + /* + * If there's something to send, do a software reset and set a + * transaction size. + */ + spi_request_bytes(regs, todo); + + /* + * Bytes are transmitted/received in pairs. Wait to receive all the + * data because then transmission will be done as well. + */ + while (in_bytes) { + int temp; + + /* Keep the fifos full/empty. */ + spi_get_fifo_levels(regs, &rx_lvl, &tx_lvl); + if (tx_lvl < spi_slave->fifo_size && out_bytes) { + temp = txp ? *txp++ : 0xff; + writel(temp, ®s->tx_data); + out_bytes--; + } + if (rx_lvl > 0 && in_bytes) { + temp = readl(®s->rx_data); + if (rxp) + *rxp++ = temp; + in_bytes--; + } + } + *dinp = rxp; + *doutp = txp; +} + +/** + * Transfer and receive data + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + * @param bitlen No of bits to tranfer or receive + * @param dout Pointer to transfer buffer + * @param din Pointer to receive buffer + * @param flags Flags for transfer begin and end + * @return zero on success else a negative value + */ +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, + void *din, unsigned long flags) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + int upto, todo; + int bytelen; + + /* spi core configured to do 8 bit transfers */ + if (bitlen % 8) { + debug("Non byte aligned SPI transfer.\n"); + return -1; + } + + /* Start the transaction, if necessary. */ + if ((flags & SPI_XFER_BEGIN)) + spi_cs_activate(slave); + + /* Exynos SPI limits each transfer to 65535 bytes */ + bytelen = bitlen / 8; + for (upto = 0; upto < bytelen; upto += todo) { + todo = min(bytelen - upto, (1 << 16) - 1); + spi_rx_tx(spi_slave, todo, &din, &dout); + } + + /* Stop the transaction, if necessary. */ + if ((flags & SPI_XFER_END)) + spi_cs_deactivate(slave); + + return 0; +} + +/** + * Validates the bus and chip select numbers + * + * @param bus ID of the bus that the slave is attached to + * @param cs ID of the chip select connected to the slave + * @return one on success else zero + */ +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ + return spi_get_bus(bus) && cs == 0; +} + +/** + * Activate the CS by driving it LOW + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_cs_activate(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + + clrbits_le32(&spi_slave->regs->cs_reg, SPI_SLAVE_SIG_INACT); + debug("Activate CS, bus %d\n", spi_slave->slave.bus); +} + +/** + * Deactivate the CS by driving it HIGH + * + * @param slave Pointer to spi_slave to which controller has to + * communicate with + */ +void spi_cs_deactivate(struct spi_slave *slave) +{ + struct exynos_spi_slave *spi_slave = to_exynos_spi(slave); + + setbits_le32(&spi_slave->regs->cs_reg, SPI_SLAVE_SIG_INACT); + debug("Deactivate CS, bus %d\n", spi_slave->slave.bus); +} + +static inline struct exynos_spi *get_spi_base(int dev_index) +{ + if (dev_index < 3) + return (struct exynos_spi *)samsung_get_base_spi() + dev_index; + else + return (struct exynos_spi *)samsung_get_base_spi_isp() + + (dev_index - 3); +} + +/* Sadly there is no error return from this function */ +void spi_init(void) +{ + int i; + struct spi_bus *bus; + + for (i = 0; i < EXYNOS5_SPI_NUM_CONTROLLERS; i++) { + bus = &spi_bus[i]; + bus->regs = get_spi_base(i); + bus->periph_id = PERIPH_ID_SPI0 + i; + + /* Although Exynos5 supports upto 50Mhz speed, + * we are setting it to 10Mhz for safe side + */ + bus->frequency = 10000000; + bus->inited = 1; + } +} diff --git a/drivers/video/Makefile b/drivers/video/Makefile index ebb6da823c..b3207c83c3 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -37,6 +37,7 @@ COBJS-$(CONFIG_EXYNOS_PWM_BL) += exynos_pwm_bl.o COBJS-$(CONFIG_FSL_DIU_FB) += fsl_diu_fb.o videomodes.o COBJS-$(CONFIG_S6E8AX0) += s6e8ax0.o COBJS-$(CONFIG_S6E63D6) += s6e63d6.o +COBJS-$(CONFIG_LD9040) += ld9040.o COBJS-$(CONFIG_SED156X) += sed156x.o COBJS-$(CONFIG_VIDEO_AMBA) += amba.o COBJS-$(CONFIG_VIDEO_CT69000) += ct69000.o videomodes.o @@ -49,6 +50,7 @@ COBJS-$(CONFIG_VIDEO_OMAP3) += omap3_dss.o COBJS-$(CONFIG_VIDEO_SED13806) += sed13806.o COBJS-$(CONFIG_VIDEO_SM501) += sm501.o COBJS-$(CONFIG_VIDEO_SMI_LYNXEM) += smiLynxEM.o videomodes.o +COBJS-$(CONFIG_VIDEO_TEGRA) += tegra.o COBJS-$(CONFIG_VIDEO_VCXK) += bus_vcxk.o COBJS := $(sort $(COBJS-y)) diff --git a/drivers/video/exynos_fb.c b/drivers/video/exynos_fb.c index e31a0fd500..d9a3f9ab18 100644 --- a/drivers/video/exynos_fb.c +++ b/drivers/video/exynos_fb.c @@ -70,8 +70,19 @@ static void draw_logo(void) int x, y; ulong addr; - x = ((panel_width - panel_info.logo_width) >> 1); - y = ((panel_height - panel_info.logo_height) >> 1) - 4; + if (panel_width >= panel_info.logo_width) { + x = ((panel_width - panel_info.logo_width) >> 1); + } else { + x = 0; + printf("Warning: image width is bigger than display width\n"); + } + + if (panel_height >= panel_info.logo_height) { + y = ((panel_height - panel_info.logo_height) >> 1) - 4; + } else { + y = 0; + printf("Warning: image height is bigger than display height\n"); + } addr = panel_info.logo_addr; bmp_display(addr, x, y); diff --git a/drivers/video/ld9040.c b/drivers/video/ld9040.c new file mode 100644 index 0000000000..c01ae12bbc --- /dev/null +++ b/drivers/video/ld9040.c @@ -0,0 +1,144 @@ +/* + * ld9040 AMOLED LCD panel driver. + * + * Copyright (C) 2012 Samsung Electronics + * Donghwa Lee <dh09.lee@samsung.com> + * + * 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 <common.h> +#include <spi.h> + +static const unsigned char SEQ_SWRESET[] = { + 0x01, +}; + +static const unsigned char SEQ_USER_SETTING[] = { + 0xF0, 0x5A, 0x5A +}; + +static const unsigned char SEQ_ELVSS_ON[] = { + 0xB1, 0x0D, 0x00, 0x16, +}; + +static const unsigned char SEQ_TEMP_SWIRE[] = { + 0xB2, 0x06, 0x06, 0x06, 0x06, +}; + +static const unsigned char SEQ_GTCON[] = { + 0xF7, 0x09, 0x00, 0x00, +}; + +static const unsigned char SEQ_PANEL_CONDITION[] = { + 0xF8, 0x05, 0x65, 0x96, 0x71, 0x7D, 0x19, 0x3B, + 0x0D, 0x19, 0x7E, 0x0D, 0xE2, 0x00, 0x00, 0x7E, + 0x7D, 0x07, 0x07, 0x20, 0x20, 0x20, 0x02, 0x02, +}; + +static const unsigned char SEQ_GAMMA_SET1[] = { + 0xF9, 0x00, 0xA7, 0xB4, 0xAE, 0xBF, 0x00, 0x91, + 0x00, 0xB2, 0xB4, 0xAA, 0xBB, 0x00, 0xAC, 0x00, + 0xB3, 0xB1, 0xAA, 0xBC, 0x00, 0xB3, +}; + +static const unsigned char SEQ_GAMMA_CTRL[] = { + 0xFB, 0x02, 0x5A, +}; + +static const unsigned char SEQ_APON[] = { + 0xF3, 0x00, 0x00, 0x00, 0x0A, 0x02, +}; + +static const unsigned char SEQ_DISPCTL[] = { + 0xF2, 0x02, 0x08, 0x08, 0x10, 0x10, +}; + +static const unsigned char SEQ_MANPWR[] = { + 0xB0, 0x04, +}; + +static const unsigned char SEQ_PWR_CTRL[] = { + 0xF4, 0x0A, 0x87, 0x25, 0x6A, 0x44, 0x02, 0x88, +}; + +static const unsigned char SEQ_SLPOUT[] = { + 0x11, +}; + +static const unsigned char SEQ_SLPIN[] = { + 0x10, +}; + +static const unsigned char SEQ_DISPON[] = { + 0x29, +}; + +static const unsigned char SEQ_DISPOFF[] = { + 0x28, +}; + +static void ld9040_spi_write(const unsigned char *wbuf, unsigned int size_cmd) +{ + int i = 0; + + /* + * Data are transmitted in 9-bit words: + * the first bit is command/parameter, the other are the value. + * The value's LSB is shifted to MSB position, to be sent as 9th bit + */ + + unsigned int data_out = 0, data_in = 0; + for (i = 0; i < size_cmd; i++) { + data_out = wbuf[i] >> 1; + if (i != 0) + data_out += 0x0080; + if (wbuf[i] & 0x01) + data_out += 0x8000; + spi_xfer(NULL, 9, &data_out, &data_in, SPI_XFER_BEGIN); + } +} + +void ld9040_cfg_ldo(void) +{ + udelay(10); + + ld9040_spi_write(SEQ_USER_SETTING, + ARRAY_SIZE(SEQ_USER_SETTING)); + ld9040_spi_write(SEQ_PANEL_CONDITION, + ARRAY_SIZE(SEQ_PANEL_CONDITION)); + ld9040_spi_write(SEQ_DISPCTL, ARRAY_SIZE(SEQ_DISPCTL)); + ld9040_spi_write(SEQ_MANPWR, ARRAY_SIZE(SEQ_MANPWR)); + ld9040_spi_write(SEQ_PWR_CTRL, ARRAY_SIZE(SEQ_PWR_CTRL)); + ld9040_spi_write(SEQ_ELVSS_ON, ARRAY_SIZE(SEQ_ELVSS_ON)); + ld9040_spi_write(SEQ_GTCON, ARRAY_SIZE(SEQ_GTCON)); + ld9040_spi_write(SEQ_GAMMA_SET1, ARRAY_SIZE(SEQ_GAMMA_SET1)); + ld9040_spi_write(SEQ_GAMMA_CTRL, ARRAY_SIZE(SEQ_GAMMA_CTRL)); + ld9040_spi_write(SEQ_SLPOUT, ARRAY_SIZE(SEQ_SLPOUT)); + + udelay(120); +} + +void ld9040_enable_ldo(unsigned int onoff) +{ + if (onoff) + ld9040_spi_write(SEQ_DISPON, ARRAY_SIZE(SEQ_DISPON)); + else + ld9040_spi_write(SEQ_DISPOFF, ARRAY_SIZE(SEQ_DISPOFF)); +} diff --git a/drivers/video/tegra.c b/drivers/video/tegra.c new file mode 100644 index 0000000000..750a283438 --- /dev/null +++ b/drivers/video/tegra.c @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * 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 <common.h> +#include <fdtdec.h> +#include <lcd.h> + +#include <asm/system.h> +#include <asm/gpio.h> + +#include <asm/arch/clock.h> +#include <asm/arch/funcmux.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/pwm.h> +#include <asm/arch/display.h> +#include <asm/arch-tegra/timer.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* These are the stages we go throuh in enabling the LCD */ +enum stage_t { + STAGE_START, + STAGE_PANEL_VDD, + STAGE_LVDS, + STAGE_BACKLIGHT_VDD, + STAGE_PWM, + STAGE_BACKLIGHT_EN, + STAGE_DONE, +}; + +static enum stage_t stage; /* Current stage we are at */ +static unsigned long timer_next; /* Time we can move onto next stage */ + +/* Our LCD config, set up in handle_stage() */ +static struct fdt_panel_config config; +struct fdt_disp_config *disp_config; /* Display controller config */ + +enum { + /* Maximum LCD size we support */ + LCD_MAX_WIDTH = 1366, + LCD_MAX_HEIGHT = 768, + LCD_MAX_LOG2_BPP = 4, /* 2^4 = 16 bpp */ +}; + +int lcd_line_length; +int lcd_color_fg; +int lcd_color_bg; + +void *lcd_base; /* Start of framebuffer memory */ +void *lcd_console_address; /* Start of console buffer */ + +short console_col; +short console_row; + +vidinfo_t panel_info = { + /* Insert a value here so that we don't end up in the BSS */ + .vl_col = -1, +}; + +char lcd_cursor_enabled; + +ushort lcd_cursor_width; +ushort lcd_cursor_height; + +#ifndef CONFIG_OF_CONTROL +#error "You must enable CONFIG_OF_CONTROL to get Tegra LCD support" +#endif + +void lcd_cursor_size(ushort width, ushort height) +{ + lcd_cursor_width = width; + lcd_cursor_height = height; +} + +void lcd_toggle_cursor(void) +{ + ushort x, y; + uchar *dest; + ushort row; + + x = console_col * lcd_cursor_width; + y = console_row * lcd_cursor_height; + dest = (uchar *)(lcd_base + y * lcd_line_length + x * (1 << LCD_BPP) / + 8); + + for (row = 0; row < lcd_cursor_height; ++row, dest += lcd_line_length) { + ushort *d = (ushort *)dest; + ushort color; + int i; + + for (i = 0; i < lcd_cursor_width; ++i) { + color = *d; + color ^= lcd_color_fg; + *d = color; + ++d; + } + } +} + +void lcd_cursor_on(void) +{ + lcd_cursor_enabled = 1; + lcd_toggle_cursor(); +} +void lcd_cursor_off(void) +{ + lcd_cursor_enabled = 0; + lcd_toggle_cursor(); +} + +char lcd_is_cursor_enabled(void) +{ + return lcd_cursor_enabled; +} + +static void update_panel_size(struct fdt_disp_config *config) +{ + panel_info.vl_col = config->width; + panel_info.vl_row = config->height; + panel_info.vl_bpix = config->log2_bpp; +} + +/* + * Main init function called by lcd driver. + * Inits and then prints test pattern if required. + */ + +void lcd_ctrl_init(void *lcdbase) +{ + int line_length, size; + int type = DCACHE_OFF; + + assert(disp_config); + + lcd_base = (void *)disp_config->frame_buffer; + + /* Make sure that we can acommodate the selected LCD */ + assert(disp_config->width <= LCD_MAX_WIDTH); + assert(disp_config->height <= LCD_MAX_HEIGHT); + assert(disp_config->log2_bpp <= LCD_MAX_LOG2_BPP); + if (disp_config->width <= LCD_MAX_WIDTH + && disp_config->height <= LCD_MAX_HEIGHT + && disp_config->log2_bpp <= LCD_MAX_LOG2_BPP) + update_panel_size(disp_config); + size = lcd_get_size(&line_length); + + /* Set up the LCD caching as requested */ + if (config.cache_type & FDT_LCD_CACHE_WRITE_THROUGH) + type = DCACHE_WRITETHROUGH; + else if (config.cache_type & FDT_LCD_CACHE_WRITE_BACK) + type = DCACHE_WRITEBACK; + mmu_set_region_dcache_behaviour(disp_config->frame_buffer, size, type); + + /* Enable flushing after LCD writes if requested */ + lcd_set_flush_dcache(config.cache_type & FDT_LCD_CACHE_FLUSH); + + debug("LCD frame buffer at %p\n", lcd_base); +} + +ulong calc_fbsize(void) +{ + return (panel_info.vl_col * panel_info.vl_row * + NBITS(panel_info.vl_bpix)) / 8; +} + +void lcd_setcolreg(ushort regno, ushort red, ushort green, ushort blue) +{ +} + +void tegra_lcd_early_init(const void *blob) +{ + /* + * Go with the maximum size for now. We will fix this up after + * relocation. These values are only used for memory alocation. + */ + panel_info.vl_col = LCD_MAX_WIDTH; + panel_info.vl_row = LCD_MAX_HEIGHT; + panel_info.vl_bpix = LCD_MAX_LOG2_BPP; +} + +/** + * Decode the panel information from the fdt. + * + * @param blob fdt blob + * @param config structure to store fdt config into + * @return 0 if ok, -ve on error + */ +static int fdt_decode_lcd(const void *blob, struct fdt_panel_config *config) +{ + int display_node; + + disp_config = tegra_display_get_config(); + if (!disp_config) { + debug("%s: Display controller is not configured\n", __func__); + return -1; + } + display_node = disp_config->panel_node; + if (display_node < 0) { + debug("%s: No panel configuration available\n", __func__); + return -1; + } + + config->pwm_channel = pwm_request(blob, display_node, "nvidia,pwm"); + if (config->pwm_channel < 0) { + debug("%s: Unable to request PWM channel\n", __func__); + return -1; + } + + config->cache_type = fdtdec_get_int(blob, display_node, + "nvidia,cache-type", + FDT_LCD_CACHE_WRITE_BACK_FLUSH); + + /* These GPIOs are all optional */ + fdtdec_decode_gpio(blob, display_node, "nvidia,backlight-enable-gpios", + &config->backlight_en); + fdtdec_decode_gpio(blob, display_node, "nvidia,lvds-shutdown-gpios", + &config->lvds_shutdown); + fdtdec_decode_gpio(blob, display_node, "nvidia,backlight-vdd-gpios", + &config->backlight_vdd); + fdtdec_decode_gpio(blob, display_node, "nvidia,panel-vdd-gpios", + &config->panel_vdd); + + return fdtdec_get_int_array(blob, display_node, "nvidia,panel-timings", + config->panel_timings, FDT_LCD_TIMINGS); +} + +/** + * Handle the next stage of device init + */ +static int handle_stage(const void *blob) +{ + debug("%s: stage %d\n", __func__, stage); + + /* do the things for this stage */ + switch (stage) { + case STAGE_START: + /* Initialize the Tegra display controller */ + if (tegra_display_probe(gd->fdt_blob, (void *)gd->fb_base)) { + printf("%s: Failed to probe display driver\n", + __func__); + return -1; + } + + /* get panel details */ + if (fdt_decode_lcd(blob, &config)) { + printf("No valid LCD information in device tree\n"); + return -1; + } + + /* + * It is possible that the FDT has requested that the LCD be + * disabled. We currently don't support this. It would require + * changes to U-Boot LCD subsystem to have LCD support + * compiled in but not used. An easier option might be to + * still have a frame buffer, but leave the backlight off and + * remove all mention of lcd in the stdout environment + * variable. + */ + + funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT); + + fdtdec_setup_gpio(&config.panel_vdd); + fdtdec_setup_gpio(&config.lvds_shutdown); + fdtdec_setup_gpio(&config.backlight_vdd); + fdtdec_setup_gpio(&config.backlight_en); + + /* + * TODO: If fdt includes output flag we can omit this code + * since fdtdec_setup_gpio will do it for us. + */ + if (fdt_gpio_isvalid(&config.panel_vdd)) + gpio_direction_output(config.panel_vdd.gpio, 0); + if (fdt_gpio_isvalid(&config.lvds_shutdown)) + gpio_direction_output(config.lvds_shutdown.gpio, 0); + if (fdt_gpio_isvalid(&config.backlight_vdd)) + gpio_direction_output(config.backlight_vdd.gpio, 0); + if (fdt_gpio_isvalid(&config.backlight_en)) + gpio_direction_output(config.backlight_en.gpio, 0); + break; + case STAGE_PANEL_VDD: + if (fdt_gpio_isvalid(&config.panel_vdd)) + gpio_direction_output(config.panel_vdd.gpio, 1); + break; + case STAGE_LVDS: + if (fdt_gpio_isvalid(&config.lvds_shutdown)) + gpio_set_value(config.lvds_shutdown.gpio, 1); + break; + case STAGE_BACKLIGHT_VDD: + if (fdt_gpio_isvalid(&config.backlight_vdd)) + gpio_set_value(config.backlight_vdd.gpio, 1); + break; + case STAGE_PWM: + /* Enable PWM at 15/16 high, 32768 Hz with divider 1 */ + pinmux_set_func(PINGRP_GPU, PMUX_FUNC_PWM); + pinmux_tristate_disable(PINGRP_GPU); + + pwm_enable(config.pwm_channel, 32768, 0xdf, 1); + break; + case STAGE_BACKLIGHT_EN: + if (fdt_gpio_isvalid(&config.backlight_en)) + gpio_set_value(config.backlight_en.gpio, 1); + break; + case STAGE_DONE: + break; + } + + /* set up timer for next stage */ + timer_next = timer_get_us(); + if (stage < FDT_LCD_TIMINGS) + timer_next += config.panel_timings[stage] * 1000; + + /* move to next stage */ + stage++; + return 0; +} + +int tegra_lcd_check_next_stage(const void *blob, int wait) +{ + if (stage == STAGE_DONE) + return 0; + + do { + /* wait if we need to */ + debug("%s: stage %d\n", __func__, stage); + if (stage != STAGE_START) { + int delay = timer_next - timer_get_us(); + + if (delay > 0) { + if (wait) + udelay(delay); + else + return 0; + } + } + + if (handle_stage(blob)) + return -1; + } while (wait && stage != STAGE_DONE); + if (stage == STAGE_DONE) + debug("%s: LCD init complete\n", __func__); + + return 0; +} + +void lcd_enable(void) +{ + /* + * Backlight and power init will be done separately in + * tegra_lcd_check_next_stage(), which should be called in + * board_late_init(). + * + * U-Boot code supports only colour depth, selected at compile time. + * The device tree setting should match this. Otherwise the display + * will not look right, and U-Boot may crash. + */ + if (disp_config->log2_bpp != LCD_BPP) { + printf("%s: Error: LCD depth configured in FDT (%d = %dbpp)" + " must match setting of LCD_BPP (%d)\n", __func__, + disp_config->log2_bpp, disp_config->bpp, LCD_BPP); + } +} diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 923acb9f3a..bc09123910 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libwatchdog.o COBJS-$(CONFIG_AT91SAM9_WATCHDOG) += at91sam9_wdt.o COBJS-$(CONFIG_FTWDT010_WATCHDOG) += ftwdt010_wdt.o COBJS-$(CONFIG_TNETV107X_WATCHDOG) += tnetv107x_wdt.o +COBJS-$(CONFIG_S5P) += s5p_wdt.o COBJS := $(COBJS-y) SRCS := $(COBJS:.o=.c) diff --git a/drivers/watchdog/s5p_wdt.c b/drivers/watchdog/s5p_wdt.c new file mode 100644 index 0000000000..94acc1e4ba --- /dev/null +++ b/drivers/watchdog/s5p_wdt.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 Samsung Electronics + * Minkyu Kang <mk7.kang@samsung.com> + * + * 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 <common.h> +#include <asm/io.h> +#include <asm/arch/watchdog.h> + +#define PRESCALER_VAL 255 + +void wdt_stop(void) +{ + struct s5p_watchdog *wdt = + (struct s5p_watchdog *)samsung_get_base_watchdog(); + unsigned int wtcon; + + wtcon = readl(&wdt->wtcon); + wtcon &= ~(WTCON_EN | WTCON_INT | WTCON_RESET); + + writel(wtcon, &wdt->wtcon); +} + +void wdt_start(unsigned int timeout) +{ + struct s5p_watchdog *wdt = + (struct s5p_watchdog *)samsung_get_base_watchdog(); + unsigned int wtcon; + + wdt_stop(); + + wtcon = readl(&wdt->wtcon); + wtcon |= (WTCON_EN | WTCON_CLK(WTCON_CLK_128)); + wtcon &= ~WTCON_INT; + wtcon |= WTCON_RESET; + wtcon |= WTCON_PRESCALER(PRESCALER_VAL); + + writel(timeout, &wdt->wtdat); + writel(timeout, &wdt->wtcnt); + writel(wtcon, &wdt->wtcon); +} |