Commit 9a6a362a authored by Mark Brown's avatar Mark Brown

Merge remote-tracking branches 'asoc/topic/samsung', 'asoc/topic/sgtl5000',...

Merge remote-tracking branches 'asoc/topic/samsung', 'asoc/topic/sgtl5000', 'asoc/topic/sti' and 'asoc/topic/sunxi' into asoc-next
Samsung Exynos Odroid X2/U3 audio complex with MAX98090 codec
Required properties:
- compatible : "samsung,odroidx2-audio" - for Odroid X2 board,
"samsung,odroidu3-audio" - for Odroid U3 board
- samsung,model : the user-visible name of this sound complex
- samsung,i2s-controller : the phandle of the I2S controller
- samsung,audio-codec : the phandle of the MAX98090 audio codec
- samsung,audio-routing : a list of the connections between audio
components; each entry is a pair of strings, the first being the
connection's sink, the second being the connection's source;
valid names for sources and sinks are the MAX98090's pins (as
documented in its binding), and the jacks on the board
For Odroid X2:
* Headphone Jack
* Mic Jack
* DMIC
For Odroid U3:
* Headphone Jack
* Speakers
Example:
sound {
compatible = "samsung,odroidu3-audio";
samsung,i2s-controller = <&i2s0>;
samsung,audio-codec = <&max98090>;
samsung,model = "Odroid-X2";
samsung,audio-routing =
"Headphone Jack", "HPL",
"Headphone Jack", "HPR",
"IN1", "Mic Jack",
"Mic Jack", "MICBIAS";
};
......@@ -7,6 +7,14 @@ Required properties:
- clocks : the clock provider of SYS_MCLK
- VDDA-supply : the regulator provider of VDDA
- VDDIO-supply: the regulator provider of VDDIO
Optional properties:
- VDDD-supply : the regulator provider of VDDD
- micbias-resistor-k-ohms : the bias resistor to be used in kOmhs
The resistor can take values of 2k, 4k or 8k.
If set to 0 it will be off.
......@@ -15,17 +23,9 @@ Required properties:
- micbias-voltage-m-volts : the bias voltage to be used in mVolts
The voltage can take values from 1.25V to 3V by 250mV steps
If this node is not mentionned or the value is unknown, then
If this node is not mentioned or the value is unknown, then
the value is set to 1.25V.
- VDDA-supply : the regulator provider of VDDA
- VDDIO-supply: the regulator provider of VDDIO
Optional properties:
- VDDD-supply : the regulator provider of VDDD
Example:
codec: sgtl5000@0a {
......
......@@ -33,11 +33,11 @@ Required properties:
"tx" for "st,sti-uni-player" compatibility
"rx" for "st,sti-uni-reader" compatibility
- version: IP version integrated in SOC.
- st,version: IP version integrated in SOC.
- dai-name: DAI name that describes the IP.
- IP mode: IP working mode depending on associated codec.
- st,mode: IP working mode depending on associated codec.
"HDMI" connected to HDMI codec and support IEC HDMI formats (player only).
"SPDIF" connected to SPDIF codec and support SPDIF formats (player only).
"PCM" PCM standard mode for I2S or TDM bus.
......@@ -47,7 +47,7 @@ Required properties ("st,sti-uni-player" compatibility only):
- clocks: CPU_DAI IP clock source, listed in the same order than the
CPU_DAI properties.
- uniperiph-id: internal SOC IP instance ID.
- st,uniperiph-id: internal SOC IP instance ID.
Optional properties:
- pinctrl-0: defined for CPU_DAI@1 and CPU_DAI@4 to describe I2S PIOs for
......@@ -84,9 +84,9 @@ Example:
dmas = <&fdma0 4 0 1>;
dai-name = "Uni Player #2 (DAC)";
dma-names = "tx";
uniperiph-id = <2>;
version = <5>;
mode = "PCM";
st,uniperiph-id = <2>;
st,version = <5>;
st,mode = "PCM";
};
sti_uni_player3: sti-uni-player@3 {
......@@ -100,9 +100,9 @@ Example:
dmas = <&fdma0 7 0 1>;
dma-names = "tx";
dai-name = "Uni Player #3 (SPDIF)";
uniperiph-id = <3>;
version = <5>;
mode = "SPDIF";
st,uniperiph-id = <3>;
st,version = <5>;
st,mode = "SPDIF";
};
sti_uni_reader1: sti-uni-reader@1 {
......@@ -115,7 +115,7 @@ Example:
dmas = <&fdma0 6 0 1>;
dma-names = "rx";
dai-name = "Uni Reader #1 (HDMI RX)";
version = <3>;
st,version = <3>;
st,mode = "PCM";
};
......
* Allwinner A10 I2S controller
The I2S bus (Inter-IC sound bus) is a serial link for digital
audio data transfer between devices in the system.
Required properties:
- compatible: should be one of the followings
- "allwinner,sun4i-a10-i2s"
- reg: physical base address of the controller and length of memory mapped
region.
- interrupts: should contain the I2S interrupt.
- dmas: DMA specifiers for tx and rx dma. See the DMA client binding,
Documentation/devicetree/bindings/dma/dma.txt
- dma-names: should include "tx" and "rx".
- clocks: a list of phandle + clock-specifer pairs, one for each entry in clock-names.
- clock-names: should contain followings:
- "apb" : clock for the I2S bus interface
- "mod" : module clock for the I2S controller
- #sound-dai-cells : Must be equal to 0
Example:
i2s0: i2s@01c22400 {
#sound-dai-cells = <0>;
compatible = "allwinner,sun4i-a10-i2s";
reg = <0x01c22400 0x400>;
interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&apb0_gates 3>, <&i2s0_clk>;
clock-names = "apb", "mod";
dmas = <&dma SUN4I_DMA_NORMAL 3>,
<&dma SUN4I_DMA_NORMAL 3>;
dma-names = "rx", "tx";
};
......@@ -1608,7 +1608,6 @@ F: drivers/*/*/*s3c2410*
F: drivers/memory/samsung/*
F: drivers/soc/samsung/*
F: drivers/spi/spi-s3c*
F: sound/soc/samsung/*
F: Documentation/arm/Samsung/
F: Documentation/devicetree/bindings/arm/samsung/
F: Documentation/devicetree/bindings/sram/samsung-sram.txt
......@@ -9903,7 +9902,9 @@ S: Maintained
F: drivers/platform/x86/samsung-laptop.c
SAMSUNG AUDIO (ASoC) DRIVERS
M: Krzysztof Kozlowski <k.kozlowski@samsung.com>
M: Sangbeom Kim <sbkim73@samsung.com>
M: Sylwester Nawrocki <s.nawrocki@samsung.com>
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
S: Supported
F: sound/soc/samsung/
......
......@@ -38,7 +38,6 @@
/* default value of sgtl5000 registers */
static const struct reg_default sgtl5000_reg_defaults[] = {
{ SGTL5000_CHIP_DIG_POWER, 0x0000 },
{ SGTL5000_CHIP_CLK_CTRL, 0x0008 },
{ SGTL5000_CHIP_I2S_CTRL, 0x0010 },
{ SGTL5000_CHIP_SSS_CTRL, 0x0010 },
{ SGTL5000_CHIP_ADCDAC_CTRL, 0x020c },
......@@ -47,12 +46,10 @@ static const struct reg_default sgtl5000_reg_defaults[] = {
{ SGTL5000_CHIP_ANA_ADC_CTRL, 0x0000 },
{ SGTL5000_CHIP_ANA_HP_CTRL, 0x1818 },
{ SGTL5000_CHIP_ANA_CTRL, 0x0111 },
{ SGTL5000_CHIP_LINREG_CTRL, 0x0000 },
{ SGTL5000_CHIP_REF_CTRL, 0x0000 },
{ SGTL5000_CHIP_MIC_CTRL, 0x0000 },
{ SGTL5000_CHIP_LINE_OUT_CTRL, 0x0000 },
{ SGTL5000_CHIP_LINE_OUT_VOL, 0x0404 },
{ SGTL5000_CHIP_ANA_POWER, 0x7060 },
{ SGTL5000_CHIP_PLL_CTRL, 0x5000 },
{ SGTL5000_CHIP_CLK_TOP_CTRL, 0x0000 },
{ SGTL5000_CHIP_ANA_STATUS, 0x0000 },
......@@ -92,35 +89,8 @@ static const char *supply_names[SGTL5000_SUPPLY_NUM] = {
"VDDD"
};
#define LDO_CONSUMER_NAME "VDDD_LDO"
#define LDO_VOLTAGE 1200000
static struct regulator_consumer_supply ldo_consumer[] = {
REGULATOR_SUPPLY(LDO_CONSUMER_NAME, NULL),
};
static struct regulator_init_data ldo_init_data = {
.constraints = {
.min_uV = 1200000,
.max_uV = 1200000,
.valid_modes_mask = REGULATOR_MODE_NORMAL,
.valid_ops_mask = REGULATOR_CHANGE_STATUS,
},
.num_consumer_supplies = 1,
.consumer_supplies = &ldo_consumer[0],
};
/*
* sgtl5000 internal ldo regulator,
* enabled when VDDD not provided
*/
struct ldo_regulator {
struct regulator_desc desc;
struct regulator_dev *dev;
int voltage;
void *codec_data;
bool enabled;
};
#define LINREG_VDDD ((1600 - LDO_VOLTAGE / 1000) / 50)
enum sgtl5000_micbias_resistor {
SGTL5000_MICBIAS_OFF = 0,
......@@ -135,7 +105,7 @@ struct sgtl5000_priv {
int master; /* i2s master or not */
int fmt; /* i2s data format */
struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM];
struct ldo_regulator *ldo;
int num_supplies;
struct regmap *regmap;
struct clk *mclk;
int revision;
......@@ -415,6 +385,9 @@ static const DECLARE_TLV_DB_RANGE(mic_gain_tlv,
/* tlv for hp volume, -51.5db to 12.0db, step .5db */
static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0);
/* tlv for lineout volume, 31 steps of .5db each */
static const DECLARE_TLV_DB_SCALE(lineout_volume, -1550, 50, 0);
static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
/* SOC_DOUBLE_S8_TLV with invert */
{
......@@ -443,6 +416,13 @@ static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL,
0, 3, 0, mic_gain_tlv),
SOC_DOUBLE_TLV("Lineout Playback Volume",
SGTL5000_CHIP_LINE_OUT_VOL,
SGTL5000_LINE_OUT_VOL_LEFT_SHIFT,
SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT,
0x1f, 1,
lineout_volume),
};
/* mute the codec used by alsa core */
......@@ -778,155 +758,6 @@ static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream,
return 0;
}
#ifdef CONFIG_REGULATOR
static int ldo_regulator_is_enabled(struct regulator_dev *dev)
{
struct ldo_regulator *ldo = rdev_get_drvdata(dev);
return ldo->enabled;
}
static int ldo_regulator_enable(struct regulator_dev *dev)
{
struct ldo_regulator *ldo = rdev_get_drvdata(dev);
struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data;
int reg;
if (ldo_regulator_is_enabled(dev))
return 0;
/* set regulator value firstly */
reg = (1600 - ldo->voltage / 1000) / 50;
reg = clamp(reg, 0x0, 0xf);
/* amend the voltage value, unit: uV */
ldo->voltage = (1600 - reg * 50) * 1000;
/* set voltage to register */
snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
SGTL5000_LINREG_VDDD_MASK, reg);
snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
SGTL5000_LINEREG_D_POWERUP,
SGTL5000_LINEREG_D_POWERUP);
/* when internal ldo is enabled, simple digital power can be disabled */
snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
SGTL5000_LINREG_SIMPLE_POWERUP,
0);
ldo->enabled = 1;
return 0;
}
static int ldo_regulator_disable(struct regulator_dev *dev)
{
struct ldo_regulator *ldo = rdev_get_drvdata(dev);
struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data;
snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
SGTL5000_LINEREG_D_POWERUP,
0);
/* clear voltage info */
snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
SGTL5000_LINREG_VDDD_MASK, 0);
ldo->enabled = 0;
return 0;
}
static int ldo_regulator_get_voltage(struct regulator_dev *dev)
{
struct ldo_regulator *ldo = rdev_get_drvdata(dev);
return ldo->voltage;
}
static struct regulator_ops ldo_regulator_ops = {
.is_enabled = ldo_regulator_is_enabled,
.enable = ldo_regulator_enable,
.disable = ldo_regulator_disable,
.get_voltage = ldo_regulator_get_voltage,
};
static int ldo_regulator_register(struct snd_soc_codec *codec,
struct regulator_init_data *init_data,
int voltage)
{
struct ldo_regulator *ldo;
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
struct regulator_config config = { };
ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL);
if (!ldo)
return -ENOMEM;
ldo->desc.name = kstrdup(dev_name(codec->dev), GFP_KERNEL);
if (!ldo->desc.name) {
kfree(ldo);
dev_err(codec->dev, "failed to allocate decs name memory\n");
return -ENOMEM;
}
ldo->desc.type = REGULATOR_VOLTAGE;
ldo->desc.owner = THIS_MODULE;
ldo->desc.ops = &ldo_regulator_ops;
ldo->desc.n_voltages = 1;
ldo->codec_data = codec;
ldo->voltage = voltage;
config.dev = codec->dev;
config.driver_data = ldo;
config.init_data = init_data;
ldo->dev = regulator_register(&ldo->desc, &config);
if (IS_ERR(ldo->dev)) {
int ret = PTR_ERR(ldo->dev);
dev_err(codec->dev, "failed to register regulator\n");
kfree(ldo->desc.name);
kfree(ldo);
return ret;
}
sgtl5000->ldo = ldo;
return 0;
}
static int ldo_regulator_remove(struct snd_soc_codec *codec)
{
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
struct ldo_regulator *ldo = sgtl5000->ldo;
if (!ldo)
return 0;
regulator_unregister(ldo->dev);
kfree(ldo->desc.name);
kfree(ldo);
return 0;
}
#else
static int ldo_regulator_register(struct snd_soc_codec *codec,
struct regulator_init_data *init_data,
int voltage)
{
dev_err(codec->dev, "this setup needs regulator support in the kernel\n");
return -EINVAL;
}
static int ldo_regulator_remove(struct snd_soc_codec *codec)
{
return 0;
}
#endif
/*
* set dac bias
* common state changes:
......@@ -940,42 +771,17 @@ static int ldo_regulator_remove(struct snd_soc_codec *codec)
static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
int ret;
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
switch (level) {
case SND_SOC_BIAS_ON:
case SND_SOC_BIAS_PREPARE:
break;
case SND_SOC_BIAS_STANDBY:
if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) {
ret = regulator_bulk_enable(
ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
if (ret)
return ret;
udelay(10);
regcache_cache_only(sgtl5000->regmap, false);
ret = regcache_sync(sgtl5000->regmap);
if (ret != 0) {
dev_err(codec->dev,
"Failed to restore cache: %d\n", ret);
regcache_cache_only(sgtl5000->regmap, true);
regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
return ret;
}
}
snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
SGTL5000_REFTOP_POWERUP,
SGTL5000_REFTOP_POWERUP);
break;
case SND_SOC_BIAS_OFF:
regcache_cache_only(sgtl5000->regmap, true);
regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
SGTL5000_REFTOP_POWERUP, 0);
break;
}
......@@ -1113,7 +919,6 @@ static const u8 vol_quot_table[] = {
* and should be set according to:
* 1. vddd provided by external or not
* 2. vdda and vddio voltage value. > 3.1v or not
* 3. chip revision >=0x11 or not. If >=0x11, not use external vddd.
*/
static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
{
......@@ -1131,7 +936,9 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
vdda = regulator_get_voltage(sgtl5000->supplies[VDDA].consumer);
vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO].consumer);
vddd = regulator_get_voltage(sgtl5000->supplies[VDDD].consumer);
vddd = (sgtl5000->num_supplies > VDDD)
? regulator_get_voltage(sgtl5000->supplies[VDDD].consumer)
: LDO_VOLTAGE;
vdda = vdda / 1000;
vddio = vddio / 1000;
......@@ -1178,25 +985,6 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
/* set voltage to register */
snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
SGTL5000_LINREG_VDDD_MASK, 0x8);
/*
* if vddd linear reg has been enabled,
* simple digital supply should be clear to get
* proper VDDD voltage.
*/
if (ana_pwr & SGTL5000_LINEREG_D_POWERUP)
snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
SGTL5000_LINREG_SIMPLE_POWERUP,
0);
else
snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
SGTL5000_LINREG_SIMPLE_POWERUP |
SGTL5000_STARTUP_POWERUP,
0);
/*
* set ADC/DAC VAG to vdda / 2,
* should stay in range (0.8v, 1.575v)
......@@ -1256,78 +1044,43 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
return 0;
}
static int sgtl5000_replace_vddd_with_ldo(struct snd_soc_codec *codec)
{
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
int ret;
/* set internal ldo to 1.2v */
ret = ldo_regulator_register(codec, &ldo_init_data, LDO_VOLTAGE);
if (ret) {
dev_err(codec->dev,
"Failed to register vddd internal supplies: %d\n", ret);
return ret;
}
sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME;
dev_info(codec->dev, "Using internal LDO instead of VDDD\n");
return 0;
}
static int sgtl5000_enable_regulators(struct snd_soc_codec *codec)
static int sgtl5000_enable_regulators(struct i2c_client *client)
{
int ret;
int i;
int external_vddd = 0;
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
struct regulator *vddd;
struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client);
for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++)
sgtl5000->supplies[i].supply = supply_names[i];
/* External VDDD only works before revision 0x11 */
if (sgtl5000->revision < 0x11) {
vddd = regulator_get_optional(codec->dev, "VDDD");
if (IS_ERR(vddd)) {
/* See if it's just not registered yet */
if (PTR_ERR(vddd) == -EPROBE_DEFER)
return -EPROBE_DEFER;
} else {
external_vddd = 1;
regulator_put(vddd);
}
}
if (!external_vddd) {
ret = sgtl5000_replace_vddd_with_ldo(codec);
if (ret)
return ret;
vddd = regulator_get_optional(&client->dev, "VDDD");
if (IS_ERR(vddd)) {
/* See if it's just not registered yet */
if (PTR_ERR(vddd) == -EPROBE_DEFER)
return -EPROBE_DEFER;
} else {
external_vddd = 1;
regulator_put(vddd);
}
ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->num_supplies = ARRAY_SIZE(sgtl5000->supplies)
- 1 + external_vddd;
ret = regulator_bulk_get(&client->dev, sgtl5000->num_supplies,
sgtl5000->supplies);
if (ret)
goto err_ldo_remove;
ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
if (ret)
goto err_regulator_free;
/* wait for all power rails bring up */
udelay(10);
return ret;
return 0;
ret = regulator_bulk_enable(sgtl5000->num_supplies,
sgtl5000->supplies);
if (!ret)
usleep_range(10, 20);
else
regulator_bulk_free(sgtl5000->num_supplies,
sgtl5000->supplies);
err_regulator_free:
regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
err_ldo_remove:
if (!external_vddd)
ldo_regulator_remove(codec);
return ret;
}
static int sgtl5000_probe(struct snd_soc_codec *codec)
......@@ -1335,10 +1088,6 @@ static int sgtl5000_probe(struct snd_soc_codec *codec)
int ret;
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
ret = sgtl5000_enable_regulators(codec);
if (ret)
return ret;
/* power up sgtl5000 */
ret = sgtl5000_set_power_regs(codec);
if (ret)
......@@ -1389,25 +1138,11 @@ static int sgtl5000_probe(struct snd_soc_codec *codec)
return 0;
err:
regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
ldo_regulator_remove(codec);
return ret;
}
static int sgtl5000_remove(struct snd_soc_codec *codec)
{
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
sgtl5000->supplies);
ldo_regulator_remove(codec);
return 0;
}
......@@ -1448,8 +1183,9 @@ static const struct regmap_config sgtl5000_regmap = {
* and avoid problems like, not being able to probe after an audio playback
* followed by a system reset or a 'reboot' command in Linux
*/
static int sgtl5000_fill_defaults(struct sgtl5000_priv *sgtl5000)
static void sgtl5000_fill_defaults(struct i2c_client *client)
{
struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client);
int i, ret, val, index;
for (i = 0; i < ARRAY_SIZE(sgtl5000_reg_defaults); i++) {
......@@ -1457,10 +1193,10 @@ static int sgtl5000_fill_defaults(struct sgtl5000_priv *sgtl5000)
index = sgtl5000_reg_defaults[i].reg;
ret = regmap_write(sgtl5000->regmap, index, val);
if (ret)
return ret;
dev_err(&client->dev,
"%s: error %d setting reg 0x%02x to 0x%04x\n",
__func__, ret, index, val);
}
return 0;
}
static int sgtl5000_i2c_probe(struct i2c_client *client,
......@@ -1470,16 +1206,23 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
int ret, reg, rev;
struct device_node *np = client->dev.of_node;
u32 value;
u16 ana_pwr;
sgtl5000 = devm_kzalloc(&client->dev, sizeof(*sgtl5000), GFP_KERNEL);
if (!sgtl5000)
return -ENOMEM;
i2c_set_clientdata(client, sgtl5000);
ret = sgtl5000_enable_regulators(client);
if (ret)
return ret;
sgtl5000->regmap = devm_regmap_init_i2c(client, &sgtl5000_regmap);
if (IS_ERR(sgtl5000->regmap)) {
ret = PTR_ERR(sgtl5000->regmap);
dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret);
return ret;
goto disable_regs;
}
sgtl5000->mclk = devm_clk_get(&client->dev, NULL);
......@@ -1488,21 +1231,25 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
dev_err(&client->dev, "Failed to get mclock: %d\n", ret);
/* Defer the probe to see if the clk will be provided later */
if (ret == -ENOENT)
return -EPROBE_DEFER;
return ret;
ret = -EPROBE_DEFER;
goto disable_regs;
}
ret = clk_prepare_enable(sgtl5000->mclk);
if (ret)
return ret;
if (ret) {
dev_err(&client->dev, "Error enabling clock %d\n", ret);
goto disable_regs;
}
/* Need 8 clocks before I2C accesses */
udelay(1);
/* read chip information */
ret = regmap_read(sgtl5000->regmap, SGTL5000_CHIP_ID, &reg);
if (ret)
if (ret) {
dev_err(&client->dev, "Error reading chip id %d\n", ret);
goto disable_clk;
}
if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) !=
SGTL5000_PARTID_PART_ID) {
......@@ -1516,6 +1263,44 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
dev_info(&client->dev, "sgtl5000 revision 0x%x\n", rev);
sgtl5000->revision = rev;
/* reconfigure the clocks in case we're using the PLL */
ret = regmap_write(sgtl5000->regmap,
SGTL5000_CHIP_CLK_CTRL,
SGTL5000_CHIP_CLK_CTRL_DEFAULT);
if (ret)
dev_err(&client->dev,
"Error %d initializing CHIP_CLK_CTRL\n", ret);
/* Follow section 2.2.1.1 of AN3663 */
ana_pwr = SGTL5000_ANA_POWER_DEFAULT;
if (sgtl5000->num_supplies <= VDDD) {
/* internal VDDD at 1.2V */
ret = regmap_update_bits(sgtl5000->regmap,
SGTL5000_CHIP_LINREG_CTRL,
SGTL5000_LINREG_VDDD_MASK,
LINREG_VDDD);
if (ret)
dev_err(&client->dev,
"Error %d setting LINREG_VDDD\n", ret);
ana_pwr |= SGTL5000_LINEREG_D_POWERUP;
dev_info(&client->dev,
"Using internal LDO instead of VDDD: check ER1\n");
} else {
/* using external LDO for VDDD
* Clear startup powerup and simple powerup
* bits to save power
*/
ana_pwr &= ~(SGTL5000_STARTUP_POWERUP
| SGTL5000_LINREG_SIMPLE_POWERUP);
dev_dbg(&client->dev, "Using external VDDD\n");
}
ret = regmap_write(sgtl5000->regmap, SGTL5000_CHIP_ANA_POWER, ana_pwr);
if (ret)
dev_err(&client->dev,
"Error %d setting CHIP_ANA_POWER to %04x\n",
ret, ana_pwr);
if (np) {
if (!of_property_read_u32(np,
"micbias-resistor-k-ohms", &value)) {
......@@ -1557,12 +1342,8 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
}
}
i2c_set_clientdata(client, sgtl5000);
/* Ensure sgtl5000 will start with sane register values */
ret = sgtl5000_fill_defaults(sgtl5000);
if (ret)
goto disable_clk;
sgtl5000_fill_defaults(client);
ret = snd_soc_register_codec(&client->dev,
&sgtl5000_driver, &sgtl5000_dai, 1);
......@@ -1573,6 +1354,11 @@ static int sgtl5000_i2c_probe(struct i2c_client *client,
disable_clk:
clk_disable_unprepare(sgtl5000->mclk);
disable_regs:
regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies);
regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies);
return ret;
}
......@@ -1582,6 +1368,9 @@ static int sgtl5000_i2c_remove(struct i2c_client *client)
snd_soc_unregister_codec(&client->dev);
clk_disable_unprepare(sgtl5000->mclk);
regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies);
regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies);
return 0;
}
......
......@@ -92,6 +92,7 @@
/*
* SGTL5000_CHIP_CLK_CTRL
*/
#define SGTL5000_CHIP_CLK_CTRL_DEFAULT 0x0008
#define SGTL5000_RATE_MODE_MASK 0x0030
#define SGTL5000_RATE_MODE_SHIFT 4
#define SGTL5000_RATE_MODE_WIDTH 2
......@@ -325,6 +326,7 @@
/*
* SGTL5000_CHIP_ANA_POWER
*/
#define SGTL5000_ANA_POWER_DEFAULT 0x7060
#define SGTL5000_DAC_STEREO 0x4000
#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000
#define SGTL5000_STARTUP_POWERUP 0x1000
......
......@@ -224,14 +224,6 @@ config SND_SOC_SNOW
Say Y if you want to add audio support for various Snow
boards based on Exynos5 series of SoCs.
config SND_SOC_ODROIDX2
tristate "Audio support for Odroid-X2 and Odroid-U3"
depends on SND_SOC_SAMSUNG && I2C
select SND_SOC_MAX98090
select SND_SAMSUNG_I2S
help
Say Y here to enable audio support for the Odroid-X2/U3.
config SND_SOC_ARNDALE_RT5631_ALC5631
tristate "Audio support for RT5631(ALC5631) on Arndale Board"
depends on SND_SOC_SAMSUNG && I2C
......
......@@ -43,7 +43,6 @@ snd-soc-tobermory-objs := tobermory.o
snd-soc-lowland-objs := lowland.o
snd-soc-littlemill-objs := littlemill.o
snd-soc-bells-objs := bells.o
snd-soc-odroidx2-max98090-objs := odroidx2_max98090.o
snd-soc-arndale-rt5631-objs := arndale_rt5631.o
obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o
......@@ -69,5 +68,4 @@ obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o
obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o
obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o
obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o
obj-$(CONFIG_SND_SOC_ODROIDX2) += snd-soc-odroidx2-max98090.o
obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o
......@@ -389,7 +389,8 @@ static int s3c_ac97_probe(struct platform_device *pdev)
goto err5;
ret = samsung_asoc_dma_platform_register(&pdev->dev,
ac97_pdata->dma_filter);
ac97_pdata->dma_filter,
NULL, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err5;
......
......@@ -26,7 +26,10 @@ struct s3c_dma_params {
void samsung_asoc_init_dma_data(struct snd_soc_dai *dai,
struct s3c_dma_params *playback,
struct s3c_dma_params *capture);
int samsung_asoc_dma_platform_register(struct device *dev,
dma_filter_fn fn);
/*
* @tx, @rx arguments can be NULL if the DMA channel names are "tx", "rx",
* otherwise actual DMA channel names must be passed to this function.
*/
int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter,
const char *tx, const char *rx);
#endif
......@@ -28,10 +28,6 @@
#include "dma.h"
static struct snd_dmaengine_pcm_config samsung_dmaengine_pcm_config = {
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
};
void samsung_asoc_init_dma_data(struct snd_soc_dai *dai,
struct s3c_dma_params *playback,
struct s3c_dma_params *capture)
......@@ -58,15 +54,28 @@ void samsung_asoc_init_dma_data(struct snd_soc_dai *dai,
}
EXPORT_SYMBOL_GPL(samsung_asoc_init_dma_data);
int samsung_asoc_dma_platform_register(struct device *dev,
dma_filter_fn filter)
int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter,
const char *tx, const char *rx)
{
samsung_dmaengine_pcm_config.compat_filter_fn = filter;
unsigned int flags = SND_DMAENGINE_PCM_FLAG_COMPAT;
struct snd_dmaengine_pcm_config *pcm_conf;
pcm_conf = devm_kzalloc(dev, sizeof(*pcm_conf), GFP_KERNEL);
if (!pcm_conf)
return -ENOMEM;
pcm_conf->prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config;
pcm_conf->compat_filter_fn = filter;
if (dev->of_node) {
pcm_conf->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = tx;
pcm_conf->chan_names[SNDRV_PCM_STREAM_CAPTURE] = rx;
} else {
flags |= SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME;
}
return devm_snd_dmaengine_pcm_register(dev,
&samsung_dmaengine_pcm_config,
SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME |
SND_DMAENGINE_PCM_FLAG_COMPAT);
return devm_snd_dmaengine_pcm_register(dev, pcm_conf, flags);
}
EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_register);
......
......@@ -18,6 +18,7 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/pm_runtime.h>
......@@ -1106,19 +1107,9 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec)
return i2s;
}
static const struct of_device_id exynos_i2s_match[];
static inline const struct samsung_i2s_dai_data *samsung_i2s_get_driver_data(
struct platform_device *pdev)
static void i2s_free_sec_dai(struct i2s_dai *i2s)
{
if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) {
const struct of_device_id *match;
match = of_match_node(exynos_i2s_match, pdev->dev.of_node);
return match ? match->data : NULL;
} else {
return (struct samsung_i2s_dai_data *)
platform_get_device_id(pdev)->driver_data;
}
platform_device_del(i2s->pdev);
}
#ifdef CONFIG_PM
......@@ -1233,9 +1224,13 @@ static int samsung_i2s_probe(struct platform_device *pdev)
const struct samsung_i2s_dai_data *i2s_dai_data;
int ret;
/* Call during Seconday interface registration */
i2s_dai_data = samsung_i2s_get_driver_data(pdev);
if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node)
i2s_dai_data = of_device_get_match_data(&pdev->dev);
else
i2s_dai_data = (struct samsung_i2s_dai_data *)
platform_get_device_id(pdev)->driver_data;
/* Call during the secondary interface registration */
if (i2s_dai_data->dai_type == TYPE_SEC) {
sec_dai = dev_get_drvdata(&pdev->dev);
if (!sec_dai) {
......@@ -1249,7 +1244,7 @@ static int samsung_i2s_probe(struct platform_device *pdev)
return ret;
return samsung_asoc_dma_platform_register(&pdev->dev,
sec_dai->filter);
sec_dai->filter, "tx-sec", NULL);
}
pri_dai = i2s_alloc_dai(pdev, false);
......@@ -1350,17 +1345,28 @@ static int samsung_i2s_probe(struct platform_device *pdev)
return -EINVAL;
}
devm_snd_soc_register_component(&pri_dai->pdev->dev,
ret = devm_snd_soc_register_component(&pri_dai->pdev->dev,
&samsung_i2s_component,
&pri_dai->i2s_dai_drv, 1);
if (ret < 0)
goto err_free_dai;
ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter,
NULL, NULL);
if (ret < 0)
goto err_free_dai;
pm_runtime_enable(&pdev->dev);
ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter);
if (ret != 0)
return ret;
ret = i2s_register_clock_provider(pdev);
if (!ret)
return 0;
return i2s_register_clock_provider(pdev);
pm_runtime_disable(&pdev->dev);
err_free_dai:
if (sec_dai)
i2s_free_sec_dai(sec_dai);
return ret;
}
static int samsung_i2s_remove(struct platform_device *pdev)
......@@ -1477,10 +1483,6 @@ static const struct samsung_i2s_dai_data i2sv5_dai_type_i2s1 = {
.i2s_variant_regs = &i2sv5_i2s1_regs,
};
static const struct samsung_i2s_dai_data samsung_dai_type_pri = {
.dai_type = TYPE_PRI,
};
static const struct samsung_i2s_dai_data samsung_dai_type_sec = {
.dai_type = TYPE_SEC,
};
......@@ -1492,9 +1494,6 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = {
}, {
.name = "samsung-i2s-sec",
.driver_data = (kernel_ulong_t)&samsung_dai_type_sec,
}, {
.name = "samsung-i2sv4",
.driver_data = (kernel_ulong_t)&i2sv5_dai_type,
},
{},
};
......
/*
* Copyright (C) 2014 Samsung Electronics Co., Ltd.
*
* 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/of.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "i2s.h"
struct odroidx2_drv_data {
const struct snd_soc_dapm_widget *dapm_widgets;
unsigned int num_dapm_widgets;
};
/* The I2S CDCLK output clock frequency for the MAX98090 codec */
#define MAX98090_MCLK 19200000
static struct snd_soc_dai_link odroidx2_dai[];
static int odroidx2_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai;
int ret;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
codec_dai = rtd->codec_dai;
cpu_dai = rtd->cpu_dai;
ret = snd_soc_dai_set_sysclk(codec_dai, 0, MAX98090_MCLK,
SND_SOC_CLOCK_IN);
if (ret < 0 || of_find_property(odroidx2_dai[0].codec_of_node,
"clocks", NULL))
return ret;
/* Set the cpu DAI configuration in order to use CDCLK */
return snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_OUT);
}
static const struct snd_soc_dapm_widget odroidx2_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
};
static const struct snd_soc_dapm_widget odroidu3_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Speakers", NULL),
};
static struct snd_soc_dai_link odroidx2_dai[] = {
{
.name = "MAX98090",
.stream_name = "MAX98090 PCM",
.codec_dai_name = "HiFi",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
}
};
static struct snd_soc_card odroidx2 = {
.owner = THIS_MODULE,
.dai_link = odroidx2_dai,
.num_links = ARRAY_SIZE(odroidx2_dai),
.fully_routed = true,
.late_probe = odroidx2_late_probe,
};
static const struct odroidx2_drv_data odroidx2_drvdata = {
.dapm_widgets = odroidx2_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(odroidx2_dapm_widgets),
};
static const struct odroidx2_drv_data odroidu3_drvdata = {
.dapm_widgets = odroidu3_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(odroidu3_dapm_widgets),
};
static const struct of_device_id odroidx2_audio_of_match[] = {
{
.compatible = "samsung,odroidx2-audio",
.data = &odroidx2_drvdata,
}, {
.compatible = "samsung,odroidu3-audio",
.data = &odroidu3_drvdata,
},
{ },
};
MODULE_DEVICE_TABLE(of, odroidx2_audio_of_match);
static int odroidx2_audio_probe(struct platform_device *pdev)
{
struct device_node *snd_node = pdev->dev.of_node;
struct snd_soc_card *card = &odroidx2;
struct device_node *i2s_node, *codec_node;
struct odroidx2_drv_data *dd;
const struct of_device_id *of_id;
int ret;
of_id = of_match_node(odroidx2_audio_of_match, snd_node);
dd = (struct odroidx2_drv_data *)of_id->data;
card->num_dapm_widgets = dd->num_dapm_widgets;
card->dapm_widgets = dd->dapm_widgets;
card->dev = &pdev->dev;
ret = snd_soc_of_parse_card_name(card, "samsung,model");
if (ret < 0)
return ret;
ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
if (ret < 0)
return ret;
codec_node = of_parse_phandle(snd_node, "samsung,audio-codec", 0);
if (!codec_node) {
dev_err(&pdev->dev,
"Failed parsing samsung,i2s-codec property\n");
return -EINVAL;
}
i2s_node = of_parse_phandle(snd_node, "samsung,i2s-controller", 0);
if (!i2s_node) {
dev_err(&pdev->dev,
"Failed parsing samsung,i2s-controller property\n");
ret = -EINVAL;
goto err_put_codec_n;
}
odroidx2_dai[0].codec_of_node = codec_node;
odroidx2_dai[0].cpu_of_node = i2s_node;
odroidx2_dai[0].platform_of_node = i2s_node;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
goto err_put_i2s_n;
}
return 0;
err_put_i2s_n:
of_node_put(i2s_node);
err_put_codec_n:
of_node_put(codec_node);
return ret;
}
static int odroidx2_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
of_node_put(odroidx2_dai[0].cpu_of_node);
of_node_put(odroidx2_dai[0].codec_of_node);
return 0;
}
static struct platform_driver odroidx2_audio_driver = {
.driver = {
.name = "odroidx2-audio",
.of_match_table = odroidx2_audio_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = odroidx2_audio_probe,
.remove = odroidx2_audio_remove,
};
module_platform_driver(odroidx2_audio_driver);
MODULE_AUTHOR("Chen Zhen <zhen1.chen@samsung.com>");
MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC Odroid X2/U3 Audio Support");
MODULE_LICENSE("GPL v2");
......@@ -576,7 +576,8 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev)
goto err5;
}
ret = samsung_asoc_dma_platform_register(&pdev->dev, filter);
ret = samsung_asoc_dma_platform_register(&pdev->dev, filter,
NULL, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err5;
......
......@@ -268,7 +268,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
iismod &= ~S3C2412_IISMOD_SLAVE;
break;
default:
pr_err("unknwon master/slave format\n");
pr_err("unknown master/slave format\n");
return -EINVAL;
}
......
......@@ -177,7 +177,8 @@ static int s3c2412_iis_dev_probe(struct platform_device *pdev)
}
ret = samsung_asoc_dma_platform_register(&pdev->dev,
pdata->dma_filter);
pdata->dma_filter,
NULL, NULL);
if (ret)
pr_err("failed to register the DMA: %d\n", ret);
......
......@@ -482,7 +482,8 @@ static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
}
ret = samsung_asoc_dma_platform_register(&pdev->dev,
pdata->dma_filter);
pdata->dma_filter,
NULL, NULL);
if (ret)
pr_err("failed to register the dma: %d\n", ret);
......
......@@ -435,7 +435,8 @@ static int spdif_probe(struct platform_device *pdev)
spdif->dma_playback = &spdif_stereo_out;
ret = samsung_asoc_dma_platform_register(&pdev->dev, filter);
ret = samsung_asoc_dma_platform_register(&pdev->dev, filter,
NULL, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to register DMA: %d\n", ret);
goto err4;
......
......@@ -1029,9 +1029,9 @@ static int uni_player_parse_dt_audio_glue(struct platform_device *pdev,
regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg");
if (!regmap) {
if (IS_ERR(regmap)) {
dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n");
return -EINVAL;
return PTR_ERR(regmap);
}
player->clk_sel = regmap_field_alloc(regmap, regfield[0]);
......
......@@ -8,6 +8,15 @@ config SND_SUN4I_CODEC
Select Y or M to add support for the Codec embedded in the Allwinner
A10 and affiliated SoCs.
config SND_SUN4I_I2S
tristate "Allwinner A10 I2S Support"
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y or M if you want to add support for codecs attached to
the Allwinner A10 I2S. You will also need to select the
individual machine drivers to support below.
config SND_SUN4I_SPDIF
tristate "Allwinner A10 SPDIF Support"
depends on OF
......
obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o
obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
/*
* Copyright (C) 2015 Andrea Venturi
* Andrea Venturi <be17068@iperbole.bo.it>
*
* Copyright (C) 2016 Maxime Ripard
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <linux/clk.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#define SUN4I_I2S_CTRL_REG 0x00
#define SUN4I_I2S_CTRL_SDO_EN_MASK GENMASK(11, 8)
#define SUN4I_I2S_CTRL_SDO_EN(sdo) BIT(8 + (sdo))
#define SUN4I_I2S_CTRL_MODE_MASK BIT(5)
#define SUN4I_I2S_CTRL_MODE_SLAVE (1 << 5)
#define SUN4I_I2S_CTRL_MODE_MASTER (0 << 5)
#define SUN4I_I2S_CTRL_TX_EN BIT(2)
#define SUN4I_I2S_CTRL_RX_EN BIT(1)
#define SUN4I_I2S_CTRL_GL_EN BIT(0)
#define SUN4I_I2S_FMT0_REG 0x04
#define SUN4I_I2S_FMT0_LRCLK_POLARITY_MASK BIT(7)
#define SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED (1 << 7)
#define SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL (0 << 7)
#define SUN4I_I2S_FMT0_BCLK_POLARITY_MASK BIT(6)
#define SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED (1 << 6)
#define SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL (0 << 6)
#define SUN4I_I2S_FMT0_SR_MASK GENMASK(5, 4)
#define SUN4I_I2S_FMT0_SR(sr) ((sr) << 4)
#define SUN4I_I2S_FMT0_WSS_MASK GENMASK(3, 2)
#define SUN4I_I2S_FMT0_WSS(wss) ((wss) << 2)
#define SUN4I_I2S_FMT0_FMT_MASK GENMASK(1, 0)
#define SUN4I_I2S_FMT0_FMT_RIGHT_J (2 << 0)
#define SUN4I_I2S_FMT0_FMT_LEFT_J (1 << 0)
#define SUN4I_I2S_FMT0_FMT_I2S (0 << 0)
#define SUN4I_I2S_FMT1_REG 0x08
#define SUN4I_I2S_FIFO_TX_REG 0x0c
#define SUN4I_I2S_FIFO_RX_REG 0x10
#define SUN4I_I2S_FIFO_CTRL_REG 0x14
#define SUN4I_I2S_FIFO_CTRL_FLUSH_TX BIT(25)
#define SUN4I_I2S_FIFO_CTRL_FLUSH_RX BIT(24)
#define SUN4I_I2S_FIFO_CTRL_TX_MODE_MASK BIT(2)
#define SUN4I_I2S_FIFO_CTRL_TX_MODE(mode) ((mode) << 2)
#define SUN4I_I2S_FIFO_CTRL_RX_MODE_MASK GENMASK(1, 0)
#define SUN4I_I2S_FIFO_CTRL_RX_MODE(mode) (mode)
#define SUN4I_I2S_FIFO_STA_REG 0x18
#define SUN4I_I2S_DMA_INT_CTRL_REG 0x1c
#define SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN BIT(7)
#define SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN BIT(3)
#define SUN4I_I2S_INT_STA_REG 0x20
#define SUN4I_I2S_CLK_DIV_REG 0x24
#define SUN4I_I2S_CLK_DIV_MCLK_EN BIT(7)
#define SUN4I_I2S_CLK_DIV_BCLK_MASK GENMASK(6, 4)
#define SUN4I_I2S_CLK_DIV_BCLK(bclk) ((bclk) << 4)
#define SUN4I_I2S_CLK_DIV_MCLK_MASK GENMASK(3, 0)
#define SUN4I_I2S_CLK_DIV_MCLK(mclk) ((mclk) << 0)
#define SUN4I_I2S_RX_CNT_REG 0x28
#define SUN4I_I2S_TX_CNT_REG 0x2c
#define SUN4I_I2S_TX_CHAN_SEL_REG 0x30
#define SUN4I_I2S_TX_CHAN_SEL(num_chan) (((num_chan) - 1) << 0)
#define SUN4I_I2S_TX_CHAN_MAP_REG 0x34
#define SUN4I_I2S_TX_CHAN_MAP(chan, sample) ((sample) << (chan << 2))
#define SUN4I_I2S_RX_CHAN_SEL_REG 0x38
#define SUN4I_I2S_RX_CHAN_MAP_REG 0x3c
struct sun4i_i2s {
struct clk *bus_clk;
struct clk *mod_clk;
struct regmap *regmap;
struct snd_dmaengine_dai_dma_data playback_dma_data;
};
struct sun4i_i2s_clk_div {
u8 div;
u8 val;
};
static const struct sun4i_i2s_clk_div sun4i_i2s_bclk_div[] = {
{ .div = 2, .val = 0 },
{ .div = 4, .val = 1 },
{ .div = 6, .val = 2 },
{ .div = 8, .val = 3 },
{ .div = 12, .val = 4 },
{ .div = 16, .val = 5 },
};
static const struct sun4i_i2s_clk_div sun4i_i2s_mclk_div[] = {
{ .div = 1, .val = 0 },
{ .div = 2, .val = 1 },
{ .div = 4, .val = 2 },
{ .div = 6, .val = 3 },
{ .div = 8, .val = 4 },
{ .div = 12, .val = 5 },
{ .div = 16, .val = 6 },
{ .div = 24, .val = 7 },
};
static int sun4i_i2s_get_bclk_div(struct sun4i_i2s *i2s,
unsigned int oversample_rate,
unsigned int word_size)
{
int div = oversample_rate / word_size / 2;
int i;
for (i = 0; i < ARRAY_SIZE(sun4i_i2s_bclk_div); i++) {
const struct sun4i_i2s_clk_div *bdiv = &sun4i_i2s_bclk_div[i];
if (bdiv->div == div)
return bdiv->val;
}
return -EINVAL;
}
static int sun4i_i2s_get_mclk_div(struct sun4i_i2s *i2s,
unsigned int oversample_rate,
unsigned int module_rate,
unsigned int sampling_rate)
{
int div = module_rate / sampling_rate / oversample_rate;
int i;
for (i = 0; i < ARRAY_SIZE(sun4i_i2s_mclk_div); i++) {
const struct sun4i_i2s_clk_div *mdiv = &sun4i_i2s_mclk_div[i];
if (mdiv->div == div)
return mdiv->val;
}
return -EINVAL;
}
static int sun4i_i2s_oversample_rates[] = { 128, 192, 256, 384, 512, 768 };
static int sun4i_i2s_set_clk_rate(struct sun4i_i2s *i2s,
unsigned int rate,
unsigned int word_size)
{
unsigned int clk_rate;
int bclk_div, mclk_div;
int ret, i;
switch (rate) {
case 176400:
case 88200:
case 44100:
case 22050:
case 11025:
clk_rate = 22579200;
break;
case 192000:
case 128000:
case 96000:
case 64000:
case 48000:
case 32000:
case 24000:
case 16000:
case 12000:
case 8000:
clk_rate = 24576000;
break;
default:
return -EINVAL;
}
ret = clk_set_rate(i2s->mod_clk, clk_rate);
if (ret)
return ret;
/* Always favor the highest oversampling rate */
for (i = (ARRAY_SIZE(sun4i_i2s_oversample_rates) - 1); i >= 0; i--) {
unsigned int oversample_rate = sun4i_i2s_oversample_rates[i];
bclk_div = sun4i_i2s_get_bclk_div(i2s, oversample_rate,
word_size);
mclk_div = sun4i_i2s_get_mclk_div(i2s, oversample_rate,
clk_rate,
rate);
if ((bclk_div >= 0) && (mclk_div >= 0))
break;
}
if ((bclk_div < 0) || (mclk_div < 0))
return -EINVAL;
regmap_write(i2s->regmap, SUN4I_I2S_CLK_DIV_REG,
SUN4I_I2S_CLK_DIV_BCLK(bclk_div) |
SUN4I_I2S_CLK_DIV_MCLK(mclk_div) |
SUN4I_I2S_CLK_DIV_MCLK_EN);
return 0;
}
static int sun4i_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai);
int sr, wss;
u32 width;
if (params_channels(params) != 2)
return -EINVAL;
switch (params_physical_width(params)) {
case 16:
width = DMA_SLAVE_BUSWIDTH_2_BYTES;
break;
default:
return -EINVAL;
}
i2s->playback_dma_data.addr_width = width;
switch (params_width(params)) {
case 16:
sr = 0;
wss = 0;
break;
default:
return -EINVAL;
}
regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG,
SUN4I_I2S_FMT0_WSS_MASK | SUN4I_I2S_FMT0_SR_MASK,
SUN4I_I2S_FMT0_WSS(wss) | SUN4I_I2S_FMT0_SR(sr));
return sun4i_i2s_set_clk_rate(i2s, params_rate(params),
params_width(params));
}
static int sun4i_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai);
u32 val;
/* DAI Mode */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
val = SUN4I_I2S_FMT0_FMT_I2S;
break;
case SND_SOC_DAIFMT_LEFT_J:
val = SUN4I_I2S_FMT0_FMT_LEFT_J;
break;
case SND_SOC_DAIFMT_RIGHT_J:
val = SUN4I_I2S_FMT0_FMT_RIGHT_J;
break;
default:
return -EINVAL;
}
regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG,
SUN4I_I2S_FMT0_FMT_MASK,
val);
/* DAI clock polarity */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_IB_IF:
/* Invert both clocks */
val = SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED |
SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED;
break;
case SND_SOC_DAIFMT_IB_NF:
/* Invert bit clock */
val = SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED |
SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL;
break;
case SND_SOC_DAIFMT_NB_IF:
/* Invert frame clock */
val = SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED |
SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL;
break;
case SND_SOC_DAIFMT_NB_NF:
/* Nothing to do for both normal cases */
val = SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL |
SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL;
break;
default:
return -EINVAL;
}
regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG,
SUN4I_I2S_FMT0_BCLK_POLARITY_MASK |
SUN4I_I2S_FMT0_LRCLK_POLARITY_MASK,
val);
/* DAI clock master masks */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* BCLK and LRCLK master */
val = SUN4I_I2S_CTRL_MODE_MASTER;
break;
case SND_SOC_DAIFMT_CBM_CFM:
/* BCLK and LRCLK slave */
val = SUN4I_I2S_CTRL_MODE_SLAVE;
break;
default:
return -EINVAL;
}
regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN4I_I2S_CTRL_MODE_MASK,
val);
/* Set significant bits in our FIFOs */
regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG,
SUN4I_I2S_FIFO_CTRL_TX_MODE_MASK |
SUN4I_I2S_FIFO_CTRL_RX_MODE_MASK,
SUN4I_I2S_FIFO_CTRL_TX_MODE(1) |
SUN4I_I2S_FIFO_CTRL_RX_MODE(1));
return 0;
}
static void sun4i_i2s_start_playback(struct sun4i_i2s *i2s)
{
/* Flush TX FIFO */
regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG,
SUN4I_I2S_FIFO_CTRL_FLUSH_TX,
SUN4I_I2S_FIFO_CTRL_FLUSH_TX);
/* Clear TX counter */
regmap_write(i2s->regmap, SUN4I_I2S_TX_CNT_REG, 0);
/* Enable TX Block */
regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN4I_I2S_CTRL_TX_EN,
SUN4I_I2S_CTRL_TX_EN);
/* Enable TX DRQ */
regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG,
SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN,
SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN);
}
static void sun4i_i2s_stop_playback(struct sun4i_i2s *i2s)
{
/* Disable TX Block */
regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN4I_I2S_CTRL_TX_EN,
0);
/* Disable TX DRQ */
regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG,
SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN,
0);
}
static int sun4i_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
sun4i_i2s_start_playback(i2s);
else
return -EINVAL;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
sun4i_i2s_stop_playback(i2s);
else
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
static int sun4i_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai);
/* Enable the whole hardware block */
regmap_write(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN4I_I2S_CTRL_GL_EN);
/* Enable the first output line */
regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN4I_I2S_CTRL_SDO_EN_MASK,
SUN4I_I2S_CTRL_SDO_EN(0));
/* Enable the first two channels */
regmap_write(i2s->regmap, SUN4I_I2S_TX_CHAN_SEL_REG,
SUN4I_I2S_TX_CHAN_SEL(2));
/* Map them to the two first samples coming in */
regmap_write(i2s->regmap, SUN4I_I2S_TX_CHAN_MAP_REG,
SUN4I_I2S_TX_CHAN_MAP(0, 0) | SUN4I_I2S_TX_CHAN_MAP(1, 1));
return clk_prepare_enable(i2s->mod_clk);
}
static void sun4i_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai);
clk_disable_unprepare(i2s->mod_clk);
/* Disable our output lines */
regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN4I_I2S_CTRL_SDO_EN_MASK, 0);
/* Disable the whole hardware block */
regmap_write(i2s->regmap, SUN4I_I2S_CTRL_REG, 0);
}
static const struct snd_soc_dai_ops sun4i_i2s_dai_ops = {
.hw_params = sun4i_i2s_hw_params,
.set_fmt = sun4i_i2s_set_fmt,
.shutdown = sun4i_i2s_shutdown,
.startup = sun4i_i2s_startup,
.trigger = sun4i_i2s_trigger,
};
static int sun4i_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, NULL);
snd_soc_dai_set_drvdata(dai, i2s);
return 0;
}
static struct snd_soc_dai_driver sun4i_i2s_dai = {
.probe = sun4i_i2s_dai_probe,
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &sun4i_i2s_dai_ops,
.symmetric_rates = 1,
};
static const struct snd_soc_component_driver sun4i_i2s_component = {
.name = "sun4i-dai",
};
static bool sun4i_i2s_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SUN4I_I2S_FIFO_TX_REG:
return false;
default:
return true;
}
}
static bool sun4i_i2s_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SUN4I_I2S_FIFO_RX_REG:
case SUN4I_I2S_FIFO_STA_REG:
return false;
default:
return true;
}
}
static bool sun4i_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SUN4I_I2S_FIFO_RX_REG:
case SUN4I_I2S_INT_STA_REG:
case SUN4I_I2S_RX_CNT_REG:
case SUN4I_I2S_TX_CNT_REG:
return true;
default:
return false;
}
}
static const struct reg_default sun4i_i2s_reg_defaults[] = {
{ SUN4I_I2S_CTRL_REG, 0x00000000 },
{ SUN4I_I2S_FMT0_REG, 0x0000000c },
{ SUN4I_I2S_FMT1_REG, 0x00004020 },
{ SUN4I_I2S_FIFO_CTRL_REG, 0x000400f0 },
{ SUN4I_I2S_DMA_INT_CTRL_REG, 0x00000000 },
{ SUN4I_I2S_CLK_DIV_REG, 0x00000000 },
{ SUN4I_I2S_TX_CHAN_SEL_REG, 0x00000001 },
{ SUN4I_I2S_TX_CHAN_MAP_REG, 0x76543210 },
{ SUN4I_I2S_RX_CHAN_SEL_REG, 0x00000001 },
{ SUN4I_I2S_RX_CHAN_MAP_REG, 0x00003210 },
};
static const struct regmap_config sun4i_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUN4I_I2S_RX_CHAN_MAP_REG,
.cache_type = REGCACHE_FLAT,
.reg_defaults = sun4i_i2s_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(sun4i_i2s_reg_defaults),
.writeable_reg = sun4i_i2s_wr_reg,
.readable_reg = sun4i_i2s_rd_reg,
.volatile_reg = sun4i_i2s_volatile_reg,
};
static int sun4i_i2s_runtime_resume(struct device *dev)
{
struct sun4i_i2s *i2s = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(i2s->bus_clk);
if (ret) {
dev_err(dev, "Failed to enable bus clock\n");
return ret;
}
regcache_cache_only(i2s->regmap, false);
regcache_mark_dirty(i2s->regmap);
ret = regcache_sync(i2s->regmap);
if (ret) {
dev_err(dev, "Failed to sync regmap cache\n");
goto err_disable_clk;
}
return 0;
err_disable_clk:
clk_disable_unprepare(i2s->bus_clk);
return ret;
}
static int sun4i_i2s_runtime_suspend(struct device *dev)
{
struct sun4i_i2s *i2s = dev_get_drvdata(dev);
regcache_cache_only(i2s->regmap, true);
clk_disable_unprepare(i2s->bus_clk);
return 0;
}
static int sun4i_i2s_probe(struct platform_device *pdev)
{
struct sun4i_i2s *i2s;
struct resource *res;
void __iomem *regs;
int irq, ret;
i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
if (!i2s)
return -ENOMEM;
platform_set_drvdata(pdev, i2s);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Can't retrieve our interrupt\n");
return irq;
}
i2s->bus_clk = devm_clk_get(&pdev->dev, "apb");
if (IS_ERR(i2s->bus_clk)) {
dev_err(&pdev->dev, "Can't get our bus clock\n");
return PTR_ERR(i2s->bus_clk);
}
i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
&sun4i_i2s_regmap_config);
if (IS_ERR(i2s->regmap)) {
dev_err(&pdev->dev, "Regmap initialisation failed\n");
return PTR_ERR(i2s->regmap);
}
i2s->mod_clk = devm_clk_get(&pdev->dev, "mod");
if (IS_ERR(i2s->mod_clk)) {
dev_err(&pdev->dev, "Can't get our mod clock\n");
return PTR_ERR(i2s->mod_clk);
}
i2s->playback_dma_data.addr = res->start + SUN4I_I2S_FIFO_TX_REG;
i2s->playback_dma_data.maxburst = 4;
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev)) {
ret = sun4i_i2s_runtime_resume(&pdev->dev);
if (ret)
goto err_pm_disable;
}
ret = devm_snd_soc_register_component(&pdev->dev,
&sun4i_i2s_component,
&sun4i_i2s_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Could not register DAI\n");
goto err_suspend;
}
ret = snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM\n");
goto err_suspend;
}
return 0;
err_suspend:
if (!pm_runtime_status_suspended(&pdev->dev))
sun4i_i2s_runtime_suspend(&pdev->dev);
err_pm_disable:
pm_runtime_disable(&pdev->dev);
return ret;
}
static int sun4i_i2s_remove(struct platform_device *pdev)
{
snd_dmaengine_pcm_unregister(&pdev->dev);
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
sun4i_i2s_runtime_suspend(&pdev->dev);
return 0;
}
static const struct of_device_id sun4i_i2s_match[] = {
{ .compatible = "allwinner,sun4i-a10-i2s", },
{}
};
MODULE_DEVICE_TABLE(of, sun4i_i2s_match);
static const struct dev_pm_ops sun4i_i2s_pm_ops = {
.runtime_resume = sun4i_i2s_runtime_resume,
.runtime_suspend = sun4i_i2s_runtime_suspend,
};
static struct platform_driver sun4i_i2s_driver = {
.probe = sun4i_i2s_probe,
.remove = sun4i_i2s_remove,
.driver = {
.name = "sun4i-i2s",
.of_match_table = sun4i_i2s_match,
.pm = &sun4i_i2s_pm_ops,
},
};
module_platform_driver(sun4i_i2s_driver);
MODULE_AUTHOR("Andrea Venturi <be17068@iperbole.bo.it>");
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Allwinner A10 I2S driver");
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment