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 47f321392..656bf4a06 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 ab7a9e33e..88471d3ed 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 271463cf1..9fac190a6 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 000000000..36f7f4dde --- /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 8fea6a6bf..1fd5592f2 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 f53c2bf00..b590992dc 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 000000000..8fdffb10e --- /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 000000000..9f3117dd9 --- /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 000000000..4c74534c9 --- /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 000000000..293903ada --- /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 000000000..a8f0de18c --- /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 000000000..f455b112f --- /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 f0b82c67f..824d357d9 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 000000000..3e6c18f87 --- /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 ebb6da823..b3207c83c 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 e31a0fd50..d9a3f9ab1 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 000000000..c01ae12bb --- /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 000000000..750a28343 --- /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 923acb9f3..bc0912391 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 000000000..94acc1e4b --- /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); +} |