cregit-Linux how code gets into the kernel

Release 4.7 sound/soc/sunxi/sun4i-codec.c

Directory: sound/soc/sunxi
/*
 * Copyright 2014 Emilio López <emilio@elopez.com.ar>
 * Copyright 2014 Jon Smirl <jonsmirl@gmail.com>
 * Copyright 2015 Maxime Ripard <maxime.ripard@free-electrons.com>
 * Copyright 2015 Adam Sampson <ats@offog.org>
 *
 * Based on the Allwinner SDK driver, released under the GPL.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>

#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <sound/initval.h>
#include <sound/dmaengine_pcm.h>

/* Codec DAC register offsets and bit fields */

#define SUN4I_CODEC_DAC_DPC			(0x00)

#define SUN4I_CODEC_DAC_DPC_EN_DA			(31)

#define SUN4I_CODEC_DAC_DPC_DVOL			(12)

#define SUN4I_CODEC_DAC_FIFOC			(0x04)

#define SUN4I_CODEC_DAC_FIFOC_DAC_FS			(29)

#define SUN4I_CODEC_DAC_FIFOC_FIR_VERSION		(28)

#define SUN4I_CODEC_DAC_FIFOC_SEND_LASAT		(26)

#define SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE		(24)

#define SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT		(21)

#define SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL		(8)

#define SUN4I_CODEC_DAC_FIFOC_MONO_EN			(6)

#define SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS		(5)

#define SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN		(4)

#define SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH		(0)

#define SUN4I_CODEC_DAC_FIFOS			(0x08)

#define SUN4I_CODEC_DAC_TXDATA			(0x0c)

#define SUN4I_CODEC_DAC_ACTL			(0x10)

#define SUN4I_CODEC_DAC_ACTL_DACAENR			(31)

#define SUN4I_CODEC_DAC_ACTL_DACAENL			(30)

#define SUN4I_CODEC_DAC_ACTL_MIXEN			(29)

#define SUN4I_CODEC_DAC_ACTL_LDACLMIXS			(15)

#define SUN4I_CODEC_DAC_ACTL_RDACRMIXS			(14)

#define SUN4I_CODEC_DAC_ACTL_LDACRMIXS			(13)

#define SUN4I_CODEC_DAC_ACTL_DACPAS			(8)

#define SUN4I_CODEC_DAC_ACTL_MIXPAS			(7)

#define SUN4I_CODEC_DAC_ACTL_PA_MUTE			(6)

#define SUN4I_CODEC_DAC_ACTL_PA_VOL			(0)

#define SUN4I_CODEC_DAC_TUNE			(0x14)

#define SUN4I_CODEC_DAC_DEBUG			(0x18)

/* Codec ADC register offsets and bit fields */

#define SUN4I_CODEC_ADC_FIFOC			(0x1c)

#define SUN4I_CODEC_ADC_FIFOC_ADC_FS			(29)

#define SUN4I_CODEC_ADC_FIFOC_EN_AD			(28)

#define SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE		(24)

#define SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL		(8)

#define SUN4I_CODEC_ADC_FIFOC_MONO_EN			(7)

#define SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS		(6)

#define SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN		(4)

#define SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH		(0)

#define SUN4I_CODEC_ADC_FIFOS			(0x20)

#define SUN4I_CODEC_ADC_RXDATA			(0x24)

#define SUN4I_CODEC_ADC_ACTL			(0x28)

#define SUN4I_CODEC_ADC_ACTL_ADC_R_EN			(31)

#define SUN4I_CODEC_ADC_ACTL_ADC_L_EN			(30)

#define SUN4I_CODEC_ADC_ACTL_PREG1EN			(29)

#define SUN4I_CODEC_ADC_ACTL_PREG2EN			(28)

#define SUN4I_CODEC_ADC_ACTL_VMICEN			(27)

#define SUN4I_CODEC_ADC_ACTL_VADCG			(20)

#define SUN4I_CODEC_ADC_ACTL_ADCIS			(17)

#define SUN4I_CODEC_ADC_ACTL_PA_EN			(4)

#define SUN4I_CODEC_ADC_ACTL_DDE			(3)

#define SUN4I_CODEC_ADC_DEBUG			(0x2c)

/* Other various ADC registers */

#define SUN4I_CODEC_DAC_TXCNT			(0x30)

#define SUN4I_CODEC_ADC_RXCNT			(0x34)

#define SUN4I_CODEC_AC_SYS_VERI			(0x38)

#define SUN4I_CODEC_AC_MIC_PHONE_CAL		(0x3c)


struct sun4i_codec {
	
struct device	*dev;
	
struct regmap	*regmap;
	
struct clk	*clk_apb;
	
struct clk	*clk_module;
	
struct gpio_desc *gpio_pa;

	
struct snd_dmaengine_dai_dma_data	capture_dma_data;
	
struct snd_dmaengine_dai_dma_data	playback_dma_data;
};


static void sun4i_codec_start_playback(struct sun4i_codec *scodec) { /* Flush TX FIFO */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH), BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH)); /* Enable DAC DRQ */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN), BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN)); }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez51100.00%1100.00%
Total51100.00%1100.00%


static void sun4i_codec_stop_playback(struct sun4i_codec *scodec) { /* Disable DAC DRQ */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN), 0); }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez28100.00%1100.00%
Total28100.00%1100.00%


static void sun4i_codec_start_capture(struct sun4i_codec *scodec) { /* Enable ADC DRQ */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN)); }

Contributors

PersonTokensPropCommitsCommitProp
maxime ripardmaxime ripard31100.00%1100.00%
Total31100.00%1100.00%


static void sun4i_codec_stop_capture(struct sun4i_codec *scodec) { /* Disable ADC DRQ */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), 0); }

Contributors

PersonTokensPropCommitsCommitProp
maxime ripardmaxime ripard28100.00%1100.00%
Total28100.00%1100.00%


static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) sun4i_codec_start_playback(scodec); else sun4i_codec_start_capture(scodec); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) sun4i_codec_stop_playback(scodec); else sun4i_codec_stop_capture(scodec); break; default: return -EINVAL; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez8475.00%150.00%
maxime ripardmaxime ripard2825.00%150.00%
Total112100.00%2100.00%


static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); /* Flush RX FIFO */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH), BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH)); /* Set RX FIFO trigger level */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, 0xf << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL, 0x7 << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL); /* * FIXME: Undocumented in the datasheet, but * Allwinner's code mentions that it is related * related to microphone gain */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_ACTL, 0x3 << 25, 0x1 << 25); if (of_device_is_compatible(scodec->dev->of_node, "allwinner,sun7i-a20-codec")) /* FIXME: Undocumented bits */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_TUNE, 0x3 << 8, 0x1 << 8); /* Fill most significant bits with valid data MSB */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE), BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE)); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
maxime ripardmaxime ripard10672.11%150.00%
emilio lopezemilio lopez4127.89%150.00%
Total147100.00%2100.00%


static int sun4i_codec_prepare_playback(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); u32 val; /* Flush the TX FIFO */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH), BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH)); /* Set TX FIFO Empty Trigger Level */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, 0x3f << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL, 0xf << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL); if (substream->runtime->rate > 32000) /* Use 64 bits FIR filter */ val = 0; else /* Use 32 bits FIR filter */ val = BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION); regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION), val); /* Send zeros when we have an underrun */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_SEND_LASAT), 0); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez9971.74%150.00%
maxime ripardmaxime ripard3928.26%150.00%
Total138100.00%2100.00%

;
static int sun4i_codec_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return sun4i_codec_prepare_playback(substream, dai); return sun4i_codec_prepare_capture(substream, dai); }

Contributors

PersonTokensPropCommitsCommitProp
maxime ripardmaxime ripard3690.00%150.00%
emilio lopezemilio lopez410.00%150.00%
Total40100.00%2100.00%


static unsigned long sun4i_codec_get_mod_freq(struct snd_pcm_hw_params *params) { unsigned int rate = params_rate(params); switch (rate) { case 176400: case 88200: case 44100: case 33075: case 22050: case 14700: case 11025: case 7350: return 22579200; case 192000: case 96000: case 48000: case 32000: case 24000: case 16000: case 12000: case 8000: return 24576000; default: return 0; } }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez7689.41%150.00%
maxime ripardmaxime ripard910.59%150.00%
Total85100.00%2100.00%


static int sun4i_codec_get_hw_rate(struct snd_pcm_hw_params *params) { unsigned int rate = params_rate(params); switch (rate) { case 192000: case 176400: return 6; case 96000: case 88200: return 7; case 48000: case 44100: return 0; case 32000: case 33075: return 1; case 24000: case 22050: return 2; case 16000: case 14700: return 3; case 12000: case 11025: return 4; case 8000: case 7350: return 5; default: return -EINVAL; } }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez103100.00%1100.00%
Total103100.00%1100.00%


