-
-
Save aventuri/c40d08b946b0d1da5081 to your computer and use it in GitHub Desktop.
sun7i I2S DAI ASoC module and codec UDA1380 for simple sound card
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
+ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 = <®_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 = <®_axp_ipsout>; | |
vin2-supply = <®_axp_ipsout>; | |
vin3-supply = <®_axp_ipsout>; | |
ldo24in-supply = <®_axp_ipsout>; | |
ldo3in-supply = <®_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 = <®_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>; | |
}; | |
}; | |
®_ahci_5v { | |
pinctrl-0 = <&ahci_pwr_pin_olinuxinolime>; | |
gpio = <&pio 2 3 GPIO_ACTIVE_HIGH>; | |
status = "okay"; | |
}; | |
®_usb1_vbus { | |
status = "okay"; | |
}; | |
®_usb2_vbus { | |
status = "okay"; | |
}; | |
&uart0 { | |
pinctrl-names = "default"; | |
pinctrl-0 = <&uart0_pins_a>; | |
status = "okay"; | |
}; | |
&usbphy { | |
usb1_vbus-supply = <®_usb1_vbus>; | |
usb2_vbus-supply = <®_usb2_vbus>; | |
status = "okay"; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_val1 ); | |
regmap_read(priv->regmap, SUNXI_DA_FAT0, ®_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, ®_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, ®_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, ®save[0]); | |
regmap_read(priv->regmap, SUNXI_DA_FAT0, ®save[1]); | |
regmap_read(priv->regmap, SUNXI_DA_FAT1, ®save[2]); | |
regmap_read(priv->regmap, SUNXI_DA_FCTL, ®save[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, ®save[4]); | |
regmap_read(priv->regmap, SUNXI_DA_CLKD, ®save[5]); | |
regmap_read(priv->regmap, SUNXI_DA_TXCHSEL, ®save[6]); | |
regmap_read(priv->regmap, SUNXI_DA_TXCHMAP, ®save[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"); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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