diff --git a/arch/arm/boot/dts/at91sam9260.dtsi b/arch/arm/boot/dts/at91sam9260.dtsi index c0e0eae16a279f..e0ee5fcefbafda 100644 --- a/arch/arm/boot/dts/at91sam9260.dtsi +++ b/arch/arm/boot/dts/at91sam9260.dtsi @@ -435,6 +435,14 @@ }; }; + i2c0 { + pinctrl_i2c0: i2c0-0 { + atmel,pins = + <0 23 0x1 0x0 /* PA23 periph A I2C0 data */ + 0 24 0x1 0x0>; /* PA24 periph A I2C0 clock */ + }; + }; + pioA: gpio@fffff400 { compatible = "atmel,at91rm9200-gpio"; reg = <0xfffff400 0x200>; @@ -563,6 +571,8 @@ interrupts = <11 IRQ_TYPE_LEVEL_HIGH 6>; #address-cells = <1>; #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c0>; status = "disabled"; }; diff --git a/arch/arm/boot/dts/at91sam9x5.dtsi b/arch/arm/boot/dts/at91sam9x5.dtsi index 6b113fb72af1c8..60cc64a4f91f51 100644 --- a/arch/arm/boot/dts/at91sam9x5.dtsi +++ b/arch/arm/boot/dts/at91sam9x5.dtsi @@ -26,6 +26,7 @@ serial1 = &usart0; serial2 = &usart1; serial3 = &usart2; + serial4 = &usart3; gpio0 = &pioA; gpio1 = &pioB; gpio2 = &pioC; @@ -277,6 +278,11 @@ reg = <7>; }; + usart3_clk: usart3_clk { + #clock-cells = <0>; + reg = <8>; + }; + twi0_clk: twi0_clk { reg = <9>; #clock-cells = <0>; @@ -570,6 +576,29 @@ }; }; + usart3 { + pinctrl_usart3: usart3-0 { + atmel,pins = + ; /* PC23 periph B */ + }; + + pinctrl_usart3_rts: usart3_rts-0 { + atmel,pins = + ; /* PC24 periph B */ + }; + + pinctrl_usart3_cts: usart3_cts-0 { + atmel,pins = + ; /* PC25 periph B */ + }; + + pinctrl_usart3_sck: usart3_sck-0 { + atmel,pins = + ; /* PC26 periph B */ + }; + }; + uart0 { pinctrl_uart0: uart0-0 { atmel,pins = @@ -944,6 +973,9 @@ reg = <0xfffff200 0x200>; interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>; pinctrl-names = "default"; + dmas = <&dma1 1 0x8>, + <&dma1 1 0x209>; + dma-names = "tx", "rx"; pinctrl-0 = <&pinctrl_dbgu>; clocks = <&mck>; clock-names = "usart"; @@ -954,6 +986,9 @@ compatible = "atmel,at91sam9260-usart"; reg = <0xf801c000 0x200>; interrupts = <5 IRQ_TYPE_LEVEL_HIGH 5>; + dmas = <&dma0 1 0x3>, + <&dma0 1 0x204>; + dma-names = "tx", "rx"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usart0>; clocks = <&usart0_clk>; @@ -965,6 +1000,9 @@ compatible = "atmel,at91sam9260-usart"; reg = <0xf8020000 0x200>; interrupts = <6 IRQ_TYPE_LEVEL_HIGH 5>; + dmas = <&dma0 1 0x5>, + <&dma0 1 0x204>; + dma-names = "tx", "rx"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usart1>; clocks = <&usart1_clk>; @@ -976,6 +1014,9 @@ compatible = "atmel,at91sam9260-usart"; reg = <0xf8024000 0x200>; interrupts = <7 IRQ_TYPE_LEVEL_HIGH 5>; + dmas = <&dma1 1 0xc>, + <&dma1 1 0x20d>; + dma-names = "tx", "rx"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usart2>; clocks = <&usart2_clk>; @@ -983,6 +1024,20 @@ status = "disabled"; }; + usart3: serial@f8028000 { + compatible = "atmel,at91sam9260-usart"; + reg = <0xf8028000 0x200>; + interrupts = <8 4 5>; + dmas = <&dma1 1 0xe>, + <&dma1 1 0x20f>; + dma-names = "tx", "rx"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usart3>; + clocks = <&usart3_clk>; + clock-names = "usart"; + status = "disabled"; + }; + i2c0: i2c@f8010000 { compatible = "atmel,at91sam9x5-i2c"; reg = <0xf8010000 0x100>; diff --git a/arch/arm/boot/dts/sama5d35ek.dts b/arch/arm/boot/dts/sama5d35ek.dts index 9089c7c6cea863..d58d2f4998e9ba 100644 --- a/arch/arm/boot/dts/sama5d35ek.dts +++ b/arch/arm/boot/dts/sama5d35ek.dts @@ -20,6 +20,10 @@ status = "okay"; }; + ssc0: ssc@f0008000 { + status = "okay"; + }; + can0: can@f000c000 { status = "okay"; }; diff --git a/arch/arm/boot/dts/sama5d3xmb.dtsi b/arch/arm/boot/dts/sama5d3xmb.dtsi index b8c6f20e780c99..3bfc9b0666df82 100644 --- a/arch/arm/boot/dts/sama5d3xmb.dtsi +++ b/arch/arm/boot/dts/sama5d3xmb.dtsi @@ -43,10 +43,11 @@ */ i2c0: i2c@f0014000 { wm8904: wm8904@1a { - compatible = "wm8904"; + compatible = "wlf,wm8904"; reg = <0x1a>; clocks = <&pck0>; clock-names = "mclk"; + status = "okay"; }; }; diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c index 597fed423d7d31..55622f544adee1 100644 --- a/drivers/clk/at91/clk-peripheral.c +++ b/drivers/clk/at91/clk-peripheral.c @@ -151,6 +151,14 @@ static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph) if (parent_rate >> shift <= periph->range.max) break; } + + if (shift == PERIPHERAL_MAX_SHIFT) { + shift--; + printk(KERN_WARNING "clk: setting frequency of clock " + "per_id=%d to value %lu above upper limit %lu\n", + periph->id, parent_rate >> shift, + periph->range.max); + } } periph->auto_div = false; diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index 079e6b1b0cdb6f..4feb78614b777c 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -1250,13 +1250,12 @@ static int atmel_spi_transfer_one_message(struct spi_master *master, static void atmel_spi_cleanup(struct spi_device *spi) { struct atmel_spi_device *asd = spi->controller_state; - unsigned gpio = (unsigned) spi->controller_data; if (!asd) return; spi->controller_state = NULL; - gpio_free(gpio); + gpio_free(asd->npcs_pin); kfree(asd); } diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 53eeea13ff165f..482d180ae41f3e 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -825,40 +825,57 @@ static void atmel_rx_from_dma(struct uart_port *port) struct dma_chan *chan = atmel_port->chan_rx; struct dma_tx_state state; enum dma_status dmastat; - size_t pending, count; + size_t pending, count, sg_len; /* Reset the UART timeout early so that we don't miss one */ UART_PUT_CR(port, ATMEL_US_STTTO); - dmastat = dmaengine_tx_status(chan, - atmel_port->cookie_rx, - &state); - /* Restart a new tasklet if DMA status is error */ - if (dmastat == DMA_ERROR) { - dev_dbg(port->dev, "Get residue error, restart tasklet\n"); - UART_PUT_IER(port, ATMEL_US_TIMEOUT); - tasklet_schedule(&atmel_port->tasklet); - return; - } - /* current transfer size should no larger than dma buffer */ - pending = sg_dma_len(&atmel_port->sg_rx) - state.residue; - BUG_ON(pending > sg_dma_len(&atmel_port->sg_rx)); - /* - * This will take the chars we have so far, - * ring->head will record the transfer size, only new bytes come - * will insert into the framework. + sg_len = sg_dma_len(&atmel_port->sg_rx); + + /* Loop untill all data are transferred from the ring buffer. + * dmaengine_tx_status() calculates residue taking in account only + * state of oldest sg buffer, so we should retry in case there is data + * in the new one. */ - if (pending > ring->head) { - count = pending - ring->head; + while (1) { + dmastat = dmaengine_tx_status(chan, + atmel_port->cookie_rx, + &state); + /* Restart a new tasklet if DMA status is error */ + if (dmastat == DMA_ERROR) { + dev_dbg(port->dev, + "Get residue error, restart tasklet\n"); + UART_PUT_IER(port, ATMEL_US_TIMEOUT); + tasklet_schedule(&atmel_port->tasklet); + return; + } + /* current transfer size should no larger than dma buffer */ + pending = sg_len - state.residue; + BUG_ON(pending > sg_len); - atmel_flip_buffer_rx_dma(port, ring->buf + ring->head, count); + /* DMA has filled ring buffer up to `pending`, and data + * up to `ring->head` was already transferred to TTY layer. + * + * Sometimes we have already rewound ring->head to 0, but + * `pending` is still at `sg_len` if no bytes were transferred. + * Second subcondition deals with this case. + */ + if ((pending > ring->head) && + (pending - ring->head != sg_len)) { + count = pending - ring->head; + + atmel_flip_buffer_rx_dma(port, ring->buf + ring->head, + count); - ring->head += count; - if (ring->head == sg_dma_len(&atmel_port->sg_rx)) - ring->head = 0; + ring->head += count; + if (ring->head == sg_len) + ring->head = 0; - port->icount.rx += count; + port->icount.rx += count; + } else { + break; + } } UART_PUT_IER(port, ATMEL_US_TIMEOUT); @@ -874,6 +891,9 @@ static int atmel_prepare_rx_dma(struct uart_port *port) int ret, nent; ring = &atmel_port->rx_ring; + ring->head = 0; + + port = &(atmel_port->uart); dma_cap_zero(mask); dma_cap_set(DMA_CYCLIC, mask); @@ -887,7 +907,7 @@ static int atmel_prepare_rx_dma(struct uart_port *port) spin_lock_init(&atmel_port->lock_rx); sg_init_table(&atmel_port->sg_rx, 1); /* UART circular rx buffer is an aligned page. */ - BUG_ON((int)port->state->xmit.buf & ~PAGE_MASK); + BUG_ON((int)ring->buf & ~PAGE_MASK); sg_set_page(&atmel_port->sg_rx, virt_to_page(ring->buf), ATMEL_SERIAL_RINGSIZE, diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 4789619a52d86d..e81787b435917d 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -61,3 +61,14 @@ config SND_AT91_SOC_AFEB9260 select SND_SOC_TLV320AIC23_I2C help Say Y here to support sound on AFEB9260 board. + +config SND_AT91_SOC_SAMA5D3_WM8904 + tristate "SoC Audio support for WM8904 based SAMA5D3-EK board" + depends on ATMEL_SSC && SND_ATMEL_SOC + select SND_ATMEL_SOC_DMA + select SND_ATMEL_SOC_SSC + select SND_SOC_WM8904 + select SND_SOC_DMAENGINE_PCM + help + Say Y if you want to add support for audio SoC on an + SAMA5D3-EK board which uses WM8904 audio codec. diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index 5baabc8bde3abf..acf60e9058fe8d 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -13,8 +13,11 @@ obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o snd-atmel-soc-wm8904-objs := atmel_wm8904.o snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o +snd-soc-sama5d3-wm8904-objs := sama5d3_wm8904.o obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o +obj-$(CONFIG_SND_AT91_SOC_SAMA5D3_WM8904) += snd-soc-sama5d3-wm8904.o + obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o diff --git a/sound/soc/atmel/sama5d3_wm8904.c b/sound/soc/atmel/sama5d3_wm8904.c new file mode 100644 index 00000000000000..fcdcf4195e4bdf --- /dev/null +++ b/sound/soc/atmel/sama5d3_wm8904.c @@ -0,0 +1,242 @@ +/* + * sama5d3ek_wm8904 - SoC audio for SAMA5D3EK based boards + * which use WM8904 as codec. + * + * Copyright (C) 2012 Atmel + * + * Author: Bo Shen + * Based on sam9g20_wm8731.c by Sedji Gaouaou + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "../codecs/wm8904.h" +#include "atmel_ssc_dai.h" + +#define MCLK_RATE 32768 + +static struct clk *mclk; + +static const struct snd_soc_dapm_widget sama5ek_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static int sama5d3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK, + 32768, params_rate(params) * 256); + if (ret < 0) { + pr_err("%s - Failed to set CODEC PLL.", __func__); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL, + 12000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("%s - Failed to set WM8904 SYSCLK\n", __func__); + return ret; + } + + return 0; +} + +static struct snd_soc_ops sama5d3_soc_ops = { + .hw_params = sama5d3_hw_params, +}; + +int sama5d3ek_snd_suspend_pre(struct snd_soc_card *card) +{ + clk_disable(mclk); + return 0; +} + +int sama5d3ek_snd_resume_pre(struct snd_soc_card *card) +{ + clk_enable(mclk); + return 0; +} + +static struct snd_soc_dai_link sama5d3ek_dai = { + .name = "WM8904", + .stream_name = "WM8904 PCM", + .codec_dai_name = "wm8904-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &sama5d3_soc_ops, +}; + +static struct snd_soc_card snd_soc_sama5d3ek = { + .name = "WM8904 @ SAMA5D3", + .dai_link = &sama5d3ek_dai, + .num_links = 1, + .suspend_pre = sama5d3ek_snd_suspend_pre, + .resume_pre = sama5d3ek_snd_resume_pre, + .dapm_widgets = sama5ek_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sama5ek_dapm_widgets), + .fully_routed = true, +}; + +static int sama5d3ek_wm8904_dt_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &snd_soc_sama5d3ek; + int ret; + + if (!np) + return -1; + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { + dev_err(&pdev->dev, "failed to parse card name\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio routing\n"); + return ret; + } + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "failed to get dai and pcm info\n"); + ret = -EINVAL; + return ret; + } + sama5d3ek_dai.cpu_of_node = cpu_np; + sama5d3ek_dai.platform_of_node = cpu_np; + of_node_put(cpu_np); + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "failed to get codec info\n"); + ret = -EINVAL; + return ret; + } + sama5d3ek_dai.codec_of_node = codec_np; + of_node_put(codec_np); + + return 0; +} + +static int sama5d3ek_wm8904_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_sama5d3ek; + struct clk *clk_src; + struct pinctrl *pinctrl; + int ret; + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + dev_err(&pdev->dev, "failed to request pinctrl\n"); + return PTR_ERR(pinctrl); + } + + ret = atmel_ssc_set_audio(0); + if (ret != 0) { + dev_err(&pdev->dev, "failed to set SSC 0 for audio\n"); + return ret; + } + + card->dev = &pdev->dev; + ret = sama5d3ek_wm8904_dt_init(pdev); + if (ret) { + dev_err(&pdev->dev, "failed to init dt info\n"); + goto err_set_audio; + } + + mclk = clk_get(NULL, "pck0"); + if (IS_ERR(mclk)) { + dev_err(&pdev->dev, "failed to get pck0\n"); + ret = PTR_ERR(mclk); + goto err_set_audio; + } + + clk_src = clk_get(NULL, "clk32k"); + if (IS_ERR(clk_src)) { + dev_err(&pdev->dev, "failed to get clk32k\n"); + ret = PTR_ERR(clk_src); + goto err_set_audio; + } + + ret = clk_set_parent(mclk, clk_src); + clk_put(clk_src); + if (ret != 0) { + dev_err(&pdev->dev, "failed to set MCLK parent\n"); + goto err_set_audio; + } + + dev_info(&pdev->dev, "setting pck0 to %dHz\n", MCLK_RATE); + + clk_set_rate(mclk, MCLK_RATE); + clk_enable(mclk); + + snd_soc_register_card(card); + + return 0; + +err_set_audio: + atmel_ssc_put_audio(0); + return ret; +} + +static int sama5d3ek_wm8904_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + clk_disable(mclk); + snd_soc_unregister_card(card); + atmel_ssc_put_audio(0); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id sama5d3ek_wm8904_dt_ids[] = { + { .compatible = "atmel,sama5d3ek-wm8904", }, + { } +}; +#endif + +static struct platform_driver sama5d3ek_wm8904_driver = { + .driver = { + .name = "sama5d3ek-audio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sama5d3ek_wm8904_dt_ids), + }, + .probe = sama5d3ek_wm8904_probe, + .remove = sama5d3ek_wm8904_remove, +}; + +module_platform_driver(sama5d3ek_wm8904_driver); + +/* Module information */ +MODULE_AUTHOR("Bo Shen "); +MODULE_DESCRIPTION("ALSA SoC machine driver for SAMA5D3 - WM8904"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index afbcce82497327..8372e016686532 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2259,10 +2259,19 @@ static const struct i2c_device_id wm8904_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, wm8904_i2c_id); +static const struct of_device_id wm8904_of_match[] = { + { .compatible = "wlf,wm8904", }, + { .compatible = "wlf,wm8912", }, + { .compatible = "wlf,wm8918", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8904_of_match); + static struct i2c_driver wm8904_i2c_driver = { .driver = { .name = "wm8904", .owner = THIS_MODULE, + .of_match_table = wm8904_of_match, }, .probe = wm8904_i2c_probe, .remove = wm8904_i2c_remove,