SOUND: SAMSUNG: Add I2S driver
This patch adds driver for I2S interface specific to samsung. Signed-off-by: R. Chandrasekar <rcsekar@samsung.com> Signed-off-by: Rajeshwari Shinde <rajeshwari.s@samsung.com> Acked-by: Simon Glass <sjg@chromium.org> Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
This commit is contained in:
		
							parent
							
								
									d984b9f89c
								
							
						
					
					
						commit
						511ed5fdd3
					
				
							
								
								
									
										1
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										1
									
								
								Makefile
								
								
								
								
							|  | @ -313,6 +313,7 @@ LIBS-y += arch/powerpc/cpu/mpc8xxx/lib8xxx.o | ||||||
| endif | endif | ||||||
| LIBS-y += drivers/rtc/librtc.o | LIBS-y += drivers/rtc/librtc.o | ||||||
| LIBS-y += drivers/serial/libserial.o | LIBS-y += drivers/serial/libserial.o | ||||||
|  | LIBS-y += drivers/sound/libsound.o | ||||||
| LIBS-$(CONFIG_GENERIC_LPC_TPM) += drivers/tpm/libtpm.o | LIBS-$(CONFIG_GENERIC_LPC_TPM) += drivers/tpm/libtpm.o | ||||||
| LIBS-y += drivers/twserial/libtws.o | LIBS-y += drivers/twserial/libtws.o | ||||||
| LIBS-y += drivers/usb/eth/libusb_eth.o | LIBS-y += drivers/usb/eth/libusb_eth.o | ||||||
|  |  | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | #
 | ||||||
|  | # 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	:= $(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 | ||||||
|  | 
 | ||||||
|  | #
 | ||||||
|  | @ -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; | ||||||
|  | } | ||||||
|  | @ -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; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,127 @@ | ||||||
|  | /*
 | ||||||
|  |  * 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 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #ifndef __I2S_H__ | ||||||
|  | #define __I2S_H__ | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * DAI hardware audio formats. | ||||||
|  |  * | ||||||
|  |  * Describes the physical PCM data formating and clocking. Add new formats | ||||||
|  |  * to the end. | ||||||
|  |  */ | ||||||
|  | #define SND_SOC_DAIFMT_I2S		1 /* I2S mode */ | ||||||
|  | #define SND_SOC_DAIFMT_RIGHT_J		2 /* Right Justified mode */ | ||||||
|  | #define SND_SOC_DAIFMT_LEFT_J		3 /* Left Justified mode */ | ||||||
|  | #define SND_SOC_DAIFMT_DSP_A		4 /* L data MSB after FRM LRC */ | ||||||
|  | #define SND_SOC_DAIFMT_DSP_B		5 /* L data MSB during FRM LRC */ | ||||||
|  | #define SND_SOC_DAIFMT_AC97		6 /* AC97 */ | ||||||
|  | #define SND_SOC_DAIFMT_PDM		7 /* Pulse density modulation */ | ||||||
|  | 
 | ||||||
|  | /* left and right justified also known as MSB and LSB respectively */ | ||||||
|  | #define SND_SOC_DAIFMT_MSB		SND_SOC_DAIFMT_LEFT_J | ||||||
|  | #define SND_SOC_DAIFMT_LSB		SND_SOC_DAIFMT_RIGHT_J | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * DAI hardware signal inversions. | ||||||
|  |  * | ||||||
|  |  * Specifies whether the DAI can also support inverted clocks for the specified | ||||||
|  |  * format. | ||||||
|  |  */ | ||||||
|  | #define SND_SOC_DAIFMT_NB_NF	(1 << 8) /* normal bit clock + frame */ | ||||||
|  | #define SND_SOC_DAIFMT_NB_IF	(2 << 8) /* normal BCLK + inv FRM */ | ||||||
|  | #define SND_SOC_DAIFMT_IB_NF	(3 << 8) /* invert BCLK + nor FRM */ | ||||||
|  | #define SND_SOC_DAIFMT_IB_IF	(4 << 8) /* invert BCLK + FRM */ | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * DAI hardware clock masters. | ||||||
|  |  * | ||||||
|  |  * This is wrt the codec, the inverse is true for the interface | ||||||
|  |  * i.e. if the codec is clk and FRM master then the interface is | ||||||
|  |  * clk and frame slave. | ||||||
|  |  */ | ||||||
|  | #define SND_SOC_DAIFMT_CBM_CFM	(1 << 12) /* codec clk & FRM master */ | ||||||
|  | #define SND_SOC_DAIFMT_CBS_CFM	(2 << 12) /* codec clk slave & FRM master */ | ||||||
|  | #define SND_SOC_DAIFMT_CBM_CFS	(3 << 12) /* codec clk master & frame slave */ | ||||||
|  | #define SND_SOC_DAIFMT_CBS_CFS	(4 << 12) /* codec clk & FRM slave */ | ||||||
|  | 
 | ||||||
|  | #define SND_SOC_DAIFMT_FORMAT_MASK	0x000f | ||||||
|  | #define SND_SOC_DAIFMT_CLOCK_MASK	0x00f0 | ||||||
|  | #define SND_SOC_DAIFMT_INV_MASK		0x0f00 | ||||||
|  | #define SND_SOC_DAIFMT_MASTER_MASK	0xf000 | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Master Clock Directions | ||||||
|  |  */ | ||||||
|  | #define SND_SOC_CLOCK_IN		0 | ||||||
|  | #define SND_SOC_CLOCK_OUT		1 | ||||||
|  | 
 | ||||||
|  | /* I2S Tx Control */ | ||||||
|  | #define I2S_TX_ON	1 | ||||||
|  | #define I2S_TX_OFF	0 | ||||||
|  | 
 | ||||||
|  | #define FIFO_LENGTH	64 | ||||||
|  | 
 | ||||||
|  | /* I2s Registers */ | ||||||
|  | struct i2s_reg { | ||||||
|  | 	unsigned int con;	/* base + 0 , Control register */ | ||||||
|  | 	unsigned int mod;	/* Mode register */ | ||||||
|  | 	unsigned int fic;	/* FIFO control register */ | ||||||
|  | 	unsigned int psr;	/* Reserved */ | ||||||
|  | 	unsigned int txd;	/* Transmit data register */ | ||||||
|  | 	unsigned int rxd;	/* Receive Data Register */ | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* This structure stores the i2s related information */ | ||||||
|  | struct i2stx_info { | ||||||
|  | 	unsigned int rfs;		/* LR clock frame size */ | ||||||
|  | 	unsigned int bfs;		/* Bit slock frame size */ | ||||||
|  | 	unsigned int audio_pll_clk;	/* Audio pll frequency in Hz */ | ||||||
|  | 	unsigned int samplingrate;	/* sampling rate */ | ||||||
|  | 	unsigned int bitspersample;	/* bits per sample */ | ||||||
|  | 	unsigned int channels;		/* audio channels */ | ||||||
|  | 	unsigned int base_address;	/* I2S Register Base */ | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Sends the given data through i2s tx | ||||||
|  |  * | ||||||
|  |  * @param pi2s_tx	pointer of i2s transmitter parameter structure. | ||||||
|  |  * @param data		address of the data buffer | ||||||
|  |  * @param data_size	array size of the int buffer (total size / size of int) | ||||||
|  |  * | ||||||
|  |  * @return		int value 0 for success, -1 in case of error | ||||||
|  |  */ | ||||||
|  | int i2s_transfer_tx_data(struct i2stx_info *pi2s_tx, unsigned *data, | ||||||
|  | 				unsigned long data_size); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Initialise i2s transmiter | ||||||
|  |  * | ||||||
|  |  * @param pi2s_tx	pointer of i2s transmitter parameter structure. | ||||||
|  |  * | ||||||
|  |  * @return		int value 0 for success, -1 in case of error | ||||||
|  |  */ | ||||||
|  | int i2s_tx_init(struct i2stx_info *pi2s_tx); | ||||||
|  | 
 | ||||||
|  | #endif /* __I2S_H__ */ | ||||||
|  | @ -0,0 +1,62 @@ | ||||||
|  | /*
 | ||||||
|  |  * 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 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #ifndef __SOUND_H__ | ||||||
|  | #define __SOUND_H__ | ||||||
|  | 
 | ||||||
|  | /* sound codec enum */ | ||||||
|  | enum en_sound_codec { | ||||||
|  | 	CODEC_WM_8994, | ||||||
|  | 	CODEC_WM_8995, | ||||||
|  | 	CODEC_MAX | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* sound codec enum */ | ||||||
|  | enum sound_compat { | ||||||
|  | 	AUDIO_COMPAT_SPI, | ||||||
|  | 	AUDIO_COMPAT_I2C, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* Codec information structure to store the info from device tree */ | ||||||
|  | struct sound_codec_info { | ||||||
|  | 	int i2c_bus; | ||||||
|  | 	int i2c_dev_addr; | ||||||
|  | 	enum en_sound_codec codec_type; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Initialises audio sub system | ||||||
|  |  * | ||||||
|  |  * @return	int value 0 for success, -1 for error | ||||||
|  |  */ | ||||||
|  | int sound_init(void); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * plays the pcm data buffer in pcm_data.h through i2s1 to make the | ||||||
|  |  * sine wave sound | ||||||
|  |  * | ||||||
|  |  * @return	int 0 for success, -1 for error | ||||||
|  |  */ | ||||||
|  | int sound_play(uint32_t msec, uint32_t frequency); | ||||||
|  | 
 | ||||||
|  | #endif  /* __SOUND__H__ */ | ||||||
		Loading…
	
		Reference in New Issue