static int sun4i_codec_hw_params_capture(struct sun4i_codec *scodec, struct snd_pcm_hw_params *params, unsigned int hwrate) { /* Set ADC sample rate */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, 7 << SUN4I_CODEC_ADC_FIFOC_ADC_FS, hwrate << SUN4I_CODEC_ADC_FIFOC_ADC_FS); /* Set the number of channels we want to use */ if (params_channels(params) == 1) regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN)); else regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), 0); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
maxime ripardmaxime ripard5664.37%150.00%
emilio lopezemilio lopez3135.63%150.00%
Total87100.00%2100.00%


static int sun4i_codec_hw_params_playback(struct sun4i_codec *scodec, struct snd_pcm_hw_params *params, unsigned int hwrate) { u32 val; /* Set DAC sample rate */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, 7 << SUN4I_CODEC_DAC_FIFOC_DAC_FS, hwrate << SUN4I_CODEC_DAC_FIFOC_DAC_FS); /* Set the number of channels we want to use */ if (params_channels(params) == 1) val = BIT(SUN4I_CODEC_DAC_FIFOC_MONO_EN); else val = 0; regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_MONO_EN), val); /* Set the number of sample bits to either 16 or 24 bits */ if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32) { regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS), BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS)); /* Set TX FIFO mode to padding the LSBs with 0 */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE), 0); scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; } else { regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS), 0); /* Set TX FIFO mode to repeat the MSB */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE), BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE)); scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez12164.02%150.00%
maxime ripardmaxime ripard6835.98%150.00%
Total189100.00%2100.00%


static int sun4i_codec_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 sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); unsigned long clk_freq; int ret, hwrate; clk_freq = sun4i_codec_get_mod_freq(params); if (!clk_freq) return -EINVAL; ret = clk_set_rate(scodec->clk_module, clk_freq); if (ret) return ret; hwrate = sun4i_codec_get_hw_rate(params); if (hwrate < 0) return hwrate; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return sun4i_codec_hw_params_playback(scodec, params, hwrate); return sun4i_codec_hw_params_capture(scodec, params, hwrate); }

Contributors

PersonTokensPropCommitsCommitProp
maxime ripardmaxime ripard11992.25%266.67%
emilio lopezemilio lopez107.75%133.33%
Total129100.00%3100.00%


static int sun4i_codec_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); /* * Stop issuing DRQ when we have room for less than 16 samples * in our TX FIFO */ regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT, 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT); return clk_prepare_enable(scodec->clk_module); }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez63100.00%1100.00%
Total63100.00%1100.00%


static void sun4i_codec_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); clk_disable_unprepare(scodec->clk_module); }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez44100.00%1100.00%
Total44100.00%1100.00%

static const struct snd_soc_dai_ops sun4i_codec_dai_ops = { .startup = sun4i_codec_startup, .shutdown = sun4i_codec_shutdown, .trigger = sun4i_codec_trigger, .hw_params = sun4i_codec_hw_params, .prepare = sun4i_codec_prepare, }; static struct snd_soc_dai_driver sun4i_codec_dai = { .name = "Codec", .ops = &sun4i_codec_dai_ops, .playback = { .stream_name = "Codec Playback", .channels_min = 1, .channels_max = 2, .rate_min = 8000, .rate_max = 192000, .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, .sig_bits = 24, }, .capture = { .stream_name = "Codec Capture", .channels_min = 1, .channels_max = 2, .rate_min = 8000, .rate_max = 192000, .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, .sig_bits = 24, }, }; /*** Codec ***/ static const struct snd_kcontrol_new sun4i_codec_pa_mute = SOC_DAPM_SINGLE("Switch", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_PA_MUTE, 1, 0); static DECLARE_TLV_DB_SCALE(sun4i_codec_pa_volume_scale, -6300, 100, 1); static const struct snd_kcontrol_new sun4i_codec_widgets[] = { SOC_SINGLE_TLV("Power Amplifier Volume", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_PA_VOL, 0x3F, 0, sun4i_codec_pa_volume_scale), }; static const struct snd_kcontrol_new sun4i_codec_left_mixer_controls[] = { SOC_DAPM_SINGLE("Left DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_LDACLMIXS, 1, 0), }; static const struct snd_kcontrol_new sun4i_codec_right_mixer_controls[] = { SOC_DAPM_SINGLE("Right DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_RDACRMIXS, 1, 0), SOC_DAPM_SINGLE("Left DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_LDACRMIXS, 1, 0), }; static const struct snd_kcontrol_new sun4i_codec_pa_mixer_controls[] = { SOC_DAPM_SINGLE("DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_DACPAS, 1, 0), SOC_DAPM_SINGLE("Mixer Playback Switch", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_MIXPAS, 1, 0), }; static const struct snd_soc_dapm_widget sun4i_codec_codec_dapm_widgets[] = { /* Digital parts of the ADCs */ SND_SOC_DAPM_SUPPLY("ADC", SUN4I_CODEC_ADC_FIFOC, SUN4I_CODEC_ADC_FIFOC_EN_AD, 0, NULL, 0), /* Digital parts of the DACs */ SND_SOC_DAPM_SUPPLY("DAC", SUN4I_CODEC_DAC_DPC, SUN4I_CODEC_DAC_DPC_EN_DA, 0, NULL, 0), /* Analog parts of the ADCs */ SND_SOC_DAPM_ADC("Left ADC", "Codec Capture", SUN4I_CODEC_ADC_ACTL, SUN4I_CODEC_ADC_ACTL_ADC_L_EN, 0), SND_SOC_DAPM_ADC("Right ADC", "Codec Capture", SUN4I_CODEC_ADC_ACTL, SUN4I_CODEC_ADC_ACTL_ADC_R_EN, 0), /* Analog parts of the DACs */ SND_SOC_DAPM_DAC("Left DAC", "Codec Playback", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_DACAENL, 0), SND_SOC_DAPM_DAC("Right DAC", "Codec Playback", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_DACAENR, 0), /* Mixers */ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, sun4i_codec_left_mixer_controls, ARRAY_SIZE(sun4i_codec_left_mixer_controls)), SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, sun4i_codec_right_mixer_controls, ARRAY_SIZE(sun4i_codec_right_mixer_controls)), /* Global Mixer Enable */ SND_SOC_DAPM_SUPPLY("Mixer Enable", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_MIXEN, 0, NULL, 0), /* VMIC */ SND_SOC_DAPM_SUPPLY("VMIC", SUN4I_CODEC_ADC_ACTL, SUN4I_CODEC_ADC_ACTL_VMICEN, 0, NULL, 0), /* Mic Pre-Amplifiers */ SND_SOC_DAPM_PGA("MIC1 Pre-Amplifier", SUN4I_CODEC_ADC_ACTL, SUN4I_CODEC_ADC_ACTL_PREG1EN, 0, NULL, 0), /* Power Amplifier */ SND_SOC_DAPM_MIXER("Power Amplifier", SUN4I_CODEC_ADC_ACTL, SUN4I_CODEC_ADC_ACTL_PA_EN, 0, sun4i_codec_pa_mixer_controls, ARRAY_SIZE(sun4i_codec_pa_mixer_controls)), SND_SOC_DAPM_SWITCH("Power Amplifier Mute", SND_SOC_NOPM, 0, 0, &sun4i_codec_pa_mute), SND_SOC_DAPM_INPUT("Mic1"), SND_SOC_DAPM_OUTPUT("HP Right"), SND_SOC_DAPM_OUTPUT("HP Left"), }; static const struct snd_soc_dapm_route sun4i_codec_codec_dapm_routes[] = { /* Left ADC / DAC Routes */ { "Left ADC", NULL, "ADC" }, { "Left DAC", NULL, "DAC" }, /* Right ADC / DAC Routes */ { "Right ADC", NULL, "ADC" }, { "Right DAC", NULL, "DAC" }, /* Right Mixer Routes */ { "Right Mixer", NULL, "Mixer Enable" }, { "Right Mixer", "Left DAC Playback Switch", "Left DAC" }, { "Right Mixer", "Right DAC Playback Switch", "Right DAC" }, /* Left Mixer Routes */ { "Left Mixer", NULL, "Mixer Enable" }, { "Left Mixer", "Left DAC Playback Switch", "Left DAC" }, /* Power Amplifier Routes */ { "Power Amplifier", "Mixer Playback Switch", "Left Mixer" }, { "Power Amplifier", "Mixer Playback Switch", "Right Mixer" }, { "Power Amplifier", "DAC Playback Switch", "Left DAC" }, { "Power Amplifier", "DAC Playback Switch", "Right DAC" }, /* Headphone Output Routes */ { "Power Amplifier Mute", "Switch", "Power Amplifier" }, { "HP Right", NULL, "Power Amplifier Mute" }, { "HP Left", NULL, "Power Amplifier Mute" }, /* Mic1 Routes */ { "Left ADC", NULL, "MIC1 Pre-Amplifier" }, { "Right ADC", NULL, "MIC1 Pre-Amplifier" }, { "MIC1 Pre-Amplifier", NULL, "Mic1"}, { "Mic1", NULL, "VMIC" }, }; static struct snd_soc_codec_driver sun4i_codec_codec = { .controls = sun4i_codec_widgets, .num_controls = ARRAY_SIZE(sun4i_codec_widgets), .dapm_widgets = sun4i_codec_codec_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(sun4i_codec_codec_dapm_widgets), .dapm_routes = sun4i_codec_codec_dapm_routes, .num_dapm_routes = ARRAY_SIZE(sun4i_codec_codec_dapm_routes), }; static const struct snd_soc_component_driver sun4i_codec_component = { .name = "sun4i-codec", }; #define SUN4I_CODEC_RATES SNDRV_PCM_RATE_8000_192000 #define SUN4I_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S32_LE)
static int sun4i_codec_dai_probe(struct snd_soc_dai *dai) { struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card); snd_soc_dai_init_dma_data(dai, &scodec->playback_dma_data, &scodec->capture_dma_data); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez4591.84%150.00%
maxime ripardmaxime ripard48.16%150.00%
Total49100.00%2100.00%

static struct snd_soc_dai_driver dummy_cpu_dai = { .name = "sun4i-codec-cpu-dai", .probe = sun4i_codec_dai_probe, .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = SUN4I_CODEC_RATES, .formats = SUN4I_CODEC_FORMATS, .sig_bits = 24, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = SUN4I_CODEC_RATES, .formats = SUN4I_CODEC_FORMATS, .sig_bits = 24, }, }; static const struct regmap_config sun4i_codec_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = SUN4I_CODEC_AC_MIC_PHONE_CAL, }; static const struct of_device_id sun4i_codec_of_match[] = { { .compatible = "allwinner,sun4i-a10-codec" }, { .compatible = "allwinner,sun7i-a20-codec" }, {} }; MODULE_DEVICE_TABLE(of, sun4i_codec_of_match);
static struct snd_soc_dai_link *sun4i_codec_create_link(struct device *dev, int *num_links) { struct snd_soc_dai_link *link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL); if (!link) return NULL; link->name = "cdc"; link->stream_name = "CDC PCM"; link->codec_dai_name = "Codec"; link->cpu_dai_name = dev_name(dev); link->codec_name = dev_name(dev); link->platform_name = dev_name(dev); link->dai_fmt = SND_SOC_DAIFMT_I2S; *num_links = 1; return link; }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez102100.00%1100.00%
Total102100.00%1100.00%

;
static int sun4i_codec_spk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct sun4i_codec *scodec = snd_soc_card_get_drvdata(w->dapm->card); if (scodec->gpio_pa) gpiod_set_value_cansleep(scodec->gpio_pa, !!SND_SOC_DAPM_EVENT_ON(event)); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
hans de goedehans de goede56100.00%1100.00%
Total56100.00%1100.00%

static const struct snd_soc_dapm_widget sun4i_codec_card_dapm_widgets[] = { SND_SOC_DAPM_SPK("Speaker", sun4i_codec_spk_event), }; static const struct snd_soc_dapm_route sun4i_codec_card_dapm_routes[] = { { "Speaker", NULL, "HP Right" }, { "Speaker", NULL, "HP Left" }, };
static struct snd_soc_card *sun4i_codec_create_card(struct device *dev) { struct snd_soc_card *card; card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!card) return NULL; card->dai_link = sun4i_codec_create_link(dev, &card->num_links); if (!card->dai_link) return NULL; card->dev = dev; card->name = "sun4i-codec"; card->dapm_widgets = sun4i_codec_card_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(sun4i_codec_card_dapm_widgets); card->dapm_routes = sun4i_codec_card_dapm_routes; card->num_dapm_routes = ARRAY_SIZE(sun4i_codec_card_dapm_routes); return card; }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez8072.73%150.00%
hans de goedehans de goede3027.27%150.00%
Total110100.00%2100.00%

;
static int sun4i_codec_probe(struct platform_device *pdev) { struct snd_soc_card *card; struct sun4i_codec *scodec; struct resource *res; void __iomem *base; int ret; scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL); if (!scodec) return -ENOMEM; scodec->dev = &pdev->dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) { dev_err(&pdev->dev, "Failed to map the registers\n"); return PTR_ERR(base); } scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, &sun4i_codec_regmap_config); if (IS_ERR(scodec->regmap)) { dev_err(&pdev->dev, "Failed to create our regmap\n"); return PTR_ERR(scodec->regmap); } /* Get the clocks from the DT */ scodec->clk_apb = devm_clk_get(&pdev->dev, "apb"); if (IS_ERR(scodec->clk_apb)) { dev_err(&pdev->dev, "Failed to get the APB clock\n"); return PTR_ERR(scodec->clk_apb); } scodec->clk_module = devm_clk_get(&pdev->dev, "codec"); if (IS_ERR(scodec->clk_module)) { dev_err(&pdev->dev, "Failed to get the module clock\n"); return PTR_ERR(scodec->clk_module); } /* Enable the bus clock */ if (clk_prepare_enable(scodec->clk_apb)) { dev_err(&pdev->dev, "Failed to enable the APB clock\n"); return -EINVAL; } scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", GPIOD_OUT_LOW); if (IS_ERR(scodec->gpio_pa)) { ret = PTR_ERR(scodec->gpio_pa); if (ret != -EPROBE_DEFER) dev_err(&pdev->dev, "Failed to get pa gpio: %d\n", ret); return ret; } /* DMA configuration for TX FIFO */ scodec->playback_dma_data.addr = res->start + SUN4I_CODEC_DAC_TXDATA; scodec->playback_dma_data.maxburst = 4; scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; /* DMA configuration for RX FIFO */ scodec->capture_dma_data.addr = res->start + SUN4I_CODEC_ADC_RXDATA; scodec->capture_dma_data.maxburst = 4; scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; ret = snd_soc_register_codec(&pdev->dev, &sun4i_codec_codec, &sun4i_codec_dai, 1); if (ret) { dev_err(&pdev->dev, "Failed to register our codec\n"); goto err_clk_disable; } ret = devm_snd_soc_register_component(&pdev->dev, &sun4i_codec_component, &dummy_cpu_dai, 1); if (ret) { dev_err(&pdev->dev, "Failed to register our DAI\n"); goto err_unregister_codec; } ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); if (ret) { dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); goto err_unregister_codec; } card = sun4i_codec_create_card(&pdev->dev); if (!card) { dev_err(&pdev->dev, "Failed to create our card\n"); goto err_unregister_codec; } platform_set_drvdata(pdev, card); snd_soc_card_set_drvdata(card, scodec); ret = snd_soc_register_card(card); if (ret) { dev_err(&pdev->dev, "Failed to register our card\n"); goto err_unregister_codec; } return 0; err_unregister_codec: snd_soc_unregister_codec(&pdev->dev); err_clk_disable: clk_disable_unprepare(scodec->clk_apb); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez50885.38%133.33%
hans de goedehans de goede589.75%133.33%
maxime ripardmaxime ripard294.87%133.33%
Total595100.00%3100.00%


static int sun4i_codec_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card); snd_soc_unregister_card(card); snd_soc_unregister_codec(&pdev->dev); clk_disable_unprepare(scodec->clk_apb); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez54100.00%1100.00%
Total54100.00%1100.00%

static struct platform_driver sun4i_codec_driver = { .driver = { .name = "sun4i-codec", .of_match_table = sun4i_codec_of_match, }, .probe = sun4i_codec_probe, .remove = sun4i_codec_remove, }; module_platform_driver(sun4i_codec_driver); MODULE_DESCRIPTION("Allwinner A10 codec driver"); MODULE_AUTHOR("Emilio López <emilio@elopez.com.ar>"); MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
emilio lopezemilio lopez259372.27%114.29%
maxime ripardmaxime ripard78121.77%228.57%
hans de goedehans de goede1995.55%342.86%
adam sampsonadam sampson150.42%114.29%
Total3588100.00%7100.00%
Directory: sound/soc/sunxi
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
{% endraw %}