Skip to content

Instantly share code, notes, and snippets.

@aventuri

aventuri/Kconfig Secret

Last active August 29, 2015 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aventuri/c40d08b946b0d1da5081 to your computer and use it in GitHub Desktop.
Save aventuri/c40d08b946b0d1da5081 to your computer and use it in GitHub Desktop.
sun7i I2S DAI ASoC module and codec UDA1380 for simple sound card
diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
index 79511ae..fa25182 100644
--- a/sound/soc/sunxi/Kconfig
+++ b/sound/soc/sunxi/Kconfig
@@ -7,4 +7,19 @@ config SND_SUNXI_SOC_CODEC
select REGMAP_MMIO
default y
+config SND_SUNXI_SOC_I2S
+ tristate "APB on-chip sun4i/sun5i/sun7i I2S"
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ default y
+
+config SND_SUNXI_SOC_I2S_UDA1380
+ tristate "Audio support for A20 with UDA1380 codec on I2S DA0"
+ depends on SND_SUNXI_SOC_I2S
+ select CONFIG_SND_SUNXI_SOC_UDA1380
+ help
+ Say Y if you want to add support for ASoC audio on an A20 board
+ with a UDA1380 codec on I2S DA0 interface.
+
+
endmenu
diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
index b8950d3..ae998c6 100644
--- a/sound/soc/sunxi/Makefile
+++ b/sound/soc/sunxi/Makefile
@@ -1,2 +1,10 @@
obj-$(CONFIG_SND_SUNXI_SOC_CODEC) += sunxi-codec.o
+# A20 Machine Support
+# 1. A20 Devel kit with Waveshare UDA1380 codec connected on I2S DA0 for test
+
+# A20 Platform Support (I2S/PCM, HDMI-audio, SPDIF..)
+# 2. versoin: starting from simle-audio-card framework
+snd-soc-sunxi-i2s-objs := sunxi-i2s.o
+obj-$(CONFIG_SND_SUNXI_SOC_I2S) += snd-soc-sunxi-i2s.o
+
/*
* Copyright 2014 - Iain Paton <ipaton0@gmail.com>
*
* This file is dual-licensed: you can use it either under the terms
* of the GPL or the X11 license, at your option. Note that this dual
* licensing only applies to this file, and not this project as a
* whole.
*
* a) This file 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 file 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.
*
* Or, alternatively,
*
* b) Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/dts-v1/;
#include "sun7i-a20.dtsi"
#include "sunxi-common-regulators.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/pinctrl/sun4i-a10.h>
/ {
model = "Olimex A20-SOM with I2S test";
compatible = "olimex,a20-som", "allwinner,sun7i-a20";
aliases {
serial0 = &uart0;
};
chosen {
stdout-path = "serial0:115200n8";
};
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&led_pins_olinuxinolime>;
green {
label = "a20-olinuxino-lime2:green:usr";
gpios = <&pio 7 2 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};
sound {
compatible = "simple-audio-card";
simple-audio-card,format = "i2s";
simple-audio-card,routing =
"Headphone Jack", "VOUTLHP",
"Headphone Jack", "VOUTRHP",
"Line Out Jack", "VOUTL",
"Line Out Jack", "VOUTR",
"VINM", "Mic Jack",
"VINL", "Line In Jack",
"VINR", "Line In Jack";
simple-audio-card,widgets =
"Headphone", "Headphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Microphone", "Mic Jack";
//simple-audio-card,mclk-fs = <256>; // as it's not supported in dai codec driver, see asoc_simple_card_hw_params() in sound/soc/generic/simple-card.c
simple-audio-card,cpu {
sound-dai = <&i2s0>;
};
simple-audio-card,codec {
sound-dai = <&ext_codec>;
};
};
reg_axp_ipsout: axp_ipsout {
compatible = "regulator-fixed";
regulator-name = "axp-ipsout";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
regulator-always-on;
};
};
&ahci {
target-supply = <&reg_ahci_5v>;
status = "okay";
};
&codec {
routing =
"Headphone Jack", "HP Left",
"Headphone Jack", "HP Right";
status = "okay";
};
&i2s0 {
pinctrl-names = "default";
pinctrl-0 = <&i2s0_mclk> , <&i2s0_bclk> , <&i2s0_lrclk> , <&i2s0_sdo0> , <&i2s0_sdi> ;
status = "okay";
};
&ehci0 {
status = "okay";
};
&ehci1 {
status = "okay";
};
&gmac {
pinctrl-names = "default";
pinctrl-0 = <&gmac_pins_rgmii_a>;
phy = <&phy1>;
phy-mode = "rgmii";
status = "okay";
phy1: ethernet-phy@1 {
reg = <1>;
};
};
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins_a>;
status = "okay";
axp209: pmic@34 {
compatible = "x-powers,axp209";
reg = <0x34>;
interrupt-parent = <&nmi_intc>;
interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
interrupt-controller;
#interrupt-cells = <1>;
acin-supply = <&reg_axp_ipsout>;
vin2-supply = <&reg_axp_ipsout>;
vin3-supply = <&reg_axp_ipsout>;
ldo24in-supply = <&reg_axp_ipsout>;
ldo3in-supply = <&reg_axp_ipsout>;
regulators {
vdd_rtc: ldo1 {
regulator-min-microvolt = <1300000>;
regulator-max-microvolt = <1300000>;
regulator-always-on;
};
avcc: ldo2 {
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
vcc_csi0: ldo3 {
regulator-min-microvolt = <700000>;
regulator-max-microvolt = <3500000>;
regulator-always-on;
};
vcc_csi1: ldo4 {
regulator-min-microvolt = <1250000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
vdd_cpu: dcdc2 {
regulator-min-microvolt = <700000>;
regulator-max-microvolt = <2275000>;
regulator-always-on;
};
vdd_int: dcdc3 {
regulator-min-microvolt = <700000>;
regulator-max-microvolt = <3500000>;
regulator-always-on;
};
};
};
};
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins_a>;
status = "okay";
/* added for test with I2S waveshare codec board based on UDA1380 */
ext_codec: uda1380@18 {
#sound-dai-cells = <0>; /* added for dmesg err: "could not get #sound-dai-cells for " */
compatible = "nxp,uda1380";
reg = <0x18>;
/* reset-gpio = <&pio 7 3 GPIO_ACTIVE_HIGH>; */
/*
* commented out so uda1380_probe move forward..
* otherwise get: asoc-simple-card sound: ASoC: failed to instantiate card -16
* power-gpio = <&pio 7 11 GPIO_ACTIVE_HIGH>;
*/
dac-clk = <1>; // SYSCLK=0, WSPLL=1
};
};
&i2c2 {
pinctrl-names = "default";
pinctrl-0 = <&i2c2_pins_a>;
status = "okay";
/* added for test with I2S waveshare codec board based on UDA1380 */
ext_codec2: uda1380@18 {
#sound-dai-cells = <0>; /* added for dmesg err: "could not get #sound-dai-cells for " */
compatible = "nxp,uda1380";
reg = <0x18>;
/* reset-gpio = <&pio 7 3 GPIO_ACTIVE_HIGH>; */
/*
* commented out so uda1380_probe move forward..
* otherwise get: asoc-simple-card sound: ASoC: failed to instantiate card -16
* power-gpio = <&pio 7 11 GPIO_ACTIVE_HIGH>;
*/
dac-clk = <1>; // SYSCLK=0, WSPLL=1
};
};
&mmc0 {
pinctrl-names = "default";
pinctrl-0 = <&mmc0_pins_a>, <&mmc0_cd_pin_reference_design>;
vmmc-supply = <&reg_vcc3v3>;
bus-width = <4>;
cd-gpios = <&pio 7 1 GPIO_ACTIVE_HIGH>; /* PH1 */
cd-inverted;
status = "okay";
};
&ohci0 {
status = "okay";
};
&ohci1 {
status = "okay";
};
&pio {
ahci_pwr_pin_olinuxinolime: ahci_pwr_pin@1 {
allwinner,pins = "PC3";
allwinner,function = "gpio_out";
allwinner,drive = <SUN4I_PINCTRL_10_MA>;
allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
};
led_pins_olinuxinolime: led_pins@0 {
allwinner,pins = "PH2";
allwinner,function = "gpio_out";
allwinner,drive = <SUN4I_PINCTRL_20_MA>;
allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
};
};
&reg_ahci_5v {
pinctrl-0 = <&ahci_pwr_pin_olinuxinolime>;
gpio = <&pio 2 3 GPIO_ACTIVE_HIGH>;
status = "okay";
};
&reg_usb1_vbus {
status = "okay";
};
&reg_usb2_vbus {
status = "okay";
};
&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins_a>;
status = "okay";
};
&usbphy {
usb1_vbus-supply = <&reg_usb1_vbus>;
usb2_vbus-supply = <&reg_usb2_vbus>;
status = "okay";
};
diff --git a/arch/arm/boot/dts/sun7i-a20.dtsi b/arch/arm/boot/dts/sun7i-a20.dtsi
index 7889552..176f305 100644
--- a/arch/arm/boot/dts/sun7i-a20.dtsi
+++ b/arch/arm/boot/dts/sun7i-a20.dtsi
@@ -304,9 +304,9 @@
reg = <0x01c20068 0x4>;
clocks = <&apb0>;
clock-output-names = "apb0_codec", "apb0_spdif",
- "apb0_ac97", "apb0_iis0", "apb0_iis1",
+ "apb0_ac97", "apb0_i2s0", "apb0_i2s1",
"apb0_pio", "apb0_ir0", "apb0_ir1",
- "apb0_iis2", "apb0_keypad";
+ "apb0_i2s2", "apb0_keypad";
};
apb1: clk@01c20058 {
@@ -450,12 +450,12 @@
clock-output-names = "ir1";
};
- iis0_clk: clk@01c200b8 {
+ i2s0_clk: clk@01c200b8 {
#clock-cells = <0>;
compatible = "allwinner,sun4i-a10-mod1-clk";
reg = <0x01c200b8 0x4>;
- clocks = <&pll2 0>, <&pll2 1>, <&pll2 2>, <&pll2 3>;
- clock-output-names = "iis0";
+ clocks = <&pll2 3>, <&pll2 2>, <&pll2 1>, <&pll2 0>;
+ clock-output-names = "i2s0";
};
ac97_clk: clk@01c200bc {
@@ -491,20 +491,20 @@
clock-output-names = "spi3";
};
- iis1_clk: clk@01c200d8 {
+ i2s1_clk: clk@01c200d8 {
#clock-cells = <0>;
compatible = "allwinner,sun4i-a10-mod1-clk";
reg = <0x01c200d8 0x4>;
clocks = <&pll2 0>, <&pll2 1>, <&pll2 2>, <&pll2 3>;
- clock-output-names = "iis1";
+ clock-output-names = "i2s1";
};
- iis2_clk: clk@01c200dc {
+ i2s2_clk: clk@01c200dc {
#clock-cells = <0>;
compatible = "allwinner,sun4i-a10-mod1-clk";
reg = <0x01c200dc 0x4>;
clocks = <&pll2 0>, <&pll2 1>, <&pll2 2>, <&pll2 3>;
- clock-output-names = "iis2";
+ clock-output-names = "i2s2";
};
codec_clk: clk@01c20140 {
@@ -1243,6 +1243,38 @@
allwinner,drive = <0>;
allwinner,pull = <0>;
};
+
+ i2s0_mclk: i2s0@0 {
+ allwinner,pins = "PB5";
+ allwinner,function = "i2s0";
+ allwinner,drive = <SUN4I_PINCTRL_10_MA>;
+ allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
+ };
+ i2s0_bclk: i2s0@1 {
+ allwinner,pins = "PB6";
+ allwinner,function = "i2s0";
+ allwinner,drive = <SUN4I_PINCTRL_10_MA>;
+ allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
+ };
+ i2s0_lrclk: i2s0@2 {
+ allwinner,pins = "PB7";
+ allwinner,function = "i2s0";
+ allwinner,drive = <SUN4I_PINCTRL_10_MA>;
+ allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
+ };
+ i2s0_sdo0: i2s0@3 {
+ allwinner,pins = "PB8";
+ allwinner,function = "i2s0";
+ allwinner,drive = <SUN4I_PINCTRL_10_MA>;
+ allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
+ };
+ i2s0_sdi: i2s0@4 {
+ allwinner,pins = "PB12";
+ allwinner,function = "i2s0";
+ //allwinner,drive = <SUN4I_PINCTRL_10_MA>; //input only
+ allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
+ };
+
};
timer@01c20c00 {
@@ -1301,14 +1333,26 @@
status = "disabled";
};
- codec: codec@01c22c00 {
+ codec: codec@01c22c00 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun7i-a20-codec";
+ reg = <0x01c22c00 0x40>;
+ interrupts = <0 30 4>;
+ clocks = <&apb0_gates 0>, <&codec_clk>;
+ clock-names = "apb", "codec";
+ dmas = <&dma 0 19>, <&dma 0 19>;
+ dma-names = "rx", "tx";
+ status = "disabled";
+ };
+
+ i2s0: i2s@01c22400 {
#sound-dai-cells = <0>;
- compatible = "allwinner,sun7i-a20-codec";
- reg = <0x01c22c00 0x40>;
- interrupts = <0 30 4>;
- clocks = <&apb0_gates 0>, <&codec_clk>;
- clock-names = "apb", "codec";
- dmas = <&dma 0 19>, <&dma 0 19>;
+ compatible = "allwinner,sun7i-a20-i2s";
+ reg = <0x01c22400 0x40>;
+ interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&apb0_gates 3>, <&pll2 0>, <&i2s0_clk>;
+ clock-names = "apb", "audio", "i2s";
+ dmas = <&dma 0 3>, <&dma 0 3>;
dma-names = "rx", "tx";
status = "disabled";
};
/*
* sunxi-i2s.c
*
* (c) 2015 Andrea Venturi <be17068@iperbole.bo.it>
*
* 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.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/mbus.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
//#include <linux/platform_data/asoc-sunxi.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <sound/dmaengine_pcm.h>
#include "sunxi.h"
#define DRV_NAME "sunxi-i2s"
#define SUNXI_I2S_FORMATS \
(SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
#define SUNXI_SPDIF_FORMATS \
(SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE)
// for suspend/resume feature
static int regsave[8];
// TODO: Initialize structure on probe, after register default configuration
//static struct sunxi_i2s_info sunxi_iis;
static struct sunxi_i2s_info sunxi_iis = {
.slave = 0,
.samp_fs = 48000,
.samp_res = 24,
.samp_format = 0,
.ws_size = 32,
.mclk_rate = 512,
.lrc_pol = 0,
.bclk_pol = 0,
.pcm_datamode = 0,
.pcm_sw = 0,
.pcm_sync_period = 0,
.pcm_sync_type = 0,
.pcm_start_slot = 0,
.pcm_lsb_first = 0,
.pcm_ch_num = 1,
};
typedef struct __BCLK_SET_INF
{
__u8 bitpersamp; // bits per sample - Word Sizes
__u8 clk_div; // clock division
__u16 mult_fs; // multiplay of sample rate
} __bclk_set_inf;
typedef struct __MCLK_SET_INF
{
__u32 samp_rate; // sample rate
__u16 mult_fs; // multiply of sample rate
__u8 clk_div; // mpll division
__u32 mclk; // select mpll, 24.576MHz/22.5792Mhz
} __mclk_set_inf;
static __bclk_set_inf BCLK_INF[] =
{
// 16bits per sample
{16, 4, 128}, {16, 6, 192}, {16, 8, 256},
{16, 12, 384}, {16, 16, 512},
//24 bits per sample
{24, 4, 192}, {24, 8, 384}, {24, 16, 768},
//32 bits per sample
{32, 2, 128}, {32, 4, 256}, {32, 6, 384},
{32, 8, 512}, {32, 12, 768},
//end flag
{0xff, 0, 0},
};
static __mclk_set_inf MCLK_INF[] =
{
// 8k bitrate
{ 8000, 128, 24, 24576000}, { 8000, 192, 16, 24576000}, { 8000, 256, 12, 24576000},
{ 8000, 384, 8, 24576000}, { 8000, 512, 6, 24576000}, { 8000, 768, 4, 24576000},
// 16k bitrate
{ 16000, 128, 12, 24576000}, { 16000, 192, 8, 24576000}, { 16000, 256, 6, 24576000},
{ 16000, 384, 4, 24576000}, { 16000, 768, 2, 24576000},
// 32k bitrate
{ 32000, 128, 6, 24576000}, { 32000, 192, 4, 24576000}, { 32000, 384, 2, 24576000},
{ 32000, 768, 1, 24576000},
// 64k bitrate
{ 64000, 192, 2, 24576000}, { 64000, 384, 1, 24576000},
//128k bitrate
{128000, 192, 1, 24576000},
// 12k bitrate
{ 12000, 128, 16, 24576000}, { 12000, 256, 8, 24576000}, { 12000, 512, 4, 24576000},
// 24k bitrate
{ 24000, 128, 8, 24576000}, { 24000, 256, 4, 24576000}, { 24000, 512, 2, 24576000},
// 48K bitrate
{ 48000, 128, 4, 24576000}, { 48000, 256, 2, 24576000}, { 48000, 512, 1, 24576000},
// 96k bitrate
{ 96000, 128 , 2, 24576000}, { 96000, 256, 1, 24576000},
//192k bitrate
{192000, 128, 1, 24576000},
//11.025k bitrate
{ 11025, 128, 16, 22579200}, { 11205, 256, 8, 22579200}, { 11205, 512, 4, 22579200},
//22.05k bitrate
{ 22050, 128, 8, 22579200}, { 22050, 256, 4, 22579200},
{ 22050, 512, 2, 22579200},
//44.1k bitrate
{ 44100, 128, 4, 22579200}, { 44100, 256, 2, 22579200}, { 44100, 512, 1, 22579200},
//88.2k bitrate
{ 88200, 128, 2, 22579200}, { 88200, 256, 1, 22579200},
//176.4k bitrate
{176400, 128, 1, 22579200},
//end flag 0xffffffff
{0xffffffff, 0, 0, 24576000},
};
static irqreturn_t sunxi_dai_isr(int irq, void *devid)
{
struct sunxi_priv *dai = (struct sunxi_priv *)devid;
struct device *dev = &dai->pdev->dev;
u32 flags, xcsr, mask;
bool irq_none = true;
dev_dbg(dev, "isr: got an IRQ, need to manage\n");
/*
* TODO: manage the IRQ from DAI I2S!
* look for hint in sound/soc/fsl/fsl_sai.c
*/
out:
if (irq_none)
return IRQ_NONE;
else
return IRQ_HANDLED;
}
/*
* TODO: Function description.
*/
//static s32 get_clock_divder(u32 sample_rate, u32 sample_width, u32 * mclk_div, u32* mpll, u32* bclk_div, u32* mult_fs)
static s32 sunxi_i2s_divisor_values(u32 * mclk_div, u32* bclk_div, u32* mclk)
{
u32 i, j, ret = -EINVAL;
printk("[I2S]Entered %s\n", __func__);
for(i=0; i< ARRAY_SIZE(MCLK_INF); i++) {
if((MCLK_INF[i].samp_rate == sunxi_iis.samp_fs) && ((MCLK_INF[i].mult_fs == 256) || (MCLK_INF[i].mult_fs == 128))) {
for(j=0; j<ARRAY_SIZE(BCLK_INF); j++) {
if((BCLK_INF[j].bitpersamp == sunxi_iis.ws_size) && (BCLK_INF[j].mult_fs == MCLK_INF[i].mult_fs)) {
//set mclk and bclk division
*mclk_div = MCLK_INF[i].clk_div;
*mclk = MCLK_INF[i].mclk;
*bclk_div = BCLK_INF[j].clk_div;
sunxi_iis.mclk_rate = MCLK_INF[i].mult_fs;
ret = 0;
break;
}
}
}
else if(MCLK_INF[i].samp_rate == 0xffffffff)
break;
}
return ret;
}
static int sunxi_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
struct sunxi_priv *priv2 = snd_soc_card_get_drvdata(rtd->card); // AV now just a check if the data structure is filled..
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(dai);
if (!priv2)
printk("[I2S]DEB Entered %s BUT missing runtime private data for DMA\n", __func__);
printk("[I2S]Entered %s: hw is %s, stream is: %s\n", __func__, substream->name, tx?"playback":"capture");
//clk_prepare_enable(priv->clk_apb);
//printk("[I2S]enabling i2s clk %s\n", __func__);
return clk_prepare_enable(priv->clk_module);
}
static void sunxi_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
//struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
printk("[I2S]Entered %s, need to clear some HW regs?\n", __func__);
clk_disable_unprepare(priv->clk_module);
}
static void sunxi_i2s_capture_start(struct sunxi_priv *priv)
{
printk("[I2S]Entered %s\n", __func__);
/* flush RXFIFO */
regmap_update_bits(priv->regmap, SUNXI_DA_FCTL, 0x1 << SUNXI_DA_FCTL_FRX, 0x1 << SUNXI_DA_FCTL_FRX);
/* clear RX counter */
regmap_update_bits(priv->regmap, SUNXI_DA_RXCNT, 0xffffffff << SUNXI_DA_RXCNT_RX_CNT, 0x0 << SUNXI_DA_RXCNT_RX_CNT);
/* enable DA_CTL RXEN */
regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_RXEN, SUNXI_DA_CTL_RXEN);
/* enable DA_INT RX_DRQ */
regmap_update_bits(priv->regmap, SUNXI_DA_INT, 0x1 << SUNXI_DA_INT_RX_DRQ, 0x1 << SUNXI_DA_INT_RX_DRQ);
}
static void sunxi_i2s_capture_stop(struct sunxi_priv *priv)
{
unsigned int rx_counter;
/* disable DA RX_DRQ */
regmap_update_bits(priv->regmap, SUNXI_DA_INT, 0x1 << SUNXI_DA_INT_RX_DRQ, 0x0 << SUNXI_DA_INT_RX_DRQ);
//regmap_update_bits(priv->regmap, SUNXI_DA_RXCNT, 0xffffffff << SUNXI_DA_RXCNT_RX_CNT, 0x0 << SUNXI_DA_RXCNT_RX_CNT);
regmap_read(priv->regmap, SUNXI_DA_RXCNT, &rx_counter);
printk("DEB: stop I2S rec: sample counter: 0x%08x\n", rx_counter);
/* disable DA_CTL RXEN */
regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_RXEN, 0x0 );
/* flush RXFIFO */
regmap_update_bits(priv->regmap, SUNXI_DA_FCTL, 0x1 << SUNXI_DA_FCTL_FRX, 0x0 << SUNXI_DA_FCTL_FRX );
}
static void sunxi_i2s_play_start(struct sunxi_priv *priv)
{
unsigned int tmp;
printk("[I2S]Entered %s\n", __func__);
regmap_read(priv->regmap, SUNXI_DA_CTL, &tmp);
printk("DEB %s: DA CTL register: 0x%08x\n", __func__, tmp);
/* flush TX FIFO */
regmap_update_bits(priv->regmap, SUNXI_DA_FCTL, 0x1 << SUNXI_DA_FCTL_FTX, 0x1 << SUNXI_DA_FCTL_FTX);
/* clear TX counter */
regmap_update_bits(priv->regmap, SUNXI_DA_TXCNT, 0xffffffff << SUNXI_DA_TXCNT_TX_CNT, 0x0 << SUNXI_DA_TXCNT_TX_CNT);
regmap_read(priv->regmap, SUNXI_DA_TXCNT, &tmp);
printk("DEB %s: sample counter: 0x%08x\n", __func__, tmp);
/* enable DA_CTL TXEN */
regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_TXEN, SUNXI_DA_CTL_TXEN);
regmap_read(priv->regmap, SUNXI_DA_CTL, &tmp);
printk("DEB %s: DA CTL registers after cfg: 0x%08x\n", __func__, tmp);
regmap_read(priv->regmap, SUNXI_DA_CLKD, &tmp);
printk("DEB %s: DA CLKD registers: 0x%08x\n", __func__, tmp);
regmap_read(priv->regmap, SUNXI_DA_FAT0, &tmp);
printk("DEB %s: DA FAT0 registers: 0x%08x\n", __func__, tmp);
/* enable DA TX_DRQ */
regmap_update_bits(priv->regmap, SUNXI_DA_INT, 0x1 << SUNXI_DA_INT_TX_DRQ, 0x1 << SUNXI_DA_INT_TX_DRQ);
regmap_read(priv->regmap, SUNXI_DA_INT, &tmp);
printk("DEB %s: DA INT registers: 0x%08x\n", __func__, tmp);
}
static void sunxi_i2s_play_stop(struct sunxi_priv *priv)
{
unsigned int tx_counter;
/* TODO: see if we need to drive PA GPIO low */
regmap_read(priv->regmap, SUNXI_DA_TXCNT, &tx_counter);
printk("DEB %s: sample counter: 0x%08x\n", __func__, tx_counter);
/* disable DA_CTL TXEN */
regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_TXEN, 0x0 );
/* disable DA TX_DRQ */
regmap_update_bits(priv->regmap, SUNXI_DA_INT, 0x1 << SUNXI_DA_INT_TX_DRQ, 0x0 << SUNXI_DA_INT_TX_DRQ);
}
static int sunxi_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(dai);
printk("DEB: %s, copy from same fmt on i2s 3.4 legacy sunxi-i2s.c\n", __func__);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
sunxi_i2s_capture_start(priv);
else
sunxi_i2s_play_start(priv);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
sunxi_i2s_capture_stop(priv);
else
sunxi_i2s_play_stop(priv);
break;
default:
return -EINVAL;
}
return 0;
}
static int sunxi_i2s_init(struct sunxi_priv *priv)
{
//unsigned long value;
//unsigned int reg_data;
printk("[I2S]Entered %s\n", __func__);
/*
* was used for parsing FEX file, and, is suffesful:
* - setting slave or master
* - requesting GPIO of I2S ctrl
* - registering platform driver
*/
return 0;
}
/*
* TODO: Function Description
* Saved in snd_soc_dai_ops sunxi_iis_dai_ops.
* Function called internally. The Machine Driver doesn't need to call this function because it is called whenever sunxi_i2s_set_clkdiv is called.
* The master clock in Allwinner SoM depends on the sampling frequency.
*/
static int sunxi_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir)
{
u32 reg_val;
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
printk("[I2S]Entered %s\n", __func__);
if(!sunxi_iis.slave)
{
switch(clk_id)
{
case SUNXI_SET_MCLK: // Set the master clock frequency.
// TODO - Check if the master clock is needed when slave mode is selected.
if (clk_set_rate(priv->clk_pll2, freq))
{
pr_err("Try to set the i2s pll2 clock failed!\n");
return -EINVAL;
}
break;
case SUNXI_MCLKO_EN: // Enables the master clock output
if(dir == 1) // Enable
regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x1 << SUNXI_DA_CLKD_MCLKO_EN, 0x1 << SUNXI_DA_CLKD_MCLKO_EN);
if(dir == 0) // Disable
regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x1 << SUNXI_DA_CLKD_MCLKO_EN, 0x0 << SUNXI_DA_CLKD_MCLKO_EN);
break;
default:
pr_err("Try to set unknown clkid: %d\n", clk_id);
return -EINVAL;
}
regmap_read(priv->regmap, SUNXI_DA_CLKD, &reg_val);
printk("DEB: DA I2S CLKD: 0x%08x\n", reg_val);
}
return 0;
}
/*
* TODO: Function Description
* Saved in snd_soc_dai_ops sunxi_iis_dai_ops.
*/
static int sunxi_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, int div_id, int value)
{
u32 reg_bk, reg_val, ret;
u32 mclk = 0;
u32 mclk_div = 0;
u32 bclk_div = 0;
u32 mclk_divreg = 0;
u32 bclk_divreg = 0;
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
// Here i should know the sample rate and the FS multiple.
printk("[I2S]Entered %s, div_id is %d\n", __func__, div_id);
switch (div_id) {
case SUNXI_DIV_MCLK: // Sets MCLKDIV
regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0xf << SUNXI_DA_CLKD_MCLKDIV, (value & 0xf) << SUNXI_DA_CLKD_MCLKDIV);
break;
case SUNXI_DIV_BCLK: // Sets BCLKDIV
regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x7 << SUNXI_DA_CLKD_BCLKDIV, (value & 0x7) << SUNXI_DA_CLKD_BCLKDIV);
break;
case SUNXI_SAMPLING_FREQ:
if(!sunxi_iis.slave)
{
reg_bk = sunxi_iis.samp_fs;
sunxi_iis.samp_fs = (u32)value;
ret = sunxi_i2s_divisor_values(&mclk_div, &bclk_div, &mclk); // Get the register values
printk("[I2S]Sampling rate is %d; selected MCLK: %d, MCLK_DIV: %d, BCLKDIV: %d\n", sunxi_iis.samp_fs, mclk, mclk_div, bclk_div);
if(ret != 0)
{
printk("[I2S]Sampling rate %d frequency not supported, turning to backup: %d.", sunxi_iis.samp_fs, reg_bk);
sunxi_iis.samp_fs = reg_bk;
return ret;
}
else
{
sunxi_iis.samp_fs = (u32)value;
sunxi_i2s_set_sysclk(cpu_dai, SUNXI_SET_MCLK, mclk, 0); // Set the master clock.
//AV for BCLK_DIV and MCLK_DIV, need to find the index from divisor value..
switch (mclk_div) {
case 1: mclk_divreg=0; break;
case 2: mclk_divreg=1; break;
case 4: mclk_divreg=2; break;
case 6: mclk_divreg=3; break;
case 8: mclk_divreg=4; break;
case 12: mclk_divreg=5; break;
case 16: mclk_divreg=6; break;
case 24: mclk_divreg=7; break;
case 32: mclk_divreg=8; break;
case 48: mclk_divreg=9; break;
case 64: mclk_divreg=10; break;
default:
printk("[I2S] %s: MCLK div unsupported %d, putting %d\n", __func__, mclk_div, mclk_divreg);
}
switch (bclk_div) {
case 2: bclk_divreg=0; break;
case 4: bclk_divreg=1; break;
case 6: bclk_divreg=2; break;
case 8: bclk_divreg=3; break;
case 12: bclk_divreg=4; break;
case 16: bclk_divreg=5; break;
case 32: bclk_divreg=6; break;
case 64: bclk_divreg=7; break;
default:
printk("[I2S] %s: BCLK div unsupported %d, putting %d\n", __func__, bclk_div, bclk_divreg);
}
//regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, (0xf << SUNXI_DA_CLKD_MCLKDIV)|(0x7 << SUNXI_DA_CLKD_BCLKDIV),
// ((mclk_div & 0xf) << SUNXI_DA_CLKD_MCLKDIV)|((bclk_div & 0x7) << SUNXI_DA_CLKD_BCLKDIV));
regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0xf << SUNXI_DA_CLKD_MCLKDIV, (mclk_divreg & 0xf) << SUNXI_DA_CLKD_MCLKDIV);
regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x7 << SUNXI_DA_CLKD_BCLKDIV, (bclk_divreg & 0x7) << SUNXI_DA_CLKD_BCLKDIV);
regmap_read(priv->regmap, SUNXI_DA_CLKD, &reg_val);
printk("DEB: DA I2S internal CLKD (MCLK / BCLK): 0x%02x\n", reg_val);
}
}
else
sunxi_iis.samp_fs = (u32)value;
break;
default:
regmap_read(priv->regmap, SUNXI_DA_CLKD, &reg_val);
printk("ERR: dev_id unknown: %d, DA I2S CLKD (MCLK / BCLK): 0x%02x\n", div_id, reg_val);
return -EINVAL;
}
regmap_read(priv->regmap, SUNXI_DA_CLKD, &reg_val);
printk("DEB: DA I2S CLKD (MCLK / BCLK): 0x%02x\n", reg_val);
return 0;
}
/*
* TODO: Function description.
* TODO: Refactor function because the configuration is with wrong scheme. Use a 4bit mask with the configuration option and then the value?
* TODO: Include TX and RX FIFO trigger levels.
* Saved in snd_soc_dai_ops sunxi_iis_dai_ops.
* Configure:
* - Master/Slave.
* - I2S/PCM mode.
* - Signal Inversion.
* - Word Select Size.
* - PCM Registers.
*/
static int sunxi_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
u32 reg_val1;
u32 reg_val2;
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
printk("[I2S]Entered %s, FMT: 0x%08x\n", __func__, fmt);
// Master/Slave Definition
//reg_val1 = readl(sunxi_iis.regs + SUNXI_IISCTL);
regmap_read(priv->regmap, SUNXI_DA_CTL, &reg_val1 );
printk("[I2S] %s: reg DA_CTL: 0x%08x\n", __func__, reg_val1);
switch(fmt & SND_SOC_DAIFMT_MASTER_MASK){
case SND_SOC_DAIFMT_CBS_CFS: // clk & frm slave
reg_val1 &= ~SUNXI_DA_CTL_MS; // 0: I2S Master!
printk("[I2S] %s, Master, so codec Slave.\n", __func__);
break;
case SND_SOC_DAIFMT_CBM_CFM: // clk & frm master
reg_val1 |= SUNXI_DA_CTL_MS; // 1: I2S Slave!
printk("[I2S] %s, Slave, so codec Master.\n", __func__);
break;
default:
printk("[I2S] %s: Master-Slave Select unknown mode: (fmt=%x)\n", __func__, fmt);
return -EINVAL;
}
regmap_write(priv->regmap, SUNXI_DA_CTL, reg_val1 );
//writel(reg_val1, sunxi_iis.regs + SUNXI_IISCTL);
// I2S or PCM mode.
regmap_read(priv->regmap, SUNXI_DA_CTL, &reg_val1 );
regmap_read(priv->regmap, SUNXI_DA_FAT0, &reg_val2 );
printk("[I2S] %s: reg DA_FAT0: 0x%08x\n", __func__, reg_val2);
//reg_val1 = readl(sunxi_iis.regs + SUNXI_IISCTL);
//reg_val2 = readl(sunxi_iis.regs + SUNXI_IISFAT0); // Register Name in User Manual V1.2: DA_FAT0 - Digital Audio Format Register 0
switch(fmt & SND_SOC_DAIFMT_FORMAT_MASK)
{
case SND_SOC_DAIFMT_I2S: /* I2S mode */
reg_val1 &= ~SUNXI_DA_CTL_PCM;
reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
reg_val2 |= SUNXI_DA_FAT0_FMT_STD; //
printk("[I2S]sunxi_i2s_set_fmt: Set I2S mode\n");
sunxi_iis.samp_format = SND_SOC_DAIFMT_I2S;
break;
case SND_SOC_DAIFMT_RIGHT_J: /* Right Justified mode */
reg_val1 &= ~SUNXI_DA_CTL_PCM;
reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
reg_val2 |= SUNXI_DA_FAT0_FMT_RIGHT; //
printk("[I2S]sunxi_i2s_set_fmt: Set Right Justified mode\n");
sunxi_iis.samp_format = SND_SOC_DAIFMT_RIGHT_J;
break;
case SND_SOC_DAIFMT_LEFT_J: /* Left Justified mode */
reg_val1 &= ~SUNXI_DA_CTL_PCM;
reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
reg_val2 |= SUNXI_DA_FAT0_FMT_LEFT; //
printk("[I2S]sunxi_i2s_set_fmt: Set Left Justified mode\n");
sunxi_iis.samp_format = SND_SOC_DAIFMT_LEFT_J;
break;
case SND_SOC_DAIFMT_DSP_A: /* L data msb after FRM LRC */
reg_val1 &= ~SUNXI_DA_CTL_PCM;
reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
reg_val2 |= SUNXI_DA_FAT0_FMT_LEFT; //
sunxi_iis.samp_format = SND_SOC_DAIFMT_DSP_A;
printk("[I2S]sunxi_i2s_set_fmt: Set L data msb after FRM LRC mode\n");
break;
case SND_SOC_DAIFMT_DSP_B: /* L data msb during FRM LRC */
reg_val1 |= SUNXI_DA_CTL_PCM;
reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
reg_val2 |= SUNXI_DA_FAT0_LRCP;
sunxi_iis.samp_format = SND_SOC_DAIFMT_DSP_B;
printk("[I2S]sunxi_i2s_set_fmt: Set L data msb during FRM LRC mode\n");
break;
default:
printk("[I2S]sunxi_i2s_set_fmt: Unknown mode\n");
return -EINVAL;
}
regmap_write(priv->regmap, SUNXI_DA_CTL, reg_val1 );
regmap_write(priv->regmap, SUNXI_DA_FAT0, reg_val2 );
//writel(reg_val1, sunxi_iis.regs + SUNXI_IISCTL);
//writel(reg_val2, sunxi_iis.regs + SUNXI_IISFAT0);
// Word select Size
regmap_read(priv->regmap, SUNXI_DA_FAT0, &reg_val1 );
//reg_val1 = readl(sunxi_iis.regs + SUNXI_IISFAT0);
switch(fmt & SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_MASK) // TODO: Refactor, wrong configuration scheme.
{
case SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_16BCLK:
reg_val1 &= ~SUNXI_DA_FAT0_WSS_32; /* clear word select size */
reg_val1 |= SUNXI_DA_FAT0_WSS_16;
sunxi_iis.ws_size = 16;
printk("[I2S]sunxi_i2s_set_fmt: Set word select size = 16.\n");
break;
case SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_20BCLK:
reg_val1 &= ~SUNXI_DA_FAT0_WSS_32; /* clear word select size */
reg_val1 |= SUNXI_DA_FAT0_WSS_20;
sunxi_iis.ws_size = 20;
printk("[I2S]sunxi_i2s_set_fmt: Set word select size = 20.\n");
break;
case SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_24BCLK:
reg_val1 &= ~SUNXI_DA_FAT0_WSS_32; /* clear word select size */
reg_val1 |= SUNXI_DA_FAT0_WSS_24;
sunxi_iis.ws_size = 24;
printk("[I2S]sunxi_i2s_set_fmt: Set word select size = 24.\n");
break;
case SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_32BCLK:
reg_val1 &= ~SUNXI_DA_FAT0_WSS_32; /* clear word select size */
reg_val1 |= SUNXI_DA_FAT0_WSS_32;
sunxi_iis.ws_size = 32;
printk("[I2S]sunxi_i2s_set_fmt: Set word select size = 32.\n");
break;
default:
printk("[I2S]sunxi_i2s_set_fmt: Unknown mode.\n");
break;
}
regmap_write(priv->regmap, SUNXI_DA_FAT0, reg_val1 );
//writel(reg_val1, sunxi_iis.regs + SUNXI_IISFAT0);
// Signal Inversion
regmap_read(priv->regmap, SUNXI_DA_FAT0, &reg_val1 );
//reg_val1 = readl(sunxi_iis.regs + SUNXI_IISFAT0);
switch(fmt & SND_SOC_DAIFMT_INV_MASK)
{
case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
reg_val1 &= ~SUNXI_DA_FAT0_LRCP;
reg_val1 &= ~SUNXI_DA_FAT0_BCP;
sunxi_iis.bclk_pol = 0;
sunxi_iis.lrc_pol = 0;
printk("[I2S]sunxi_i2s_set_fmt: Normal bit clock + frame\n");
break;
case SND_SOC_DAIFMT_NB_IF: /* normal bclk + inverted frame */
reg_val1 |= SUNXI_DA_FAT0_LRCP;
reg_val1 &= ~SUNXI_DA_FAT0_BCP;
sunxi_iis.bclk_pol = 0;
sunxi_iis.lrc_pol = 1;
printk("[I2S]sunxi_i2s_set_fmt: Normal bclk + inverted frame\n");
break;
case SND_SOC_DAIFMT_IB_NF: /* inverted bclk + normal frame */
reg_val1 &= ~SUNXI_DA_FAT0_LRCP;
reg_val1 |= SUNXI_DA_FAT0_BCP;
sunxi_iis.bclk_pol = 1;
sunxi_iis.lrc_pol = 0;
printk("[I2S]sunxi_i2s_set_fmt: Inverted bclk + normal frame\n");
break;
case SND_SOC_DAIFMT_IB_IF: /* inverted bclk + frame */
reg_val1 |= SUNXI_DA_FAT0_LRCP;;
reg_val1 |= SUNXI_DA_FAT0_BCP;
sunxi_iis.bclk_pol = 1;
sunxi_iis.lrc_pol = 1;
printk("[I2S]sunxi_i2s_set_fmt: Inverted bclk + frame\n");
break;
default:
printk("[I2S]sunxi_i2s_set_fmt: Unknown mode\n");
return -EINVAL;
}
regmap_write(priv->regmap, SUNXI_DA_FAT0, reg_val1 );
//writel(reg_val1, sunxi_iis.regs + SUNXI_IISFAT0);
// sunxi_i2s_printk_register_values();
return 0;
}
static int sunxi_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
// struct sunxi_priv *priv = snd_soc_card_get_drvdata(rtd->card); BROKEN
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(dai);
//int is_mono = !!(params_channels(params) == 1);
int is_24bit = !!(hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32);
unsigned int rate = params_rate(params);
unsigned int hwrate;
unsigned int tmp;
unsigned int reg_val1;
printk("[I2S]Entered %s\n", __func__);
switch (rate) {
case 176400:
case 88200:
case 44100:
case 33075:
case 22050:
case 14700:
case 11025:
case 7350:
default:
clk_set_rate(priv->clk_module, 22579200);
break;
case 192000:
case 96000:
case 48000:
case 32000:
case 24000:
case 16000:
case 12000:
case 8000:
clk_set_rate(priv->clk_module, 24576000);
break;
}
switch (rate) {
case 192000:
case 176400:
hwrate = 6;
break;
case 96000:
case 88200:
hwrate = 7;
break;
default:
case 48000:
case 44100:
hwrate = 0;
break;
case 32000:
case 33075:
hwrate = 1;
break;
case 24000:
case 22050:
hwrate = 2;
break;
case 16000:
case 14700:
hwrate = 3;
break;
case 12000:
case 11025:
hwrate = 4;
break;
case 8000:
case 7350:
hwrate = 5;
break;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
printk("[I2S]sunxi_i2s_hw_params: SNDRV_PCM_STREAM_PLAYBACK. chan: %d\n", params_channels(params));
switch (params_channels(params)) { // Enables the outputs and sets the map of the samples, on crescent order.
// FIXME: always 2 channels, for draft
default:
printk("[I2S] %s: channels selected different then 2 but not implemented\n", __func__);
case 2:
regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_SDO0_EN|SUNXI_DA_CTL_SDO1_EN|SUNXI_DA_CTL_SDO2_EN|SUNXI_DA_CTL_SDO3_EN, SUNXI_DA_CTL_SDO0_EN);
regmap_update_bits(priv->regmap, SUNXI_DA_TXCHSEL, 7, SUNXI_DA_TXCHSEL_CHNUM(2)); /* mask 3 lsbs */
regmap_update_bits(priv->regmap, SUNXI_DA_TXCHMAP, 0x3f, SUNXI_DA_TXCHMAP_TX_CH(1)|SUNXI_DA_TXCHMAP_TX_CH(2)); //FIXME: ugly masks!
//regmap_read(priv->regmap, SUNXI_DA_TXCHMAP, &tmp);
//printk("DEB %s: DA TXCHMAP register: %x\n", __func__, tmp);
//reg_val1 |= SUNXI_IISCTL_SDO0EN;
//reg_val2 |= SUNXI_TXCHSEL_CHNUM(2); // TX Channel Select 2-ch.
//reg_val3 |= ((0x0 << 0) | (0x1 << 4)); // TX Channel0 Mapping 1st sample, TX Channel1 Mapping 2nd sample.
printk("[I2S]sunxi_i2s_hw_params: SDO0 enabled, 2 channels selected.\n");
break;
}
if (is_24bit) // FIXME need supporting also 20 bit properly, here it's two bytes only
priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
else
priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
} else {
printk("[I2S]sunxi_i2s_hw_params: SNDRV_PCM_STREAM_CAPTURE.\n");
switch (params_channels(params)) { // Enables the outputs and sets the map of the samples, on crescent order.
// FIXME: always 2 channels, for draft
default:
printk("[I2S]sunxi_i2s_hw_params: channels selected different then 2 but...\n");
case 2:
regmap_update_bits(priv->regmap, SUNXI_DA_RXCHSEL, 7, SUNXI_DA_RXCHSEL_CHNUM(2)); /* mask 3 lsbs */
regmap_update_bits(priv->regmap, SUNXI_DA_RXCHMAP, 0x3f, SUNXI_DA_RXCHMAP_RX_CH(1)|SUNXI_DA_RXCHMAP_RX_CH(2)); //FIXME: ugly masks!
printk("[I2S]sunxi_i2s_hw_params: SDO0 enabled, 2 channels selected.\n");
break;
}
if (is_24bit) // FIXME need supporting also 20 bit properly, here it's two bytes only
priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
else
priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
}
// Sample Rate.
if(sunxi_iis.slave == 0) // Only master has to configure the clock registers for sample rate setting.
{
sunxi_iis.samp_fs = params_rate(params);
sunxi_i2s_set_clkdiv(dai, SUNXI_SAMPLING_FREQ, sunxi_iis.samp_fs);
}
// Sample Format.
// TODO: Support SNDRV_PCM_FORMAT_S20_3LE and SNDRV_PCM_FMTBIT_S24_3LE formats. Must check the Word Size and change it for 24bits ("3LE").
//reg_val1 = readl(sunxi_iis.regs + SUNXI_IISFAT0);
//reg_val1 &= ~SUNXI_IISFAT0_SR_RVD; // Clear sample resolution select size
reg_val1 = 0; // Clear sample resolution select size
switch (params_format(params))
{
case SNDRV_PCM_FORMAT_S16_LE:
//reg_val1 |= SUNXI_IISFAT0_SR_16BIT;
reg_val1 |= SUNXI_DA_FAT0_SR(0)|SUNXI_DA_FAT0_WSS(0);
sunxi_iis.samp_res = 16;
printk("[I2S] %s: SNDRV_PCM_FORMAT_S16_LE.\n", __func__);
break;
case SNDRV_PCM_FORMAT_S24_3LE:
//reg_val1 |= SUNXI_IISFAT0_SR_24BIT;
reg_val1 |= SUNXI_DA_FAT0_SR(2);
sunxi_iis.samp_res = 24;
if(sunxi_iis.ws_size != 32) // If the Word Size is not equal to 32, sets word size to 32.
{
//reg_val1 |= SUNXI_IISFAT0_WSS_32BCLK;
reg_val1 |= SUNXI_DA_FAT0_WSS(3);
sunxi_iis.ws_size = 32;
printk("[I2S] sunxi_i2s_hw_params: Changing word slect size to 32bit.\n");
}
printk("[I2S] sunxi_i2s_hw_params: SNDRV_PCM_FORMAT_S24_3LE.\n");
break;
case SNDRV_PCM_FORMAT_S24_LE:
//reg_val1 |= SUNXI_IISFAT0_SR_24BIT;
reg_val1 |= SUNXI_DA_FAT0_SR(2);
sunxi_iis.samp_res = 24;
if(sunxi_iis.ws_size != 32) // If the Word Size is not equal to 32, sets word size to 32.
{
//reg_val1 |= SUNXI_IISFAT0_WSS_32BCLK;
reg_val1 |= SUNXI_DA_FAT0_WSS(3);
sunxi_iis.ws_size = 32;
printk("[I2S] sunxi_i2s_hw_params: Changing word slect size to 32bit.\n");
}
printk("[I2S] sunxi_i2s_hw_params: SNDRV_PCM_FORMAT_S24_LE.\n");
break;
default:
printk("[I2S] sunxi_i2s_hw_params: Unsupported format (%d).\n", (int)params_format(params));
//reg_val1 |= SUNXI_IISFAT0_SR_24BIT;
//reg_val1 |= SUNXI_IISFAT0_WSS_32BCLK;
reg_val1 |= SUNXI_DA_FAT0_SR(2)|SUNXI_DA_FAT0_WSS(3);
sunxi_iis.samp_res = 24;
sunxi_iis.ws_size = 32;
printk("[I2S] sunxi_i2s_hw_params: Setting 24 bit format and changing word slect size to 32bit.\n");
break;
}
//writel(reg_val1, sunxi_iis.regs + SUNXI_IISFAT0); // from legacy 3.4 linux-sunxi i2s driver
regmap_update_bits(priv->regmap, SUNXI_DA_FAT0, SUNXI_DA_FAT0_WSS(3)|SUNXI_DA_FAT0_SR(3), reg_val1);
regmap_read(priv->regmap, SUNXI_DA_FAT0, &tmp);
printk("DEB %s: DA FAT0 register: %x\n", __func__, tmp);
return 0;
}
/*
* TODO: Function Description.
* Saved in snd_soc_dai_driver sunxi_iis_dai.
*/
static int sunxi_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
{
// priv=snd_soc_dai_get_drvdata(cpu_dai) OR priv=dev_get_drvdata(cpu_dai->dev) is the same if you look at snd_soc_dai_get_drvdata as a wrapper!!
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
snd_soc_dai_init_dma_data(cpu_dai, &priv->playback_dma_data, &priv->capture_dma_data);
printk("[I2S]Entered %s\n", __func__);
// I2S Default Register Configuration
sunxi_iis.slave = 0, // put as default Master
sunxi_iis.samp_fs = 48000,
sunxi_iis.samp_res = 24,
sunxi_iis.samp_format = SND_SOC_DAIFMT_I2S,
sunxi_iis.ws_size = 32,
sunxi_iis.mclk_rate = 512,
sunxi_iis.lrc_pol = 0,
sunxi_iis.bclk_pol = 0,
sunxi_iis.pcm_datamode = 0,
sunxi_iis.pcm_sw = 0,
sunxi_iis.pcm_sync_period = 0,
sunxi_iis.pcm_sync_type = 0,
sunxi_iis.pcm_start_slot = 0,
sunxi_iis.pcm_lsb_first = 0,
sunxi_iis.pcm_ch_num = 2,
// Digital Audio Register Default Values
// DIGITAL AUDIO CONTROL REGISTER DEF
regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_GEN, SUNXI_DA_CTL_GEN);
//reg_val = SUNXI_IISCTL_MS | SUNXI_IISCTL_GEN;
//writel(reg_val, sunxi_iis.regs + SUNXI_IISCTL);
// DIGITAL AUDIO FORMAT REGISTER 0
regmap_write(priv->regmap, SUNXI_DA_FAT0, SUNXI_DA_FAT0_FMT_STD|SUNXI_DA_FAT0_SR_24|SUNXI_DA_FAT0_WSS_32);
//reg_val = SUNXI_IISFAT0_FMT_I2S | SUNXI_IISFAT0_SR_24BIT | SUNXI_IISFAT0_WSS_32BCLK;
//writel(reg_val, sunxi_iis.regs + SUNXI_IISFAT0);
// FIFO control register. TODO: Understand how to optimize this parameter.
//reg_val = (1<<0); // Expanding received sample sign bit at MSB of DA_RXFIFO register. TODO: Check if this configuration works.
//reg_val |= (1<<2); // Valid data at the LSB of TXFIFO register
//reg_val |= SUNXI_IISFCTL_RXTL(0xf); //RX FIFO trigger level - try to make it multiple of 8 to enable DMA burst of 8.
//reg_val |= SUNXI_IISFCTL_TXTL(0x40); //TX FIFO empty trigger level - try to make it multiple of 8 to enable DMA burst of 8
//writel(reg_val, sunxi_iis.regs + SUNXI_IISFCTL);
regmap_write(priv->regmap, SUNXI_DA_FCTL, (1<<SUNXI_DA_FCTL_RXOM)|(1<<SUNXI_DA_FCTL_TXIM)|(0x0f<<SUNXI_DA_FCTL_RXTL)|(0x40<<SUNXI_DA_FCTL_TXTL));
//enable MCLK output
//reg_val = readl(sunxi_iis.regs + SUNXI_IISCLKD);
//reg_val |= SUNXI_IISCLKD_MCLKOEN;
//writel(reg_val, sunxi_iis.regs + SUNXI_IISCLKD);
regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x1 << SUNXI_DA_CLKD_MCLKO_EN, 0x1 << SUNXI_DA_CLKD_MCLKO_EN);
printk("[IIS-0] sunxi_i2s_set_clkdiv: enable MCLK\n");
printk("[I2S]I2S default register configuration complete.\n");
return 0;
}
/*
* TODO: Function Description.
* Saved in snd_soc_dai_driver sunxi_iis_dai.
*/
static int sunxi_i2s_dai_remove(struct snd_soc_dai *dai)
{
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(dai);
printk("[I2S]Entered %s\n", __func__);
// DIGITAL AUDIO CONTROL REGISTER
regmap_write(priv->regmap, SUNXI_DA_CTL, 0);
//writel(0, sunxi_iis.regs + SUNXI_IISCTL);
return 0;
}
/*
* TODO: Function description.
*/
static void iisregsave(struct sunxi_priv *priv)
{
printk("[I2S]Entered %s\n", __func__);
regmap_read(priv->regmap, SUNXI_DA_CTL, &regsave[0]);
regmap_read(priv->regmap, SUNXI_DA_FAT0, &regsave[1]);
regmap_read(priv->regmap, SUNXI_DA_FAT1, &regsave[2]);
regmap_read(priv->regmap, SUNXI_DA_FCTL, &regsave[3]); //| (0x3<<24); // TODO: Bit 24- FRX - Write ‘1’ to flush RX FIFO, self clear to ‘0’. Really needed?
regmap_read(priv->regmap, SUNXI_DA_INT, &regsave[4]);
regmap_read(priv->regmap, SUNXI_DA_CLKD, &regsave[5]);
regmap_read(priv->regmap, SUNXI_DA_TXCHSEL, &regsave[6]);
regmap_read(priv->regmap, SUNXI_DA_TXCHMAP, &regsave[7]);
}
/*
* TODO: Function description.
*/
static void iisregrestore(struct sunxi_priv *priv)
{
printk("[I2S]Entered %s\n", __func__);
regmap_write(priv->regmap, SUNXI_DA_CTL, regsave[0]);
regmap_write(priv->regmap, SUNXI_DA_FAT0, regsave[1]);
regmap_write(priv->regmap, SUNXI_DA_FAT1, regsave[2]);
regmap_write(priv->regmap, SUNXI_DA_FCTL, regsave[3]);
regmap_write(priv->regmap, SUNXI_DA_INT, regsave[4]);
regmap_write(priv->regmap, SUNXI_DA_CLKD, regsave[5]);
regmap_write(priv->regmap, SUNXI_DA_TXCHSEL, regsave[6]);
regmap_write(priv->regmap, SUNXI_DA_TXCHMAP, regsave[7]);
}
/*
* TODO: Function Description.
* Saved in snd_soc_dai_driver sunxi_iis_dai.
*/
static int sunxi_i2s_suspend(struct snd_soc_dai *cpu_dai)
{
//u32 reg_val;
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
printk("[I2S]Entered %s\n", __func__);
//Global Disable Digital Audio Interface
regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_GEN, 0x0 );
//reg_val = readl(sunxi_iis.regs + SUNXI_IISCTL);
//reg_val &= ~SUNXI_IISCTL_GEN;
//writel(reg_val, sunxi_iis.regs + SUNXI_IISCTL);
iisregsave(priv);
if(!sunxi_iis.slave) {
//release the module clock, only for master mode
clk_disable(priv->clk_module);
}
clk_disable(priv->clk_apb);
//printk("[I2S]PLL2 0x01c20008 = %#x\n", *(volatile int*)0xF1C20008);
// printk("[I2S]SPECIAL CLK 0x01c20068 = %#x, line= %d\n", *(volatile int*)0xF1C20068, __LINE__);
// printk("[I2S]SPECIAL CLK 0x01c200B8 = %#x, line = %d\n", *(volatile int*)0xF1C200B8, __LINE__);
// TODO: Understand this printk!
return 0;
}
/*
* TODO: Function Description.
* Saved in snd_soc_dai_driver sunxi_iis_dai.
*/
static int sunxi_i2s_resume(struct snd_soc_dai *cpu_dai)
{
//u32 reg_val;
struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
printk("[I2S]Entered %s\n", __func__);
//enable the module clock
clk_enable(priv->clk_apb);
if(!sunxi_iis.slave) {
//enable the module clock
clk_enable(priv->clk_module);
}
iisregrestore(priv);
//Global Enable Digital Audio Interface
regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_GEN, SUNXI_DA_CTL_GEN);
return 0;
}
static const struct regmap_config sunxi_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUNXI_DA_RXCHMAP,
};
static const struct snd_soc_component_driver sunxi_i2s_component = {
.name = DRV_NAME,
};
static struct snd_soc_dai_ops sunxi_i2s_dai_ops = {
.startup = sunxi_i2s_startup,
.shutdown = sunxi_i2s_shutdown,
.set_sysclk = sunxi_i2s_set_sysclk,
.set_clkdiv = sunxi_i2s_set_clkdiv,
.set_fmt = sunxi_i2s_set_fmt,
.hw_params = sunxi_i2s_hw_params,
.trigger = sunxi_i2s_trigger,
};
static struct snd_soc_dai_driver sunxi_i2s_dai = {
.name = "sunxi-i2s-dai",
.probe = sunxi_i2s_dai_probe,
//.remove = sunxi_i2s_dai_remove,
//.suspend = sunxi_i2s_suspend,
//.resume = sunxi_i2s_resume,
.ops = &sunxi_i2s_dai_ops,
.capture = {
.stream_name = "pcm0c",
// TODO: Support SNDRV_PCM_FMTBIT_S20_3LE and SNDRV_PCM_FMTBIT_S24_3LE.
.formats = SUNXI_I2S_CAPTURE_FORMATS,
.rates = SUNXI_I2S_RATES,
.channels_min = 1,
.channels_max = 2,
},
.playback = {
.stream_name = "pcm0p",
// TODO: Support SNDRV_PCM_FMTBIT_S20_3LE and SNDRV_PCM_FMTBIT_S24_3LE. Implies in changing the word select size in *_set_fmt.
.formats = SUNXI_I2S_PLAYBACK_FORMATS,
.rates = SUNXI_I2S_RATES,
.channels_min = 1,
.channels_max = 2,
},
.symmetric_rates = 1,
};
#ifdef CONFIG_OF
static const struct of_device_id sunxi_i2s_of_match[] = {
{ .compatible = "allwinner,sun7i-a20-i2s" },
{ }
};
MODULE_DEVICE_TABLE(of, sunxi_i2s_of_match);
#endif
static int sunxi_digitalaudio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
//struct snd_soc_dai_driver *soc_dai = sunxi_i2s_dai;
struct sunxi_priv *priv;
const struct of_device_id *of_id;
struct device *dev = &pdev->dev;
struct resource *res;
void __iomem *base;
int irq, i, tmp;
// WRONG struct snd_soc_card *card = &sunxi_dev;
int ret;
printk("[I2S]Entered %s\n", __func__);
//return -ENODEV; // AV test to check if driver is spitted out
if (!of_device_is_available(np))
return -ENODEV;
of_id = of_match_device(sunxi_i2s_of_match, dev);
if (!of_id)
return -EINVAL;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
printk("[AV]alloc ok %s\n", __func__);
priv->pdev = pdev;
priv->revision = (enum sunxi_soc_family)of_id->data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
&sunxi_i2s_regmap_config);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
// AV: some tests to check if the register address space can be read and written from the pointer assignes
/*
for (i = SUNXI_DA_CTL; i<= SUNXI_DA_RXCHMAP; i+=4) {
regmap_read(priv->regmap, SUNXI_DA_CTL, &tmp);
printk("DEB %s: DA CTL register 0x%02x: %x\n", __func__, i, tmp);
}
*/
/* Get the clocks from the DT */
priv->clk_apb = devm_clk_get(dev, "apb");
if (IS_ERR(priv->clk_apb)) {
dev_err(dev, "failed to get apb clock\n");
return PTR_ERR(priv->clk_apb);
}
/* Enable the bus clock */
if (clk_prepare_enable(priv->clk_apb)) {
dev_err(dev, "failed to enable apb clock\n");
return -EINVAL;
}
priv->clk_pll2 = devm_clk_get(dev, "audio");
if (IS_ERR(priv->clk_pll2)) {
dev_err(dev, "failed to get audio pll2 clock\n");
return PTR_ERR(priv->clk_pll2);
}
priv->clk_module = devm_clk_get(dev, "i2s");
if (IS_ERR(priv->clk_module)) {
dev_err(dev, "failed to get i2s module clock\n");
return PTR_ERR(priv->clk_module);
}
/* Enable the clock on a basic rate */
ret = clk_set_rate(priv->clk_pll2, 24576000);
if (ret) {
dev_err(dev, "failed to set pll2 base clock rate\n");
return -EINVAL;
}
if (clk_prepare_enable(priv->clk_pll2)) {
dev_err(dev, "failed to enable pll2 clock\n");
ret = -EINVAL;
goto exit_clkdisable_apb_clk;
}
if (clk_prepare_enable(priv->clk_module)) {
dev_err(dev, "failed to enable i2s module clock\n");
ret = -EINVAL;
goto exit_clkdisable_pll2_apb_clk;
}
dev_info(dev, "[AV] set clock and rate on i2s, %s\n", __func__);
// AV: some tests to check if the register address space can be read and written from the pointer assignes
/*
for (i = SUNXI_DA_CTL; i<= SUNXI_DA_RXCHMAP; i+=4) {
regmap_read(priv->regmap, SUNXI_DA_CTL, &tmp);
printk("DEB %s: Sunxi I2S DA register 0x%02x: %x\n", __func__, i, tmp);
}
*/
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
}
ret = devm_request_irq(&pdev->dev, irq, sunxi_dai_isr, 0, np->name, priv);
if (ret) {
dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
return ret;
}
dev_info(dev, "[AV] got assigned irq: %d\n", irq);
/* DMA configuration for TX FIFO */
priv->playback_dma_data.addr = res->start + SUNXI_DA_TXFIFO;
priv->playback_dma_data.maxburst = 4;
priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
/* DMA configuration for RX FIFO */
priv->capture_dma_data.addr = res->start + SUNXI_DA_RXFIFO;
priv->capture_dma_data.maxburst = 4;
priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
//WRONG dev_set_drvdata(&pdev->dev, priv);
platform_set_drvdata(pdev, priv);
dev_info(dev, "%s, [AV] set sunxi_priv into platform\n", __func__);
ret = devm_snd_soc_register_component(&pdev->dev, &sunxi_i2s_component, &sunxi_i2s_dai, 1);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_component failed (%d)\n", ret);
goto err_clk_disable;
}
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_dmaengine failed (%d)\n", ret);
goto err_clk_disable;
}
/*
* no platform in this module, because we are using dma_engine so do not need the platform.
* check sound/soc/fsl/fsl_sai.c (regmap/dmaengine) against sound/soc/kirkwood/kirkwood-i2s.c (we used as template but wrongly..)
*/
/*
ret = snd_soc_register_platform(&pdev->dev, &sunxi_soc_platform);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_platform failed\n");
goto err_platform;
}
*/
sunxi_i2s_init(priv);
return 0;
err_platform:
dev_info(&pdev->dev, "AV snd_soc_register_platform failed\n");
snd_soc_unregister_component(&pdev->dev);
err_clk_disable:
dev_info(&pdev->dev, "AV snd_soc_register_* failed\n");
if (!IS_ERR(priv->clk_module))
clk_disable_unprepare(priv->clk_module);
clk_disable_unprepare(priv->clk_apb);
return ret;
exit_clkdisable_pll2_apb_clk:
clk_disable_unprepare(priv->clk_pll2);
exit_clkdisable_apb_clk:
clk_disable_unprepare(priv->clk_apb);
return ret;
}
static int sunxi_digitalaudio_remove(struct platform_device *pdev)
{
struct sunxi_priv *priv = dev_get_drvdata(&pdev->dev);
//snd_soc_unregister_platform(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
if (!IS_ERR(priv->clk_apb))
clk_disable_unprepare(priv->clk_apb);
if (!IS_ERR(priv->clk_module))
clk_disable_unprepare(priv->clk_module);
return 0;
}
static struct platform_driver sunxi_i2s_driver = {
.probe = sunxi_digitalaudio_probe,
.remove = sunxi_digitalaudio_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = of_match_ptr(sunxi_i2s_of_match),
},
};
module_platform_driver(sunxi_i2s_driver);
/* Module information */
MODULE_DEVICE_TABLE(of, sunxi_i2s_of_match); // autoload from: https://lwn.net/Articles/448502/
MODULE_AUTHOR("Andrea Venturi, <be17068@iperbole.bo.it>");
MODULE_DESCRIPTION("Sunxi I2S ASoC Interface");
MODULE_LICENSE("GPL");
/*
* sunxi.h
*
* (c) 2015 Andrea Venturi <be17068@iperbole.bo.it>
*
* 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 _SUNXI_AUDIO_H
#define _SUNXI_AUDIO_H
/* Supported SoC families - used for quirks */
enum sunxi_soc_family {
SUN4IA, /* A10 SoC - revision A */
SUN4I, /* A10 SoC - later revisions */
SUN5I, /* A10S/A13 SoCs */
SUN7I, /* A20 SoC */
};
struct sunxi_priv {
struct platform_device *pdev;
struct regmap *regmap;
struct clk *clk_apb, *clk_module, *clk_pll2, *clk_pll2x8;
enum sunxi_soc_family revision;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct snd_dmaengine_dai_dma_data capture_dma_data;
};
/* Clock set cases*/
#define SUNXI_SET_MCLK 0
#define SUNXI_MCLKO_EN 1
/* Clock divider cases*/
#define SUNXI_DIV_MCLK 0
#define SUNXI_DIV_BCLK 1
#define SUNXI_SAMPLING_FREQ 2
struct sunxi_i2s_info {
//legacy void __iomem *regs; /* IIS BASE */
//legacy void __iomem *ccmregs; //CCM BASE
//legacy void __iomem *ioregs; //IO BASE
u32 slave; //0: master, 1: slave
u32 samp_fs; //audio sample rate (unit in Hz)
u32 samp_res; //16 bits, 20 bits , 24 bits, 32 bits)
u32 samp_format; //audio sample format (0: standard I2S, 1: left-justified, 2: right-justified, 3: pcm - MSB on 2nd BCLK, 4: pcm - MSB on 1st BCLK)
u32 ws_size; //16 BCLK, 20 BCLK, 24 BCLK, 32 BCLK)
u32 mclk_rate; //mclk frequency divide by fs (128fs, 192fs, 256fs, 384fs, 512fs, 768fs)
u32 lrc_pol; //LRC clock polarity (0: normal ,1: inverted)
u32 bclk_pol; //BCLK polarity (0: normal, 1: inverted)
u32 pcm_datamode; //PCM transmitter type (0: 16-bits linear mode, 1: 8-bits linear mode, 2: u-law, 3: A-law)
u32 pcm_sync_type; //PCM sync symbol size (0: short sync, 1: long sync)
u32 pcm_sw; //PCM slot width (8: 8 bits, 16: 16 bits)
u32 pcm_start_slot; //PCM start slot index (1--4)
u32 pcm_lsb_first; //0: MSB first, 1: LSB first
u32 pcm_sync_period;//PCM sync period (16/32/64/128/256)
u32 pcm_ch_num; //PCM channel number (1: one channel, 2: two channel)
};
/* I2S register offsets and bit fields */
/* for DA0 and presumibly for DA1 in A20 */
#define SUNXI_DA_CTL (0x00)
#define SUNXI_DA_CTL_SDO3_EN (1<<11)
#define SUNXI_DA_CTL_SDO2_EN (1<<10)
#define SUNXI_DA_CTL_SDO1_EN (1<<9)
#define SUNXI_DA_CTL_SDO0_EN (1<<8)
#define SUNXI_DA_CTL_ASS (1<<6) // Audio Sample Select, 0:zero, 1:last sample
#define SUNXI_DA_CTL_MS (1<<5) // 0:Master, 1:Slave
#define SUNXI_DA_CTL_PCM (1<<4) // 0:I2S, 1:PCM mode
#define SUNXI_DA_CTL_TXEN (1<<2)
#define SUNXI_DA_CTL_RXEN (1<<1)
#define SUNXI_DA_CTL_GEN (1<<0)
#define SUNXI_DA_FAT0 (0x04)
#define SUNXI_DA_FAT0_LRCP (1<<7) // in PCM mode, means MSB on first (1) or second (0) clock after LRCLK rising
#define SUNXI_DA_FAT0_BCP (1<<6)
#define SUNXI_DA_FAT0_SR(x) ((x)<<4) // Sample Resolution, 00:16, 01:20, 10:24, 11:reserved
#define SUNXI_DA_FAT0_SR_16 SUNXI_DA_FAT0_SR(0)
#define SUNXI_DA_FAT0_SR_20 SUNXI_DA_FAT0_SR(1)
#define SUNXI_DA_FAT0_SR_24 SUNXI_DA_FAT0_SR(2)
#define SUNXI_DA_FAT0_SR_MASK SUNXI_DA_FAT0_SR(3)
#define SUNXI_DA_FAT0_WSS(x) ((x)<<2) // WordSelectSize, BCLK 00:16, 01:20, 10:24, 11:32
#define SUNXI_DA_FAT0_WSS_16 SUNXI_DA_FAT0_WSS(0)
#define SUNXI_DA_FAT0_WSS_20 SUNXI_DA_FAT0_WSS(1)
#define SUNXI_DA_FAT0_WSS_24 SUNXI_DA_FAT0_WSS(2)
#define SUNXI_DA_FAT0_WSS_32 SUNXI_DA_FAT0_WSS(3)
#define SUNXI_DA_FAT0_FMT(x) ((x)<<0) // I2S format, 00:std, 01:left, 10:right, 11:reserved
#define SUNXI_DA_FAT0_FMT_STD SUNXI_DA_FAT0_FMT(0)
#define SUNXI_DA_FAT0_FMT_LEFT SUNXI_DA_FAT0_FMT(1)
#define SUNXI_DA_FAT0_FMT_RIGHT SUNXI_DA_FAT0_FMT(2)
#define SUNXI_DA_FAT1 (0x08)
#define SUNXI_DA_FAT1_PCM_SYNC_PERIOD(x) ((x)<<0)
#define SUNXI_DA_TXFIFO (0x0c)
#define SUNXI_DA_RXFIFO (0x10)
#define SUNXI_DA_FCTL (0x14)
#define SUNXI_DA_FCTL_FIFOSRC (31)
#define SUNXI_DA_FCTL_FTX (25)
#define SUNXI_DA_FCTL_FRX (24)
#define SUNXI_DA_FCTL_TXTL (12)
#define SUNXI_DA_FCTL_RXTL (4)
#define SUNXI_DA_FCTL_TXIM (2)
#define SUNXI_DA_FCTL_RXOM (0)
#define SUNXI_DA_FSTA (0x18) // Fifo Status Register
#define SUNXI_DA_INT (0x1c)
#define SUNXI_DA_INT_TX_DRQ (7)
#define SUNXI_DA_INT_RX_DRQ (3)
#define SUNXI_DA_ISTA (0x20) // Interrupt Status Register, could be used for check if TX/RX under/overrun
#define SUNXI_DA_CLKD (0x24) // FIXME CLKD, lot's of config to be done here
#define SUNXI_DA_CLKD_MCLKO_EN (7) // can be output also if the i2s intf is in slave mode
#define SUNXI_DA_CLKD_BCLKDIV (4) // 3 bits for bit clock divider from MCLK
#define SUNXI_DA_CLKD_MCLKDIV (0) // 4 bits for bit clock divider from MCLK
#define SUNXI_DA_TXCNT (0x28)
#define SUNXI_DA_TXCNT_TX_CNT (0)
#define SUNXI_DA_RXCNT (0x2c)
#define SUNXI_DA_RXCNT_RX_CNT (0)
#define SUNXI_DA_TXCHSEL (0x30)
#define SUNXI_DA_TXCHSEL_CHNUM(x) (((x)-1)<<0)
#define SUNXI_DA_TXCHMAP (0x34)
#define SUNXI_DA_TXCHMAP_TX_CH(x) (x<<(x<<2))
#define SUNXI_DA_RXCHSEL (0x38)
#define SUNXI_DA_RXCHSEL_CHNUM(x) (((x)-1)<<0)
#define SUNXI_DA_RXCHMAP (0x3c)
#define SUNXI_DA_RXCHMAP_RX_CH(x) (x<<(x<<2))
// SND_SOC_DAIFMT extension from legacy linux-sunxi I2S driver
// Format enumerations for completing the aSoC defines.
#define SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_MASK (3<<16)
#define SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_16BCLK (0<<16)
#define SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_20BCLK (1<<16)
#define SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_24BCLK (2<<16)
#define SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_32BCLK (3<<16)
#define SUNXI_IISFAT1_MASK (0xfff)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_PDM_MASK (3<<18) // RX and TC PCM Data Mode (PDM) are equal.
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_PDM_16PCM (0<<18)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_PDM_8PCM (1<<18)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_PDM_8ULAW (2<<18)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_PDM_8ALAW (3<<18)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SSYNC (1<<20)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SW (1<<21)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SI_MASK (3<<22)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SI_1ST (0<<22)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SI_2ND (1<<22)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SI_3RD (2<<22)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SI_4TH (3<<22)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SEXT (1<<24)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_MLS (1<<25)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_OUTMUTE (1<<26)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SYNCOUTEN (1<<27)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SYNCLEN_MASK (7<<28)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SYNCLEN_16BCLK (0<<28)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SYNCLEN_32BCLK (1<<28)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SYNCLEN_64BCLK (2<<28)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SYNCLEN_128BCLK (3<<28)
#define SND_SOC_DAIFMT_SUNXI_IISFAT1_SYNCLEN_256BCLK (4<<28)
#define SUNXI_I2S_RATES (SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT)
#define SUNXI_I2S_PLAYBACK_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
#define SUNXI_I2S_CAPTURE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
// FIXME from kirkwood-i2s, obsoleted by dmaengine, i suppose
struct sunxi_dma_data {
void __iomem *io;
struct clk *clk;
struct clk *extclk;
uint32_t ctl_play;
uint32_t ctl_rec;
struct snd_pcm_substream *substream_play;
struct snd_pcm_substream *substream_rec;
int irq;
int burst;
};
extern struct snd_soc_platform_driver sunxi_soc_platform;
#endif
diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c
index dc7778b..9a236d2 100644
--- a/sound/soc/codecs/uda1380.c
+++ b/sound/soc/codecs/uda1380.c
@@ -149,7 +149,10 @@ static int uda1380_reset(struct snd_soc_codec *codec)
{
struct uda1380_platform_data *pdata = codec->dev->platform_data;
+ dev_info(codec->dev, "%s: codec soft reset\n", __func__);
+ return 0; // bypassed the underlying "soft reset" as it was
if (gpio_is_valid(pdata->gpio_reset)) {
+ dev_err(codec->dev, "%s: gpio reset is valid\n", __func__);
gpio_set_value(pdata->gpio_reset, 1);
mdelay(1);
gpio_set_value(pdata->gpio_reset, 0);
@@ -403,8 +406,11 @@ static const struct snd_soc_dapm_route uda1380_dapm_routes[] = {
{"Input Mux", "Line", "Left PGA"},
/* right input */
- {"Right ADC", "Mic + Line R", "Right PGA"},
- {"Right ADC", "Line", "Right PGA"},
+ {"Right ADC", NULL, "Input Mux"},
+ {"Input Mux", "Mic", "Mic LNA"},
+ {"Input Mux", "Mic + Line R", "Right PGA"},
+ {"Input Mux", "Line L", "Left PGA"},
+ {"Input Mux", "Line", "Right PGA"},
/* inputs */
{"Mic LNA", NULL, "VINM"},
@@ -601,11 +607,14 @@ static int uda1380_set_bias_level(struct snd_soc_codec *codec,
break;
case SND_SOC_BIAS_STANDBY:
if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /*
if (gpio_is_valid(pdata->gpio_power)) {
- gpio_set_value(pdata->gpio_power, 1);
+ printk("uda1380: AV bias_level, GPIO power is valid?! \n");
+ //gpio_set_value(pdata->gpio_power, 1); FIXME AV gpio_power should now be valid
mdelay(1);
uda1380_reset(codec);
}
+ */
uda1380_sync_cache(codec);
}
@@ -696,20 +705,47 @@ static struct snd_soc_dai_driver uda1380_dai[] = {
static int uda1380_probe(struct snd_soc_codec *codec)
{
struct uda1380_platform_data *pdata =codec->dev->platform_data;
+ void *np =codec->dev->of_node; // AV added for codec data coming from DT and not from legacy platform data
struct uda1380_priv *uda1380 = snd_soc_codec_get_drvdata(codec);
int ret;
uda1380->codec = codec;
+ printk("uda1380: AV probe %x dac_clk %x\n", 1, uda1380->dac_clk);
codec->hw_write = (hw_write_t)i2c_master_send;
codec->control_data = uda1380->control_data;
- if (!pdata)
- return -EINVAL;
+ // if pdata is NULL, it could be the platform data are coming from DT..
+ // see: https://lwn.net/Articles/448502/
+ if (!pdata) {
+ if (np) {
+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(codec->dev,
+ "ASoC: %s can't allocate platform data for %s\n",
+ __func__, codec->component.name);
+ return -ENOMEM;
+ }
+ pdata->gpio_power = -1; // put not valid gpio if not overridden by DT
+ pdata->gpio_reset = -1; // put not valid gpio if not overridden by DT
+ printk("uda1380: AV platf-data from DT\n");
+ of_property_read_u32(np, "dac-clk", &pdata->dac_clk);
+ of_property_read_u32(np, "power-gpio", &pdata->gpio_power);
+ //if (!pdata->gpio_power) pdata->gpio_power = -EINVAL; // so gpio_is_valid() is false
+ of_property_read_u32(np, "reset-gpio", &pdata->gpio_reset);
+ // copy dac_clk from platform_data to private_data (redundant?)
+ uda1380->dac_clk=pdata->dac_clk;
+ printk("uda1380: AV dac_clk %x\n", uda1380->dac_clk);
+ printk("uda1380: AV gpio reset %x\n", pdata->gpio_reset);
+ }
+ else
+ return -EINVAL;
+ }
if (gpio_is_valid(pdata->gpio_reset)) {
ret = gpio_request_one(pdata->gpio_reset, GPIOF_OUT_INIT_LOW,
"uda1380 reset");
+ printk("uda1380: AV get reset gpio, ret %x\n", ret);
if (ret)
goto err_out;
}
@@ -717,14 +753,17 @@ static int uda1380_probe(struct snd_soc_codec *codec)
if (gpio_is_valid(pdata->gpio_power)) {
ret = gpio_request_one(pdata->gpio_power, GPIOF_OUT_INIT_LOW,
"uda1380 power");
+ printk("uda1380: AV get power gpio, ret %x\n", ret);
if (ret)
goto err_free_gpio;
} else {
+ printk("uda1380: AV doing reset, ret %x\n", ret);
ret = uda1380_reset(codec);
if (ret)
goto err_free_gpio;
}
+ printk("uda1380: init work\n");
INIT_WORK(&uda1380->work, uda1380_flush_work);
/* set clock input */
@@ -736,6 +775,10 @@ static int uda1380_probe(struct snd_soc_codec *codec)
uda1380_write_reg_cache(codec, UDA1380_CLK,
R00_DAC_CLK);
break;
+ default:
+ printk("uda1380: AV got funny dac_clk, val %x\n", pdata->dac_clk);
+ uda1380_write_reg_cache(codec, UDA1380_CLK, 0);
+ break;
}
return 0;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment