import G965FXXU7DTAA OSRC

*First release for Android (Q).

Signed-off-by: FAROVITUS <farovitus@gmail.com>
This commit is contained in:
FAROVITUS 2020-02-04 13:44:48 +02:00
parent 856452b4f2
commit 2b92eefa41
7696 changed files with 3763754 additions and 92661 deletions

View file

@ -507,6 +507,10 @@ static int snd_compr_allocate_buffer(struct snd_compr_stream *stream,
unsigned int buffer_size;
void *buffer;
if (params->buffer.fragment_size == 0 ||
params->buffer.fragments > SIZE_MAX / params->buffer.fragment_size)
return -EINVAL;
buffer_size = params->buffer.fragment_size * params->buffer.fragments;
if (stream->ops->copy) {
buffer = NULL;
@ -655,9 +659,10 @@ snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg)
static inline int
snd_compr_tstamp(struct snd_compr_stream *stream, unsigned long arg)
{
struct snd_compr_tstamp tstamp = {0};
struct snd_compr_tstamp tstamp;
int ret;
memset(&tstamp, 0, sizeof(tstamp));
ret = snd_compr_update_tstamp(stream, &tstamp);
if (ret == 0)
ret = copy_to_user((struct snd_compr_tstamp __user *)arg,
@ -669,10 +674,11 @@ static int snd_compr_pause(struct snd_compr_stream *stream)
{
int retval;
if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING)
if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING
&& stream->runtime->state != SNDRV_PCM_STATE_DRAINING)
return -EPERM;
retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_PUSH);
if (!retval)
if (!retval && stream->runtime->state != SNDRV_PCM_STATE_DRAINING)
stream->runtime->state = SNDRV_PCM_STATE_PAUSED;
return retval;
}
@ -681,10 +687,11 @@ static int snd_compr_resume(struct snd_compr_stream *stream)
{
int retval;
if (stream->runtime->state != SNDRV_PCM_STATE_PAUSED)
if (stream->runtime->state != SNDRV_PCM_STATE_PAUSED
&& stream->runtime->state != SNDRV_PCM_STATE_DRAINING)
return -EPERM;
retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_RELEASE);
if (!retval)
if (!retval && stream->runtime->state != SNDRV_PCM_STATE_DRAINING)
stream->runtime->state = SNDRV_PCM_STATE_RUNNING;
return retval;
}
@ -705,8 +712,7 @@ static int snd_compr_stop(struct snd_compr_stream *stream)
{
int retval;
if (stream->runtime->state == SNDRV_PCM_STATE_PREPARED ||
stream->runtime->state == SNDRV_PCM_STATE_SETUP)
if (stream->runtime->state == SNDRV_PCM_STATE_PREPARED)
return -EPERM;
retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP);
if (!retval) {

View file

@ -343,6 +343,7 @@ static int snd_disconnect_release(struct inode *inode, struct file *file)
}
panic("%s(%p, %p) failed!", __func__, inode, file);
return 0;
}
static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait)
@ -408,14 +409,7 @@ int snd_card_disconnect(struct snd_card *card)
card->shutdown = 1;
spin_unlock(&card->files_lock);
/* phase 1: disable fops (user space) operations for ALSA API */
mutex_lock(&snd_card_mutex);
snd_cards[card->number] = NULL;
clear_bit(card->number, snd_cards_lock);
mutex_unlock(&snd_card_mutex);
/* phase 2: replace file->f_op with special dummy operations */
/* replace file->f_op with special dummy operations */
spin_lock(&card->files_lock);
list_for_each_entry(mfile, &card->files_list, list) {
/* it's critical part, use endless loop */
@ -431,7 +425,7 @@ int snd_card_disconnect(struct snd_card *card)
}
spin_unlock(&card->files_lock);
/* phase 3: notify all connected devices about disconnection */
/* notify all connected devices about disconnection */
/* at this point, they cannot respond to any calls except release() */
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
@ -447,6 +441,13 @@ int snd_card_disconnect(struct snd_card *card)
device_del(&card->card_dev);
card->registered = false;
}
/* disable fops (user space) operations for ALSA API */
mutex_lock(&snd_card_mutex);
snd_cards[card->number] = NULL;
clear_bit(card->number, snd_cards_lock);
mutex_unlock(&snd_card_mutex);
#ifdef CONFIG_PM
wake_up(&card->power_sleep);
#endif

View file

@ -1916,7 +1916,7 @@ static int wait_for_avail(struct snd_pcm_substream *substream,
if (runtime->no_period_wakeup)
wait_time = MAX_SCHEDULE_TIMEOUT;
else {
wait_time = 10;
wait_time = 1;
if (runtime->rate) {
long t = runtime->period_size * 2 / runtime->rate;
wait_time = max(t, wait_time);

View file

@ -180,3 +180,4 @@ static long snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd,
mutex_unlock(&tu->ioctl_lock);
return ret;
}

View file

@ -59,6 +59,11 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CS42XX8_I2C if I2C
select SND_SOC_CS4349 if I2C
select SND_SOC_CS47L24 if MFD_CS47L24
select SND_SOC_CS47L35 if MFD_CS47L35
select SND_SOC_CS47L85 if MFD_CS47L85
select SND_SOC_CS47L90 if MFD_CS47L90
select SND_SOC_CS47L92 if MFD_CS47L92
select SND_SOC_CS53L30 if I2C
select SND_SOC_CX20442 if TTY
select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI
@ -86,6 +91,8 @@ config SND_SOC_ALL_CODECS
select SND_SOC_MAX9867 if I2C
select SND_SOC_MAX98925 if I2C
select SND_SOC_MAX98926 if I2C
select SND_SOC_MAX98506 if I2C
select SND_SOC_MAX98512 if I2C
select SND_SOC_MAX9850 if I2C
select SND_SOC_MAX9860 if I2C
select SND_SOC_MAX9768 if I2C
@ -238,10 +245,12 @@ config SND_SOC_WM_HUBS
config SND_SOC_WM_ADSP
tristate
select SND_SOC_COMPRESS
default y if SND_SOC_MADERA=y
default y if SND_SOC_CS47L24=y
default y if SND_SOC_WM5102=y
default y if SND_SOC_WM5110=y
default y if SND_SOC_WM2200=y
default m if SND_SOC_MADERA=m
default m if SND_SOC_CS47L24=m
default m if SND_SOC_WM5102=m
default m if SND_SOC_WM5110=m
@ -469,6 +478,18 @@ config SND_SOC_CS4349
config SND_SOC_CS47L24
tristate
config SND_SOC_CS47L35
tristate
config SND_SOC_CS47L85
tristate
config SND_SOC_CS47L90
tristate
config SND_SOC_CS47L92
tristate
# Cirrus Logic Quad-Channel ADC
config SND_SOC_CS53L30
tristate "Cirrus Logic CS53L30 CODEC"
@ -545,6 +566,17 @@ config SND_SOC_ISABELLE
config SND_SOC_LM49453
tristate
config SND_SOC_MADERA
tristate
default y if SND_SOC_CS47L35=y
default y if SND_SOC_CS47L85=y
default y if SND_SOC_CS47L90=y
default y if SND_SOC_CS47L92=y
default m if SND_SOC_CS47L35=m
default m if SND_SOC_CS47L85=m
default m if SND_SOC_CS47L90=m
default m if SND_SOC_CS47L92=m
config SND_SOC_MAX98088
tristate
@ -564,6 +596,30 @@ config SND_SOC_MAX98504
tristate "Maxim MAX98504 speaker amplifier"
depends on I2C
config SND_SOC_MAX98506
bool "Support MAX98506"
depends on I2C
help
MAX98506 driver
config SND_SOC_MAX98512
bool "Support MAX98512"
depends on I2C
help
MAX98512 driver
config SND_SOC_MAXIM_DSM
bool "Support MAXIM DSM"
depends on SND_SOC_MAX98506 || SND_SOC_MAX98512
help
MAXIM DSM driver
config SND_SOC_MAXIM_DSM_CAL
tristate "Support MAXIM DSM Calibration"
depends on SND_SOC_MAXIM_DSM
help
MAX985xx Calibration Driver
config SND_SOC_MAX9867
tristate

View file

@ -52,6 +52,10 @@ snd-soc-cs42xx8-objs := cs42xx8.o
snd-soc-cs42xx8-i2c-objs := cs42xx8-i2c.o
snd-soc-cs4349-objs := cs4349.o
snd-soc-cs47l24-objs := cs47l24.o
snd-soc-cs47l35-objs := cs47l35.o
snd-soc-cs47l85-objs := cs47l85.o
snd-soc-cs47l90-objs := cs47l90.o
snd-soc-cs47l92-objs := cs47l92.o
snd-soc-cs53l30-objs := cs53l30.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-da7210-objs := da7210.o
@ -73,6 +77,7 @@ snd-soc-jz4740-codec-objs := jz4740.o
snd-soc-l3-objs := l3.o
snd-soc-lm4857-objs := lm4857.o
snd-soc-lm49453-objs := lm49453.o
snd-soc-madera-objs := madera.o
snd-soc-max9768-objs := max9768.o
snd-soc-max98088-objs := max98088.o
snd-soc-max98090-objs := max98090.o
@ -83,7 +88,11 @@ snd-soc-max9867-objs := max9867.o
snd-soc-max98925-objs := max98925.o
snd-soc-max98926-objs := max98926.o
snd-soc-max9850-objs := max9850.o
snd-soc-max98506-objs := max98506.o
snd-soc-max9860-objs := max9860.o
snd-soc-max98512-objs := max98512.o
snd-soc-maxim-dsm-objs := maxim_dsm.o
snd-soc-maxim-dsm-cal-objs := maxim_dsm_cal.o maxim_dsm_power.o
snd-soc-mc13783-objs := mc13783.o
snd-soc-ml26124-objs := ml26124.o
snd-soc-nau8810-objs := nau8810.o
@ -277,6 +286,10 @@ obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o
obj-$(CONFIG_SND_SOC_CS42XX8_I2C) += snd-soc-cs42xx8-i2c.o
obj-$(CONFIG_SND_SOC_CS4349) += snd-soc-cs4349.o
obj-$(CONFIG_SND_SOC_CS47L24) += snd-soc-cs47l24.o
obj-$(CONFIG_SND_SOC_CS47L35) += snd-soc-cs47l35.o
obj-$(CONFIG_SND_SOC_CS47L85) += snd-soc-cs47l85.o
obj-$(CONFIG_SND_SOC_CS47L90) += snd-soc-cs47l90.o
obj-$(CONFIG_SND_SOC_CS47L92) += snd-soc-cs47l92.o
obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
@ -298,6 +311,7 @@ obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o
obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o
obj-$(CONFIG_SND_SOC_MADERA) += snd-soc-madera.o
obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o
obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o
obj-$(CONFIG_SND_SOC_MAX98090) += snd-soc-max98090.o
@ -307,7 +321,11 @@ obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o
obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o
obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o
obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
obj-$(CONFIG_SND_SOC_MAX98506) += snd-soc-max98506.o
obj-$(CONFIG_SND_SOC_MAX9860) += snd-soc-max9860.o
obj-$(CONFIG_SND_SOC_MAX98512) += snd-soc-max98512.o
obj-$(CONFIG_SND_SOC_MAXIM_DSM) += snd-soc-maxim-dsm.o
obj-$(CONFIG_SND_SOC_MAXIM_DSM_CAL) += snd-soc-maxim-dsm-cal.o
obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o
obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o
obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o

View file

@ -191,6 +191,7 @@ extern unsigned int arizona_mixer_values[ARIZONA_NUM_MIXER_INPUTS];
#define ARIZONA_DSP_ROUTES(name) \
{ name, NULL, name " Preloader"}, \
{ name " Preloader", NULL, "SYSCLK" }, \
{ name " Preload", NULL, name " Preloader"}, \
{ name, NULL, name " Aux 1" }, \
{ name, NULL, name " Aux 2" }, \
{ name, NULL, name " Aux 3" }, \

View file

@ -173,6 +173,9 @@ SOC_ENUM("ISRC2 FSH", arizona_isrc_fsh[1]),
SOC_ENUM("ISRC3 FSH", arizona_isrc_fsh[2]),
SOC_ENUM("ASRC RATE 1", arizona_asrc_rate1),
WM_ADSP2_PRELOAD_SWITCH("DSP2", 2),
WM_ADSP2_PRELOAD_SWITCH("DSP3", 3),
ARIZONA_MIXER_CONTROLS("DSP2L", ARIZONA_DSP2LMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("DSP2R", ARIZONA_DSP2RMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("DSP3L", ARIZONA_DSP3LMIX_INPUT_1_SOURCE),

1793
sound/soc/codecs/cs47l35.c Normal file

File diff suppressed because it is too large Load diff

2755
sound/soc/codecs/cs47l85.c Normal file

File diff suppressed because it is too large Load diff

2725
sound/soc/codecs/cs47l90.c Normal file

File diff suppressed because it is too large Load diff

2214
sound/soc/codecs/cs47l92.c Normal file

File diff suppressed because it is too large Load diff

5186
sound/soc/codecs/madera.c Normal file

File diff suppressed because it is too large Load diff

494
sound/soc/codecs/madera.h Normal file
View file

@ -0,0 +1,494 @@
/*
* madera.h - Cirrus Logic Madera class codecs common support
*
* Copyright 2015-2017 Cirrus Logic
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef ASOC_MADERA_H
#define ASOC_MADERA_H
#include <linux/completion.h>
#include <sound/soc.h>
#include <sound/madera-pdata.h>
#include "wm_adsp.h"
#define MADERA_FLL1_REFCLK 1
#define MADERA_FLL2_REFCLK 2
#define MADERA_FLL3_REFCLK 3
#define MADERA_FLLAO_REFCLK 4
#define MADERA_FLL1_SYNCCLK 5
#define MADERA_FLL2_SYNCCLK 6
#define MADERA_FLL3_SYNCCLK 7
#define MADERA_FLLAO_SYNCCLK 8
#define MADERA_FLL_SRC_NONE -1
#define MADERA_FLL_SRC_MCLK1 0
#define MADERA_FLL_SRC_MCLK2 1
#define MADERA_FLL_SRC_SLIMCLK 3
#define MADERA_FLL_SRC_FLL1 4
#define MADERA_FLL_SRC_FLL2 5
#define MADERA_FLL_SRC_AIF1BCLK 8
#define MADERA_FLL_SRC_AIF2BCLK 9
#define MADERA_FLL_SRC_AIF3BCLK 10
#define MADERA_FLL_SRC_AIF4BCLK 11
#define MADERA_FLL_SRC_AIF1LRCLK 12
#define MADERA_FLL_SRC_AIF2LRCLK 13
#define MADERA_FLL_SRC_AIF3LRCLK 14
#define MADERA_FLL_SRC_AIF4LRCLK 15
#define MADERA_CLK_SYSCLK_1 1
#define MADERA_CLK_ASYNCCLK_1 2
#define MADERA_CLK_OPCLK 3
#define MADERA_CLK_ASYNC_OPCLK 4
#define MADERA_CLK_SYSCLK_2 5
#define MADERA_CLK_SYSCLK_3 6
#define MADERA_CLK_ASYNCCLK_2 7
#define MADERA_CLK_DSPCLK 8
#define MADERA_CLK_OUTCLK 9
#define MADERA_CLK_SRC_MCLK1 0x0
#define MADERA_CLK_SRC_MCLK2 0x1
#define MADERA_CLK_SRC_FLL1 0x4
#define MADERA_CLK_SRC_FLL2 0x5
#define MADERA_CLK_SRC_FLL3 0x6
#define MADERA_CLK_SRC_FLLAO_HI 0x7
#define MADERA_CLK_SRC_FLL1_DIV6 0x7
#define MADERA_CLK_SRC_AIF1BCLK 0x8
#define MADERA_CLK_SRC_AIF2BCLK 0x9
#define MADERA_CLK_SRC_AIF3BCLK 0xA
#define MADERA_CLK_SRC_AIF4BCLK 0xB
#define MADERA_CLK_SRC_FLLAO 0xF
#define MADERA_OUTCLK_SYSCLK 0
#define MADERA_OUTCLK_ASYNCCLK 1
#define MADERA_OUTCLK_MCLK1 4
#define MADERA_OUTCLK_MCLK2 5
#define MADERA_OUTCLK_MCLK3 6
#define MADERA_MIXER_VOL_MASK 0x00FE
#define MADERA_MIXER_VOL_SHIFT 1
#define MADERA_MIXER_VOL_WIDTH 7
#define MADERA_DOM_GRP_FX 0
#define MADERA_DOM_GRP_ASRC1_RATE_1 1
#define MADERA_DOM_GRP_ASRC1_RATE_2 2
#define MADERA_DOM_GRP_ASRC2_RATE_1 3
#define MADERA_DOM_GRP_ASRC2_RATE_2 4
#define MADERA_DOM_GRP_ISRC1_DEC 5
#define MADERA_DOM_GRP_ISRC1_INT 6
#define MADERA_DOM_GRP_ISRC2_DEC 7
#define MADERA_DOM_GRP_ISRC2_INT 8
#define MADERA_DOM_GRP_ISRC3_DEC 9
#define MADERA_DOM_GRP_ISRC3_INT 10
#define MADERA_DOM_GRP_ISRC4_DEC 11
#define MADERA_DOM_GRP_ISRC4_INT 12
#define MADERA_DOM_GRP_OUT 13
#define MADERA_DOM_GRP_SPD 14
#define MADERA_DOM_GRP_DSP1 15
#define MADERA_DOM_GRP_DSP2 16
#define MADERA_DOM_GRP_DSP3 17
#define MADERA_DOM_GRP_DSP4 18
#define MADERA_DOM_GRP_DSP5 19
#define MADERA_DOM_GRP_DSP6 20
#define MADERA_DOM_GRP_DSP7 21
#define MADERA_DOM_GRP_AIF1 22
#define MADERA_DOM_GRP_AIF2 23
#define MADERA_DOM_GRP_AIF3 24
#define MADERA_DOM_GRP_AIF4 25
#define MADERA_DOM_GRP_SLIMBUS 26
#define MADERA_DOM_GRP_PWM 27
#define MADERA_DOM_GRP_DFC 28
#define MADERA_N_DOM_GRPS 29
#define MADERA_MAX_DAI 11
#define MADERA_MAX_ADSP 7
#define MADERA_NUM_MIXER_INPUTS 148
struct madera;
struct wm_adsp;
struct madera_voice_trigger_info {
/** Which core triggered, 1-based (1 = DSP1, ...) */
int core_num;
};
struct madera_dai_priv {
int clk;
struct snd_pcm_hw_constraint_list constraint;
};
struct madera_priv {
struct wm_adsp adsp[MADERA_MAX_ADSP];
struct madera *madera;
struct device *dev;
int sysclk;
int asyncclk;
int dspclk;
struct madera_dai_priv dai[MADERA_MAX_DAI];
int num_inputs;
unsigned int in_pending;
unsigned int out_up_pending;
unsigned int out_up_delay;
unsigned int out_down_pending;
unsigned int out_down_delay;
unsigned int adsp_rate_cache[MADERA_MAX_ADSP];
struct mutex rate_lock;
int tdm_width[MADERA_MAX_AIF];
int tdm_slots[MADERA_MAX_AIF];
int domain_group_ref[MADERA_N_DOM_GRPS];
};
struct madera_fll_cfg {
int n;
unsigned int theta;
unsigned int lambda;
int refdiv;
int fratio;
int gain;
int alt_gain;
};
struct madera_fll {
struct madera *madera;
int id;
unsigned int base;
unsigned int fout;
int sync_src;
unsigned int sync_freq;
int ref_src;
unsigned int ref_freq;
struct madera_fll_cfg ref_cfg;
};
struct madera_enum {
struct soc_enum mixer_enum;
int val;
};
extern const unsigned int madera_ana_tlv[];
extern const unsigned int madera_eq_tlv[];
extern const unsigned int madera_digital_tlv[];
extern const unsigned int madera_noise_tlv[];
extern const unsigned int madera_ng_tlv[];
extern const unsigned int madera_mixer_tlv[];
extern const char * const madera_mixer_texts[MADERA_NUM_MIXER_INPUTS];
extern unsigned int madera_mixer_values[MADERA_NUM_MIXER_INPUTS];
#define MADERA_GAINMUX_CONTROLS(name, base) \
SOC_SINGLE_RANGE_TLV(name " Input Volume", base + 1, \
MADERA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \
madera_mixer_tlv)
#define MADERA_MIXER_CONTROLS(name, base) \
SOC_SINGLE_RANGE_TLV(name " Input 1 Volume", base + 1, \
MADERA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \
madera_mixer_tlv), \
SOC_SINGLE_RANGE_TLV(name " Input 2 Volume", base + 3, \
MADERA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \
madera_mixer_tlv), \
SOC_SINGLE_RANGE_TLV(name " Input 3 Volume", base + 5, \
MADERA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \
madera_mixer_tlv), \
SOC_SINGLE_RANGE_TLV(name " Input 4 Volume", base + 7, \
MADERA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \
madera_mixer_tlv)
#define MADERA_MUX_ENUM_DECL(name, reg) \
SOC_VALUE_ENUM_SINGLE_AUTODISABLE_DECL( \
name, reg, 0, 0xff, madera_mixer_texts, madera_mixer_values)
#define MADERA_MUX_CTL_DECL(name) \
const struct snd_kcontrol_new name##_mux = \
SOC_DAPM_ENUM("Route", name##_enum)
#define MADERA_MUX_ENUMS(name, base_reg) \
static MADERA_MUX_ENUM_DECL(name##_enum, base_reg); \
static MADERA_MUX_CTL_DECL(name)
#define MADERA_MIXER_ENUMS(name, base_reg) \
MADERA_MUX_ENUMS(name##_in1, base_reg); \
MADERA_MUX_ENUMS(name##_in2, base_reg + 2); \
MADERA_MUX_ENUMS(name##_in3, base_reg + 4); \
MADERA_MUX_ENUMS(name##_in4, base_reg + 6)
#define MADERA_DSP_AUX_ENUMS(name, base_reg) \
MADERA_MUX_ENUMS(name##_aux1, base_reg); \
MADERA_MUX_ENUMS(name##_aux2, base_reg + 8); \
MADERA_MUX_ENUMS(name##_aux3, base_reg + 16); \
MADERA_MUX_ENUMS(name##_aux4, base_reg + 24); \
MADERA_MUX_ENUMS(name##_aux5, base_reg + 32); \
MADERA_MUX_ENUMS(name##_aux6, base_reg + 40)
#define MADERA_MUX(name, ctrl) \
SND_SOC_DAPM_MUX(name, SND_SOC_NOPM, 0, 0, ctrl)
#define MADERA_MUX_WIDGETS(name, name_str) \
MADERA_MUX(name_str " Input 1", &name##_mux)
#define MADERA_MIXER_WIDGETS(name, name_str) \
MADERA_MUX(name_str " Input 1", &name##_in1_mux), \
MADERA_MUX(name_str " Input 2", &name##_in2_mux), \
MADERA_MUX(name_str " Input 3", &name##_in3_mux), \
MADERA_MUX(name_str " Input 4", &name##_in4_mux), \
SND_SOC_DAPM_MIXER(name_str " Mixer", SND_SOC_NOPM, 0, 0, NULL, 0)
#define MADERA_DSP_WIDGETS(name, name_str) \
MADERA_MIXER_WIDGETS(name##L, name_str "L"), \
MADERA_MIXER_WIDGETS(name##R, name_str "R"), \
MADERA_MUX(name_str " Aux 1", &name##_aux1_mux), \
MADERA_MUX(name_str " Aux 2", &name##_aux2_mux), \
MADERA_MUX(name_str " Aux 3", &name##_aux3_mux), \
MADERA_MUX(name_str " Aux 4", &name##_aux4_mux), \
MADERA_MUX(name_str " Aux 5", &name##_aux5_mux), \
MADERA_MUX(name_str " Aux 6", &name##_aux6_mux)
#define MADERA_MUX_ROUTES(widget, name) \
{ widget, NULL, name " Input 1" }, \
MADERA_MIXER_INPUT_ROUTES(name " Input 1")
#define MADERA_MIXER_ROUTES(widget, name) \
{ widget, NULL, name " Mixer" }, \
{ name " Mixer", NULL, name " Input 1" }, \
{ name " Mixer", NULL, name " Input 2" }, \
{ name " Mixer", NULL, name " Input 3" }, \
{ name " Mixer", NULL, name " Input 4" }, \
MADERA_MIXER_INPUT_ROUTES(name " Input 1"), \
MADERA_MIXER_INPUT_ROUTES(name " Input 2"), \
MADERA_MIXER_INPUT_ROUTES(name " Input 3"), \
MADERA_MIXER_INPUT_ROUTES(name " Input 4")
#define MADERA_DSP_ROUTES(name) \
{ name, NULL, name " Preloader"}, \
{ name " Preload", NULL, name " Preloader"}, \
{ name, NULL, "SYSCLK"}, \
{ name, NULL, "DSPCLK"}, \
{ name, NULL, name " Aux 1" }, \
{ name, NULL, name " Aux 2" }, \
{ name, NULL, name " Aux 3" }, \
{ name, NULL, name " Aux 4" }, \
{ name, NULL, name " Aux 5" }, \
{ name, NULL, name " Aux 6" }, \
MADERA_MIXER_INPUT_ROUTES(name " Aux 1"), \
MADERA_MIXER_INPUT_ROUTES(name " Aux 2"), \
MADERA_MIXER_INPUT_ROUTES(name " Aux 3"), \
MADERA_MIXER_INPUT_ROUTES(name " Aux 4"), \
MADERA_MIXER_INPUT_ROUTES(name " Aux 5"), \
MADERA_MIXER_INPUT_ROUTES(name " Aux 6"), \
MADERA_MIXER_ROUTES(name, name "L"), \
MADERA_MIXER_ROUTES(name, name "R")
#define MADERA_SAMPLE_RATE_CONTROL(name, domain) \
SOC_ENUM(name, madera_sample_rate[(domain) - 2])
#define MADERA_RATE_ENUM(xname, xenum) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
.info = snd_soc_info_enum_double, \
.get = snd_soc_get_enum_double, .put = madera_rate_put, \
.private_value = (unsigned long)&xenum }
#define MADERA_EQ_CONTROL(xname, xbase) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \
.put = madera_eq_coeff_put, .private_value = \
((unsigned long)&(struct soc_bytes) { .base = xbase, \
.num_regs = 20, .mask = ~MADERA_EQ1_B1_MODE }) }
#define MADERA_LHPF_CONTROL(xname, xbase) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \
.put = madera_lhpf_coeff_put, .private_value = \
((unsigned long)&(struct soc_bytes) { .base = xbase, \
.num_regs = 1 }) }
#define MADERA_RATES SNDRV_PCM_RATE_KNOT
#define MADERA_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
#define MADERA_OSR_ENUM_SIZE 5
#define MADERA_SYNC_RATE_ENUM_SIZE 3
#define MADERA_ASYNC_RATE_ENUM_SIZE 2
#define MADERA_RATE_ENUM_SIZE \
(MADERA_SYNC_RATE_ENUM_SIZE + MADERA_ASYNC_RATE_ENUM_SIZE)
#define MADERA_SAMPLE_RATE_ENUM_SIZE 16
#define MADERA_DFC_TYPE_ENUM_SIZE 5
#define MADERA_DFC_WIDTH_ENUM_SIZE 5
extern const struct snd_soc_dai_ops madera_dai_ops;
extern const struct snd_soc_dai_ops madera_simple_dai_ops;
extern const struct snd_kcontrol_new madera_inmux[];
extern const char * const madera_rate_text[MADERA_RATE_ENUM_SIZE];
extern const unsigned int madera_rate_val[MADERA_RATE_ENUM_SIZE];
extern const char * const madera_sample_rate_text[MADERA_SAMPLE_RATE_ENUM_SIZE];
extern const unsigned int madera_sample_rate_val[MADERA_SAMPLE_RATE_ENUM_SIZE];
extern const char * const madera_dfc_width_text[MADERA_DFC_WIDTH_ENUM_SIZE];
extern const unsigned int madera_dfc_width_val[MADERA_DFC_WIDTH_ENUM_SIZE];
extern const char * const madera_dfc_type_text[MADERA_DFC_TYPE_ENUM_SIZE];
extern const unsigned int madera_dfc_type_val[MADERA_DFC_TYPE_ENUM_SIZE];
extern const struct soc_enum madera_sample_rate[];
extern const struct soc_enum madera_isrc_fsl[];
extern const struct soc_enum madera_isrc_fsh[];
extern const struct soc_enum madera_asrc1_rate[];
extern const struct soc_enum madera_asrc1_bidir_rate[];
extern const struct soc_enum madera_asrc2_rate[];
extern const struct soc_enum madera_output_rate;
extern const struct soc_enum madera_output_ext_rate;
extern const struct soc_enum madera_input_rate[];
extern const struct soc_enum madera_dfc_width[];
extern const struct soc_enum madera_dfc_type[];
extern const struct soc_enum madera_fx_rate;
extern const struct soc_enum madera_spdif_rate;
extern const struct soc_enum madera_in_vi_ramp;
extern const struct soc_enum madera_in_vd_ramp;
extern const struct soc_enum madera_out_vi_ramp;
extern const struct soc_enum madera_out_vd_ramp;
extern const struct soc_enum madera_lhpf1_mode;
extern const struct soc_enum madera_lhpf2_mode;
extern const struct soc_enum madera_lhpf3_mode;
extern const struct soc_enum madera_lhpf4_mode;
extern const struct soc_enum madera_ng_hold;
extern const struct soc_enum madera_in_hpf_cut_enum;
extern const struct soc_enum madera_in_dmic_osr[];
extern const struct soc_enum madera_output_anc_src[];
extern const struct soc_enum madera_anc_input_src[];
extern const struct soc_enum madera_anc_ng_enum;
extern const struct snd_kcontrol_new madera_dsp_trigger_output_mux[];
extern const struct snd_kcontrol_new madera_drc_activity_output_mux[];
extern const struct snd_kcontrol_new madera_adsp_rate_controls[];
const char *madera_sample_rate_val_to_name(unsigned int rate_val);
int madera_dfc_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_lp_mode_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_in_rate_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_out1_demux_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_dre_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_rate_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_eq_coeff_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_lhpf_coeff_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_sysclk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
int madera_spk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
int madera_in_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
int madera_out_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
int madera_hp_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
int madera_anc_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
int madera_domain_clk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol,
int event);
int madera_adsp_rate_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
int madera_adsp_rate_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_adsp_rate_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int madera_set_adsp_clk(struct madera_priv *priv, int dsp_num,
unsigned int freq);
int madera_set_sysclk(struct snd_soc_codec *codec, int clk_id, int source,
unsigned int freq, int dir);
int madera_get_legacy_dspclk_setting(struct madera *madera, unsigned int freq);
void madera_spin_sysclk(struct madera_priv *priv);
int madera_init_fll(struct madera *madera, int id, int base,
struct madera_fll *fll);
int madera_set_fll_refclk(struct madera_fll *fll, int source,
unsigned int Fref, unsigned int Fout);
int madera_set_fll_syncclk(struct madera_fll *fll, int source,
unsigned int Fref, unsigned int Fout);
int madera_set_fll_ao_refclk(struct madera_fll *fll, int source,
unsigned int fin, unsigned int fout);
int madera_fllhj_set_refclk(struct madera_fll *fll, int source,
unsigned int fin, unsigned int fout);
int madera_core_init(struct madera_priv *priv);
int madera_core_destroy(struct madera_priv *priv);
int madera_init_overheat(struct madera_priv *priv);
int madera_free_overheat(struct madera_priv *priv);
int madera_init_inputs(struct snd_soc_codec *codec,
const char * const *dmic_inputs,
int n_dmic_inputs,
const char * const *dmic_refs,
int n_dmic_refs);
int madera_init_outputs(struct snd_soc_codec *codec, int n_mono_routes);
int madera_init_aif(struct snd_soc_codec *codec);
int madera_init_bus_error_irq(struct madera_priv *priv, int dsp_num,
irq_handler_t handler);
void madera_destroy_bus_error_irq(struct madera_priv *priv, int dsp_num);
int madera_init_dai(struct madera_priv *priv, int dai);
int madera_set_output_mode(struct snd_soc_codec *codec, int output, bool diff);
/* Following functions are for use by machine drivers */
static inline int madera_register_notifier(struct snd_soc_codec *codec,
struct notifier_block *nb)
{
struct madera *madera = dev_get_drvdata(codec->dev->parent);
return blocking_notifier_chain_register(&madera->notifier, nb);
}
static inline int madera_unregister_notifier(struct snd_soc_codec *codec,
struct notifier_block *nb)
{
struct madera *madera = dev_get_drvdata(codec->dev->parent);
return blocking_notifier_chain_unregister(&madera->notifier, nb);
}
struct regmap *madera_get_regmap_dsp(struct snd_soc_codec *codec);
bool madera_get_moisture_state(struct snd_soc_codec *codec);
int madera_enable_force_bypass(struct snd_soc_codec *codec);
int madera_disable_force_bypass(struct snd_soc_codec *codec);
#endif

2065
sound/soc/codecs/max98506.c Normal file

File diff suppressed because it is too large Load diff

721
sound/soc/codecs/max98506.h Normal file
View file

@ -0,0 +1,721 @@
/*
* max98506.h -- MAX98506 ALSA SoC Audio driver
*
* Copyright 2013-2015 Maxim Integrated Products
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef _MAX98506_H
#define _MAX98506_H
/*
* The version of max98506
*/
#define MAX98506_VERSION0 0x40
#define MAX98506_VERSION1 0x50
#define MAX98506_VERSION2 0x80
/*
* Driver revision
*/
#define MAX98506_REVISION "0.00.0010"
/*
* MAX98506 Register Definitions
*/
#define MAX98506_R002_LIVE_STATUS0 0x02
#define MAX98506_R003_LIVE_STATUS1 0x03
#define MAX98506_R004_LIVE_STATUS2 0x04
#define MAX98506_R005_STATE0 0x05
#define MAX98506_R006_STATE1 0x06
#define MAX98506_R007_STATE2 0x07
#define MAX98506_R008_FLAG0 0x08
#define MAX98506_R009_FLAG1 0x09
#define MAX98506_R00A_FLAG2 0x0A
#define MAX98506_R00B_IRQ_ENABLE0 0x0B
#define MAX98506_R00C_IRQ_ENABLE1 0x0C
#define MAX98506_R00D_IRQ_ENABLE2 0x0D
#define MAX98506_R00E_IRQ_CLEAR0 0x0E
#define MAX98506_R00F_IRQ_CLEAR1 0x0F
#define MAX98506_R010_IRQ_CLEAR2 0x10
#define MAX98506_R01A_DAI_CLK_MODE1 0x1A
#define MAX98506_R01B_DAI_CLK_MODE2 0x1B
#define MAX98506_R01F_DAI_CLK_DIV_N_LSBS 0x1F
#define MAX98506_R020_FORMAT 0x20
#define MAX98506_R021_TDM_SLOT_SELECT 0x21
#define MAX98506_R022_DOUT_CFG_VMON 0x22
#define MAX98506_R023_DOUT_CFG_IMON 0x23
#define MAX98506_R024_DAI_INT_CFG 0x24
#define MAX98506_R026_DOUT_CFG_FLAG 0x26
#define MAX98506_R027_DOUT_HIZ_CFG1 0x27
#define MAX98506_R028_DOUT_HIZ_CFG2 0x28
#define MAX98506_R029_DOUT_HIZ_CFG3 0x29
#define MAX98506_R02A_DOUT_HIZ_CFG4 0x2A
#define MAX98506_R02B_DOUT_DRV_STRENGTH 0x2B
#define MAX98506_R02C_FILTERS 0x2C
#define MAX98506_R02D_GAIN 0x2D
#define MAX98506_R02E_GAIN_RAMPING 0x2E
#define MAX98506_R02F_SPK_AMP 0x2F
#define MAX98506_R030_THRESHOLD 0x30
#define MAX98506_R031_ALC_ATTACK 0x31
#define MAX98506_R032_ALC_ATTEN_RLS 0x32
#define MAX98506_R033_ALC_HOLD_RLS 0x33
#define MAX98506_R034_ALC_CONFIGURATION 0x34
#define MAX98506_R035_BOOST_CONVERTER 0x35
#define MAX98506_R036_BLOCK_ENABLE 0x36
#define MAX98506_R037_CONFIGURATION 0x37
#define MAX98506_R038_GLOBAL_ENABLE 0x38
#define MAX98506_R03A_BOOST_LIMITER 0x3A
#define MAX98506_R0FF_VERSION 0xFF
#define MAX98506_REG_CNT (MAX98506_R03A_BOOST_LIMITER+1)
/* MAX98506 Register Bit Fields */
/* MAX98506_R002_LIVE_STATUS0 */
#define MAX98506_THERMWARN_STATUS_MASK (1<<3)
#define MAX98506_THERMWARN_STATUS_SHIFT 3
#define MAX98506_THERMWARN_STATUS_WIDTH 1
#define MAX98506_THERMSHDN_STATUS_MASK (1<<1)
#define MAX98506_THERMSHDN_STATUS_SHIFT 1
#define MAX98506_THERMSHDN_STATUS_WIDTH 1
/* MAX98506_R003_LIVE_STATUS1 */
#define MAX98506_SPKCURNT_STATUS_MASK (1<<5)
#define MAX98506_SPKCURNT_STATUS_SHIFT 5
#define MAX98506_SPKCURNT_STATUS_WIDTH 1
#define MAX98506_WATCHFAIL_STATUS_MASK (1<<4)
#define MAX98506_WATCHFAIL_STATUS_SHIFT 4
#define MAX98506_WATCHFAIL_STATUS_WIDTH 1
#define MAX98506_ALCINFH_STATUS_MASK (1<<3)
#define MAX98506_ALCINFH_STATUS_SHIFT 3
#define MAX98506_ALCINFH_STATUS_WIDTH 1
#define MAX98506_ALCACT_STATUS_MASK (1<<2)
#define MAX98506_ALCACT_STATUS_SHIFT 2
#define MAX98506_ALCACT_STATUS_WIDTH 1
#define MAX98506_ALCMUT_STATUS_MASK (1<<1)
#define MAX98506_ALCMUT_STATUS_SHIFT 1
#define MAX98506_ALCMUT_STATUS_WIDTH 1
#define MAX98506_ACLP_STATUS_MASK (1<<0)
#define MAX98506_ACLP_STATUS_SHIFT 0
#define MAX98506_ACLP_STATUS_WIDTH 1
/* MAX98506_R004_LIVE_STATUS2 */
#define MAX98506_SLOTOVRN_STATUS_MASK (1<<6)
#define MAX98506_SLOTOVRN_STATUS_SHIFT 6
#define MAX98506_SLOTOVRN_STATUS_WIDTH 1
#define MAX98506_INVALSLOT_STATUS_MASK (1<<5)
#define MAX98506_INVALSLOT_STATUS_SHIFT 5
#define MAX98506_INVALSLOT_STATUS_WIDTH 1
#define MAX98506_SLOTCNFLT_STATUS_MASK (1<<4)
#define MAX98506_SLOTCNFLT_STATUS_SHIFT 4
#define MAX98506_SLOTCNFLT_STATUS_WIDTH 1
#define MAX98506_VBSTOVFL_STATUS_MASK (1<<3)
#define MAX98506_VBSTOVFL_STATUS_SHIFT 3
#define MAX98506_VBSTOVFL_STATUS_WIDTH 1
#define MAX98506_VBATOVFL_STATUS_MASK (1<<2)
#define MAX98506_VBATOVFL_STATUS_SHIFT 2
#define MAX98506_VBATOVFL_STATUS_WIDTH 1
#define MAX98506_IMONOVFL_STATUS_MASK (1<<1)
#define MAX98506_IMONOVFL_STATUS_SHIFT 1
#define MAX98506_IMONOVFL_STATUS_WIDTH 1
#define MAX98506_VMONOVFL_STATUS_MASK (1<<0)
#define MAX98506_VMONOVFL_STATUS_SHIFT 0
#define MAX98506_VMONOVFL_STATUS_WIDTH 1
/* MAX98506_R005_STATE0 */
#define MAX98506_THERMWARN_END_STATE_MASK (1<<3)
#define MAX98506_THERMWARN_END_STATE_SHIFT 3
#define MAX98506_THERMWARN_END_STATE_WIDTH 1
#define MAX98506_THERMWARN_BGN_STATE_MASK (1<<2)
#define MAX98506_THERMWARN_BGN_STATE_SHIFT 1
#define MAX98506_THERMWARN_BGN_STATE_WIDTH 1
#define MAX98506_THERMSHDN_END_STATE_MASK (1<<1)
#define MAX98506_THERMSHDN_END_STATE_SHIFT 1
#define MAX98506_THERMSHDN_END_STATE_WIDTH 1
#define MAX98506_THERMSHDN_BGN_STATE_MASK (1<<0)
#define MAX98506_THERMSHDN_BGN_STATE_SHIFT 0
#define MAX98506_THERMSHDN_BGN_STATE_WIDTH 1
/* MAX98506_R006_STATE1 */
#define MAX98506_SPRCURNT_STATE_MASK (1<<5)
#define MAX98506_SPRCURNT_STATE_SHIFT 5
#define MAX98506_SPRCURNT_STATE_WIDTH 1
#define MAX98506_WATCHFAIL_STATE_MASK (1<<4)
#define MAX98506_WATCHFAIL_STATE_SHIFT 4
#define MAX98506_WATCHFAIL_STATE_WIDTH 1
#define MAX98506_ALCINFH_STATE_MASK (1<<3)
#define MAX98506_ALCINFH_STATE_SHIFT 3
#define MAX98506_ALCINFH_STATE_WIDTH 1
#define MAX98506_ALCACT_STATE_MASK (1<<2)
#define MAX98506_ALCACT_STATE_SHIFT 2
#define MAX98506_ALCACT_STATE_WIDTH 1
#define MAX98506_ALCMUT_STATE_MASK (1<<1)
#define MAX98506_ALCMUT_STATE_SHIFT 1
#define MAX98506_ALCMUT_STATE_WIDTH 1
#define MAX98506_ALCP_STATE_MASK (1<<0)
#define MAX98506_ALCP_STATE_SHIFT 0
#define MAX98506_ALCP_STATE_WIDTH 1
/* MAX98506_R007_STATE2 */
#define MAX98506_SLOTOVRN_STATE_MASK (1<<6)
#define MAX98506_SLOTOVRN_STATE_SHIFT 6
#define MAX98506_SLOTOVRN_STATE_WIDTH 1
#define MAX98506_INVALSLOT_STATE_MASK (1<<5)
#define MAX98506_INVALSLOT_STATE_SHIFT 5
#define MAX98506_INVALSLOT_STATE_WIDTH 1
#define MAX98506_SLOTCNFLT_STATE_MASK (1<<4)
#define MAX98506_SLOTCNFLT_STATE_SHIFT 4
#define MAX98506_SLOTCNFLT_STATE_WIDTH 1
#define MAX98506_VBSTOVFL_STATE_MASK (1<<3)
#define MAX98506_VBSTOVFL_STATE_SHIFT 3
#define MAX98506_VBSTOVFL_STATE_WIDTH 1
#define MAX98506_VBATOVFL_STATE_MASK (1<<2)
#define MAX98506_VBATOVFL_STATE_SHIFT 2
#define MAX98506_VBATOVFL_STATE_WIDTH 1
#define MAX98506_IMONOVFL_STATE_MASK (1<<1)
#define MAX98506_IMONOVFL_STATE_SHIFT 1
#define MAX98506_IMONOVFL_STATE_WIDTH 1
#define MAX98506_VMONOVFL_STATE_MASK (1<<0)
#define MAX98506_VMONOVFL_STATE_SHIFT 0
#define MAX98506_VMONOVFL_STATE_WIDTH 1
/* MAX98506_R008_FLAG0 */
#define MAX98506_THERMWARN_END_FLAG_MASK (1<<3)
#define MAX98506_THERMWARN_END_FLAG_SHIFT 3
#define MAX98506_THERMWARN_END_FLAG_WIDTH 1
#define MAX98506_THERMWARN_BGN_FLAG_MASK (1<<2)
#define MAX98506_THERMWARN_BGN_FLAG_SHIFT 2
#define MAX98506_THERMWARN_BGN_FLAG_WIDTH 1
#define MAX98506_THERMSHDN_END_FLAG_MASK (1<<1)
#define MAX98506_THERMSHDN_END_FLAG_SHIFT 1
#define MAX98506_THERMSHDN_END_FLAG_WIDTH 1
#define MAX98506_THERMSHDN_BGN_FLAG_MASK (1<<0)
#define MAX98506_THERMSHDN_BGN_FLAG_SHIFT 0
#define MAX98506_THERMSHDN_BGN_FLAG_WIDTH 1
/* MAX98506_R009_FLAG1 */
#define MAX98506_SPKCURNT_FLAG_MASK (1<<5)
#define MAX98506_SPKCURNT_FLAG_SHIFT 5
#define MAX98506_SPKCURNT_FLAG_WIDTH 1
#define MAX98506_WATCHFAIL_FLAG_MASK (1<<4)
#define MAX98506_WATCHFAIL_FLAG_SHIFT 4
#define MAX98506_WATCHFAIL_FLAG_WIDTH 1
#define MAX98506_ALCINFH_FLAG_MASK (1<<3)
#define MAX98506_ALCINFH_FLAG_SHIFT 3
#define MAX98506_ALCINFH_FLAG_WIDTH 1
#define MAX98506_ALCACT_FLAG_MASK (1<<2)
#define MAX98506_ALCACT_FLAG_SHIFT 2
#define MAX98506_ALCACT_FLAG_WIDTH 1
#define MAX98506_ALCMUT_FLAG_MASK (1<<1)
#define MAX98506_ALCMUT_FLAG_SHIFT 1
#define MAX98506_ALCMUT_FLAG_WIDTH 1
#define MAX98506_ALCP_FLAG_MASK (1<<0)
#define MAX98506_ALCP_FLAG_SHIFT 0
#define MAX98506_ALCP_FLAG_WIDTH 1
/* MAX98506_R00A_FLAG2 */
#define MAX98506_SLOTOVRN_FLAG_MASK (1<<6)
#define MAX98506_SLOTOVRN_FLAG_SHIFT 6
#define MAX98506_SLOTOVRN_FLAG_WIDTH 1
#define MAX98506_INVALSLOT_FLAG_MASK (1<<5)
#define MAX98506_INVALSLOT_FLAG_SHIFT 5
#define MAX98506_INVALSLOT_FLAG_WIDTH 1
#define MAX98506_SLOTCNFLT_FLAG_MASK (1<<4)
#define MAX98506_SLOTCNFLT_FLAG_SHIFT 4
#define MAX98506_SLOTCNFLT_FLAG_WIDTH 1
#define MAX98506_VBSTOVFL_FLAG_MASK (1<<3)
#define MAX98506_VBSTOVFL_FLAG_SHIFT 3
#define MAX98506_VBSTOVFL_FLAG_WIDTH 1
#define MAX98506_VBATOVFL_FLAG_MASK (1<<2)
#define MAX98506_VBATOVFL_FLAG_SHIFT 2
#define MAX98506_VBATOVFL_FLAG_WIDTH 1
#define MAX98506_IMONOVFL_FLAG_MASK (1<<1)
#define MAX98506_IMONOVFL_FLAG_SHIFT 1
#define MAX98506_IMONOVFL_FLAG_WIDTH 1
#define MAX98506_VMONOVFL_FLAG_MASK (1<<0)
#define MAX98506_VMONOVFL_FLAG_SHIFT 0
#define MAX98506_VMONOVFL_FLAG_WIDTH 1
/* MAX98506_R00B_IRQ_ENABLE0 */
#define MAX98506_THERMWARN_END_EN_MASK (1<<3)
#define MAX98506_THERMWARN_END_EN_SHIFT 3
#define MAX98506_THERMWARN_END_EN_WIDTH 1
#define MAX98506_THERMWARN_BGN_EN_MASK (1<<2)
#define MAX98506_THERMWARN_BGN_EN_SHIFT 2
#define MAX98506_THERMWARN_BGN_EN_WIDTH 1
#define MAX98506_THERMSHDN_END_EN_MASK (1<<1)
#define MAX98506_THERMSHDN_END_EN_SHIFT 1
#define MAX98506_THERMSHDN_END_EN_WIDTH 1
#define MAX98506_THERMSHDN_BGN_EN_MASK (1<<0)
#define MAX98506_THERMSHDN_BGN_EN_SHIFT 0
#define MAX98506_THERMSHDN_BGN_EN_WIDTH 1
/* MAX98506_R00C_IRQ_ENABLE1 */
#define MAX98506_SPKCURNT_EN_MASK (1<<5)
#define MAX98506_SPKCURNT_EN_SHIFT 5
#define MAX98506_SPKCURNT_EN_WIDTH 1
#define MAX98506_WATCHFAIL_EN_MASK (1<<4)
#define MAX98506_WATCHFAIL_EN_SHIFT 4
#define MAX98506_WATCHFAIL_EN_WIDTH 1
#define MAX98506_ALCINFH_EN_MASK (1<<3)
#define MAX98506_ALCINFH_EN_SHIFT 3
#define MAX98506_ALCINFH_EN_WIDTH 1
#define MAX98506_ALCACT_EN_MASK (1<<2)
#define MAX98506_ALCACT_EN_SHIFT 2
#define MAX98506_ALCACT_EN_WIDTH 1
#define MAX98506_ALCMUT_EN_MASK (1<<1)
#define MAX98506_ALCMUT_EN_SHIFT 1
#define MAX98506_ALCMUT_EN_WIDTH 1
#define MAX98506_ALCP_EN_MASK (1<<0)
#define MAX98506_ALCP_EN_SHIFT 0
#define MAX98506_ALCP_EN_WIDTH 1
/* MAX98506_R00D_IRQ_ENABLE2 */
#define MAX98506_SLOTOVRN_EN_MASK (1<<6)
#define MAX98506_SLOTOVRN_EN_SHIFT 6
#define MAX98506_SLOTOVRN_EN_WIDTH 1
#define MAX98506_INVALSLOT_EN_MASK (1<<5)
#define MAX98506_INVALSLOT_EN_SHIFT 5
#define MAX98506_INVALSLOT_EN_WIDTH 1
#define MAX98506_SLOTCNFLT_EN_MASK (1<<4)
#define MAX98506_SLOTCNFLT_EN_SHIFT 4
#define MAX98506_SLOTCNFLT_EN_WIDTH 1
#define MAX98506_VBSTOVFL_EN_MASK (1<<3)
#define MAX98506_VBSTOVFL_EN_SHIFT 3
#define MAX98506_VBSTOVFL_EN_WIDTH 1
#define MAX98506_VBATOVFL_EN_MASK (1<<2)
#define MAX98506_VBATOVFL_EN_SHIFT 2
#define MAX98506_VBATOVFL_EN_WIDTH 1
#define MAX98506_IMONOVFL_EN_MASK (1<<1)
#define MAX98506_IMONOVFL_EN_SHIFT 1
#define MAX98506_IMONOVFL_EN_WIDTH 1
#define MAX98506_VMONOVFL_EN_MASK (1<<0)
#define MAX98506_VMONOVFL_EN_SHIFT 0
#define MAX98506_VMONOVFL_EN_WIDTH 1
/* MAX98506_R00E_IRQ_CLEAR0 */
#define MAX98506_THERMWARN_END_CLR_MASK (1<<3)
#define MAX98506_THERMWARN_END_CLR_SHIFT 3
#define MAX98506_THERMWARN_END_CLR_WIDTH 1
#define MAX98506_THERMWARN_BGN_CLR_MASK (1<<2)
#define MAX98506_THERMWARN_BGN_CLR_SHIFT 2
#define MAX98506_THERMWARN_BGN_CLR_WIDTH 1
#define MAX98506_THERMSHDN_END_CLR_MASK (1<<1)
#define MAX98506_THERMSHDN_END_CLR_SHIFT 1
#define MAX98506_THERMSHDN_END_CLR_WIDTH 1
#define MAX98506_THERMSHDN_BGN_CLR_MASK (1<<0)
#define MAX98506_THERMSHDN_BGN_CLR_SHIFT 0
#define MAX98506_THERMSHDN_BGN_CLR_WIDTH 1
/* MAX98506_R00F_IRQ_CLEAR1 */
#define MAX98506_SPKCURNT_CLR_MASK (1<<5)
#define MAX98506_SPKCURNT_CLR_SHIFT 5
#define MAX98506_SPKCURNT_CLR_WIDTH 1
#define MAX98506_WATCHFAIL_CLR_MASK (1<<4)
#define MAX98506_WATCHFAIL_CLR_SHIFT 4
#define MAX98506_WATCHFAIL_CLR_WIDTH 1
#define MAX98506_ALCINFH_CLR_MASK (1<<3)
#define MAX98506_ALCINFH_CLR_SHIFT 3
#define MAX98506_ALCINFH_CLR_WIDTH 1
#define MAX98506_ALCACT_CLR_MASK (1<<2)
#define MAX98506_ALCACT_CLR_SHIFT 2
#define MAX98506_ALCACT_CLR_WIDTH 1
#define MAX98506_ALCMUT_CLR_MASK (1<<1)
#define MAX98506_ALCMUT_CLR_SHIFT 1
#define MAX98506_ALCMUT_CLR_WIDTH 1
#define MAX98506_ALCP_CLR_MASK (1<<0)
#define MAX98506_ALCP_CLR_SHIFT 0
#define MAX98506_ALCP_CLR_WIDTH 1
/* MAX98506_R010_IRQ_CLEAR2 */
#define MAX98506_SLOTOVRN_CLR_MASK (1<<6)
#define MAX98506_SLOTOVRN_CLR_SHIFT 6
#define MAX98506_SLOTOVRN_CLR_WIDTH 1
#define MAX98506_INVALSLOT_CLR_MASK (1<<5)
#define MAX98506_INVALSLOT_CLR_SHIFT 5
#define MAX98506_INVALSLOT_CLR_WIDTH 1
#define MAX98506_SLOTCNFLT_CLR_MASK (1<<4)
#define MAX98506_SLOTCNFLT_CLR_SHIFT 4
#define MAX98506_SLOTCNFLT_CLR_WIDTH 1
#define MAX98506_VBSTOVFL_CLR_MASK (1<<3)
#define MAX98506_VBSTOVFL_CLR_SHIFT 3
#define MAX98506_VBSTOVFL_CLR_WIDTH 1
#define MAX98506_VBATOVFL_CLR_MASK (1<<2)
#define MAX98506_VBATOVFL_CLR_SHIFT 2
#define MAX98506_VBATOVFL_CLR_WIDTH 1
#define MAX98506_IMONOVFL_CLR_MASK (1<<1)
#define MAX98506_IMONOVFL_CLR_SHIFT 1
#define MAX98506_IMONOVFL_CLR_WIDTH 1
#define MAX98506_VMONOVFL_CLR_MASK (1<<0)
#define MAX98506_VMONOVFL_CLR_SHIFT 0
#define MAX98506_VMONOVFL_CLR_WIDTH 1
/* MAX98506_R01A_DAI_CLK_MODE1 */
#define MAX98506_DAI_CLK_SOURCE_MASK (1<<6)
#define MAX98506_DAI_CLK_SOURCE_SHIFT 6
#define MAX98506_DAI_CLK_SOURCE_WIDTH 1
#define MAX98506_MDLL_MULT_MASK (0x0F<<0)
#define MAX98506_MDLL_MULT_SHIFT 0
#define MAX98506_MDLL_MULT_WIDTH 4
#define MAX98506_MDLL_MULT_MCLKx8 6
#define MAX98506_MDLL_MULT_MCLKx16 8
/* MAX98506_R01B_DAI_CLK_MODE2 */
#define MAX98506_DAI_SR_MASK (0x0F<<4)
#define MAX98506_DAI_SR_SHIFT 4
#define MAX98506_DAI_SR_WIDTH 4
#define MAX98506_DAI_MAS_MASK (1<<3)
#define MAX98506_DAI_MAS_SHIFT 3
#define MAX98506_DAI_MAS_WIDTH 1
#define MAX98506_DAI_BSEL_MASK (0x07<<0)
#define MAX98506_DAI_BSEL_SHIFT 0
#define MAX98506_DAI_BSEL_WIDTH 3
#define MAX98506_DAI_BSEL_32 (0 << MAX98506_DAI_BSEL_SHIFT)
#define MAX98506_DAI_BSEL_48 (1 << MAX98506_DAI_BSEL_SHIFT)
#define MAX98506_DAI_BSEL_64 (2 << MAX98506_DAI_BSEL_SHIFT)
#define MAX98506_DAI_BSEL_256 (6 << MAX98506_DAI_BSEL_SHIFT)
/* MAX98506_R01F_DAI_CLK_DIV_N_LSBS */
#define MAX98506_DAI_N_LSBS_MASK (0xFF<<0)
#define MAX98506_DAI_N_LSBS_SHIFT 0
#define MAX98506_DAI_N_LSBS_WIDTH 8
/* MAX98506_R020_FORMAT */
#define MAX98506_DAI_CHANSZ_MASK (0x03<<6)
#define MAX98506_DAI_CHANSZ_SHIFT 6
#define MAX98506_DAI_CHANSZ_WIDTH 2
#define MAX98506_DAI_INTERLEAVE_MASK (1<<5)
#define MAX98506_DAI_INTERLEAVE_SHIFT 5
#define MAX98506_DAI_INTERLEAVE_WIDTH 1
#define MAX98506_DAI_EXTBCLK_HIZ_MASK (1<<4)
#define MAX98506_DAI_EXTBCLK_HIZ_SHIFT 4
#define MAX98506_DAI_EXTBCLK_HIZ_WIDTH 1
#define MAX98506_DAI_WCI_MASK (1<<3)
#define MAX98506_DAI_WCI_SHIFT 3
#define MAX98506_DAI_WCI_WIDTH 1
#define MAX98506_DAI_BCI_MASK (1<<2)
#define MAX98506_DAI_BCI_SHIFT 2
#define MAX98506_DAI_BCI_WIDTH 1
#define MAX98506_DAI_DLY_MASK (1<<1)
#define MAX98506_DAI_DLY_SHIFT 1
#define MAX98506_DAI_DLY_WIDTH 1
#define MAX98506_DAI_TDM_MASK (1<<0)
#define MAX98506_DAI_TDM_SHIFT 0
#define MAX98506_DAI_TDM_WIDTH 1
#define MAX98506_DAI_CHANSZ_16 (1 << MAX98506_DAI_CHANSZ_SHIFT)
#define MAX98506_DAI_CHANSZ_24 (2 << MAX98506_DAI_CHANSZ_SHIFT)
#define MAX98506_DAI_CHANSZ_32 (3 << MAX98506_DAI_CHANSZ_SHIFT)
/* MAX98506_R021_TDM_SLOT_SELECT */
#define MAX98506_DAI_DO_EN_MASK (1<<7)
#define MAX98506_DAI_DO_EN_SHIFT 7
#define MAX98506_DAI_DO_EN_WIDTH 1
#define MAX98506_DAI_DIN_EN_MASK (1<<6)
#define MAX98506_DAI_DIN_EN_SHIFT 6
#define MAX98506_DAI_DIN_EN_WIDTH 1
#define MAX98506_DAI_INR_SOURCE_MASK (0x07<<3)
#define MAX98506_DAI_INR_SOURCE_SHIFT 3
#define MAX98506_DAI_INR_SOURCE_WIDTH 3
#define MAX98506_DAI_INL_SOURCE_MASK (0x07<<0)
#define MAX98506_DAI_INL_SOURCE_SHIFT 0
#define MAX98506_DAI_INL_SOURCE_WIDTH 3
/* MAX98506_R022_DOUT_CFG_VMON */
#define MAX98506_DAI_VMON_EN_MASK (1<<5)
#define MAX98506_DAI_VMON_EN_SHIFT 5
#define MAX98506_DAI_VMON_EN_WIDTH 1
#define MAX98506_DAI_VMON_SLOT_MASK (0x1F<<0)
#define MAX98506_DAI_VMON_SLOT_SHIFT 0
#define MAX98506_DAI_VMON_SLOT_WIDTH 5
#define MAX98506_DAI_VMON_SLOT_00_01 (0 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_01_02 (1 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_02_03 (2 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_03_04 (3 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_04_05 (4 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_05_06 (5 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_06_07 (6 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_07_08 (7 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_08_09 (8 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_09_0A (9 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_0A_0B (10 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_0B_0C (11 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_0C_0D (12 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_0D_0E (13 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_0E_0F (14 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_0F_10 (15 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_10_11 (16 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_11_12 (17 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_12_13 (18 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_13_14 (19 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_14_15 (20 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_15_16 (21 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_16_17 (22 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_17_18 (23 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_18_19 (24 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_19_1A (25 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_1A_1B (26 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_1B_1C (27 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_1C_1D (28 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_1D_1E (29 << MAX98506_DAI_VMON_SLOT_SHIFT)
#define MAX98506_DAI_VMON_SLOT_1E_1F (30 << MAX98506_DAI_VMON_SLOT_SHIFT)
/* MAX98506_R023_DOUT_CFG_IMON */
#define MAX98506_DAI_IMON_EN_MASK (1<<5)
#define MAX98506_DAI_IMON_EN_SHIFT 5
#define MAX98506_DAI_IMON_EN_WIDTH 1
#define MAX98506_DAI_IMON_SLOT_MASK (0x1F<<0)
#define MAX98506_DAI_IMON_SLOT_SHIFT 0
#define MAX98506_DAI_IMON_SLOT_WIDTH 5
#define MAX98506_DAI_IMON_SLOT_00_01 (0 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_01_02 (1 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_02_03 (2 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_03_04 (3 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_04_05 (4 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_05_06 (5 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_06_07 (6 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_07_08 (7 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_08_09 (8 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_09_0A (9 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_0A_0B (10 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_0B_0C (11 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_0C_0D (12 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_0D_0E (13 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_0E_0F (14 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_0F_10 (15 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_10_11 (16 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_11_12 (17 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_12_13 (18 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_13_14 (19 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_14_15 (20 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_15_16 (21 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_16_17 (22 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_17_18 (23 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_18_19 (24 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_19_1A (25 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_1A_1B (26 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_1B_1C (27 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_1C_1D (28 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_1D_1E (29 << MAX98506_DAI_IMON_SLOT_SHIFT)
#define MAX98506_DAI_IMON_SLOT_1E_1F (30 << MAX98506_DAI_IMON_SLOT_SHIFT)
/* MAX98506_R024_DOUT_CFG_VBAT */
#define MAX98506_DAI_VBAT_EN_MASK (1<<5)
#define MAX98506_DAI_VBAT_EN_SHIFT 5
#define MAX98506_DAI_VBAT_EN_WIDTH 1
#define MAX98506_DAI_VBAT_SLOT_MASK (0x1F<<0)
#define MAX98506_DAI_VBAT_SLOT_SHIFT 0
#define MAX98506_DAI_VBAT_SLOT_WIDTH 5
/* MAX98506_R025_DOUT_CFG_VBST */
#define MAX98506_DAI_VBST_EN_MASK (1<<5)
#define MAX98506_DAI_VBST_EN_SHIFT 5
#define MAX98506_DAI_VBST_EN_WIDTH 1
#define MAX98506_DAI_VBST_SLOT_MASK (0x1F<<0)
#define MAX98506_DAI_VBST_SLOT_SHIFT 0
#define MAX98506_DAI_VBST_SLOT_WIDTH 5
/* MAX98506_R026_DOUT_CFG_FLAG */
#define MAX98506_DAI_FLAG_EN_MASK (1<<5)
#define MAX98506_DAI_FLAG_EN_SHIFT 5
#define MAX98506_DAI_FLAG_EN_WIDTH 1
#define MAX98506_DAI_FLAG_SLOT_MASK (0x1F<<0)
#define MAX98506_DAI_FLAG_SLOT_SHIFT 0
#define MAX98506_DAI_FLAG_SLOT_WIDTH 5
/* MAX98506_R027_DOUT_HIZ_CFG1 */
#define MAX98506_DAI_SLOT_HIZ_CFG1_MASK (0xFF<<0)
#define MAX98506_DAI_SLOT_HIZ_CFG1_SHIFT 0
#define MAX98506_DAI_SLOT_HIZ_CFG1_WIDTH 8
/* MAX98506_R028_DOUT_HIZ_CFG2 */
#define MAX98506_DAI_SLOT_HIZ_CFG2_MASK (0xFF<<0)
#define MAX98506_DAI_SLOT_HIZ_CFG2_SHIFT 0
#define MAX98506_DAI_SLOT_HIZ_CFG2_WIDTH 8
/* MAX98506_R029_DOUT_HIZ_CFG3 */
#define MAX98506_DAI_SLOT_HIZ_CFG3_MASK (0xFF<<0)
#define MAX98506_DAI_SLOT_HIZ_CFG3_SHIFT 0
#define MAX98506_DAI_SLOT_HIZ_CFG3_WIDTH 8
/* MAX98506_R02A_DOUT_HIZ_CFG4 */
#define MAX98506_DAI_SLOT_HIZ_CFG4_MASK (0xFF<<0)
#define MAX98506_DAI_SLOT_HIZ_CFG4_SHIFT 0
#define MAX98506_DAI_SLOT_HIZ_CFG4_WIDTH 8
/* MAX98506_R02B_DOUT_DRV_STRENGTH */
#define MAX98506_DAI_OUT_DRIVE_MASK (0x03<<0)
#define MAX98506_DAI_OUT_DRIVE_SHIFT 0
#define MAX98506_DAI_OUT_DRIVE_WIDTH 2
/* MAX98506_R02C_FILTERS */
#define MAX98506_ADC_DITHER_EN_MASK (1<<7)
#define MAX98506_ADC_DITHER_EN_SHIFT 7
#define MAX98506_ADC_DITHER_EN_WIDTH 1
#define MAX98506_IV_DCB_EN_MASK (1<<6)
#define MAX98506_IV_DCB_EN_SHIFT 6
#define MAX98506_IV_DCB_EN_WIDTH 1
#define MAX98506_DAC_DITHER_EN_MASK (1<<4)
#define MAX98506_DAC_DITHER_EN_SHIFT 4
#define MAX98506_DAC_DITHER_EN_WIDTH 1
#define MAX98506_DAC_FILTER_MODE_MASK (1<<3)
#define MAX98506_DAC_FILTER_MODE_SHIFT 3
#define MAX98506_DAC_FILTER_MODE_WIDTH 1
#define MAX98506_DAC_HPF_MASK (0x07<<0)
#define MAX98506_DAC_HPF_SHIFT 0
#define MAX98506_DAC_HPF_WIDTH 3
/* MAX98506_R02D_GAIN */
#define MAX98506_DAC_IN_SEL_MASK (0x03<<5)
#define MAX98506_DAC_IN_SEL_SHIFT 5
#define MAX98506_DAC_IN_SEL_WIDTH 2
#define MAX98506_SPK_GAIN_MASK (0x1F<<0)
#define MAX98506_SPK_GAIN_SHIFT 0
#define MAX98506_SPK_GAIN_WIDTH 5
#define MAX98506_DAC_IN_SEL_LEFT_DAI (0 << MAX98506_DAC_IN_SEL_SHIFT)
#define MAX98506_DAC_IN_SEL_RIGHT_DAI (1 << MAX98506_DAC_IN_SEL_SHIFT)
#define MAX98506_DAC_IN_SEL_SUMMED_DAI (2 << MAX98506_DAC_IN_SEL_SHIFT)
#define MAX98506_DAC_IN_SEL_DIV2_SUMMED_DAI (3 << MAX98506_DAC_IN_SEL_SHIFT)
/* MAX98506_R02E_GAIN_RAMPING */
#define MAX98506_SPK_RMP_EN_MASK (1<<1)
#define MAX98506_SPK_RMP_EN_SHIFT 1
#define MAX98506_SPK_RMP_EN_WIDTH 1
#define MAX98506_SPK_ZCD_EN_MASK (1<<0)
#define MAX98506_SPK_ZCD_EN_SHIFT 0
#define MAX98506_SPK_ZCD_EN_WIDTH 1
/* MAX98506_R02F_SPK_AMP */
#define MAX98506_SPK_MODE_MASK (1<<0)
#define MAX98506_SPK_MODE_SHIFT 0
#define MAX98506_SPK_MODE_WIDTH 1
#define MAX98506_SPK_INSEL_MASK (1<<1)
/* MAX98506_R030_THRESHOLD */
#define MAX98506_ALC_EN_MASK (1<<5)
#define MAX98506_ALC_EN_SHIFT 5
#define MAX98506_ALC_EN_WIDTH 1
#define MAX98506_ALC_TH_MASK (0x1F<<0)
#define MAX98506_ALC_TH_SHIFT 0
#define MAX98506_ALC_TH_WIDTH 5
/* MAX98506_R031_ALC_ATTACK */
#define MAX98506_ALC_ATK_STEP_MASK (0x0F<<4)
#define MAX98506_ALC_ATK_STEP_SHIFT 4
#define MAX98506_ALC_ATK_STEP_WIDTH 4
#define MAX98506_ALC_ATK_RATE_MASK (0x7<<0)
#define MAX98506_ALC_ATK_RATE_SHIFT 0
#define MAX98506_ALC_ATK_RATE_WIDTH 3
/* MAX98506_R032_ALC_ATTEN_RLS */
#define MAX98506_ALC_MAX_ATTEN_MASK (0x0F<<4)
#define MAX98506_ALC_MAX_ATTEN_SHIFT 4
#define MAX98506_ALC_MAX_ATTEN_WIDTH 4
#define MAX98506_ALC_RLS_RATE_MASK (0x7<<0)
#define MAX98506_ALC_RLS_RATE_SHIFT 0
#define MAX98506_ALC_RLS_RATE_WIDTH 3
/* MAX98506_R033_ALC_HOLD_RLS */
#define MAX98506_ALC_RLS_TGR_MASK (1<<0)
#define MAX98506_ALC_RLS_TGR_SHIFT 0
#define MAX98506_ALC_RLS_TGR_WIDTH 1
/* MAX98506_R034_ALC_CONFIGURATION */
#define MAX98506_ALC_MUTE_EN_MASK (1<<7)
#define MAX98506_ALC_MUTE_EN_SHIFT 7
#define MAX98506_ALC_MUTE_EN_WIDTH 1
#define MAX98506_ALC_MUTE_DLY_MASK (0x07<<4)
#define MAX98506_ALC_MUTE_DLY_SHIFT 4
#define MAX98506_ALC_MUTE_DLY_WIDTH 3
#define MAX98506_ALC_RLS_DBT_MASK (0x07<<0)
#define MAX98506_ALC_RLS_DBT_SHIFT 0
#define MAX98506_ALC_RLS_DBT_WIDTH 3
/* MAX98506_R035_BOOST_CONVERTER */
#define MAX98506_BST_SYNC_MASK (1<<7)
#define MAX98506_BST_SYNC_SHIFT 7
#define MAX98506_BST_SYNC_WIDTH 1
#define MAX98506_BST_PHASE_MASK (0x03<<4)
#define MAX98506_BST_PHASE_SHIFT 4
#define MAX98506_BST_PHASE_WIDTH 2
#define MAX98506_BST_SKIP_MODE_MASK (0x03<<0)
#define MAX98506_BST_SKIP_MODE_SHIFT 0
#define MAX98506_BST_SKIP_MODE_WIDTH 2
/* MAX98506_R036_BLOCK_ENABLE */
#define MAX98506_BST_EN_MASK (1<<7)
#define MAX98506_BST_EN_SHIFT 7
#define MAX98506_BST_EN_WIDTH 1
#define MAX98506_WATCH_EN_MASK (1<<6)
#define MAX98506_WATCH_EN_SHIFT 6
#define MAX98506_WATCH_EN_WIDTH 1
#define MAX98506_CLKMON_EN_MASK (1<<5)
#define MAX98506_CLKMON_EN_SHIFT 5
#define MAX98506_CLKMON_EN_WIDTH 1
#define MAX98506_SPK_EN_MASK (1<<4)
#define MAX98506_SPK_EN_SHIFT 4
#define MAX98506_SPK_EN_WIDTH 1
#define MAX98506_ADC_VBST_EN_MASK (1<<3)
#define MAX98506_ADC_VBST_EN_SHIFT 3
#define MAX98506_ADC_VBST_EN_WIDTH 1
#define MAX98506_ADC_VBAT_EN_MASK (1<<2)
#define MAX98506_ADC_VBAT_EN_SHIFT 2
#define MAX98506_ADC_VBAT_EN_WIDTH 1
#define MAX98506_ADC_IMON_EN_MASK (1<<1)
#define MAX98506_ADC_IMON_EN_SHIFT 1
#define MAX98506_ADC_IMON_EN_WIDTH 1
#define MAX98506_ADC_VMON_EN_MASK (1<<0)
#define MAX98506_ADC_VMON_EN_SHIFT 0
#define MAX98506_ADC_VMON_EN_WIDTH 1
#define MAX98506_ADC_VIMON_EN_MASK (3<<0)
#define MAX98506_ADC_VIMON_EN_SHIFT 0
#define MAX98506_ADC_VIMON_EN_WIDTH 2
/* MAX98506_R037_CONFIGURATION */
#define MAX98506_BST_VOUT_MASK (0x0F<<4)
#define MAX98506_BST_VOUT_SHIFT 4
#define MAX98506_BST_VOUT_WIDTH 4
#define MAX98506_THERMWARN_LEVEL_MASK (0x03<<2)
#define MAX98506_THERMWARN_LEVEL_SHIFT 2
#define MAX98506_THERMWARN_LEVEL_WIDTH 2
#define MAX98506_WATCH_TIME_MASK (0x03<<0)
#define MAX98506_WATCH_TIME_SHIFT 0
#define MAX98506_WATCH_TIME_WIDTH 2
/* MAX98506_R038_GLOBAL_ENABLE */
#define MAX98506_EN_MASK (1<<7)
#define MAX98506_EN_SHIFT 7
#define MAX98506_EN_WIDTH 1
/* MAX98506_R03A_BOOST_LIMITER */
#define MAX98506_BST_ILIM_MASK (0x1F<<3)
#define MAX98506_BST_ILIM_SHIFT 3
#define MAX98506_BST_ILIM_WIDTH 5
/* MAX98506_R0FF_VERSION */
#define MAX98506_REV_ID_MASK (0xFF<<0)
#define MAX98506_REV_ID_SHIFT 0
#define MAX98506_REV_ID_WIDTH 8
enum max98506_type {
MAX98506,
};
struct max98506_priv {
struct regmap *regmap;
struct snd_soc_codec *codec;
struct max98506_pdata *pdata;
struct max98506_pc_active pca;
struct max98506_volume_step_info vstep;
#ifdef USE_DSM_LOG
struct class *dev_log_class;
struct device *dev_log;
#endif /* USE_DSM_LOG */
struct i2c_client *sub_i2c;
struct regmap *sub_regmap;
int speaker_dac_enable;
};
#endif /* _MAX98506_H */

2940
sound/soc/codecs/max98512.c Normal file

File diff suppressed because it is too large Load diff

620
sound/soc/codecs/max98512.h Normal file
View file

@ -0,0 +1,620 @@
/*
* max98512.c -- MAX98512 ALSA Soc Audio driver
*
* Copyright (C) 2017 Maxim Integrated Products
* Author: Ryan Lee <ryans.lee@maximintegrated.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.
*/
#ifndef _MAX98512_H
#define _MAX98512_H
#include <sound/maxim_dsm.h>
#define MAX98512 0
#define MAX98512L 0
#define MAX98512R 1
#define MAX98512B 2
#define MAX_TRY_COUNT 3
/* Register Values */
#define MAX98512_R0001_INT_RAW1 0x0001
#define MAX98512_R0002_INT_RAW2 0x0002
#define MAX98512_R0003_INT_RAW3 0x0003
#define MAX98512_R0004_INT_STATE1 0x0004
#define MAX98512_R0005_INT_STATE2 0x0005
#define MAX98512_R0006_INT_STATE3 0x0006
#define MAX98512_R0007_INT_FLAG1 0x0007
#define MAX98512_R0008_INT_FLAG2 0x0008
#define MAX98512_R0009_INT_FLAG3 0x0009
#define MAX98512_R000A_INT_EN1 0x000A
#define MAX98512_R000B_INT_EN2 0x000B
#define MAX98512_R000C_INT_EN3 0x000C
#define MAX98512_R000D_INT_FLAG_CLR1 0x000D
#define MAX98512_R000E_INT_FLAG_CLR2 0x000E
#define MAX98512_R000F_INT_FLAG_CLR3 0x000F
#define MAX98512_R0010_IRQ_CTRL 0x0010
#define MAX98512_R0011_CLK_MON 0x0011
#define MAX98512_R0012_WDOG_CTRL 0x0012
#define MAX98512_R0013_WDOG_RST 0x0013
#define MAX98512_R0014_MEAS_ADC_THERM_WARN_THRESH 0x0014
#define MAX98512_R0015_MEAS_ADC_THERM_SHDN_THRESH 0x0015
#define MAX98512_R0016_MEAS_ADC_THERM_HYSTERESIS 0x0016
#define MAX98512_R0017_PIN_CFG 0x0017
#define MAX98512_R0018_PCM_RX_EN_A 0x0018
#define MAX98512_R0019_PCM_RX_EN_B 0x0019
#define MAX98512_R001A_PCM_TX_EN_A 0x001A
#define MAX98512_R001B_PCM_TX_EN_B 0x001B
#define MAX98512_R001C_PCM_TX_HIZ_CTRL_A 0x001C
#define MAX98512_R001D_PCM_TX_HIZ_CTRL_B 0x001D
#define MAX98512_R001E_PCM_TX_CH_SRC_A 0x001E
#define MAX98512_R001F_PCM_TX_CH_SRC_B 0x001F
#define MAX98512_R0020_PCM_MODE_CFG 0x0020
#define MAX98512_R0021_PCM_MASTER_MODE 0x0021
#define MAX98512_R0022_PCM_CLK_SETUP 0x0022
#define MAX98512_R0023_PCM_SR_SETUP1 0x0023
#define MAX98512_R0024_PCM_SR_SETUP2 0x0024
#define MAX98512_R0025_PCM_TO_SPK_MONOMIX_A 0x0025
#define MAX98512_R0026_PCM_TO_SPK_MONOMIX_B 0x0026
#define MAX98512_R0027_ICC_RX_EN_A 0x0027
#define MAX98512_R0028_ICC_RX_EN_B 0x0028
#define MAX98512_R002B_ICC_TX_EN_A 0x002B
#define MAX98512_R002C_ICC_TX_EN_B 0x002C
#define MAX98512_R002D_ICC_HIZ_MANUAL_MODE 0x002D
#define MAX98512_R002E_ICC_TX_HIZ_EN_A 0x002E
#define MAX98512_R002F_ICC_TX_HIZ_EN_B 0x002F
#define MAX98512_R0030_ICC_LNK_EN 0x0030
#define MAX98512_R0031_PDM_TX_EN 0x0031
#define MAX98512_R0032_PDM_TX_HIZ_CTRL 0x0032
#define MAX98512_R0033_PDM_TX_CTRL 0x0033
#define MAX98512_R0034_PDM_RX_CTRL 0x0034
#define MAX98512_R0035_AMP_VOL_CTRL 0x0035
#define MAX98512_R0036_AMP_DSP_CFG 0x0036
#define MAX98512_R0037_TONE_GEN_DC_CFG 0x0037
#define MAX98512_R0038_AMP_EN 0x0038
#define MAX98512_R0039_SPK_SRC_SEL 0x0039
#define MAX98512_R003A_SPK_GAIN 0x003A
#define MAX98512_R003B_SSM_CFG 0x003B
#define MAX98512_R003C_MEAS_EN 0x003C
#define MAX98512_R003D_MEAS_DSP_CFG 0x003D
#define MAX98512_R003E_BOOST_CTRL0 0x003E
#define MAX98512_R003F_BOOST_CTRL3 0x003F
#define MAX98512_R0040_BOOST_CTRL1 0x0040
#define MAX98512_R0041_MEAS_ADC_CFG 0x0041
#define MAX98512_R0042_MEAS_ADC_BASE_MSB 0x0042
#define MAX98512_R0043_MEAS_ADC_BASE_LSB 0x0043
#define MAX98512_R0044_ADC_CH0_DIVIDE 0x0044
#define MAX98512_R0045_ADC_CH1_DIVIDE 0x0045
#define MAX98512_R0046_ADC_CH2_DIVIDE 0x0046
#define MAX98512_R0047_ADC_CH0_FILT_CFG 0x0047
#define MAX98512_R0048_ADC_CH1_FILT_CFG 0x0048
#define MAX98512_R0049_ADC_CH2_FILT_CFG 0x0049
#define MAX98512_R004A_MEAS_ADC_CH0_READ 0x004A
#define MAX98512_R004B_MEAS_ADC_CH1_READ 0x004B
#define MAX98512_R004C_MEAS_ADC_CH2_READ 0x004C
#define MAX98512_R004E_SQUELCH 0x004E
#define MAX98512_R004F_BROWNOUT_STATUS 0x004F
#define MAX98512_R0050_BROWNOUT_EN 0x0050
#define MAX98512_R0051_BROWNOUT_INFINITE_HOLD 0x0051
#define MAX98512_R0052_BROWNOUT_INFINITE_HOLD_CLR 0x0052
#define MAX98512_R0053_BROWNOUT_LVL_HOLD 0x0053
#define MAX98512_R0058_BROWNOUT_LVL1_THRESH 0x0058
#define MAX98512_R0059_BROWNOUT_LVL2_THRESH 0x0059
#define MAX98512_R005A_BROWNOUT_LVL3_THRESH 0x005A
#define MAX98512_R005B_BROWNOUT_LVL4_THRESH 0x005B
#define MAX98512_R005C_BROWNOUT_THRESH_HYSTERYSIS 0x005C
#define MAX98512_R005D_BROWNOUT_AMP_LIMITER_ATK_REL 0x005D
#define MAX98512_R005E_BROWNOUT_AMP_GAIN_ATK_REL 0x005E
#define MAX98512_R005F_BROWNOUT_AMP1_CLIP_MODE 0x005F
#define MAX98512_R0070_BROWNOUT_LVL1_CUR_LIMIT 0x0070
#define MAX98512_R0071_BROWNOUT_LVL1_AMP1_CTRL1 0x0071
#define MAX98512_R0072_BROWNOUT_LVL1_AMP1_CTRL2 0x0072
#define MAX98512_R0073_BROWNOUT_LVL1_AMP1_CTRL3 0x0073
#define MAX98512_R0074_BROWNOUT_LVL2_CUR_LIMIT 0x0074
#define MAX98512_R0075_BROWNOUT_LVL2_AMP1_CTRL1 0x0075
#define MAX98512_R0076_BROWNOUT_LVL2_AMP1_CTRL2 0x0076
#define MAX98512_R0077_BROWNOUT_LVL2_AMP1_CTRL3 0x0077
#define MAX98512_R0078_BROWNOUT_LVL3_CUR_LIMIT 0x0078
#define MAX98512_R0079_BROWNOUT_LVL3_AMP1_CTRL1 0x0079
#define MAX98512_R007A_BROWNOUT_LVL3_AMP1_CTRL2 0x007A
#define MAX98512_R007B_BROWNOUT_LVL3_AMP1_CTRL3 0x007B
#define MAX98512_R007C_BROWNOUT_LVL4_CUR_LIMIT 0x007C
#define MAX98512_R007D_BROWNOUT_LVL4_AMP1_CTRL1 0x007D
#define MAX98512_R007E_BROWNOUT_LVL4_AMP1_CTRL2 0x007E
#define MAX98512_R007F_BROWNOUT_LVL4_AMP1_CTRL3 0x007F
#define MAX98512_R0080_ENV_TRACK_VOUT_HEADROOM 0x0080
#define MAX98512_R0081_ENV_TRACK_BOOST_VOUT_DELAY 0x0081
#define MAX98512_R0082_ENV_TRACK_REL_RATE 0x0082
#define MAX98512_R0083_ENV_TRACK_HOLD_RATE 0x0083
#define MAX98512_R0084_ENV_TRACK_CTRL 0x0084
#define MAX98512_R0085_ENV_TRACK_BOOST_VOUT_READ 0x0085
#define MAX98512_R0086_BOOST_BYPASS_1 0x0086
#define MAX98512_R0087_BOOST_BYPASS_2 0x0087
#define MAX98512_R0088_BOOST_BYPASS_3 0x0088
#define MAX98512_R0089_FET_SCALING_1 0x0089
#define MAX98512_R008A_FET_SCALING_2 0x008A
#define MAX98512_R008B_FET_SCALING_3 0x008B
#define MAX98512_R008C_FET_SCALING_4 0x008C
#define MAX98512_R008D_IVADC_BYPASS 0x008D
#define MAX98512_R0400_GLOBAL_SHDN 0x0400
#define MAX98512_R0401_SOFT_RESET 0x0401
#define MAX98512_R0402_REV_ID 0x0402
#define MAX98512B_R0001_INT_RAW1 0x0001
#define MAX98512B_R0002_INT_RAW2 0x0002
#define MAX98512B_R0003_INT_RAW3 0x0003
#define MAX98512B_R0004_INT_STATE1 0x0004
#define MAX98512B_R0005_INT_STATE2 0x0005
#define MAX98512B_R0006_INT_STATE3 0x0006
#define MAX98512B_R0007_INT_FLAG1 0x0007
#define MAX98512B_R0008_INT_FLAG2 0x0008
#define MAX98512B_R0009_INT_FLAG3 0x0009
#define MAX98512B_R000A_INT_EN1 0x000A
#define MAX98512B_R000B_INT_EN2 0x000B
#define MAX98512B_R000C_INT_EN3 0x000C
#define MAX98512B_R000D_INT_FLAG_CLR1 0x000D
#define MAX98512B_R000E_INT_FLAG_CLR2 0x000E
#define MAX98512B_R000F_INT_FLAG_CLR3 0x000F
#define MAX98512B_R0010_IRQ_CTRL 0x0010
#define MAX98512B_R0011_CLK_MON 0x0011
#define MAX98512B_R0012_WDOG_CTRL 0x0012
#define MAX98512B_R0013_WDOG_RST 0x0013
#define MAX98512B_R0014_MEAS_ADC_THERM_WARN_THRESH 0x0014
#define MAX98512B_R0015_MEAS_ADC_THERM_SHDN_THRESH 0x0015
#define MAX98512B_R0016_MEAS_ADC_THERM_HYSTERESIS 0x0016
#define MAX98512B_R0017_PIN_CFG 0x0017
#define MAX98512B_R0018_PCM_RX_EN_A 0x0018
#define MAX98512B_R0019_PCM_RX_EN_B 0x0019
#define MAX98512B_R001A_PCM_TX_EN_A 0x001A
#define MAX98512B_R001B_PCM_TX_EN_B 0x001B
#define MAX98512B_R001C_PCM_TX_HIZ_CTRL_A 0x001C
#define MAX98512B_R001D_PCM_TX_HIZ_CTRL_B 0x001D
#define MAX98512B_R001E_PCM_TX_CH_SRC_A 0x001E
#define MAX98512B_R001F_PCM_TX_CH_SRC_B 0x001F
#define MAX98512B_R0023_PCM_MODE_CFG 0x0023
#define MAX98512B_R0024_PCM_MASTER_MODE 0x0024
#define MAX98512B_R0025_PCM_CLK_SETUP 0x0025
#define MAX98512B_R0026_PCM_SR_SETUP1 0x0026
#define MAX98512B_R0027_PCM_SR_SETUP2 0x0027
#define MAX98512B_R0028_PCM_TO_SPK_MONOMIX_A 0x0028
#define MAX98512B_R0029_PCM_TO_SPK_MONOMIX_B 0x0029
#define MAX98512B_R002A_ICC_RX_EN_A 0x002A
#define MAX98512B_R002B_ICC_RX_EN_B 0x002B
#define MAX98512B_R002C_ICC_TX_EN_A 0x002C
#define MAX98512B_R002D_ICC_TX_EN_B 0x002D
#define MAX98512B_R002E_ICC_HIZ_MANUAL_MODE 0x002E
#define MAX98512B_R002F_ICC_TX_HIZ_EN_A 0x002F
#define MAX98512B_R0030_ICC_TX_HIZ_EN_B 0x0030
#define MAX98512B_R0031_ICC_LNK_EN 0x0031
#define MAX98512B_R0032_PDM_TX_EN 0x0032
#define MAX98512B_R0033_PDM_TX_HIZ_CTRL 0x0033
#define MAX98512B_R0034_PDM_TX_CTRL 0x0034
#define MAX98512B_R0035_PDM_RX_CTRL 0x0035
#define MAX98512B_R0036_AMP_VOL_CTRL 0x0036
#define MAX98512B_R0037_AMP_DSP_CFG 0x0037
#define MAX98512B_R0038_TONE_GEN_DC_CFG 0x0038
#define MAX98512B_R0039_AMP_EN 0x0039
#define MAX98512B_R003A_SPK_SRC_SEL 0x003A
#define MAX98512B_R003B_SPK_GAIN 0x003B
#define MAX98512B_R003C_SSM_CFG 0x003C
#define MAX98512B_R003D_MEAS_EN 0x003D
#define MAX98512B_R003E_MEAS_DSP_CFG 0x003E
#define MAX98512B_R003F_BOOST_CTRL0 0x003F
#define MAX98512B_R0040_BOOST_CTRL3 0x0040
#define MAX98512B_R0041_BOOST_CTRL1 0x0041
#define MAX98512B_R0042_MEAS_ADC_CFG 0x0042
#define MAX98512B_R0043_MEAS_ADC_BASE_MSB 0x0043
#define MAX98512B_R0044_MEAS_ADC_BASE_LSB 0x0044
#define MAX98512B_R0045_ADC_CH0_DIVIDE 0x0045
#define MAX98512B_R0046_ADC_CH1_DIVIDE 0x0046
#define MAX98512B_R0047_ADC_CH2_DIVIDE 0x0047
#define MAX98512B_R0048_ADC_CH0_FILT_CFG 0x0048
#define MAX98512B_R0049_ADC_CH1_FILT_CFG 0x0049
#define MAX98512B_R004A_ADC_CH2_FILT_CFG 0x004A
#define MAX98512B_R004B_MEAS_ADC_CH0_READ 0x004B
#define MAX98512B_R004C_MEAS_ADC_CH1_READ 0x004C
#define MAX98512B_R004D_MEAS_ADC_CH2_READ 0x004D
#define MAX98512B_R004E_SQUELCH 0x004E
#define MAX98512B_R0051_BROWNOUT_STATUS 0x0051
#define MAX98512B_R0052_BROWNOUT_EN 0x0052
#define MAX98512B_R0053_BROWNOUT_INFINITE_HOLD 0x0053
#define MAX98512B_R0054_BROWNOUT_INFINITE_HOLD_CLR 0x0054
#define MAX98512B_R0055_BROWNOUT_LVL_HOLD 0x0055
#define MAX98512B_R0056_BROWNOUT_LVL1_THRESH 0x0056
#define MAX98512B_R0057_BROWNOUT_LVL2_THRESH 0x0057
#define MAX98512B_R0058_BROWNOUT_LVL3_THRESH 0x0058
#define MAX98512B_R0059_BROWNOUT_LVL4_THRESH 0x0059
#define MAX98512B_R005A_BROWNOUT_THRESH_HYSTERYSIS 0x005A
#define MAX98512B_R005B_BROWNOUT_AMP_LIMITER_ATK_REL 0x005B
#define MAX98512B_R005C_BROWNOUT_AMP_GAIN_ATK_REL 0x005C
#define MAX98512B_R005D_BROWNOUT_AMP1_CLIP_MODE 0x005D
#define MAX98512B_R005E_BROWNOUT_LVL1_CUR_LIMIT 0x005E
#define MAX98512B_R005F_BROWNOUT_LVL1_AMP1_CTRL1 0x005F
#define MAX98512B_R0060_BROWNOUT_LVL1_AMP1_CTRL2 0x0060
#define MAX98512B_R0061_BROWNOUT_LVL1_AMP1_CTRL3 0x0061
#define MAX98512B_R0062_BROWNOUT_LVL2_CUR_LIMIT 0x0062
#define MAX98512B_R0063_BROWNOUT_LVL2_AMP1_CTRL1 0x0063
#define MAX98512B_R0064_BROWNOUT_LVL2_AMP1_CTRL2 0x0064
#define MAX98512B_R0065_BROWNOUT_LVL2_AMP1_CTRL3 0x0065
#define MAX98512B_R0066_BROWNOUT_LVL3_CUR_LIMIT 0x0066
#define MAX98512B_R0067_BROWNOUT_LVL3_AMP1_CTRL1 0x0067
#define MAX98512B_R0068_BROWNOUT_LVL3_AMP1_CTRL2 0x0068
#define MAX98512B_R0069_BROWNOUT_LVL3_AMP1_CTRL3 0x0069
#define MAX98512B_R006A_BROWNOUT_LVL4_CUR_LIMIT 0x006A
#define MAX98512B_R006B_BROWNOUT_LVL4_AMP1_CTRL1 0x006B
#define MAX98512B_R006C_BROWNOUT_LVL4_AMP1_CTRL2 0x006C
#define MAX98512B_R006D_BROWNOUT_LVL4_AMP1_CTRL3 0x006D
#define MAX98512B_R0073_ENV_TRACK_VOUT_HEADROOM 0x0073
#define MAX98512B_R0074_ENV_TRACK_BOOST_VOUT_DELAY 0x0074
#define MAX98512B_R0075_ENV_TRACK_REL_RATE 0x0075
#define MAX98512B_R0076_ENV_TRACK_HOLD_RATE 0x0076
#define MAX98512B_R0077_ENV_TRACK_CTRL 0x0077
#define MAX98512B_R0078_ENV_TRACK_BOOST_VOUT_READ 0x0078
#define MAX98512B_R0079_BOOST_BYPASS_1 0x0079
#define MAX98512B_R007A_BOOST_BYPASS_2 0x007A
#define MAX98512B_R007B_BOOST_BYPASS_3 0x007B
#define MAX98512B_R007C_FET_SCALING_1 0x007C
#define MAX98512B_R007D_FET_SCALING_2 0x007D
#define MAX98512B_R007E_FET_SCALING_3 0x007E
#define MAX98512B_R007F_FET_SCALING_4 0x007F
#define MAX98512B_R0080_IVADC_BYPASS 0x0080
#define MAX98512B_R0081_ADVANCED_SETUP 0x0081
#define MAX98512B_R0500_GLOBAL_SHDN 0x0500
#define MAX98512B_R0501_SOFT_RESET 0x0501
#define MAX98512B_R0600_REV_ID 0x0600
#define MAX98512B_R0FAA_ENABLE_HVDD 0x0FAA
/* MAX98512_R0018_PCM_RX_EN_A */
#define MAX98512_PCM_RX_CH0_EN (0x1 << 0)
#define MAX98512_PCM_RX_CH1_EN (0x1 << 1)
#define MAX98512_PCM_RX_CH2_EN (0x1 << 2)
#define MAX98512_PCM_RX_CH3_EN (0x1 << 3)
#define MAX98512_PCM_RX_CH4_EN (0x1 << 4)
#define MAX98512_PCM_RX_CH5_EN (0x1 << 5)
#define MAX98512_PCM_RX_CH6_EN (0x1 << 6)
#define MAX98512_PCM_RX_CH7_EN (0x1 << 7)
/* MAX98512_R0019_PCM_RX_EN_B */
#define MAX98512_PCM_RX_CH8_EN (0x1 << 0)
#define MAX98512_PCM_RX_CH9_EN (0x1 << 1)
#define MAX98512_PCM_RX_CH10_EN (0x1 << 2)
#define MAX98512_PCM_RX_CH11_EN (0x1 << 3)
#define MAX98512_PCM_RX_CH12_EN (0x1 << 4)
#define MAX98512_PCM_RX_CH13_EN (0x1 << 5)
#define MAX98512_PCM_RX_CH14_EN (0x1 << 6)
#define MAX98512_PCM_RX_CH15_EN (0x1 << 7)
/* MAX98512_R001A_PCM_TX_EN_A */
#define MAX98512_PCM_TX_CH0_EN (0x1 << 0)
#define MAX98512_PCM_TX_CH1_EN (0x1 << 1)
#define MAX98512_PCM_TX_CH2_EN (0x1 << 2)
#define MAX98512_PCM_TX_CH3_EN (0x1 << 3)
#define MAX98512_PCM_TX_CH4_EN (0x1 << 4)
#define MAX98512_PCM_TX_CH5_EN (0x1 << 5)
#define MAX98512_PCM_TX_CH6_EN (0x1 << 6)
#define MAX98512_PCM_TX_CH7_EN (0x1 << 7)
/* MAX98512_R001B_PCM_TX_EN_B */
#define MAX98512_PCM_TX_CH8_EN (0x1 << 0)
#define MAX98512_PCM_TX_CH9_EN (0x1 << 1)
#define MAX98512_PCM_TX_CH10_EN (0x1 << 2)
#define MAX98512_PCM_TX_CH11_EN (0x1 << 3)
#define MAX98512_PCM_TX_CH12_EN (0x1 << 4)
#define MAX98512_PCM_TX_CH13_EN (0x1 << 5)
#define MAX98512_PCM_TX_CH14_EN (0x1 << 6)
#define MAX98512_PCM_TX_CH15_EN (0x1 << 7)
/* MAX98512_R001C_PCM_TX_HIZ_CTRL_A */
#define MAX98512_PCM_TX_CH0_HIZ (0x1 << 0)
#define MAX98512_PCM_TX_CH1_HIZ (0x1 << 1)
#define MAX98512_PCM_TX_CH2_HIZ (0x1 << 2)
#define MAX98512_PCM_TX_CH3_HIZ (0x1 << 3)
#define MAX98512_PCM_TX_CH4_HIZ (0x1 << 4)
#define MAX98512_PCM_TX_CH5_HIZ (0x1 << 5)
#define MAX98512_PCM_TX_CH6_HIZ (0x1 << 6)
#define MAX98512_PCM_TX_CH7_HIZ (0x1 << 7)
/* MAX98512_R001D_PCM_TX_HIZ_CTRL_B */
#define MAX98512_PCM_TX_CH8_HIZ (0x1 << 0)
#define MAX98512_PCM_TX_CH9_HIZ (0x1 << 1)
#define MAX98512_PCM_TX_CH10_HIZ (0x1 << 2)
#define MAX98512_PCM_TX_CH11_HIZ (0x1 << 3)
#define MAX98512_PCM_TX_CH12_HIZ (0x1 << 4)
#define MAX98512_PCM_TX_CH13_HIZ (0x1 << 5)
#define MAX98512_PCM_TX_CH14_HIZ (0x1 << 6)
#define MAX98512_PCM_TX_CH15_HIZ (0x1 << 7)
/* MAX98512_R001E_PCM_TX_CH_SRC_A */
#define MAX98512_PCM_TX_CH_SRC_A_V_SHIFT (0)
#define MAX98512_PCM_TX_CH_SRC_A_I_SHIFT (4)
/* MAX98512_R001F_PCM_TX_CH_SRC_B */
#define MAX98512_PCM_TX_CH_INTERLEAVE_MASK (0x1 << 5)
/* MAX98512_R0020_PCM_MODE_CFG */
#define MAX98512_PCM_MODE_CFG_PCM_BCLKEDGE (0x1 << 2)
#define MAX98512_PCM_MODE_CFG_FORMAT_MASK (0x7 << 3)
#define MAX98512_PCM_MODE_CFG_PCM_CH_SIZE_MASK (0x3 << 6)
#define MAX98512_PCM_MODE_CFG_FORMAT_SHIFT (3)
#define MAX98512_PCM_FORMAT_I2S (0x0 << 0)
#define MAX98512_PCM_FORMAT_LJ (0x1 << 0)
#define MAX98512_PCM_FORMAT_TDM_MODE0 (0x3 << 0)
#define MAX98512_PCM_FORMAT_TDM_MODE1 (0x4 << 0)
#define MAX98512_PCM_FORMAT_TDM_MODE2 (0x5 << 0)
#define MAX98512_PCM_MODE_CFG_CHANSZ_MASK (0x3 << 6)
#define MAX98512_PCM_MODE_CFG_CHANSZ_16 (0x1 << 6)
#define MAX98512_PCM_MODE_CFG_CHANSZ_24 (0x2 << 6)
#define MAX98512_PCM_MODE_CFG_CHANSZ_32 (0x3 << 6)
/* MAX98512_R0021_PCM_MASTER_MODE */
#define MAX98512_PCM_MASTER_MODE_MASK (0x3 << 0)
#define MAX98512_PCM_MASTER_MODE_SLAVE (0x0 << 0)
#define MAX98512_PCM_MASTER_MODE_MASTER (0x3 << 0)
#define MAX98512_PCM_MASTER_MODE_MCLK_MASK (0xF << 2)
#define MAX98512_PCM_MASTER_MODE_MCLK_RATE_SHIFT (2)
/* MAX98512_R0022_PCM_CLK_SETUP */
#define MAX98512_PCM_CLK_SETUP_BSEL_MASK (0xF << 0)
/* MAX98512_R0023_PCM_SR_SETUP1 */
#define MAX98512_PCM_SR_SET1_SR_MASK (0xF << 0)
#define MAX98512_PCM_SR_SET1_SR_8000 (0x0 << 0)
#define MAX98512_PCM_SR_SET1_SR_11025 (0x1 << 0)
#define MAX98512_PCM_SR_SET1_SR_12000 (0x2 << 0)
#define MAX98512_PCM_SR_SET1_SR_16000 (0x3 << 0)
#define MAX98512_PCM_SR_SET1_SR_22050 (0x4 << 0)
#define MAX98512_PCM_SR_SET1_SR_24000 (0x5 << 0)
#define MAX98512_PCM_SR_SET1_SR_32000 (0x6 << 0)
#define MAX98512_PCM_SR_SET1_SR_44100 (0x7 << 0)
#define MAX98512_PCM_SR_SET1_SR_48000 (0x8 << 0)
#define MAX98512_PCM_SR_SET1_SR_88200 (0x9 << 0)
#define MAX98512_PCM_SR_SET1_SR_96000 (0x10 << 0)
#define MAX98512_PCM_SR_SET1_SR_176400 (0x11 << 0)
#define MAX98512_PCM_SR_SET1_SR_192000 (0x12 << 0)
/* MAX98512_R0024_PCM_SR_SETUP2 */
#define MAX98512_PCM_SR_SET2_SR_MASK (0xF << 4)
#define MAX98512_PCM_SR_SET2_SR_SHIFT (4)
#define MAX98512_PCM_SR_SET2_IVADC_SR_MASK (0xF << 0)
/* MAX98512_R0025_PCM_TO_SPK_MONOMIX_A */
#define MAX98512_PCM_TO_SPK_MONOMIX_CFG_MASK (0x3 << 6)
#define MAX98512_PCM_TO_SPK_MONOMIX_A_CH0_SRC_MASK (0xF << 0)
#define MAX98512_PCM_TO_SPK_MONOMIX_CFG_SHIFT (6)
/* MAX98512_R0033_PDM_TX_CTRL */
#define MAX98512_PDM_TX_ENABLES_PDM_TX_EN (0x1 << 0)
#define MAX98512_PDM_TX_CTRL_PDM_TX_CH0_SRC (0x1 << 0)
#define MAX98512_PDM_TX_CTRL_PDM_TX_CH1_SRC (0x1 << 1)
/* MAX98512_R0034_PDM_RX_CTRL */
#define MAX98512_PDM_RX_EN_MASK (0x1 << 0)
/* MAX98512_R0035_AMP_VOL_CTRL */
#define MAX98512_AMP_VOL_SEL (0x1 << 7)
#define MAX98512_AMP_VOL_SEL_WIDTH (1)
#define MAX98512_AMP_VOL_SEL_SHIFT (7)
#define MAX98512_AMP_VOL_MASK (0x7F << 0)
#define MAX98512_AMP_VOL_WIDTH (7)
#define MAX98512_AMP_VOL_SHIFT (0)
/* MAX98512_R0036_AMP_DSP_CFG */
#define MAX98512_AMP_DSP_CFG_DCBLK_EN (0x1 << 0)
#define MAX98512_AMP_DSP_CFG_DITH_EN (0x1 << 1)
#define MAX98512_AMP_DSP_CFG_RMP_BYPASS (0x1 << 4)
#define MAX98512_AMP_DSP_CFG_DAC_INV (0x1 << 5)
#define MAX98512_AMP_DSP_CFG_RMP_SHIFT (4)
/* MAX98512_R0038_AMP_EN */
#define MAX98512_DEM_EN_MASK (0x1 << 7)
#define MAX98512_DEM_OFF_TRIM_MASK (0x1 << 6)
#define MAX98512_AMP_EN_MASK (0x1 << 0)
/* MAX98512_R0039_SPK_SRC_SEL */
#define MAX98512_SPK_SRC_MASK (0x3 << 0)
#define MAX98512_SPK_SOURCE_DIGITAL (0x0 << 0)
#define MAX98512_SPK_SOURCE_TONE_GEN (0x2 << 0)
#define MAX98512_SPK_SOURCE_PDM_IN (0x3 << 0)
/* MAX98512_R003A_SPK_GAIN */
#define MAX98512_SPK_PCM_GAIN_MASK (0x7 << 0)
#define MAX98512_SPK_PDM_GAIN_MASK (0x7 << 4)
#define MAX98512_SPK_GAIN_WIDTH (3)
/* MAX98512_R003C_MEAS_EN */
#define MAX98512_MEAS_V_EN (0x1 << 0)
#define MAX98512_MEAS_I_EN (0x1 << 1)
#define MAX98512_MEAS_VI_EN (0x3 << 0)
/* MAX98512_R003E_BOOST_CTRL0 */
#define MAX98512_BOOST_CTRL0_VOUT_MASK (0x1F << 0)
#define MAX98512_BOOST_CTRL0_PVDD_MASK (0x1 << 7)
#define MAX98512_BOOST_CTRL0_PVDD_EN_SHIFT (7)
/* MAX98512_R0052_BROWNOUT_EN */
#define MAX98512_BROWNOUT_BDE_EN (0x1 << 0)
#define MAX98512_BROWNOUT_AMP_EN (0x1 << 1)
#define MAX98512_BROWNOUT_DSP_EN (0x1 << 2)
#define MAX98512_BROWNOUT_DSP_SHIFT (2)
#define MAX98512_BROWNOUT_AMP_LIM_ATK_SHIFT (4)
#define MAX98512_BROWNOUT_THRES_HYST_MASK (0xFF << 0)
#define MAX98512_BROWNOUT_AMP_LIM_ATK_MASK (0xF << 4)
#define MAX98512_BROWNOUT_AMP_LIM_RLS_MASK (0xF << 0)
#define MAX98512_BROWNOUT_LVL_HOLD_MASK (0xFF << 0)
#define MAX98512_BROWNOUT_AMP1_CLIP_MODE_MODE (0x1 << 0)
#define MAX98512_BROWNOUT_LVL_INF_HOLD_CLEAR_L4 (0x1 << 1)
#define MAX98512_BROWNOUT_LVL_INF_HOLD_L4 (0x1 << 1)
/* MAX98512_R0100_SOFT_RESET */
#define MAX98512_SOFT_RESET (0x1 << 0)
/* MAX98512_R00FF_GLOBAL_SHDN */
#define MAX98512_GLOBAL_EN_MASK (0x1 << 0)
/* MAX98512 volume step */
enum volume_step {
MAX98512_VSTEP_0 = 0,
MAX98512_VSTEP_1,
MAX98512_VSTEP_2,
MAX98512_VSTEP_3,
MAX98512_VSTEP_4,
MAX98512_VSTEP_5,
MAX98512_VSTEP_6,
MAX98512_VSTEP_7,
MAX98512_VSTEP_8,
MAX98512_VSTEP_9,
MAX98512_VSTEP_10,
MAX98512_VSTEP_11,
MAX98512_VSTEP_12,
MAX98512_VSTEP_13,
MAX98512_VSTEP_14,
MAX98512_VSTEP_15,
MAX98512_VSTEP_MAX,
};
/* MAX98512 one stop mode */
enum one_stop_mode {
MAX98512_OSM_STEREO = 0,
MAX98512_OSM_STEREO_MODE2,
MAX98512_OSM_MONO_L,
MAX98512_OSM_MONO_R,
MAX98512_OSM_RCV_L,
MAX98512_OSM_RCV_R,
MAX98512_OSM_STEREO_L,
MAX98512_OSM_STEREO_R,
MAX98512_OSM_MAX,
};
#ifdef CONFIG_SND_SOC_MAXIM_DSM_CAL
extern struct class *g_class;
#else
struct class *g_class;
#endif /* CONFIG_SND_SOC_MAXIM_DSM_CAL */
struct max98512_volume_step_info {
int length;
int vol_step;
int adc_thres;
int boost_step[MAX98512_VSTEP_MAX];
bool adc_status;
};
struct max98512_pc_active {
u32 capture_active;
u32 playback_active:1;
};
#ifdef CONFIG_SND_SOC_MAXIM_DSM
#define MAX98512_PINFO_SZ PARAM_OFFSET_MAX
#else
#define MAX98512_PINFO_SZ 6
#endif /* CONFIG_SND_SOC_MAXIM_DSM */
struct max98512_pdata {
uint32_t pinfo[MAX98512_PINFO_SZ];
const uint32_t *reg_arr;
uint32_t reg_arr_len;
uint32_t boostv;
int osm;
int boost_mode;
bool nodsm;
int sub_reg;
uint32_t ppr_info[PARAM_OFFSET_PPR_MAX];
};
struct max98512_priv {
struct device *i2c_dev;
struct i2c_client *sub_i2c;
struct regmap *regmap_l;
struct regmap *regmap_r;
struct regmap *regmap;
struct snd_soc_codec *codec;
struct max98512_pdata *pdata;
struct max98512_pc_active pca;
struct max98512_volume_step_info vstep;
#if defined(USE_DSM_LOG) || defined(USE_DSM_UPDATE_CAL)
struct class *class;
struct device *dev;
#endif /* USE_DSM_LOG */
unsigned int spk_gain;
unsigned int spk_gain_rcv;
unsigned int spk_gain_left;
unsigned int spk_gain_right;
unsigned int sysclk;
unsigned int v_l_slot;
unsigned int i_l_slot;
unsigned int v_r_slot;
unsigned int i_r_slot;
bool mono_stereo;
bool interleave_mode;
unsigned int ch_size;
unsigned int rate;
unsigned int iface;
unsigned int master;
unsigned int digital_gain;
unsigned int digital_gain_rcv;
unsigned int current_limit_left;
unsigned int current_limit_right;
unsigned int thres_hyste;
unsigned int level5_hold;
unsigned int level6_hold;
unsigned int level7_hold;
unsigned int level8_hold;
unsigned int amp_limit;
unsigned int amp_limit_rel;
unsigned int amp1_level;
unsigned int amp2_level;
unsigned int amp3_level;
unsigned int amp1_level8;
unsigned int amp2_level8;
unsigned int amp3_level8;
unsigned int amp1_level7;
unsigned int amp2_level7;
unsigned int amp3_level7;
unsigned int amp1_level6;
unsigned int amp2_level6;
unsigned int amp3_level6;
unsigned int amp1_level5;
unsigned int amp2_level5;
unsigned int amp3_level5;
unsigned int pdm_gain;
unsigned int level_hold;
int revID;
int revID_r;
};
#define MAX98512_REV_ID_0 0x40
enum MAX98512_REV_ID {
ID_MAX98512_REV = 0,
ID_MAX98512_REV_B = 1
};
#define MAX98512_GLOBAL_SHIFT 0
#define M98512_DAI_MSEL_SHIFT 4
#define M98512_DAI_BSEL_SHIFT 0
#define M98512_DAI_BSEL_32 (2 << M98512_DAI_BSEL_SHIFT)
#define M98512_DAI_BSEL_48 (3 << M98512_DAI_BSEL_SHIFT)
#define M98512_DAI_BSEL_64 (4 << M98512_DAI_BSEL_SHIFT)
#define M98512_DAI_MSEL_32 (2 << M98512_DAI_MSEL_SHIFT)
#define M98512_DAI_MSEL_48 (3 << M98512_DAI_MSEL_SHIFT)
#define M98512_DAI_MSEL_64 (4 << M98512_DAI_MSEL_SHIFT)
#define MAX98512_SPK_RMP_EN_SHIFT 4
#define MAX98512_PDM_GAIN_SHIFT 4
#define MAX98512_PDM_GAIN_WIDTH 3
#define MAX98512_AMP_VOL_LOCATION_SHIFT 7
#define MAX98512_PDM_RX_ENABLE_PDM_CH_SHIFT 3
#define MAX98512_PCM_TO_SPEAKER_MONOMIX_A_SHIFT 6
#define MAX98512_PCM_SAMPLE_RATE_SETUP_2_DIG_IF_SR_48000 (0x8 << 4)
#define MAX98512_PCM_FORMAT_DSP_A (0x3 << 3)
#define MAX98512_BDE_DSP_SHIFT 2
#define MAX98512_BDE_AMP_SHIFT 1
#endif

3434
sound/soc/codecs/maxim_dsm.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,726 @@
/*
* maxim_dsm_power.c -- Module for Rdc calibration
*
* Copyright 2015 Maxim Integrated Products
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <sound/maxim_dsm.h>
#include <sound/maxim_dsm_cal.h>
#include <sound/maxim_dsm_power.h>
#define DEBUG_MAXIM_DSM_POWER
#ifdef DEBUG_MAXIM_DSM_POWER
#define dbg_maxdsm(format, args...) \
pr_info("[MAXIM_DSM_POWER] %s: " format "\n", __func__, ## args)
#else
#define dbg_maxdsm(format, args...)
#endif /* DEBUG_MAXIM_DSM_POWER */
#define MAXIM_DSM_POWER_MEASUREMENT
struct maxim_dsm_power *g_mdp;
static struct maxim_dsm_ppr_init_values ppr_init_value;
static int maxdsm_power_check(
struct maxim_dsm_power *mdp, int action, int delay)
{
int ret = 0;
if (delayed_work_pending(&mdp->work))
cancel_delayed_work(&mdp->work);
if (action) {
mdp->info.remaining = mdp->info.duration;
mdp->values.count = mdp->values.avg = mdp->values.avg_r = 0;
mdp->info.previous_jiffies = jiffies;
queue_delayed_work(mdp->wq,
&mdp->work,
msecs_to_jiffies(delay));
}
return ret;
}
void maxdsm_power_control(int state)
{
maxdsm_power_check(g_mdp, state, 0);
}
EXPORT_SYMBOL_GPL(maxdsm_power_control);
static void maxdsm_power_work(struct work_struct *work)
{
struct maxim_dsm_power *mdp;
unsigned int power = 0, power_r = 0, stereo = 0;
unsigned long diff;
mdp = container_of(work, struct maxim_dsm_power, work.work);
mutex_lock(&mdp->mutex);
#ifdef CONFIG_SND_SOC_MAXIM_DSM
stereo = maxdsm_is_stereo();
#else
stereo = 0;
#endif
#ifdef CONFIG_SND_SOC_MAXIM_DSM
power = maxdsm_get_power_measurement();
if (stereo)
power_r = maxdsm_get_power_measurement_r();
#endif /* CONFIG_SND_SOC_MAXIM_DSM */
if (power) {
mdp->values.avg += power;
if (power_r)
mdp->values.avg_r += power_r;
mdp->values.count++;
}
diff = jiffies - mdp->info.previous_jiffies;
mdp->info.remaining -= jiffies_to_msecs(diff);
dbg_maxdsm("power=0x%08x remaining=%d duration=%d",
power,
mdp->info.remaining,
mdp->info.duration);
if (mdp->info.remaining > 0
&& mdp->values.status) {
mdp->info.previous_jiffies = jiffies;
queue_delayed_work(mdp->wq,
&mdp->work,
msecs_to_jiffies(mdp->info.interval));
} else {
mdp->values.count > 0 ?
do_div(mdp->values.avg, mdp->values.count) : 0;
if (stereo)
mdp->values.count > 0 ?
do_div(mdp->values.avg_r, mdp->values.count) : 0;
mdp->values.power = mdp->values.avg;
mdp->values.power_r = mdp->values.avg_r;
if (stereo)
dbg_maxdsm("power=0x%08x power_r=0x%08x", mdp->values.power, mdp->values.power_r);
else
dbg_maxdsm("power=0x%08x", mdp->values.power);
#ifdef MAXIM_DSM_POWER_MEASUREMENT
if (g_mdp->values.status) {
if (maxdsm_power_check(g_mdp, g_mdp->values.status,
MAXDSM_POWER_START_DELAY * 10)) {
pr_err("%s: The codec was connected?\n",
__func__);
mutex_lock(&g_mdp->mutex);
g_mdp->values.status = 0;
mutex_unlock(&g_mdp->mutex);
}
}
#else
g_mdp->values.status = 0;
#endif
}
mutex_unlock(&mdp->mutex);
}
static int maxdsm_power_ppr_state(struct maxim_dsm_power *mdp, int ch)
{
unsigned long diff;
int state = g_mdp->values_ppr[ch].status;
dbg_maxdsm("set state %d channel %d", state, ch);
diff = jiffies - mdp->info_ppr.amp_uptime_jiffies;
switch (g_mdp->values_ppr[ch].status) {
case PPR_ON:
case PPR_OFF:
case PPR_NORMAL:
case PPR_RUN:
if (jiffies_to_msecs(diff) > mdp->info_ppr.target_min_time_ms) {
if ((mdp->values_ppr[ch].env_temp + 500) < mdp->values_ppr[ch].spk_t &&
mdp->values_ppr[ch].spk_t > mdp->values_ppr[ch].target_temp &&
mdp->values_ppr[ch].spk_t > mdp->values_ppr[ch].exit_temp) {
state = PPR_RUN;
maxdsm_set_ppr_enable(1, mdp->values_ppr[ch].cutoff_frequency, mdp->values_ppr[ch].threshold_level, ch);
} else if (mdp->values_ppr[ch].spk_t < mdp->values_ppr[ch].exit_temp) {
maxdsm_set_ppr_enable(0, 0x1401, mdp->values_ppr[ch].threshold_level, ch);
state = PPR_NORMAL;
}
}
}
return state;
}
static int maxdsm_power_ppr_check(struct maxim_dsm_power *mdp, int state, int deley)
{
if (delayed_work_pending(&mdp->work_ppr))
cancel_delayed_work(&mdp->work_ppr);
dbg_maxdsm("set state %d", state);
if (state) {
mdp->info_ppr.previous_jiffies = jiffies;
queue_delayed_work(mdp->wq,
&mdp->work_ppr,
deley);
}
return 0;
}
void maxdsm_power_ppr_control(int state)
{
if (state == g_mdp->values_ppr[MAXDSM_LEFT].status && state == g_mdp->values_ppr[MAXDSM_RIGHT].status) {
dbg_maxdsm("ppr ignored. state %d Left state %d Right state %d", state, g_mdp->values_ppr[MAXDSM_LEFT].status,
g_mdp->values_ppr[MAXDSM_RIGHT].status);
} else if (g_mdp->info_ppr.global_enable != 0) {
mutex_lock(&g_mdp->mutex);
g_mdp->values_ppr[MAXDSM_LEFT].status = state;
g_mdp->values_ppr[MAXDSM_RIGHT].status = state;
g_mdp->info_ppr.amp_uptime_jiffies = jiffies;
mutex_unlock(&g_mdp->mutex);
if (maxdsm_power_ppr_check(g_mdp, state, 0)) {
pr_err("%s: The codec was connected?\n",
__func__);
mutex_lock(&g_mdp->mutex);
g_mdp->values_ppr[MAXDSM_LEFT].status = PPR_OFF;
g_mdp->values_ppr[MAXDSM_RIGHT].status = PPR_OFF;
mutex_unlock(&g_mdp->mutex);
}
}
}
EXPORT_SYMBOL_GPL(maxdsm_power_ppr_control);
static void maxdsm_power_work_ppr(struct work_struct *work)
{
struct maxim_dsm_power *mdp;
int state, i;
mdp = container_of(work, struct maxim_dsm_power, work_ppr.work);
mutex_lock(&mdp->mutex);
for (i = 0 ; i < MAXDSM_CHANNEL ; i++) {
state = maxdsm_power_ppr_state(mdp, i);
if (state != g_mdp->values_ppr[i].status)
g_mdp->values_ppr[i].status = state;
dbg_maxdsm("g_mdp->values_ppr[i].status %d", g_mdp->values_ppr[i].status);
}
if (g_mdp->values_ppr[MAXDSM_LEFT].status || g_mdp->values_ppr[MAXDSM_RIGHT].status) {
if (maxdsm_power_ppr_check(g_mdp, 1, MAXDSM_POWER_START_DELAY * 10)) {
pr_err("%s: The codec was connected?\n",
__func__);
g_mdp->values_ppr[MAXDSM_LEFT].status = 0;
g_mdp->values_ppr[MAXDSM_RIGHT].status = 0;
}
}
mutex_unlock(&mdp->mutex);
}
int maxdsm_update_ppr_info(uint32_t *ppr_info)
{
int ret = 0;
int32_t *data = ppr_info;
if (ppr_info == NULL) {
pr_err("%s: ppr_info was not set.\n",
__func__);
ret = -EINVAL;
return ret;
}
ppr_init_value.target_temp[MAXDSM_LEFT] = data[PARAM_OFFSET_PPR_TARGET_TEMP];
ppr_init_value.target_temp[MAXDSM_RIGHT] = data[PARAM_OFFSET_PPR_TARGET_TEMP_R];
ppr_init_value.exit_temp[MAXDSM_LEFT] = data[PARAM_OFFSET_PPR_EXIT_TEMP];
ppr_init_value.exit_temp[MAXDSM_RIGHT] = data[PARAM_OFFSET_PPR_EXIT_TEMP_R];
return ret;
}
EXPORT_SYMBOL_GPL(maxdsm_update_ppr_info);
static ssize_t maxdsm_power_duration_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->info.duration);
}
static ssize_t maxdsm_power_duration_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->info.duration))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
if (g_mdp->info.duration < 10000)
g_mdp->info.duration = 10000;
return size;
}
static DEVICE_ATTR(duration, 0664,
maxdsm_power_duration_show, maxdsm_power_duration_store);
static ssize_t maxdsm_power_value_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%x", g_mdp->values.power);
}
static DEVICE_ATTR(value, 0664, maxdsm_power_value_show, NULL);
static ssize_t maxdsm_power_r_value_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%x", g_mdp->values.power_r);
}
static DEVICE_ATTR(value_r, 0664, maxdsm_power_r_value_show, NULL);
static ssize_t maxdsm_power_interval_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->info.interval);
}
static ssize_t maxdsm_power_interval_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->info.interval))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(interval, 0664,
maxdsm_power_interval_show, maxdsm_power_interval_store);
static ssize_t maxdsm_power_ppr_target_min_time_ms_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->info_ppr.target_min_time_ms);
}
static ssize_t maxdsm_power_ppr_target_min_time_ms_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->info_ppr.target_min_time_ms))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(target_min_time_ms, 0664,
maxdsm_power_ppr_target_min_time_ms_show, maxdsm_power_ppr_target_min_time_ms_store);
static ssize_t maxdsm_power_ppr_target_temp_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_LEFT].target_temp);
}
static ssize_t maxdsm_power_ppr_target_temp_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_LEFT].target_temp))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(target_temp, 0664,
maxdsm_power_ppr_target_temp_show, maxdsm_power_ppr_target_temp_store);
static ssize_t maxdsm_power_ppr_target_temp_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_RIGHT].target_temp);
}
static ssize_t maxdsm_power_ppr_target_temp_r_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_RIGHT].target_temp))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(target_temp_r, 0664,
maxdsm_power_ppr_target_temp_r_show, maxdsm_power_ppr_target_temp_r_store);
static ssize_t maxdsm_power_ppr_cut_off_freq_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_LEFT].cutoff_frequency);
}
static ssize_t maxdsm_power_ppr_cut_off_freq_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_LEFT].cutoff_frequency))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
maxdsm_set_ppr_xover_freq(g_mdp->values_ppr[MAXDSM_LEFT].cutoff_frequency, MAXDSM_LEFT);
return size;
}
static DEVICE_ATTR(cutoff_frequency, 0664,
maxdsm_power_ppr_cut_off_freq_show, maxdsm_power_ppr_cut_off_freq_store);
static ssize_t maxdsm_power_ppr_cut_off_freq_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_RIGHT].cutoff_frequency);
}
static ssize_t maxdsm_power_ppr_cut_off_freq_r_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_RIGHT].cutoff_frequency))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
maxdsm_set_ppr_xover_freq(g_mdp->values_ppr[MAXDSM_RIGHT].cutoff_frequency, MAXDSM_RIGHT);
return size;
}
static DEVICE_ATTR(cutoff_frequency_r, 0664,
maxdsm_power_ppr_cut_off_freq_r_show, maxdsm_power_ppr_cut_off_freq_r_store);
static ssize_t maxdsm_power_ppr_threshold_level_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_LEFT].threshold_level);
}
static ssize_t maxdsm_power_ppr_threshold_level_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_LEFT].threshold_level))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
maxdsm_set_ppr_threshold_db(g_mdp->values_ppr[MAXDSM_LEFT].threshold_level, MAXDSM_LEFT);
return size;
}
static DEVICE_ATTR(threshold_level, 0664,
maxdsm_power_ppr_threshold_level_show, maxdsm_power_ppr_threshold_level_store);
static ssize_t maxdsm_power_ppr_threshold_level_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_RIGHT].threshold_level);
}
static ssize_t maxdsm_power_ppr_threshold_level_r_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_RIGHT].threshold_level))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
maxdsm_set_ppr_threshold_db(g_mdp->values_ppr[MAXDSM_RIGHT].threshold_level, MAXDSM_RIGHT);
return size;
}
static DEVICE_ATTR(threshold_level_r, 0664,
maxdsm_power_ppr_threshold_level_r_show, maxdsm_power_ppr_threshold_level_r_store);
static ssize_t maxdsm_power_ppr_env_temp_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_LEFT].env_temp);
}
static ssize_t maxdsm_power_ppr_env_temp_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtos32(buf, 0, &g_mdp->values_ppr[MAXDSM_LEFT].env_temp))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(env_temp, 0664,
maxdsm_power_ppr_env_temp_show, maxdsm_power_ppr_env_temp_store);
static ssize_t maxdsm_power_ppr_env_temp_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_RIGHT].env_temp);
}
static ssize_t maxdsm_power_ppr_env_temp_r_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtos32(buf, 0, &g_mdp->values_ppr[MAXDSM_RIGHT].env_temp))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(env_temp_r, 0664,
maxdsm_power_ppr_env_temp_r_show, maxdsm_power_ppr_env_temp_r_store);
static ssize_t maxdsm_power_ppr_spk_t_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_LEFT].spk_t);
}
static ssize_t maxdsm_power_ppr_spk_t_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_LEFT].spk_t))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(spk_t, 0664,
maxdsm_power_ppr_spk_t_show, maxdsm_power_ppr_spk_t_store);
static ssize_t maxdsm_power_ppr_spk_t_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_RIGHT].spk_t);
}
static ssize_t maxdsm_power_ppr_spk_t_r_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_RIGHT].spk_t))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(spk_t_r, 0664,
maxdsm_power_ppr_spk_t_r_show, maxdsm_power_ppr_spk_t_r_store);
static ssize_t maxdsm_power_ppr_exit_temp_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_LEFT].exit_temp);
}
static ssize_t maxdsm_power_ppr_exit_temp_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_LEFT].exit_temp))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(exit_temp, 0664,
maxdsm_power_ppr_exit_temp_show, maxdsm_power_ppr_exit_temp_store);
static ssize_t maxdsm_power_ppr_exit_temp_r_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->values_ppr[MAXDSM_RIGHT].exit_temp);
}
static ssize_t maxdsm_power_ppr_exit_temp_r_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->values_ppr[MAXDSM_RIGHT].exit_temp))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(exit_temp_r, 0664,
maxdsm_power_ppr_exit_temp_r_show, maxdsm_power_ppr_exit_temp_r_store);
static ssize_t maxdsm_power_ppr_global_enable_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d", g_mdp->info_ppr.global_enable);
}
static ssize_t maxdsm_power_ppr_global_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (kstrtou32(buf, 0, &g_mdp->info_ppr.global_enable))
dev_err(dev, "%s: Failed converting from str to u32.\n",
__func__);
return size;
}
static DEVICE_ATTR(global_enable, 0664,
maxdsm_power_ppr_global_enable_show, maxdsm_power_ppr_global_enable_store);
static ssize_t maxdsm_power_status_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%s\n",
g_mdp->values.status ? "Enabled" : "Disabled");
}
static ssize_t maxdsm_power_status_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int status = 0;
if (!kstrtou32(buf, 0, &status)) {
status = status > 0 ? 1 : 0;
if (status == g_mdp->values.status) {
dbg_maxdsm("Already run. It will be ignored.");
} else {
mutex_lock(&g_mdp->mutex);
g_mdp->values.status = status;
mutex_unlock(&g_mdp->mutex);
if (maxdsm_power_check(g_mdp, status,
MAXDSM_POWER_START_DELAY)) {
pr_err("%s: The codec was connected?\n",
__func__);
mutex_lock(&g_mdp->mutex);
g_mdp->values.status = 0;
mutex_unlock(&g_mdp->mutex);
}
}
}
return size;
}
static DEVICE_ATTR(status, 0664,
maxdsm_power_status_show, maxdsm_power_status_store);
static struct attribute *maxdsm_power_attr[] = {
&dev_attr_duration.attr,
&dev_attr_value.attr,
&dev_attr_value_r.attr,
&dev_attr_interval.attr,
&dev_attr_status.attr,
&dev_attr_target_min_time_ms.attr,
&dev_attr_target_temp.attr,
&dev_attr_target_temp_r.attr,
&dev_attr_exit_temp.attr,
&dev_attr_exit_temp_r.attr,
&dev_attr_cutoff_frequency.attr,
&dev_attr_cutoff_frequency_r.attr,
&dev_attr_threshold_level.attr,
&dev_attr_threshold_level_r.attr,
&dev_attr_env_temp.attr,
&dev_attr_env_temp_r.attr,
&dev_attr_spk_t.attr,
&dev_attr_spk_t_r.attr,
&dev_attr_global_enable.attr,
NULL,
};
static struct attribute_group maxdsm_power_attr_grp = {
.attrs = maxdsm_power_attr,
};
static int __init maxdsm_power_init(void)
{
struct class *class = NULL;
struct maxim_dsm_power *mdp;
int i, ret = 0;
g_mdp = kzalloc(sizeof(struct maxim_dsm_power), GFP_KERNEL);
if (g_mdp == NULL)
return -ENOMEM;
mdp = g_mdp;
mdp->wq = create_singlethread_workqueue(MAXDSM_POWER_WQ_NAME);
if (mdp->wq == NULL) {
kfree(g_mdp);
return -ENOMEM;
}
INIT_DELAYED_WORK(&g_mdp->work, maxdsm_power_work);
INIT_DELAYED_WORK(&g_mdp->work_ppr, maxdsm_power_work_ppr);
mutex_init(&g_mdp->mutex);
mdp->info.duration = 10000; /* 10 secs */
mdp->info.remaining = mdp->info.duration;
mdp->info.interval = 10000; /* 10 secs */
mdp->values.power = 0xFFFFFFFF;
mdp->values.power_r = 0xFFFFFFFF;
mdp->platform_type = 0xFFFFFFFF;
mdp->info_ppr.target_min_time_ms = 300000;
mdp->info_ppr.global_enable = 1;
for (i = 0 ; i < MAXDSM_CHANNEL ; i++) {
mdp->values_ppr[i].cutoff_frequency = 0x3E801;
mdp->values_ppr[i].env_temp = 2500;
mdp->values_ppr[i].target_temp = ppr_init_value.target_temp[i];
mdp->values_ppr[i].spk_t = 2500;
mdp->values_ppr[i].threshold_level = 0xE20189;
mdp->values_ppr[i].exit_temp = ppr_init_value.exit_temp[i];
}
#ifdef CONFIG_SND_SOC_MAXIM_DSM_CAL
class = maxdsm_cal_get_class();
#else
if (!class)
class = class_create(THIS_MODULE,
MAXDSM_POWER_DSM_NAME);
#endif /* CONFIG_SND_SOC_MAXIM_DSM_CAL */
mdp->class = class;
if (mdp->class) {
mdp->dev = device_create(mdp->class, NULL, 1, NULL,
MAXDSM_POWER_CLASS_NAME);
if (!IS_ERR(mdp->dev)) {
if (sysfs_create_group(&mdp->dev->kobj,
&maxdsm_power_attr_grp))
dbg_maxdsm(
"Failed to create sysfs group. ret=%d",
ret);
}
}
dbg_maxdsm("Completed initialization");
return ret;
}
module_init(maxdsm_power_init);
static void __exit maxdsm_power_exit(void)
{
kfree(g_mdp);
};
module_exit(maxdsm_power_exit);
MODULE_DESCRIPTION("For power measurement of DSM");
MODULE_AUTHOR("Kyounghun Jeon<hun.jeon@maximintegrated.com");
MODULE_LICENSE("GPL");

View file

@ -852,6 +852,8 @@ ARIZONA_LHPF_CONTROL("LHPF2 Coefficients", ARIZONA_HPLPF2_2),
ARIZONA_LHPF_CONTROL("LHPF3 Coefficients", ARIZONA_HPLPF3_2),
ARIZONA_LHPF_CONTROL("LHPF4 Coefficients", ARIZONA_HPLPF4_2),
WM_ADSP2_PRELOAD_SWITCH("DSP1", 1),
ARIZONA_MIXER_CONTROLS("DSP1L", ARIZONA_DSP1LMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("DSP1R", ARIZONA_DSP1RMIX_INPUT_1_SOURCE),

View file

@ -776,6 +776,11 @@ SOC_ENUM("ISRC2 FSH", arizona_isrc_fsh[1]),
SOC_ENUM("ISRC3 FSH", arizona_isrc_fsh[2]),
SOC_ENUM("ASRC RATE 1", arizona_asrc_rate1),
WM_ADSP2_PRELOAD_SWITCH("DSP1", 1),
WM_ADSP2_PRELOAD_SWITCH("DSP2", 2),
WM_ADSP2_PRELOAD_SWITCH("DSP3", 3),
WM_ADSP2_PRELOAD_SWITCH("DSP4", 4),
ARIZONA_MIXER_CONTROLS("DSP1L", ARIZONA_DSP1LMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("DSP1R", ARIZONA_DSP1RMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("DSP2L", ARIZONA_DSP2LMIX_INPUT_1_SOURCE),

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,23 @@
#define WM_ADSP_COMPR_OK 0
#define WM_ADSP_COMPR_VOICE_TRIGGER 1
#define WM_ADSP2_REGION_0 BIT(0)
#define WM_ADSP2_REGION_1 BIT(1)
#define WM_ADSP2_REGION_2 BIT(2)
#define WM_ADSP2_REGION_3 BIT(3)
#define WM_ADSP2_REGION_4 BIT(4)
#define WM_ADSP2_REGION_5 BIT(5)
#define WM_ADSP2_REGION_6 BIT(6)
#define WM_ADSP2_REGION_7 BIT(7)
#define WM_ADSP2_REGION_8 BIT(8)
#define WM_ADSP2_REGION_9 BIT(9)
#define WM_ADSP2_REGION_1_9 (WM_ADSP2_REGION_1 | \
WM_ADSP2_REGION_2 | WM_ADSP2_REGION_3 | \
WM_ADSP2_REGION_4 | WM_ADSP2_REGION_5 | \
WM_ADSP2_REGION_6 | WM_ADSP2_REGION_7 | \
WM_ADSP2_REGION_8 | WM_ADSP2_REGION_9)
#define WM_ADSP2_REGION_ALL (WM_ADSP2_REGION_0 | WM_ADSP2_REGION_1_9)
struct wm_adsp_region {
int type;
unsigned int base;
@ -38,13 +55,37 @@ struct wm_adsp_alg_region {
struct wm_adsp_compr;
struct wm_adsp_compr_buf;
struct wm_adsp_buffer_region_def {
unsigned int mem_type;
unsigned int base_offset;
unsigned int size_offset;
};
struct wm_adsp_fw_caps {
u32 id;
struct snd_codec_desc desc;
int num_regions;
struct wm_adsp_buffer_region_def *region_defs;
};
struct wm_adsp_fw_defs {
const char *file;
const char *binfile;
bool fullname;
int compr_direction;
int num_caps;
struct wm_adsp_fw_caps *caps;
bool voice_trigger;
};
struct wm_adsp {
const char *part;
int rev;
int num;
int type;
struct device *dev;
struct regmap *regmap;
struct snd_soc_card *card;
struct snd_soc_codec *codec;
int base;
int sysclk_reg;
@ -62,9 +103,13 @@ struct wm_adsp {
int fw;
int fw_ver;
bool preloaded;
bool booted;
bool running;
int num_firmwares;
struct wm_adsp_fw_defs *firmwares;
struct list_head ctl_list;
struct work_struct boot_work;
@ -74,6 +119,8 @@ struct wm_adsp {
struct mutex pwr_lock;
unsigned int lock_regions;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_root;
char *wmfw_file_name;
@ -86,7 +133,12 @@ struct wm_adsp {
SND_SOC_DAPM_PGA_E(wname, SND_SOC_NOPM, num, 0, NULL, 0, \
wm_adsp1_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)
#define WM_ADSP2_PRELOAD_SWITCH(wname, num) \
SOC_SINGLE_EXT(wname " Preload Switch", SND_SOC_NOPM, num, 1, 0, \
wm_adsp2_preloader_get, wm_adsp2_preloader_put)
#define WM_ADSP2(wname, num, event_fn) \
SND_SOC_DAPM_SPK(wname " Preload", NULL), \
{ .id = snd_soc_dapm_supply, .name = wname " Preloader", \
.reg = SND_SOC_NOPM, .shift = num, .event = event_fn, \
.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD, \
@ -107,21 +159,29 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w,
int wm_adsp2_early_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event,
unsigned int freq);
int wm_adsp2_lock(struct wm_adsp *adsp, unsigned int regions);
irqreturn_t wm_adsp2_bus_error(struct wm_adsp *adsp);
int wm_adsp2_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
extern int wm_adsp_compr_open(struct wm_adsp *dsp,
struct snd_compr_stream *stream);
extern int wm_adsp_compr_free(struct snd_compr_stream *stream);
extern int wm_adsp_compr_set_params(struct snd_compr_stream *stream,
struct snd_compr_params *params);
extern int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
struct snd_compr_caps *caps);
extern int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd);
extern int wm_adsp_compr_handle_irq(struct wm_adsp *dsp);
extern int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
struct snd_compr_tstamp *tstamp);
extern int wm_adsp_compr_copy(struct snd_compr_stream *stream,
char __user *buf, size_t count);
int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int wm_adsp2_preloader_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream);
int wm_adsp_compr_free(struct snd_compr_stream *stream);
int wm_adsp_compr_set_params(struct snd_compr_stream *stream,
struct snd_compr_params *params);
int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
struct snd_compr_caps *caps);
int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd);
int wm_adsp_compr_handle_irq(struct wm_adsp *dsp);
int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
struct snd_compr_tstamp *tstamp);
int wm_adsp_compr_copy(struct snd_compr_stream *stream,
char __user *buf, size_t count);
#endif

View file

@ -26,6 +26,10 @@
#define WMFW_CTL_FLAG_WRITEABLE 0x0002
#define WMFW_CTL_FLAG_READABLE 0x0001
/* Non-ALSA coefficient types start at 0x1000 */
#define WMFW_CTL_TYPE_ACKED 0x1000 /* acked control */
#define WMFW_CTL_TYPE_HOSTEVENT 0x1001 /* event control */
struct wmfw_header {
char magic[4];
__le32 len;

View file

@ -33,6 +33,9 @@ config SND_SAMSUNG_SPDIF
config SND_SAMSUNG_I2S
tristate "Samsung I2S interface support"
config SND_SOC_SAMSUNG_DISPLAYPORT
tristate "Samsung display port audio interface support"
config SND_SOC_SAMSUNG_NEO1973_WM8753
tristate "Audio support for Openmoko Neo1973 Smartphones (GTA02)"
depends on MACH_NEO1973_GTA02
@ -223,10 +226,82 @@ 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_SAMSUNG_EXYNOS8895
tristate "Audio support on exynos8895"
select SND_SOC_SAMSUNG_ABOX
select SND_SOC_SAMSUNG_VTS
select SND_SOC_SAMSUNG_MAILBOX
select SND_SOC_SAMSUNG_DISPLAYPORT
help
Say Y if you want to add support for audio on the Exynos8895.
config SND_SOC_SAMSUNG_EXYNOS8895_MOON
tristate "Moon audio codec support on exynos8895"
select SND_SOC_CS47L92
select MFD_MADERA_SPI
select MFD_CS47L92
select REGULATOR_MADERA_LDO1
select REGULATOR_MADERA_MICSUPP
select SWITCH
select SWITCH_MADERA
config SND_SOC_SAMSUNG_EXYNOS8895_MAX98506
tristate "MAX98506 amp support on exynos8895"
select SND_SOC_MAX98506
config SND_SOC_SAMSUNG_EXYNOS9810
tristate "Audio support on exynos9810"
select SND_SOC_SAMSUNG_ABOX
#select SND_SOC_SAMSUNG_VTS
#select SND_SOC_SAMSUNG_MAILBOX
select SND_SOC_SAMSUNG_DISPLAYPORT
help
Say Y if you want to add support for audio on the Exynos9810.
config SND_SOC_SAMSUNG_EXYNOS9810_MOON
tristate "Moon audio codec support on exynos9810"
select SND_SOC_CS47L92
select MFD_MADERA_SPI
select MFD_CS47L92
select REGULATOR_ARIZONA_LDO1
select REGULATOR_ARIZONA_MICSUPP
select EXTCON_MADERA
select EXTCON_MADERA_INPUT_EVENT
config SND_SOC_SAMSUNG_STAR_ASHETON
tristate "Asheton audio codec support on Star project"
select SND_SOC_CS47L92
select MFD_MADERA_SPI
select MFD_CS47L92
select REGULATOR_ARIZONA_LDO1
select REGULATOR_ARIZONA_MICSUPP
select EXTCON_MADERA
select EXTCON_MADERA_INPUT_EVENT
config SND_SOC_SAMSUNG_EXYNOS9810_MAX98506
tristate "MAX98506 amp support on exynos9810"
select SND_SOC_MAX98506
config SND_SOC_SAMSUNG_EXYNOS9810_MAX98512
tristate "MAX98512 amp support on exynos9810"
select SND_SOC_MAX98512
config SND_SOC_SAMSUNG_EXYNOS8895_COD3033
tristate "S.LSI audio codec COD3033 support on exynos8895"
select SND_SOC_COD3033X
config SND_SOC_ARNDALE_RT5631_ALC5631
tristate "Audio support for RT5631(ALC5631) on Arndale Board"
depends on I2C
select SND_SAMSUNG_I2S
select SND_SOC_RT5631
config SND_SOC_SAMSUNG_AUDIO
tristate "Audio support for Samsung Projects"
help
Say Y here to enable audio support for the Samsung Audio.
source "sound/soc/samsung/abox/Kconfig"
source "sound/soc/samsung/vts/Kconfig"
endif #SND_SOC_SAMSUNG

View file

@ -8,6 +8,7 @@ snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o
snd-soc-samsung-spdif-objs := spdif.o
snd-soc-pcm-objs := pcm.o
snd-soc-i2s-objs := i2s.o
snd-soc-dp_dma-objs := dp_dma.o dummy_cpu_dai.o
obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-s3c-dma.o
obj-$(CONFIG_SND_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o
@ -18,6 +19,9 @@ obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o
obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o
obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o
obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-idma.o
obj-$(CONFIG_SND_SOC_SAMSUNG_DISPLAYPORT) += snd-soc-dp_dma.o
obj-$(CONFIG_SND_SOC_SAMSUNG_VTS) += vts/
obj-$(CONFIG_SND_SOC_SAMSUNG_ABOX) += abox/
# S3C24XX Machine Support
snd-soc-jive-wm8750-objs := jive_wm8750.o
@ -44,6 +48,8 @@ snd-soc-lowland-objs := lowland.o
snd-soc-littlemill-objs := littlemill.o
snd-soc-bells-objs := bells.o
snd-soc-arndale-rt5631-objs := arndale_rt5631.o
snd-soc-star-madera-objs := star_madera.o jack_madera_sysfs_cb.o
snd-soc-sec-audio-objs := sec_audio_sysfs.o sec_audio_debug.o
obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o
obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
@ -69,3 +75,8 @@ 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_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o
obj-$(CONFIG_SND_SOC_SAMSUNG_EXYNOS8895_MOON) += exynos8895_madera.o
obj-$(CONFIG_SND_SOC_SAMSUNG_EXYNOS8895_COD3033) += exynos8895_sound.o
obj-$(CONFIG_SND_SOC_SAMSUNG_EXYNOS9810_MOON) += exynos9810_madera.o
obj-$(CONFIG_SND_SOC_SAMSUNG_STAR_ASHETON) += snd-soc-star-madera.o
obj-$(CONFIG_SND_SOC_SAMSUNG_AUDIO) += snd-soc-sec-audio.o

View file

@ -0,0 +1,17 @@
config SND_SOC_SAMSUNG_ABOX
tristate "ASoC support for Samsung ABOX Audio"
select REGMAP_MMIO
select SND_SOC_COMPRESS
select SND_HWDEP
help
Say Y or M if you want to add support for codecs attached to
the Samsung SoC ABOX interface. You will also need to
select the audio interfaces to support below.
config SEC_SND_ADAPTATION
tristate "Samsung ABOX Adaptation for Audio"
depends on SND_SOC_SAMSUNG_ABOX
help
Say Y or M if you want to add support for external devices attached to
the Samsung SoC ABOX interface. You will also need to
select the audio interfaces to support below.

View file

@ -0,0 +1,2 @@
obj-$(CONFIG_SND_SOC_SAMSUNG_ABOX) += abox_util.o abox_dbg.o abox_dump.o abox_log.o abox_gic.o abox.o abox_rdma.o abox_wdma.o abox_if.o abox_effect.o abox_vss.o abox_failsafe.o abox_vdma.o abox_mmapfd.o
obj-$(CONFIG_SEC_SND_ADAPTATION) += abox_adaptation.o

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,259 @@
/* sound/soc/samsung/abox/abox_adaptation.c
*
* ALSA SoC Audio Layer - Samsung Abox adaptation driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <sound/sec_adaptation.h>
#include "abox.h"
#define TIMEOUT_MS 130
#define READ_WRITE_ALL_PARAM 0
#define DEBUG_ABOX_ADAPTATION
#ifdef DEBUG_ABOX_ADAPTATION
#define dbg_abox_adaptation(format, args...) \
pr_info("[ABOX_ADAPTATION] %s: " format "\n", __func__, ## args)
#else
#define dbg_abox_adaptation(format, args...)
#endif /* DEBUG_ABOX_ADAPTATION */
static DECLARE_WAIT_QUEUE_HEAD(wq_read);
static DECLARE_WAIT_QUEUE_HEAD(wq_write);
struct abox_platform_data *data;
struct maxim_dsm *read_maxdsm;
bool abox_ipc_irq_read_avail;
bool abox_ipc_irq_write_avail;
int dsm_offset;
int dsm_param_size;
int maxim_dsm_read(int offset, int size, void *dsm_data)
{
ABOX_IPC_MSG msg;
int ret = 0;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
read_maxdsm = (struct maxim_dsm *)dsm_data;
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_EXTRA;
erap_msg->param.raw.params[0] = 0;
erap_msg->param.raw.params[1] = offset;
erap_msg->param.raw.params[2] = size;
dbg_abox_adaptation("");
abox_ipc_irq_read_avail = false;
if (offset >= 185)
dsm_offset = offset % 185;
else
dsm_offset = offset;
dsm_param_size = size;
ret = abox_request_ipc(&data->pdev_abox->dev, IPC_ERAP,
&msg, sizeof(msg), 0, 0);
if (ret) {
pr_err("%s: abox_request_ipc is failed: %d\n", __func__, ret);
return ret;
}
ret = wait_event_interruptible_timeout(wq_read,
abox_ipc_irq_read_avail != false, msecs_to_jiffies(TIMEOUT_MS));
if (!ret)
pr_err("%s: wait_event timeout\n", __func__);
return ret;
}
EXPORT_SYMBOL_GPL(maxim_dsm_read);
int maxim_dsm_write(uint32_t *dsm_data, int offset, int size)
{
ABOX_IPC_MSG msg;
int ret = 0;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_EXTRA;
erap_msg->param.raw.params[0] = 1;
erap_msg->param.raw.params[1] = offset;
erap_msg->param.raw.params[2] = size;
memcpy(&erap_msg->param.raw.params[3],
dsm_data,
min((sizeof(uint32_t) * size)
, sizeof(erap_msg->param.raw)));
dbg_abox_adaptation("");
abox_ipc_irq_write_avail = false;
dsm_offset = READ_WRITE_ALL_PARAM;
ret = abox_request_ipc(&data->pdev_abox->dev, IPC_ERAP,
&msg, sizeof(msg), 0, 0);
if (ret) {
pr_err("%s: abox_request_ipc is failed: %d\n", __func__, ret);
return ret;
}
ret = wait_event_interruptible_timeout(wq_write,
abox_ipc_irq_write_avail != false, msecs_to_jiffies(TIMEOUT_MS));
if (!ret)
pr_err("%s: wait_event timeout\n", __func__);
return ret;
}
EXPORT_SYMBOL_GPL(maxim_dsm_write);
static irqreturn_t abox_adaptation_irq_handler(int irq,
void *dev_id, ABOX_IPC_MSG *msg)
{
struct IPC_ERAP_MSG *erap_msg = &msg->msg.erap;
dbg_abox_adaptation("irq=%d, param[0]=%d",
irq, erap_msg->param.raw.params[0]);
switch (irq) {
case IPC_ERAP:
switch (erap_msg->msgtype) {
case REALTIME_EXTRA:
if ((dsm_offset != READ_WRITE_ALL_PARAM) &&
(dsm_offset != PARAM_DSM_5_0_ABOX_GET_LOGGING) &&
(dsm_offset != PARAM_DSM_5_0_ABOX_GET_LOGGING_R)) {
if ((dsm_offset + dsm_param_size) > read_maxdsm->param_size)
dsm_param_size = read_maxdsm->param_size - dsm_offset;
memcpy(&read_maxdsm->param[dsm_offset],
&erap_msg->param.raw.params[0],
sizeof(uint32_t) * dsm_param_size);
abox_ipc_irq_read_avail = true;
dbg_abox_adaptation("read_avail after parital read[%d]",
abox_ipc_irq_read_avail);
if (abox_ipc_irq_read_avail && waitqueue_active(&wq_read))
wake_up_interruptible(&wq_read);
} else if ((erap_msg->param.raw.params[0] > 0)
&& (erap_msg->param.raw.params[0]
<= sizeof(erap_msg->param.raw.params))) {
if (erap_msg->param.raw.params[0] > read_maxdsm->param_size)
erap_msg->param.raw.params[0] = read_maxdsm->param_size;
memcpy(&read_maxdsm->param[0],
&erap_msg->param.raw.params[0],
sizeof(uint32_t) * erap_msg->param.raw.params[0]);
abox_ipc_irq_read_avail = true;
dbg_abox_adaptation("read_avail after full read[%d]",
abox_ipc_irq_read_avail);
if (abox_ipc_irq_read_avail && waitqueue_active(&wq_read))
wake_up_interruptible(&wq_read);
} else if (erap_msg->param.raw.params[0]
== PARAM_DSM_5_0_ABOX_WRITE_CB) {
abox_ipc_irq_write_avail = true;
dbg_abox_adaptation("write_avail[%d]",
abox_ipc_irq_write_avail);
if (abox_ipc_irq_write_avail && waitqueue_active(&wq_write))
wake_up_interruptible(&wq_write);
}
break;
default:
pr_err("%s: unknown message type\n", __func__);
break;
}
break;
default:
pr_err("%s: unknown command\n", __func__);
break;
}
return IRQ_HANDLED;
}
static struct snd_soc_platform_driver abox_adaptation = {
};
static int samsung_abox_adaptation_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *np_abox;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data) {
dev_err(dev, "Failed to allocate memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, data);
dsm_offset = READ_WRITE_ALL_PARAM;
np_abox = of_parse_phandle(np, "abox", 0);
if (!np_abox) {
dev_err(dev, "Failed to get abox device node\n");
return -EPROBE_DEFER;
}
data->pdev_abox = of_find_device_by_node(np_abox);
if (!data->pdev_abox) {
dev_err(dev, "Failed to get abox platform device\n");
return -EPROBE_DEFER;
}
data->abox_data = platform_get_drvdata(data->pdev_abox);
abox_register_irq_handler(&data->pdev_abox->dev, IPC_ERAP,
abox_adaptation_irq_handler, pdev);
dev_info(dev, "%s\n", __func__);
return snd_soc_register_platform(&pdev->dev, &abox_adaptation);
}
static int samsung_abox_adaptation_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static const struct of_device_id samsung_abox_adaptation_match[] = {
{
.compatible = "samsung,abox-adaptation",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_abox_adaptation_match);
static struct platform_driver samsung_abox_adaptation_driver = {
.probe = samsung_abox_adaptation_probe,
.remove = samsung_abox_adaptation_remove,
.driver = {
.name = "samsung-abox-adaptation",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_abox_adaptation_match),
},
};
module_platform_driver(samsung_abox_adaptation_driver);
/* Module information */
MODULE_AUTHOR("SeokYoung Jang, <quartz.jang@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box Adaptation Driver");
MODULE_ALIAS("platform:samsung-abox-adaptation");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,588 @@
/* sound/soc/samsung/abox/abox_dbg.c
*
* ALSA SoC Audio Layer - Samsung Abox Debug driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <linux/io.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/iommu.h>
#include <linux/of_reserved_mem.h>
#include <linux/pm_runtime.h>
#include <linux/mm_types.h>
#include <asm/cacheflush.h>
#include "abox_dbg.h"
#include "abox_gic.h"
#define ABOX_DBG_DUMP_LIMIT_NS (5 * NSEC_PER_SEC)
static struct dentry *abox_dbg_root_dir __read_mostly;
struct dentry *abox_dbg_get_root_dir(void)
{
pr_debug("%s\n", __func__);
if (abox_dbg_root_dir == NULL)
abox_dbg_root_dir = debugfs_create_dir("abox", NULL);
return abox_dbg_root_dir;
}
void abox_dbg_print_gpr_from_addr(struct device *dev, struct abox_data *data,
unsigned int *addr)
{
int i;
char version[4];
memcpy(version, &data->calliope_version, sizeof(version));
dev_info(dev, "========================================\n");
dev_info(dev, "A-Box CPU register dump (%c%c%c%c)\n",
version[3], version[2], version[1], version[0]);
dev_info(dev, "----------------------------------------\n");
for (i = 0; i <= 14; i++)
dev_info(dev, "CA7_R%02d : %08x\n", i, *addr++);
dev_info(dev, "CA7_PC : %08x\n", *addr++);
dev_info(dev, "========================================\n");
}
void abox_dbg_print_gpr(struct device *dev, struct abox_data *data)
{
int i;
char version[4];
memcpy(version, &data->calliope_version, sizeof(version));
dev_info(dev, "========================================\n");
dev_info(dev, "A-Box CPU register dump (%c%c%c%c)\n",
version[3], version[2], version[1], version[0]);
dev_info(dev, "----------------------------------------\n");
for (i = 0; i <= 14; i++) {
dev_info(dev, "CA7_R%02d : %08x\n", i,
readl(data->sfr_base + ABOX_CPU_R(i)));
}
dev_info(dev, "CA7_PC : %08x\n",
readl(data->sfr_base + ABOX_CPU_PC));
dev_info(dev, "CA7_L2C_STATUS : %08x\n",
readl(data->sfr_base + ABOX_CPU_L2C_STATUS));
dev_info(dev, "========================================\n");
}
struct abox_dbg_dump {
char sram[SZ_512K];
char iva[IVA_FIRMWARE_SIZE];
char dram[DRAM_FIRMWARE_SIZE];
u32 sfr[SZ_64K / sizeof(u32)];
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
unsigned int gpr[17];
long long time;
char reason[SZ_32];
};
struct abox_dbg_dump_min {
char sram[SZ_512K];
char iva[IVA_FIRMWARE_SIZE];
void *dram;
struct page **pages;
u32 sfr[SZ_64K / sizeof(u32)];
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
unsigned int gpr[17];
long long time;
char reason[SZ_32];
};
static struct abox_dbg_dump (*p_abox_dbg_dump)[ABOX_DBG_DUMP_COUNT];
static struct abox_dbg_dump_min (*p_abox_dbg_dump_min)[ABOX_DBG_DUMP_COUNT];
static struct reserved_mem *abox_rmem;
static void *abox_rmem_vmap(struct reserved_mem *rmem)
{
phys_addr_t phys = rmem->base;
size_t size = rmem->size;
unsigned int num_pages = DIV_ROUND_UP(size, PAGE_SIZE);
pgprot_t prot = pgprot_writecombine(PAGE_KERNEL);
struct page **pages, **page;
void *vaddr = NULL;
pages = kcalloc(num_pages, sizeof(pages[0]), GFP_KERNEL);
if (!pages) {
pr_err("%s: malloc failed\n", __func__);
goto out;
}
for (page = pages; (page - pages < num_pages); page++) {
*page = phys_to_page(phys);
phys += PAGE_SIZE;
}
vaddr = vmap(pages, num_pages, VM_MAP, prot);
kfree(pages);
out:
return vaddr;
}
static int __init abox_rmem_setup(struct reserved_mem *rmem)
{
pr_info("%s: base=%pa, size=%pa\n", __func__, &rmem->base, &rmem->size);
abox_rmem = rmem;
return 0;
}
RESERVEDMEM_OF_DECLARE(abox_rmem, "exynos,abox_rmem", abox_rmem_setup);
static void *abox_dbg_alloc_mem_atomic(struct device *dev,
struct abox_dbg_dump_min *p_dump)
{
int i, j;
int npages = DRAM_FIRMWARE_SIZE / PAGE_SIZE;
struct page **tmp;
gfp_t alloc_gfp_flag = GFP_ATOMIC;
p_dump->pages = kzalloc(sizeof(struct page *) * npages, alloc_gfp_flag);
if (!p_dump->pages) {
dev_info(dev, "Failed to allocate array of struct pages\n");
return NULL;
}
tmp = p_dump->pages;
for (i = 0; i < npages; i++, tmp++) {
*tmp = alloc_page(alloc_gfp_flag);
if (*tmp == NULL) {
pr_err("Failed to allocate pages for abox debug\n");
goto free_pg;
}
}
return vm_map_ram(p_dump->pages, npages, -1, PAGE_KERNEL);
free_pg:
tmp = p_dump->pages;
for (j = 0; j < i; j++, tmp++)
__free_pages(*tmp, 0);
kfree(p_dump->pages);
p_dump->pages = NULL;
return NULL;
}
void abox_dbg_dump_gpr_from_addr(struct device *dev, unsigned int *addr,
enum abox_dbg_dump_src src, const char *reason)
{
int i;
static unsigned long long called[ABOX_DBG_DUMP_COUNT];
unsigned long long time = sched_clock();
dev_dbg(dev, "%s\n", __func__);
if (!abox_is_on()) {
dev_info(dev, "%s is skipped due to no power\n", __func__);
return;
}
if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) {
dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src);
called[src] = time;
return;
}
called[src] = time;
if (p_abox_dbg_dump) {
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
p_dump->time = time;
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
for (i = 0; i <= 14; i++)
p_dump->gpr[i] = *addr++;
p_dump->gpr[i++] = *addr++;
} else if (p_abox_dbg_dump_min) {
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
p_dump->time = time;
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
for (i = 0; i <= 14; i++)
p_dump->gpr[i] = *addr++;
p_dump->gpr[i++] = *addr++;
}
}
void abox_dbg_dump_gpr(struct device *dev, struct abox_data *data,
enum abox_dbg_dump_src src, const char *reason)
{
int i;
static unsigned long long called[ABOX_DBG_DUMP_COUNT];
unsigned long long time = sched_clock();
dev_dbg(dev, "%s\n", __func__);
if (!abox_is_on()) {
dev_info(dev, "%s is skipped due to no power\n", __func__);
return;
}
if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) {
dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src);
called[src] = time;
return;
}
called[src] = time;
if (p_abox_dbg_dump) {
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
p_dump->time = time;
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
for (i = 0; i <= 14; i++)
p_dump->gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i));
p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC);
p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS);
} else if (p_abox_dbg_dump_min) {
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
p_dump->time = time;
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
for (i = 0; i <= 14; i++)
p_dump->gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i));
p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC);
p_dump->gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS);
}
}
void abox_dbg_dump_mem(struct device *dev, struct abox_data *data,
enum abox_dbg_dump_src src, const char *reason)
{
static unsigned long long called[ABOX_DBG_DUMP_COUNT];
unsigned long long time = sched_clock();
struct abox_gic_data *gic_data = dev_get_drvdata(data->dev_gic);
dev_dbg(dev, "%s\n", __func__);
if (!abox_is_on()) {
dev_info(dev, "%s is skipped due to no power\n", __func__);
return;
}
if (called[src] && time - called[src] < ABOX_DBG_DUMP_LIMIT_NS) {
dev_dbg_ratelimited(dev, "%s(%d): skipped\n", __func__, src);
called[src] = time;
return;
}
called[src] = time;
if (p_abox_dbg_dump) {
struct abox_dbg_dump *p_dump = &(*p_abox_dbg_dump)[src];
p_dump->time = time;
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
memcpy_fromio(p_dump->sram, data->sram_base, data->sram_size);
if (data->ima_claimed)
memcpy_fromio(p_dump->iva, data->ima_vaddr,
sizeof(p_dump->iva));
else
memcpy(p_dump->iva, data->iva_base,
sizeof(p_dump->iva));
memcpy(p_dump->dram, data->dram_base, sizeof(p_dump->dram));
memcpy_fromio(p_dump->sfr, data->sfr_base, sizeof(p_dump->sfr));
memcpy_fromio(p_dump->sfr_gic_gicd, gic_data->gicd_base,
sizeof(p_dump->sfr_gic_gicd));
} else if (p_abox_dbg_dump_min) {
struct abox_dbg_dump_min *p_dump = &(*p_abox_dbg_dump_min)[src];
p_dump->time = time;
strncpy(p_dump->reason, reason, sizeof(p_dump->reason) - 1);
memcpy_fromio(p_dump->sram, data->sram_base, data->sram_size);
if (data->ima_claimed)
memcpy_fromio(p_dump->iva, data->ima_vaddr,
sizeof(p_dump->iva));
else
memcpy(p_dump->iva, data->iva_base,
sizeof(p_dump->iva));
memcpy_fromio(p_dump->sfr, data->sfr_base, sizeof(p_dump->sfr));
memcpy_fromio(p_dump->sfr_gic_gicd, gic_data->gicd_base,
sizeof(p_dump->sfr_gic_gicd));
if (!p_dump->dram)
p_dump->dram = abox_dbg_alloc_mem_atomic(dev, p_dump);
if (!IS_ERR_OR_NULL(p_dump->dram)) {
memcpy(p_dump->dram, data->dram_base,
DRAM_FIRMWARE_SIZE);
flush_cache_all();
} else {
dev_info(dev, "Failed to save ABOX dram\n");
}
}
}
void abox_dbg_dump_gpr_mem(struct device *dev, struct abox_data *data,
enum abox_dbg_dump_src src, const char *reason)
{
abox_dbg_dump_gpr(dev, data, src, reason);
abox_dbg_dump_mem(dev, data, src, reason);
}
struct abox_dbg_dump_simple {
char sram[SZ_512K];
char iva[IVA_FIRMWARE_SIZE];
u32 sfr[SZ_64K / sizeof(u32)];
u32 sfr_gic_gicd[SZ_4K / sizeof(u32)];
unsigned int gpr[17];
long long time;
char reason[SZ_32];
};
static struct abox_dbg_dump_simple abox_dump_simple;
void abox_dbg_dump_simple(struct device *dev, struct abox_data *data,
const char *reason)
{
static unsigned long long called;
unsigned long long time = sched_clock();
struct abox_gic_data *gic_data = dev_get_drvdata(data->dev_gic);
int i;
dev_info(dev, "%s\n", __func__);
if (!abox_is_on()) {
dev_info(dev, "%s is skipped due to no power\n", __func__);
return;
}
if (called && time - called < ABOX_DBG_DUMP_LIMIT_NS) {
dev_dbg_ratelimited(dev, "%s: skipped\n", __func__);
called = time;
return;
}
called = time;
abox_dump_simple.time = time;
strncpy(abox_dump_simple.reason, reason,
sizeof(abox_dump_simple.reason) - 1);
for (i = 0; i <= 14; i++)
abox_dump_simple.gpr[i] = readl(data->sfr_base + ABOX_CPU_R(i));
abox_dump_simple.gpr[i++] = readl(data->sfr_base + ABOX_CPU_PC);
abox_dump_simple.gpr[i++] = readl(data->sfr_base + ABOX_CPU_L2C_STATUS);
memcpy_fromio(abox_dump_simple.sram, data->sram_base, data->sram_size);
if (data->ima_claimed) {
memcpy_fromio(abox_dump_simple.iva, data->ima_vaddr,
sizeof(abox_dump_simple.iva));
} else {
memcpy(abox_dump_simple.iva, data->iva_base,
sizeof(abox_dump_simple.iva));
}
memcpy_fromio(abox_dump_simple.sfr, data->sfr_base,
sizeof(abox_dump_simple.sfr));
memcpy_fromio(abox_dump_simple.sfr_gic_gicd, gic_data->gicd_base,
sizeof(abox_dump_simple.sfr_gic_gicd));
}
static atomic_t abox_error_count = ATOMIC_INIT(0);
void abox_dbg_report_status(struct device *dev, bool ok)
{
char env[32] = {0,};
char *envp[2] = {env, NULL};
dev_info(dev, "%s\n", __func__);
if (ok)
atomic_set(&abox_error_count, 0);
else
atomic_inc(&abox_error_count);
snprintf(env, sizeof(env), "ERR_CNT=%d",
atomic_read(&abox_error_count));
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
}
int abox_dbg_get_error_count(struct device *dev)
{
int count = atomic_read(&abox_error_count);
dev_dbg(dev, "%s: %d\n", __func__, count);
return count;
}
static ssize_t calliope_sram_read(struct file *file, struct kobject *kobj,
struct bin_attribute *battr, char *buf,
loff_t off, size_t size)
{
struct device *dev = kobj_to_dev(kobj);
struct device *dev_abox = dev->parent;
dev_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size);
if (pm_runtime_get_if_in_use(dev_abox) > 0) {
memcpy_fromio(buf, battr->private + off, size);
pm_runtime_put(dev_abox);
} else {
memset(buf, 0x0, size);
}
return size;
}
static ssize_t calliope_iva_read(struct file *file, struct kobject *kobj,
struct bin_attribute *battr, char *buf,
loff_t off, size_t size)
{
struct device *dev = kobj_to_dev(kobj);
dev_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size);
memcpy(buf, battr->private + off, size);
return size;
}
static ssize_t calliope_dram_read(struct file *file, struct kobject *kobj,
struct bin_attribute *battr, char *buf,
loff_t off, size_t size)
{
struct device *dev = kobj_to_dev(kobj);
dev_dbg(dev, "%s(%lld, %zu)\n", __func__, off, size);
memcpy(buf, battr->private + off, size);
return size;
}
/* size will be updated later */
static BIN_ATTR_RO(calliope_sram, 0);
static BIN_ATTR_RO(calliope_iva, IVA_FIRMWARE_SIZE);
static BIN_ATTR_RO(calliope_dram, DRAM_FIRMWARE_SIZE);
static struct bin_attribute *calliope_bin_attrs[] = {
&bin_attr_calliope_sram,
&bin_attr_calliope_iva,
&bin_attr_calliope_dram,
};
static ssize_t gpr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct abox_data *data = dev_get_drvdata(dev->parent);
char version[4];
char *pbuf = buf;
int i;
if (!abox_is_on()) {
dev_info(dev, "%s is skipped due to no power\n", __func__);
return -EFAULT;
}
memcpy(version, &data->calliope_version, sizeof(version));
pbuf += sprintf(pbuf, "========================================\n");
pbuf += sprintf(pbuf, "A-Box CPU register dump (%c%c%c%c)\n",
version[3], version[2], version[1], version[0]);
pbuf += sprintf(pbuf, "----------------------------------------\n");
for (i = 0; i <= 14; i++) {
pbuf += sprintf(pbuf, "CA7_R%02d : %08x\n", i,
readl(data->sfr_base + ABOX_CPU_R(i)));
}
pbuf += sprintf(pbuf, "CA7_PC : %08x\n",
readl(data->sfr_base + ABOX_CPU_PC));
pbuf += sprintf(pbuf, "CA7_L2C_STATUS : %08x\n",
readl(data->sfr_base + ABOX_CPU_L2C_STATUS));
pbuf += sprintf(pbuf, "========================================\n");
return pbuf - buf;
}
static DEVICE_ATTR_RO(gpr);
static int samsung_abox_debug_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device *abox_dev = dev->parent;
struct abox_data *data = dev_get_drvdata(abox_dev);
int i, ret;
dev_dbg(dev, "%s\n", __func__);
if (abox_rmem) {
if (sizeof(*p_abox_dbg_dump) <= abox_rmem->size) {
p_abox_dbg_dump = abox_rmem_vmap(abox_rmem);
data->dump_base = p_abox_dbg_dump;
} else if (sizeof(*p_abox_dbg_dump_min) <= abox_rmem->size) {
p_abox_dbg_dump_min = abox_rmem_vmap(abox_rmem);
data->dump_base = p_abox_dbg_dump_min;
}
data->dump_base_phys = abox_rmem->base;
iommu_map(data->iommu_domain, IOVA_DUMP_BUFFER, abox_rmem->base,
abox_rmem->size, 0);
}
memset(data->dump_base, 0x0, abox_rmem->size);
ret = device_create_file(dev, &dev_attr_gpr);
bin_attr_calliope_sram.size = data->sram_size;
bin_attr_calliope_sram.private = data->sram_base;
bin_attr_calliope_iva.private = data->iva_base;
bin_attr_calliope_dram.private = data->dram_base;
for (i = 0; i < ARRAY_SIZE(calliope_bin_attrs); i++) {
struct bin_attribute *battr = calliope_bin_attrs[i];
ret = device_create_bin_file(dev, battr);
if (ret < 0)
dev_warn(dev, "Failed to create file: %s\n",
battr->attr.name);
}
memset(data->dump_base, 0x0, abox_rmem->size);
return ret;
}
static int samsung_abox_debug_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int i;
dev_dbg(dev, "%s\n", __func__);
for (i = 0; i < ABOX_DBG_DUMP_COUNT; i++) {
struct page **tmp = p_abox_dbg_dump_min[i]->pages;
if (p_abox_dbg_dump_min[i]->dram)
vm_unmap_ram(p_abox_dbg_dump_min[i]->dram,
DRAM_FIRMWARE_SIZE);
if (tmp) {
int j;
for (j = 0; j < DRAM_FIRMWARE_SIZE / PAGE_SIZE; j++, tmp++)
__free_pages(*tmp, 0);
kfree(p_abox_dbg_dump_min[i]->pages);
p_abox_dbg_dump_min[i]->pages = NULL;
}
}
return 0;
}
static const struct of_device_id samsung_abox_debug_match[] = {
{
.compatible = "samsung,abox-debug",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_abox_debug_match);
static struct platform_driver samsung_abox_debug_driver = {
.probe = samsung_abox_debug_probe,
.remove = samsung_abox_debug_remove,
.driver = {
.name = "samsung-abox-debug",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_abox_debug_match),
},
};
module_platform_driver(samsung_abox_debug_driver);
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box Debug Driver");
MODULE_ALIAS("platform:samsung-abox-debug");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,101 @@
/* sound/soc/samsung/abox/abox_dbg.h
*
* ALSA SoC - Samsung Abox Debug driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_DEBUG_H
#define __SND_SOC_ABOX_DEBUG_H
#include "abox.h"
enum abox_dbg_dump_src {
ABOX_DBG_DUMP_KERNEL,
ABOX_DBG_DUMP_FIRMWARE,
ABOX_DBG_DUMP_COUNT,
};
/**
* Initialize abox debug driver
* @return dentry of abox debugfs root directory
*/
extern struct dentry *abox_dbg_get_root_dir(void);
/**
* print gpr into the kmsg from memory
* @param[in] dev pointer to device which invokes this API
* @param[in] data pointer to abox_data structure
* @param[in] addr pointer to gpr dump
*/
extern void abox_dbg_print_gpr_from_addr(struct device *dev,
struct abox_data *data, unsigned int *addr);
/**
* print gpr into the kmsg
* @param[in] dev pointer to device which invokes this API
* @param[in] data pointer to abox_data structure
*/
extern void abox_dbg_print_gpr(struct device *dev, struct abox_data *data);
/**
* dump gpr from memory
* @param[in] dev pointer to device which invokes this API
* @param[in] addr pointer to gpr dump
* @param[in] src source of the dump request
* @param[in] reason reason description
*/
extern void abox_dbg_dump_gpr_from_addr(struct device *dev, unsigned int *addr,
enum abox_dbg_dump_src src, const char *reason);
/**
* dump gpr
* @param[in] dev pointer to device which invokes this API
* @param[in] data pointer to abox_data structure
* @param[in] src source of the dump request
* @param[in] reason reason description
*/
extern void abox_dbg_dump_gpr(struct device *dev, struct abox_data *data,
enum abox_dbg_dump_src src, const char *reason);
/**
* dump memory
* @param[in] dev pointer to device which invokes this API
* @param[in] data pointer to abox_data structure
* @param[in] src source of the dump request
* @param[in] reason reason description
*/
extern void abox_dbg_dump_mem(struct device *dev, struct abox_data *data,
enum abox_dbg_dump_src src, const char *reason);
/**
* dump gpr and memory
* @param[in] dev pointer to device which invokes this API
* @param[in] data pointer to abox_data structure
* @param[in] src source of the dump request
* @param[in] reason reason description
*/
extern void abox_dbg_dump_gpr_mem(struct device *dev, struct abox_data *data,
enum abox_dbg_dump_src src, const char *reason);
/**
* dump gpr and memory except DRAM
* @param[in] dev pointer to device which invokes this API
* @param[in] data pointer to abox_data structure
* @param[in] reason reason description
*/
extern void abox_dbg_dump_simple(struct device *dev, struct abox_data *data,
const char *reason);
/**
* Push status of the abox
* @param[in] dev pointer to abox device
* @param[in] ok true for okay, false on otherwise
*/
extern void abox_dbg_report_status(struct device *dev, bool ok);
#endif /* __SND_SOC_ABOX_DEBUG_H */

View file

@ -0,0 +1,672 @@
/* sound/soc/samsung/abox/abox_dump.c
*
* ALSA SoC Audio Layer - Samsung Abox Internal Buffer Dumping driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/spinlock.h>
#include <linux/vmalloc.h>
#include <linux/pm_runtime.h>
#include <sound/samsung/abox.h>
#include "abox_util.h"
#include "abox.h"
#include "abox_dbg.h"
#include "abox_log.h"
#define BUFFER_MAX (SZ_64)
#define NAME_LENGTH (SZ_32)
struct abox_dump_buffer_info {
struct device *dev;
struct list_head list;
int id;
char name[NAME_LENGTH];
struct mutex lock;
struct snd_dma_buffer buffer;
struct snd_pcm_substream *substream;
size_t pointer;
bool started;
bool auto_started;
bool file_created;
struct file *filp;
ssize_t auto_pointer;
struct work_struct auto_work;
};
static struct device *abox_dump_dev_abox;
static struct abox_dump_buffer_info abox_dump_list[BUFFER_MAX];
static LIST_HEAD(abox_dump_list_head);
static struct abox_dump_buffer_info *abox_dump_get_buffer_info(int id)
{
struct abox_dump_buffer_info *info;
list_for_each_entry(info, &abox_dump_list_head, list) {
if (info->id == id)
return info;
}
return NULL;
}
static struct abox_dump_buffer_info *abox_dump_get_buffer_info_by_name(
const char *name)
{
struct abox_dump_buffer_info *info;
list_for_each_entry(info, &abox_dump_list_head, list) {
if (strncmp(info->name, name, sizeof(info->name)) == 0)
return info;
}
return NULL;
}
static void abox_dump_request_dump(int id)
{
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
ABOX_IPC_MSG msg;
struct IPC_SYSTEM_MSG *system = &msg.msg.system;
bool start = info->started || info->auto_started;
dev_dbg(abox_dump_dev_abox, "%s(%d)\n", __func__, id);
msg.ipcid = IPC_SYSTEM;
system->msgtype = ABOX_REQUEST_DUMP;
system->param1 = id;
system->param2 = start ? 1 : 0;
abox_request_ipc(abox_dump_dev_abox, msg.ipcid, &msg, sizeof(msg),
1, 0);
}
static ssize_t abox_dump_auto_read(struct file *file, char __user *data,
size_t count, loff_t *ppos, bool enable)
{
struct abox_dump_buffer_info *info;
char buffer[SZ_256] = {0,}, *buffer_p = buffer;
dev_dbg(abox_dump_dev_abox, "%s(%zu, %lld, %d)\n", __func__, count,
*ppos, enable);
list_for_each_entry(info, &abox_dump_list_head, list) {
if (info->auto_started == enable) {
buffer_p += snprintf(buffer_p, sizeof(buffer) -
(buffer_p - buffer),
"%d(%s) ", info->id, info->name);
}
}
snprintf(buffer_p, 2, "\n");
return simple_read_from_buffer(data, count, ppos, buffer,
buffer_p - buffer);
}
static ssize_t abox_dump_auto_write(struct file *file, const char __user *data,
size_t count, loff_t *ppos, bool enable)
{
char buffer[SZ_256] = {0,}, name[NAME_LENGTH];
char *p_buffer = buffer, *token = NULL;
unsigned int id;
struct abox_dump_buffer_info *info;
ssize_t ret;
dev_dbg(abox_dump_dev_abox, "%s(%zu, %lld, %d)\n", __func__, count,
*ppos, enable);
ret = simple_write_to_buffer(buffer, sizeof(buffer), ppos, data, count);
if (ret < 0)
return ret;
while ((token = strsep(&p_buffer, " ")) != NULL) {
if (sscanf(token, "%11u", &id) == 1)
info = abox_dump_get_buffer_info(id);
else if (sscanf(token, "%31s", name) == 1)
info = abox_dump_get_buffer_info_by_name(name);
else
info = NULL;
if (IS_ERR_OR_NULL(info)) {
dev_err(abox_dump_dev_abox, "invalid argument\n");
continue;
}
if (info->auto_started != enable) {
struct device *dev = info->dev;
struct platform_device *pdev_abox;
pdev_abox = to_platform_device(abox_dump_dev_abox);
if (enable)
pm_runtime_get(dev);
else
pm_runtime_put(dev);
}
info->auto_started = enable;
if (enable) {
info->file_created = false;
info->auto_pointer = -1;
}
abox_dump_request_dump(info->id);
}
return count;
}
static ssize_t abox_dump_auto_start_read(struct file *file,
char __user *data, size_t count, loff_t *ppos)
{
return abox_dump_auto_read(file, data, count, ppos, true);
}
static ssize_t abox_dump_auto_start_write(struct file *file,
const char __user *data, size_t count, loff_t *ppos)
{
return abox_dump_auto_write(file, data, count, ppos, true);
}
static ssize_t abox_dump_auto_stop_read(struct file *file,
char __user *data, size_t count, loff_t *ppos)
{
return abox_dump_auto_read(file, data, count, ppos, false);
}
static ssize_t abox_dump_auto_stop_write(struct file *file,
const char __user *data, size_t count, loff_t *ppos)
{
return abox_dump_auto_write(file, data, count, ppos, false);
}
static const struct file_operations abox_dump_auto_start_fops = {
.read = abox_dump_auto_start_read,
.write = abox_dump_auto_start_write,
};
static const struct file_operations abox_dump_auto_stop_fops = {
.read = abox_dump_auto_stop_read,
.write = abox_dump_auto_stop_write,
};
static int __init samsung_abox_dump_late_initcall(void)
{
pr_info("%s\n", __func__);
debugfs_create_file("dump_auto_start", 0660, abox_dbg_get_root_dir(),
NULL, &abox_dump_auto_start_fops);
debugfs_create_file("dump_auto_stop", 0660, abox_dbg_get_root_dir(),
NULL, &abox_dump_auto_stop_fops);
return 0;
}
late_initcall(samsung_abox_dump_late_initcall);
static struct snd_soc_dai_link abox_dump_dai_links[BUFFER_MAX];
static struct snd_soc_card abox_dump_card = {
.name = "abox_dump",
.owner = THIS_MODULE,
.dai_link = abox_dump_dai_links,
.num_links = 0,
};
static void abox_dump_auto_dump_work_func(struct work_struct *work)
{
struct abox_dump_buffer_info *info = container_of(work,
struct abox_dump_buffer_info, auto_work);
struct device *dev = info->dev;
int id = info->id;
if (info->auto_started) {
mm_segment_t old_fs;
char filename[SZ_64];
struct file *filp;
sprintf(filename, "/data/abox_dump-%d.raw", id);
old_fs = get_fs();
set_fs(KERNEL_DS);
if (likely(info->file_created)) {
filp = filp_open(filename, O_RDWR | O_APPEND | O_CREAT,
0660);
dev_dbg(dev, "appended\n");
} else {
filp = filp_open(filename, O_RDWR | O_TRUNC | O_CREAT,
0660);
info->file_created = true;
dev_dbg(dev, "created\n");
}
if (!IS_ERR_OR_NULL(filp)) {
void *area = info->buffer.area;
size_t bytes = info->buffer.bytes;
size_t pointer = info->pointer;
bool first = false;
if (unlikely(info->auto_pointer < 0)) {
info->auto_pointer = pointer;
first = true;
}
dev_dbg(dev, "%pad, %pK, %zx, %zx)\n",
&info->buffer.addr, area, bytes,
info->auto_pointer);
if (pointer < info->auto_pointer || first) {
vfs_write(filp, area + info->auto_pointer,
bytes - info->auto_pointer,
&filp->f_pos);
dev_dbg(dev, "vfs_write(%pK, %zx)\n",
area + info->auto_pointer,
bytes - info->auto_pointer);
info->auto_pointer = 0;
}
vfs_write(filp, area + info->auto_pointer,
pointer - info->auto_pointer,
&filp->f_pos);
dev_dbg(dev, "vfs_write(%pK, %zx)\n",
area + info->auto_pointer,
pointer - info->auto_pointer);
info->auto_pointer = pointer;
vfs_fsync(filp, 1);
filp_close(filp, NULL);
} else {
dev_err(dev, "dump file %d open error: %ld\n", id,
PTR_ERR(filp));
}
set_fs(old_fs);
}
}
void abox_dump_register_buffer_work_func(struct work_struct *work)
{
int id;
struct abox_dump_buffer_info *info;
if (!abox_dump_card.dev) {
platform_device_register_data(abox_dump_dev_abox,
"samsung-abox-dump", -1, NULL, 0);
}
dev_dbg(abox_dump_card.dev, "%s\n", __func__);
for (info = &abox_dump_list[0]; (info - &abox_dump_list[0]) <
ARRAY_SIZE(abox_dump_list); info++) {
id = info->id;
if (info->dev && !abox_dump_get_buffer_info(id)) {
dev_info(info->dev, "%s(%d, %s, %#zx)\n", __func__,
id, info->name, info->buffer.bytes);
list_add_tail(&info->list, &abox_dump_list_head);
platform_device_register_data(info->dev,
"samsung-abox-dump", id, NULL, 0);
}
}
}
static DECLARE_WORK(abox_dump_register_buffer_work,
abox_dump_register_buffer_work_func);
int abox_dump_register_buffer(struct device *dev, int id, const char *name,
void *area, phys_addr_t addr, size_t bytes)
{
struct abox_dump_buffer_info *info;
dev_dbg(dev, "%s[%d](%s, %#zx)\n", __func__, id, name, bytes);
if (id < 0 || id >= ARRAY_SIZE(abox_dump_list)) {
dev_err(dev, "invalid id: %d\n", id);
return -EINVAL;
}
if (abox_dump_get_buffer_info(id)) {
dev_dbg(dev, "already registered dump: %d\n", id);
return 0;
}
info = &abox_dump_list[id];
mutex_init(&info->lock);
info->id = id;
strncpy(info->name, name, sizeof(info->name) - 1);
info->buffer.area = area;
info->buffer.addr = addr;
info->buffer.bytes = bytes;
INIT_WORK(&info->auto_work, abox_dump_auto_dump_work_func);
abox_dump_dev_abox = info->dev = dev;
schedule_work(&abox_dump_register_buffer_work);
return 0;
}
EXPORT_SYMBOL(abox_dump_register_buffer);
static struct snd_pcm_hardware abox_dump_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_BLOCK_TRANSFER
| SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_MMAP_VALID,
.formats = ABOX_SAMPLE_FORMATS,
.rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT,
.rate_min = 8000,
.rate_max = 384000,
.channels_min = 1,
.channels_max = 8,
.periods_min = 2,
.periods_max = 32,
};
static int abox_dump_platform_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
struct snd_dma_buffer *dmab = &substream->dma_buffer;
dev_dbg(dev, "%s[%d]\n", __func__, id);
abox_dump_hardware.buffer_bytes_max = dmab->bytes;
abox_dump_hardware.period_bytes_min = dmab->bytes /
abox_dump_hardware.periods_max;
abox_dump_hardware.period_bytes_max = dmab->bytes /
abox_dump_hardware.periods_min;
snd_soc_set_runtime_hwparams(substream, &abox_dump_hardware);
info->substream = substream;
return 0;
}
static int abox_dump_platform_close(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
dev_dbg(dev, "%s[%d]\n", __func__, id);
info->substream = NULL;
return 0;
}
static int abox_dump_platform_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
dev_dbg(dev, "%s[%d]\n", __func__, id);
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
}
static int abox_dump_platform_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
dev_dbg(dev, "%s[%d]\n", __func__, id);
return snd_pcm_lib_free_pages(substream);
}
static int abox_dump_platform_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
dev_dbg(dev, "%s[%d]\n", __func__, id);
info->pointer = 0;
return 0;
}
static int abox_dump_platform_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
dev_dbg(dev, "%s[%d](%d)\n", __func__, id, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
info->started = true;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
info->started = false;
break;
default:
dev_err(dev, "invalid command: %d\n", cmd);
return -EINVAL;
}
abox_dump_request_dump(id);
return 0;
}
void abox_dump_period_elapsed(int id, size_t pointer)
{
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
struct device *dev = info->dev;
dev_dbg(dev, "%s[%d](%zx)\n", __func__, id, pointer);
info->pointer = pointer;
schedule_work(&info->auto_work);
snd_pcm_period_elapsed(info->substream);
}
EXPORT_SYMBOL(abox_dump_period_elapsed);
static snd_pcm_uframes_t abox_dump_platform_pointer(
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
dev_dbg(dev, "%s[%d]\n", __func__, id);
return bytes_to_frames(substream->runtime, info->pointer);
}
static struct snd_pcm_ops abox_dump_platform_ops = {
.open = abox_dump_platform_open,
.close = abox_dump_platform_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = abox_dump_platform_hw_params,
.hw_free = abox_dump_platform_hw_free,
.prepare = abox_dump_platform_prepare,
.trigger = abox_dump_platform_trigger,
.pointer = abox_dump_platform_pointer,
};
static void abox_dump_register_card_work_func(struct work_struct *work)
{
int i;
pr_debug("%s\n", __func__);
snd_soc_unregister_card(&abox_dump_card);
for (i = 0; i < abox_dump_card.num_links; i++) {
struct snd_soc_dai_link *link = &abox_dump_card.dai_link[i];
if (link->name)
continue;
link->name = link->stream_name =
kasprintf(GFP_KERNEL, "dummy%d", i);
link->cpu_name = "snd-soc-dummy";
link->cpu_dai_name = "snd-soc-dummy-dai";
link->codec_name = "snd-soc-dummy";
link->codec_dai_name = "snd-soc-dummy-dai";
link->no_pcm = 1;
}
snd_soc_register_card(&abox_dump_card);
}
DECLARE_DELAYED_WORK(abox_dump_register_card_work,
abox_dump_register_card_work_func);
static void abox_dump_add_dai_link(struct device *dev)
{
int id = to_platform_device(dev)->id;
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
struct snd_soc_dai_link *link = &abox_dump_dai_links[id];
dev_dbg(dev, "%s[%d]\n", __func__, id);
if (id > ARRAY_SIZE(abox_dump_dai_links)) {
dev_err(dev, "Too many dump request\n");
return;
}
cancel_delayed_work_sync(&abox_dump_register_card_work);
info->dev = dev;
kfree(link->name);
link->name = link->stream_name = kstrdup(info->name, GFP_KERNEL);
link->cpu_name = "snd-soc-dummy";
link->cpu_dai_name = "snd-soc-dummy-dai";
link->platform_name = dev_name(dev);
link->codec_name = "snd-soc-dummy";
link->codec_dai_name = "snd-soc-dummy-dai";
link->ignore_suspend = 1;
link->ignore_pmdown_time = 1;
link->no_pcm = 0;
link->capture_only = true;
if (abox_dump_card.num_links <= id)
abox_dump_card.num_links = id + 1;
schedule_delayed_work(&abox_dump_register_card_work, HZ);
}
static int abox_dump_platform_probe(struct snd_soc_platform *platform)
{
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
dev_dbg(dev, "%s[%d]\n", __func__, id);
return 0;
}
static int abox_dump_platform_new(struct snd_soc_pcm_runtime *runtime)
{
struct device *dev = runtime->platform->dev;
struct snd_pcm *pcm = runtime->pcm;
struct snd_pcm_str *stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
struct snd_pcm_substream *substream = stream->substream;
struct snd_dma_buffer *dmab = &substream->dma_buffer;
int id = to_platform_device(dev)->id;
struct abox_dump_buffer_info *info = abox_dump_get_buffer_info(id);
dev_dbg(dev, "%s[%d]\n", __func__, id);
dmab->dev.type = SNDRV_DMA_TYPE_DEV;
dmab->dev.dev = dev;
dmab->area = info->buffer.area;
dmab->addr = info->buffer.addr;
dmab->bytes = info->buffer.bytes;
return 0;
}
static void abox_dump_platform_free(struct snd_pcm *pcm)
{
struct snd_soc_pcm_runtime *runtime = pcm->private_data;
struct device *dev = runtime->platform->dev;
int id = to_platform_device(dev)->id;
dev_dbg(dev, "%s[%d]\n", __func__, id);
}
struct snd_soc_platform_driver abox_dump_platform = {
.probe = abox_dump_platform_probe,
.ops = &abox_dump_platform_ops,
.pcm_new = abox_dump_platform_new,
.pcm_free = abox_dump_platform_free,
};
static int samsung_abox_dump_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int id = to_platform_device(dev)->id;
dev_dbg(dev, "%s[%d]\n", __func__, id);
if (id >= 0) {
pm_runtime_no_callbacks(dev);
pm_runtime_enable(dev);
devm_snd_soc_register_platform(dev, &abox_dump_platform);
abox_dump_add_dai_link(dev);
} else {
abox_dump_card.dev = &pdev->dev;
}
return 0;
}
static int samsung_abox_dump_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int id = to_platform_device(dev)->id;
dev_dbg(dev, "%s[%d]\n", __func__, id);
return 0;
}
static const struct platform_device_id samsung_abox_dump_driver_ids[] = {
{
.name = "samsung-abox-dump",
},
{},
};
MODULE_DEVICE_TABLE(platform, samsung_abox_dump_driver_ids);
static struct platform_driver samsung_abox_dump_driver = {
.probe = samsung_abox_dump_probe,
.remove = samsung_abox_dump_remove,
.driver = {
.name = "samsung-abox-dump",
.owner = THIS_MODULE,
},
.id_table = samsung_abox_dump_driver_ids,
};
module_platform_driver(samsung_abox_dump_driver);
/* Module information */
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box Internal Buffer Dumping Driver");
MODULE_ALIAS("platform:samsung-abox-dump");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,38 @@
/* sound/soc/samsung/abox/abox_dump.h
*
* ALSA SoC Audio Layer - Samsung Abox Internal Buffer Dumping driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_DUMP_H
#define __SND_SOC_ABOX_DUMP_H
#include <linux/device.h>
#include <sound/samsung/abox.h>
/**
* Report dump data written
* @param[in] id unique buffer id
* @param[in] pointer byte index of the written data
*/
extern void abox_dump_period_elapsed(int id, size_t pointer);
/**
* Register abox dump buffer
* @param[in] dev pointer to abox device
* @param[in] id unique buffer id
* @param[in] name unique buffer name
* @param[in] area virtual address of the buffer
* @param[in] addr pysical address of the buffer
* @param[in] bytes buffer size in bytes
* @return error code if any
*/
extern int abox_dump_register_buffer(struct device *dev, int id,
const char *name, void *area, phys_addr_t addr, size_t bytes);
#endif /* __SND_SOC_ABOX_DUMP_H */

View file

@ -0,0 +1,321 @@
/* sound/soc/samsung/abox/abox_effect.c
*
* ALSA SoC Audio Layer - Samsung Abox Effect driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/samsung/abox.h>
#include "abox.h"
#include "abox_util.h"
#include "abox_effect.h"
struct abox_ctl_eq_switch {
unsigned int base;
unsigned int count;
unsigned int min;
unsigned int max;
};
static int abox_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_soc_component *cmpnt = snd_kcontrol_chip(kcontrol);
struct device *dev = cmpnt->dev;
struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value;
dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = params->count;
uinfo->value.integer.min = params->min;
uinfo->value.integer.max = params->max;
return 0;
}
static int abox_ctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct device *dev = cmpnt->dev;
struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value;
int i;
int ret = 0;
dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name);
pm_runtime_get_sync(dev);
for (i = 0; i < params->count; i++) {
unsigned int reg, val;
reg = (unsigned int)(params->base + PARAM_OFFSET +
(i * sizeof(u32)));
ret = snd_soc_component_read(cmpnt, reg, &val);
if (ret < 0) {
dev_err(dev, "reading fail at 0x%08X\n", reg);
break;
}
ucontrol->value.integer.value[i] = val;
dev_dbg(dev, "%s[%d] = %u\n", kcontrol->id.name, i, val);
}
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
static int abox_ctl_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct device *dev = cmpnt->dev;
struct abox_ctl_eq_switch *params = (void *)kcontrol->private_value;
int i;
dev_dbg(dev, "%s: %s\n", __func__, kcontrol->id.name);
pm_runtime_get_sync(dev);
for (i = 0; i < params->count; i++) {
unsigned int reg, val;
reg = (unsigned int)(params->base + PARAM_OFFSET +
(i * sizeof(u32)));
val = (unsigned int)ucontrol->value.integer.value[i];
snd_soc_component_write(cmpnt, reg, val);
dev_dbg(dev, "%s[%d] <= %u\n", kcontrol->id.name, i, val);
}
snd_soc_component_write(cmpnt, params->base, CHANGE_BIT);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
}
#define ABOX_CTL_EQ_SWITCH(xname, xbase, xcount, xmin, xmax) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = abox_ctl_info, .get = abox_ctl_get, \
.put = abox_ctl_put, .private_value = \
((unsigned long)&(struct abox_ctl_eq_switch) \
{.base = xbase, .count = xcount, \
.min = xmin, .max = xmax}) }
#define DECLARE_ABOX_CTL_EQ_SWITCH(name, prefix) \
ABOX_CTL_EQ_SWITCH(name, prefix##_BASE, \
prefix##_MAX_COUNT, prefix##_VALUE_MIN, \
prefix##_VALUE_MAX)
static const struct snd_kcontrol_new abox_effect_controls[] = {
DECLARE_ABOX_CTL_EQ_SWITCH("SA data", SA),
DECLARE_ABOX_CTL_EQ_SWITCH("Audio DHA data", MYSOUND),
DECLARE_ABOX_CTL_EQ_SWITCH("VSP data", VSP),
DECLARE_ABOX_CTL_EQ_SWITCH("LRSM data", LRSM),
DECLARE_ABOX_CTL_EQ_SWITCH("MSP data", MYSPACE),
DECLARE_ABOX_CTL_EQ_SWITCH("ESA BBoost data", BB),
DECLARE_ABOX_CTL_EQ_SWITCH("ESA EQ data", EQ),
DECLARE_ABOX_CTL_EQ_SWITCH("NXP BDL data", NXPBDL),
DECLARE_ABOX_CTL_EQ_SWITCH("NXP RVB ctx data", NXPRVB_CTX),
DECLARE_ABOX_CTL_EQ_SWITCH("NXP RVB param data", NXPRVB_PARAM),
DECLARE_ABOX_CTL_EQ_SWITCH("SB rotation", SB),
DECLARE_ABOX_CTL_EQ_SWITCH("UPSCALER", UPSCALER),
};
#define ABOX_EFFECT_ACCESSIABLE_REG(name, reg) \
(reg >= name##_BASE && reg <= name##_BASE + PARAM_OFFSET + \
(name##_MAX_COUNT * sizeof(u32)))
static bool abox_effect_accessible_reg(struct device *dev, unsigned int reg)
{
return ABOX_EFFECT_ACCESSIABLE_REG(SA, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(MYSOUND, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(VSP, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(LRSM, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(MYSPACE, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(BB, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(EQ, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(ELPE, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(NXPBDL, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(NXPRVB_CTX, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(NXPRVB_PARAM, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(SB, reg) ||
ABOX_EFFECT_ACCESSIABLE_REG(UPSCALER, reg);
}
#define ABOX_EFFECT_VOLATILE_REG(name, reg) (reg == name##_BASE)
static bool abox_effect_volatile_reg(struct device *dev, unsigned int reg)
{
return ABOX_EFFECT_VOLATILE_REG(SA, reg) ||
ABOX_EFFECT_VOLATILE_REG(MYSOUND, reg) ||
ABOX_EFFECT_VOLATILE_REG(VSP, reg) ||
ABOX_EFFECT_VOLATILE_REG(LRSM, reg) ||
ABOX_EFFECT_VOLATILE_REG(MYSPACE, reg) ||
ABOX_EFFECT_VOLATILE_REG(BB, reg) ||
ABOX_EFFECT_VOLATILE_REG(EQ, reg) ||
ABOX_EFFECT_VOLATILE_REG(ELPE, reg) ||
ABOX_EFFECT_VOLATILE_REG(NXPBDL, reg) ||
ABOX_EFFECT_VOLATILE_REG(NXPRVB_CTX, reg) ||
ABOX_EFFECT_VOLATILE_REG(NXPRVB_PARAM, reg) ||
ABOX_EFFECT_VOLATILE_REG(SB, reg) ||
ABOX_EFFECT_VOLATILE_REG(UPSCALER, reg);
}
static const struct regmap_config abox_effect_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = ABOX_EFFECT_MAX_REGISTERS,
.writeable_reg = abox_effect_accessible_reg,
.readable_reg = abox_effect_accessible_reg,
.volatile_reg = abox_effect_volatile_reg,
.cache_type = REGCACHE_FLAT,
};
static const struct snd_soc_component_driver abox_effect = {
.controls = abox_effect_controls,
.num_controls = ARRAY_SIZE(abox_effect_controls),
};
static struct abox_effect_data *p_abox_effect_data;
void abox_effect_restore(void)
{
if (p_abox_effect_data && p_abox_effect_data->pdev) {
struct device *dev = &p_abox_effect_data->pdev->dev;
pm_runtime_get(dev);
pm_runtime_put_autosuspend(dev);
}
}
static int abox_effect_runtime_suspend(struct device *dev)
{
struct abox_effect_data *data = dev_get_drvdata(dev);
dev_dbg(dev, "%s\n", __func__);
regcache_cache_only(data->regmap, true);
regcache_mark_dirty(data->regmap);
return 0;
}
static int abox_effect_runtime_resume(struct device *dev)
{
struct abox_effect_data *data = dev_get_drvdata(dev);
dev_dbg(dev, "%s\n", __func__);
regcache_cache_only(data->regmap, false);
regcache_sync(data->regmap);
return 0;
}
const struct dev_pm_ops samsung_abox_effect_pm = {
SET_RUNTIME_PM_OPS(abox_effect_runtime_suspend,
abox_effect_runtime_resume, NULL)
};
static int samsung_abox_effect_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *np_tmp;
struct abox_effect_data *data;
dev_dbg(dev, "%s\n", __func__);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
platform_set_drvdata(pdev, data);
p_abox_effect_data = data;
np_tmp = of_parse_phandle(np, "abox", 0);
if (!np_tmp) {
dev_err(dev, "Failed to get abox device node\n");
return -EPROBE_DEFER;
}
data->pdev_abox = of_find_device_by_node(np_tmp);
if (!data->pdev_abox) {
dev_err(dev, "Failed to get abox platform device\n");
return -EPROBE_DEFER;
}
data->pdev = pdev;
data->base = devm_not_request_and_map(pdev, "reg", 0, NULL, NULL);
if (IS_ERR(data->base)) {
dev_err(dev, "base address request failed: %ld\n",
PTR_ERR(data->base));
return PTR_ERR(data->base);
}
data->regmap = devm_regmap_init_mmio(dev, data->base,
&abox_effect_regmap_config);
if (IS_ERR(data->regmap)) {
dev_err(dev, "regmap init failed: %ld\n",
PTR_ERR(data->regmap));
return PTR_ERR(data->regmap);
}
pm_runtime_enable(dev);
pm_runtime_set_autosuspend_delay(dev, 1000);
pm_runtime_use_autosuspend(dev);
return devm_snd_soc_register_component(&pdev->dev, &abox_effect, NULL,
0);
}
static int samsung_abox_effect_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
dev_dbg(dev, "%s\n", __func__);
pm_runtime_disable(dev);
return 0;
}
static const struct of_device_id samsung_abox_effect_match[] = {
{
.compatible = "samsung,abox-effect",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_abox_effect_match);
static struct platform_driver samsung_abox_effect_driver = {
.probe = samsung_abox_effect_probe,
.remove = samsung_abox_effect_remove,
.driver = {
.name = "samsung-abox-effect",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_abox_effect_match),
.pm = &samsung_abox_effect_pm,
},
};
module_platform_driver(samsung_abox_effect_driver);
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box Effect Driver");
MODULE_ALIAS("platform:samsung-abox-effect");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,176 @@
/* sound/soc/samsung/abox/abox_effect.h
*
* ALSA SoC Audio Layer - Samsung Abox Effect driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_EFFECT_H
#define __SND_SOC_ABOX_EFFECT_H
enum {
SOUNDALIVE = 0,
MYSOUND,
PLAYSPEED,
SOUNDBALANCE,
MYSPACE,
BASSBOOST,
EQUALIZER,
NXPBUNDLE, /* includes BB,EQ, virtualizer, volume */
NXPREVERB_CTX, /* Reverb context inforamtion */
NXPREVERB_PARAM, /* Reverb effect parameters */
SOUNDBOOSTER,
};
/* Effect offset */
#define SA_BASE (0x000)
#define SA_CHANGE_BIT (0x000)
#define SA_OUT_DEVICE (0x010)
#define SA_PRESET (0x014)
#define SA_EQ_BEGIN (0x018)
#define SA_EQ_END (0x038)
#define SA_3D_LEVEL (0x03C)
#define SA_BE_LEVEL (0x040)
#define SA_REVERB (0x044)
#define SA_ROOMSIZE (0x048)
#define SA_CLA_LEVEL (0x04C)
#define SA_VOLUME_LEVEL (0x050)
#define SA_SQUARE_ROW (0x054)
#define SA_SQUARE_COLUMN (0x058)
#define SA_TAB_INFO (0x05C)
#define SA_NEW_UI (0x060)
#define SA_3D_ON (0x064)
#define SA_3D_ANGLE_L (0x068)
#define SA_3D_ANGLE_R (0x06C)
#define SA_3D_GAIN_L (0x070)
#define SA_3D_GAIN_R (0x074)
#define SA_MAX_COUNT (26)
#define SA_VALUE_MIN (0)
#define SA_VALUE_MAX (100)
#define MYSOUND_BASE (0x100)
#define MYSOUND_CHANGE_BIT (0x100)
#define MYSOUND_DHA_ENABLE (0x110)
#define MYSOUND_GAIN_BEGIN (0x114)
#define MYSOUND_OUT_DEVICE (0x148)
#define MYSOUND_MAX_COUNT (14)
#define MYSOUND_VALUE_MIN (0)
#define MYSOUND_VALUE_MAX (100)
#define VSP_BASE (0x200)
#define VSP_CHANGE_BIT (0x200)
#define VSP_INDEX (0x210)
#define VSP_MAX_COUNT (2)
#define VSP_VALUE_MIN (0)
#define VSP_VALUE_MAX (1000)
#define LRSM_BASE (0x300)
#define LRSM_CHANGE_BIT (0x300)
#define LRSM_INDEX0 (0x310)
#define LRSM_INDEX1 (0x320)
#define LRSM_MAX_COUNT (3)
#define LRSM_VALUE_MIN (-50)
#define LRSM_VALUE_MAX (50)
#define MYSPACE_BASE (0x340)
#define MYSPACE_CHANGE_BIT (0x340)
#define MYSPACE_PRESET (0x350)
#define MYSPACE_MAX_COUNT (2)
#define MYSPACE_VALUE_MIN (0)
#define MYSPACE_VALUE_MAX (5)
#define BB_BASE (0x400)
#define BB_CHANGE_BIT (0x400)
#define BB_STATUS (0x410)
#define BB_STRENGTH (0x414)
#define BB_MAX_COUNT (2)
#define BB_VALUE_MIN (0)
#define BB_VALUE_MAX (100)
#define EQ_BASE (0x500)
#define EQ_CHANGE_BIT (0x500)
#define EQ_STATUS (0x510)
#define EQ_PRESET (0x514)
#define EQ_NBAND (0x518)
#define EQ_NBAND_LEVEL (0x51c) /* 10 nband levels */
#define EQ_NBAND_FREQ (0x544) /* 10 nband frequencies */
#define EQ_MAX_COUNT (23)
#define EQ_VALUE_MIN (0)
#define EQ_VALUE_MAX (14000)
/* CommBox ELPE Parameter */
#define ELPE_BASE (0x600)
#define ELPE_CMD (0x600)
#define ELPE_ARG0 (0x604)
#define ELPE_ARG1 (0x608)
#define ELPE_ARG2 (0x60C)
#define ELPE_ARG3 (0x610)
#define ELPE_ARG4 (0x614)
#define ELPE_ARG5 (0x618)
#define ELPE_ARG6 (0x61C)
#define ELPE_ARG7 (0x620)
#define ELPE_ARG8 (0x624)
#define ELPE_ARG9 (0x628)
#define ELPE_ARG10 (0x62C)
#define ELPE_ARG11 (0x630)
#define ELPE_ARG12 (0x634)
#define ELPE_RET (0x638)
#define ELPE_DONE (0x63C)
#define ELPE_MAX_COUNT (11)
/* NXP Bundle control parameters */
#define NXPBDL_BASE (0x700)
#define NXPBDL_CHANGE_BIT (0x700)
#define NXPBDL_MAX_COUNT (35) /* bundle common struct param count */
#define NXPBDL_VALUE_MIN (0)
#define NXPBDL_VALUE_MAX (192000)
/* NXP Reverb control parameters */
#define NXPRVB_PARAM_BASE (0x800)
#define NXPRVB_PARAM_CHANGE_BIT (0x800)
#define NXPRVB_PARAM_MAX_COUNT (10) /* reverb common struct param count */
#define NXPRVB_PARAM_VALUE_MIN (0)
#define NXPRVB_PARAM_VALUE_MAX (192000)
/* NXP reverb context parameters */
#define NXPRVB_CTX_BASE (0x880)
#define NXPRVB_CTX_CHANGE_BIT (0x880)
#define NXPRVB_CTX_MAX_COUNT (7) /* reverb context param count */
#define NXPRVB_CTX_VALUE_MIN (0)
#define NXPRVB_CTX_VALUE_MAX (192000)
#define SB_BASE (0x900)
#define SB_CHANGE_BIT (0x900)
#define SB_ROTATION (0x910)
#define SB_MAX_COUNT (1)
#define SB_VALUE_MIN (0)
#define SB_VALUE_MAX (3)
#define UPSCALER_BASE (0xA00)
#define UPSCALER_CHANGE_BIT (0xA00)
#define UPSCALER_ROTATION (0xA10)
#define UPSCALER_MAX_COUNT (1)
#define UPSCALER_VALUE_MIN (0)
#define UPSCALER_VALUE_MAX (2)
#define ABOX_EFFECT_MAX_REGISTERS (0xB00)
#define PARAM_OFFSET (0x10)
#define CHANGE_BIT (1)
struct abox_effect_data {
struct platform_device *pdev;
struct platform_device *pdev_abox;
void __iomem *base;
struct regmap *regmap;
};
/**
* Restore abox effect register map
*/
extern void abox_effect_restore(void);
#endif /* __SND_SOC_ABOX_EFFECT_H */

View file

@ -0,0 +1,162 @@
/* sound/soc/samsung/abox/abox_failsafe.c
*
* ALSA SoC Audio Layer - Samsung Abox Failsafe driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/proc_fs.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
#include <linux/pm_runtime.h>
#include <sound/samsung/abox.h>
#include "abox_util.h"
#include "abox.h"
#include "abox_log.h"
#define SMART_FAILSAFE
static int abox_failsafe_reset_count;
static struct device *abox_failsafe_dev;
static struct device *abox_failsafe_dev_abox;
static struct abox_data *abox_failsafe_abox_data;
static atomic_t abox_failsafe_reported;
static bool abox_failsafe_service;
static int abox_failsafe_start(struct device *dev, struct abox_data *data)
{
int ret = 0;
dev_dbg(dev, "%s\n", __func__);
if (atomic_read(&abox_failsafe_reported)) {
if (abox_failsafe_service)
pm_runtime_put(dev);
dev_info(dev, "%s\n", __func__);
abox_clear_cpu_gear_requests(dev, data);
}
return ret;
}
static int abox_failsafe_end(struct device *dev)
{
int ret = 0;
dev_dbg(dev, "%s\n", __func__);
if (atomic_cmpxchg(&abox_failsafe_reported, 1, 0)) {
dev_info(dev, "%s\n", __func__);
abox_failsafe_abox_data->failsafe = false;
}
return ret;
}
static void abox_failsafe_report_work_func(struct work_struct *work)
{
struct device *dev = abox_failsafe_dev;
char env[32] = {0,};
char *envp[2] = {env, NULL};
dev_dbg(dev, "%s\n", __func__);
pm_runtime_barrier(dev);
snprintf(env, sizeof(env), "COUNT=%d", abox_failsafe_reset_count);
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
}
DECLARE_WORK(abox_failsafe_report_work, abox_failsafe_report_work_func);
#ifdef SMART_FAILSAFE
void abox_failsafe_report(struct device *dev)
{
dev_dbg(dev, "%s\n", __func__);
abox_failsafe_dev = dev;
abox_failsafe_reset_count++;
if (!atomic_cmpxchg(&abox_failsafe_reported, 0, 1)) {
abox_failsafe_abox_data->failsafe = true;
if (abox_failsafe_service)
pm_runtime_get(dev);
schedule_work(&abox_failsafe_report_work);
}
}
#else
/* TODO: Use SMART_FAILSAFE.
* SMART_FAILSAFE needs support from user space.
*/
void abox_failsafe_report(struct device *dev)
{
struct abox_data *data = dev_get_drvdata(dev);
dev_dbg(dev, "%s\n", __func__);
abox_failsafe_start(dev, data);
}
#endif
void abox_failsafe_report_reset(struct device *dev)
{
dev_dbg(dev, "%s\n", __func__);
abox_failsafe_end(dev);
}
static int abox_failsafe_reset(struct device *dev, struct abox_data *data)
{
struct device *dev_abox = &data->pdev->dev;
dev_dbg(dev, "%s\n", __func__);
return abox_failsafe_start(dev_abox, data);
}
static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
static const char code[] = "CALLIOPE";
struct abox_data *data = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "%s(%zu)\n", __func__, count);
if (!strncmp(code, buf, min(sizeof(code) - 1, count))) {
ret = abox_failsafe_reset(dev, data);
if (ret < 0)
return ret;
}
return count;
}
static DEVICE_ATTR_WO(reset);
static DEVICE_BOOL_ATTR(service, 0660, abox_failsafe_service);
static DEVICE_INT_ATTR(reset_count, 0660, abox_failsafe_reset_count);
void abox_failsafe_init(struct device *dev)
{
int ret;
abox_failsafe_dev_abox = dev;
abox_failsafe_abox_data = (struct abox_data *)dev_get_drvdata(dev);
ret = device_create_file(dev, &dev_attr_reset);
if (ret < 0)
dev_warn(dev, "%s: %s file creation failed: %d\n",
__func__, "reset", ret);
ret = device_create_file(dev, &dev_attr_service.attr);
if (ret < 0)
dev_warn(dev, "%s: %s file creation failed: %d\n",
__func__, "service", ret);
ret = device_create_file(dev, &dev_attr_reset_count.attr);
if (ret < 0)
dev_warn(dev, "%s: %s file creation failed: %d\n",
__func__, "reset_count", ret);
}

View file

@ -0,0 +1,36 @@
/* sound/soc/samsung/abox/abox_failsafe.h
*
* ALSA SoC - Samsung Abox Fail-safe driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_FAILSAFE_H
#define __SND_SOC_ABOX_FAILSAFE_H
#include <linux/device.h>
#include <sound/samsung/abox.h>
/**
* Report failure to user space
* @param[in] dev pointer to abox device
*/
extern void abox_failsafe_report(struct device *dev);
/**
* Report reset
* @param[in] dev pointer to abox device
*/
extern void abox_failsafe_report_reset(struct device *dev);
/**
* Register abox fail-safe
* @param[in] dev pointer to abox device
*/
extern void abox_failsafe_init(struct device *dev);
#endif /* __SND_SOC_ABOX_FAILSAFE_H */

View file

@ -0,0 +1,272 @@
/* sound/soc/samsung/abox/abox_gic.c
*
* ALSA SoC Audio Layer - Samsung ABOX GIC driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/smc.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/delay.h>
#include "abox_util.h"
#include "abox_gic.h"
#define GIC_IS_SECURE_FREE
void abox_gic_generate_interrupt(struct device *dev, unsigned int irq)
{
#ifdef GIC_IS_SECURE_FREE
struct abox_gic_data *data = dev_get_drvdata(dev);
#endif
dev_dbg(dev, "%s(%d)\n", __func__, irq);
#ifdef GIC_IS_SECURE_FREE
writel((0x1 << 16) | (irq & 0xF),
data->gicd_base + GIC_DIST_SOFTINT);
#else
dev_dbg(dev, "exynos_smc() is called\n");
exynos_smc(SMC_CMD_REG,
SMC_REG_ID_SFR_W(0x13EF1000 + GIC_DIST_SOFTINT),
(0x1 << 16) | (hw_irq & 0xF), 0);
#endif
}
EXPORT_SYMBOL(abox_gic_generate_interrupt);
int abox_gic_register_irq_handler(struct device *dev, unsigned int irq,
irq_handler_t handler, void *dev_id)
{
struct abox_gic_data *data = dev_get_drvdata(dev);
dev_info(dev, "%s(%u, %pf)\n", __func__, irq, handler);
if (irq >= ARRAY_SIZE(data->handler)) {
dev_err(dev, "invalid irq: %d\n", irq);
return -EINVAL;
}
data->handler[irq].handler = handler;
data->handler[irq].dev_id = dev_id;
return 0;
}
EXPORT_SYMBOL(abox_gic_register_irq_handler);
int abox_gic_unregister_irq_handler(struct device *dev, unsigned int irq)
{
struct abox_gic_data *data = dev_get_drvdata(dev);
dev_info(dev, "%s(%u)\n", __func__, irq);
if (irq >= ARRAY_SIZE(data->handler)) {
dev_err(dev, "invalid irq: %d\n", irq);
return -EINVAL;
}
data->handler[irq].handler = NULL;
data->handler[irq].dev_id = NULL;
return 0;
}
EXPORT_SYMBOL(abox_gic_unregister_irq_handler);
static irqreturn_t __abox_gic_irq_handler(struct abox_gic_data *data, u32 irqnr)
{
struct abox_gic_irq_handler_t *handler;
if (irqnr >= ARRAY_SIZE(data->handler))
return IRQ_NONE;
handler = &data->handler[irqnr];
if (!handler->handler)
return IRQ_NONE;
return handler->handler(irqnr, handler->dev_id);
}
static irqreturn_t abox_gic_irq_handler(int irq, void *dev_id)
{
struct device *dev = dev_id;
struct abox_gic_data *data = dev_get_drvdata(dev);
irqreturn_t ret = IRQ_NONE;
u32 irqstat, irqnr;
dev_dbg(dev, "%s\n", __func__);
do {
irqstat = readl(data->gicc_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
dev_dbg(dev, "IAR: %08X\n", irqstat);
if (likely(irqnr < 16)) {
writel(irqstat, data->gicc_base + GIC_CPU_EOI);
writel(irqstat, data->gicc_base + GIC_CPU_DEACTIVATE);
ret |= __abox_gic_irq_handler(data, irqnr);
continue;
} else if (unlikely(irqnr > 15 && irqnr < 1021)) {
writel(irqstat, data->gicc_base + GIC_CPU_EOI);
ret |= __abox_gic_irq_handler(data, irqnr);
continue;
}
break;
} while (1);
return ret;
}
void abox_gic_init_gic(struct device *dev)
{
struct abox_gic_data *data = dev_get_drvdata(dev);
unsigned long arg;
int i, ret;
dev_info(dev, "%s\n", __func__);
#ifdef GIC_IS_SECURE_FREE
writel(0x000000FF, data->gicc_base + GIC_CPU_PRIMASK);
writel(0x3, data->gicd_base + GIC_DIST_CTRL);
#else
arg = SMC_REG_ID_SFR_W(data->gicc_base_phys + GIC_CPU_PRIMASK);
ret = exynos_smc(SMC_CMD_REG, arg, 0x000000FF, 0);
arg = SMC_REG_ID_SFR_W(data->gicd_base_phys + GIC_DIST_CTRL);
ret = exynos_smc(SMC_CMD_REG, arg, 0x3, 0);
#endif
if (is_secure_gic()) {
for (i = 0; i < 1; i++) {
arg = SMC_REG_ID_SFR_W(data->gicd_base_phys +
GIC_DIST_IGROUP + (i * 4));
ret = exynos_smc(SMC_CMD_REG, arg, 0xFFFFFFFF, 0);
}
}
for (i = 0; i < 40; i++) {
#ifdef GIC_IS_SECURE_FREE
writel(0x10101010, data->gicd_base + GIC_DIST_PRI + (i * 4));
#else
arg = SMC_REG_ID_SFR_W(data->gicd_base_phys +
GIC_DIST_PRI + (i * 4));
ret = exynos_smc(SMC_CMD_REG, arg, 0x10101010, 0);
#endif
}
writel(0x3, data->gicc_base + GIC_CPU_CTRL);
}
EXPORT_SYMBOL(abox_gic_init_gic);
int abox_gic_enable_irq(struct device *dev)
{
struct abox_gic_data *data = dev_get_drvdata(dev);
if (likely(data->disabled)) {
dev_info(dev, "%s\n", __func__);
data->disabled = false;
enable_irq(data->irq);
}
return 0;
}
int abox_gic_disable_irq(struct device *dev)
{
struct abox_gic_data *data = dev_get_drvdata(dev);
if (likely(!data->disabled)) {
dev_info(dev, "%s\n", __func__);
data->disabled = true;
disable_irq(data->irq);
}
return 0;
}
static int samsung_abox_gic_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct abox_gic_data *data;
int ret;
dev_info(dev, "%s\n", __func__);
data = devm_kzalloc(dev, sizeof(struct abox_gic_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
data->gicd_base = devm_request_and_map_byname(pdev, "gicd",
&data->gicd_base_phys, NULL);
if (IS_ERR(data->gicd_base))
return PTR_ERR(data->gicd_base);
data->gicc_base = devm_request_and_map_byname(pdev, "gicc",
&data->gicc_base_phys, NULL);
if (IS_ERR(data->gicc_base))
return PTR_ERR(data->gicc_base);
data->irq = platform_get_irq(pdev, 0);
if (data->irq < 0) {
dev_err(dev, "Failed to get irq\n");
return data->irq;
}
ret = devm_request_irq(dev, data->irq, abox_gic_irq_handler,
IRQF_TRIGGER_RISING, pdev->name, dev);
if (ret < 0) {
dev_err(dev, "Failed to request irq\n");
return ret;
}
ret = enable_irq_wake(data->irq);
if (ret < 0)
dev_err(dev, "Failed to enable irq wake\n");
#ifndef CONFIG_PM
abox_gic_resume(dev);
#endif
dev_info(dev, "%s: probe complete\n", __func__);
return 0;
}
static int samsung_abox_gic_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "%s\n", __func__);
return 0;
}
static const struct of_device_id samsung_abox_gic_of_match[] = {
{
.compatible = "samsung,abox_gic",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_abox_gic_of_match);
static struct platform_driver samsung_abox_gic_driver = {
.probe = samsung_abox_gic_probe,
.remove = samsung_abox_gic_remove,
.driver = {
.name = "samsung-abox-gic",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_abox_gic_of_match),
},
};
module_platform_driver(samsung_abox_gic_driver);
/* Module information */
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box GIC Driver");
MODULE_ALIAS("platform:samsung-abox-gic");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,78 @@
/* sound/soc/samsung/abox/abox_gic.h
*
* ALSA SoC Audio Layer - Samsung Abox GIC driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_GIC_H
#define __SND_SOC_ABOX_GIC_H
#define ABOX_GIC_IRQ_COUNT 16
struct abox_gic_irq_handler_t {
irq_handler_t handler;
void *dev_id;
};
struct abox_gic_data {
void __iomem *gicd_base;
void __iomem *gicc_base;
phys_addr_t gicd_base_phys;
phys_addr_t gicc_base_phys;
int irq;
struct abox_gic_irq_handler_t handler[ABOX_GIC_IRQ_COUNT];
bool disabled;
};
/**
* Generate interrupt
* @param[in] dev pointer to abox_gic device
* @param[in] irq irq number
*/
extern void abox_gic_generate_interrupt(struct device *dev, unsigned int irq);
/**
* Register interrupt handler
* @param[in] dev pointer to abox_gic device
* @param[in] irq irq number
* @param[in] handler function to be called on interrupt
* @param[in] dev_id cookie for interrupt.
* @return error code or 0
*/
extern int abox_gic_register_irq_handler(struct device *dev,
unsigned int irq, irq_handler_t handler, void *dev_id);
/**
* Unregister interrupt handler
* @param[in] dev pointer to abox_gic device
* @param[in] irq irq number
* @return error code or 0
*/
extern int abox_gic_unregister_irq_handler(struct device *dev,
unsigned int irq);
/**
* Enable abox gic irq
* @param[in] dev pointer to abox_gic device
*/
extern int abox_gic_enable_irq(struct device *dev);
/**
* Disable abox gic irq
* @param[in] dev pointer to abox_gic device
*/
extern int abox_gic_disable_irq(struct device *dev);
/**
* Initialize abox gic
* @param[in] dev pointer to abox_gic device
*/
extern void abox_gic_init_gic(struct device *dev);
#endif /* __SND_SOC_ABOX_GIC_H */

View file

@ -0,0 +1,885 @@
/* sound/soc/samsung/abox/abox_uaif.c
*
* ALSA SoC Audio Layer - Samsung Abox UAIF driver
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <sound/pcm_params.h>
#include <sound/samsung/abox.h>
#include "abox_util.h"
#include "abox.h"
#include "abox_if.h"
static int abox_if_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct snd_soc_component *cmpnt = data->cmpnt;
struct abox_data *abox_data = data->abox_data;
int ret;
dev_info(dev, "%s[%c]\n", __func__,
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
'C' : 'P');
abox_request_cpu_gear_dai(dev, abox_data, dai, abox_data->cpu_gear_min);
ret = clk_enable(data->clk_bclk);
if (ret < 0) {
dev_err(dev, "Failed to enable bclk: %d\n", ret);
goto err;
}
ret = clk_enable(data->clk_bclk_gate);
if (ret < 0) {
dev_err(dev, "Failed to enable bclk_gate: %d\n", ret);
goto err;
}
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
snd_soc_component_update_bits(cmpnt, ABOX_UAIF_CTRL0(data->id),
ABOX_DATA_MODE_MASK | ABOX_IRQ_MODE_MASK,
(1 << ABOX_DATA_MODE_L) |
(0 << ABOX_IRQ_MODE_L));
err:
return ret;
}
static void abox_if_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct abox_data *abox_data = data->abox_data;
dev_info(dev, "%s[%c]\n", __func__,
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
'C' : 'P');
clk_disable(data->clk_bclk_gate);
clk_disable(data->clk_bclk);
abox_request_cpu_gear_dai(dev, abox_data, dai, ABOX_CPU_GEAR_MIN);
}
static int abox_if_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct abox_data *abox_data = data->abox_data;
dev_info(dev, "%s[%c]\n", __func__,
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
'C' : 'P');
abox_register_bclk_usage(dev, abox_data, dai->id, 0, 0, 0);
return 0;
}
static int abox_dsif_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct abox_data *abox_data = data->abox_data;
unsigned int rate;
int ret;
dev_info(dev, "%s\n", __func__);
rate = dai->rate;
ret = abox_register_bclk_usage(dev, abox_data, dai->id, rate, 1, ratio);
if (ret < 0)
dev_err(dev, "Unable to register bclk usage: %d\n", ret);
ret = clk_set_rate(data->clk_bclk, rate * ratio);
if (ret < 0) {
dev_err(dev, "bclk set error=%d\n", ret);
} else {
dev_info(dev, "rate=%u, bclk=%lu\n", rate,
clk_get_rate(data->clk_bclk));
}
return ret;
}
static int abox_dsif_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct snd_soc_component *cmpnt = data->cmpnt;
unsigned int ctrl;
int ret = 0;
dev_info(dev, "%s(0x%08x)\n", __func__, fmt);
pm_runtime_get_sync(dev);
snd_soc_component_read(cmpnt, ABOX_DSIF_CTRL, &ctrl);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_PDM:
break;
default:
ret = -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
case SND_SOC_DAIFMT_NB_IF:
set_value_by_name(ctrl, ABOX_DSIF_BCLK_POLARITY, 1);
break;
case SND_SOC_DAIFMT_IB_NF:
case SND_SOC_DAIFMT_IB_IF:
set_value_by_name(ctrl, ABOX_DSIF_BCLK_POLARITY, 0);
break;
default:
ret = -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
break;
default:
ret = -EINVAL;
}
snd_soc_component_write(cmpnt, ABOX_DSIF_CTRL, ctrl);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
static int abox_dsif_set_channel_map(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct snd_soc_component *cmpnt = data->cmpnt;
dev_info(dev, "%s\n", __func__);
snd_soc_component_update_bits(cmpnt, ABOX_DSIF_CTRL, ABOX_ORDER_MASK,
(tx_slot[0] ? 1 : 0) << ABOX_ORDER_L);
return 0;
}
static int abox_dsif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
unsigned int channels, rate, width;
dev_info(dev, "%s[%c]\n", __func__,
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
'C' : 'P');
channels = params_channels(hw_params);
rate = params_rate(hw_params);
width = params_width(hw_params);
dev_info(dev, "rate=%u, width=%d, channel=%u, bclk=%lu\n",
rate,
width,
channels,
clk_get_rate(data->clk_bclk));
switch (params_format(hw_params)) {
case SNDRV_PCM_FORMAT_S32:
break;
default:
return -EINVAL;
}
switch (channels) {
case 2:
break;
default:
return -EINVAL;
}
return 0;
}
static int abox_dsif_trigger(struct snd_pcm_substream *substream,
int trigger, struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct snd_soc_component *cmpnt = data->cmpnt;
dev_info(dev, "%s[%c](%d)\n", __func__,
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
'C' : 'P', trigger);
switch (trigger) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_soc_component_update_bits(cmpnt, ABOX_DSIF_CTRL,
ABOX_ENABLE_MASK, 1 << ABOX_ENABLE_L);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
snd_soc_component_update_bits(cmpnt, ABOX_DSIF_CTRL,
ABOX_ENABLE_MASK, 0 << ABOX_ENABLE_L);
break;
default:
return -EINVAL;
}
return 0;
}
static int abox_uaif_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct snd_soc_component *cmpnt = data->cmpnt;
int id = data->id;
unsigned int ctrl0, ctrl1;
int ret = 0;
dev_info(dev, "%s(0x%08x)\n", __func__, fmt);
pm_runtime_get_sync(dev);
snd_soc_component_read(cmpnt, ABOX_UAIF_CTRL0(id), &ctrl0);
snd_soc_component_read(cmpnt, ABOX_UAIF_CTRL1(id), &ctrl1);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
set_value_by_name(ctrl1, ABOX_WS_MODE, 0);
break;
case SND_SOC_DAIFMT_DSP_A:
set_value_by_name(ctrl1, ABOX_WS_MODE, 1);
break;
default:
ret = -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
set_value_by_name(ctrl1, ABOX_BCLK_POLARITY, 1);
set_value_by_name(ctrl1, ABOX_WS_POLAR, 0);
break;
case SND_SOC_DAIFMT_NB_IF:
set_value_by_name(ctrl1, ABOX_BCLK_POLARITY, 1);
set_value_by_name(ctrl1, ABOX_WS_POLAR, 1);
break;
case SND_SOC_DAIFMT_IB_NF:
set_value_by_name(ctrl1, ABOX_BCLK_POLARITY, 0);
set_value_by_name(ctrl1, ABOX_WS_POLAR, 0);
break;
case SND_SOC_DAIFMT_IB_IF:
set_value_by_name(ctrl1, ABOX_BCLK_POLARITY, 0);
set_value_by_name(ctrl1, ABOX_WS_POLAR, 1);
break;
default:
ret = -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
set_value_by_name(ctrl0, ABOX_MODE, 0);
break;
case SND_SOC_DAIFMT_CBS_CFS:
set_value_by_name(ctrl0, ABOX_MODE, 1);
break;
default:
ret = -EINVAL;
}
snd_soc_component_write(cmpnt, ABOX_UAIF_CTRL0(id), ctrl0);
snd_soc_component_write(cmpnt, ABOX_UAIF_CTRL1(id), ctrl1);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
static int abox_uaif_set_tristate(struct snd_soc_dai *dai, int tristate)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct abox_data *abox_data = data->abox_data;
int id = data->id;
enum qchannel clk;
dev_info(dev, "%s(%d)\n", __func__, tristate);
switch (id) {
case 0:
clk = ABOX_BCLK_UAIF0;
break;
case 1:
clk = ABOX_BCLK_UAIF1;
break;
case 2:
clk = ABOX_BCLK_UAIF2;
break;
case 3:
clk = ABOX_BCLK_UAIF3;
break;
default:
return -EINVAL;
}
return abox_disable_qchannel(dev, abox_data, clk, !tristate);
}
static int abox_uaif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct abox_data *abox_data = data->abox_data;
struct snd_soc_component *cmpnt = data->cmpnt;
int id = data->id;
unsigned int ctrl1;
unsigned int channels, rate, width;
int ret;
dev_info(dev, "%s[%c]\n", __func__,
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
'C' : 'P');
channels = params_channels(hw_params);
rate = params_rate(hw_params);
width = params_width(hw_params);
ret = abox_register_bclk_usage(dev, abox_data, dai->id, rate, channels,
width);
if (ret < 0)
dev_err(dev, "Unable to register bclk usage: %d\n", ret);
ret = clk_set_rate(data->clk_bclk, rate * channels * width);
if (ret < 0) {
dev_err(dev, "bclk set error=%d\n", ret);
return ret;
}
dev_info(dev, "rate=%u, width=%d, channel=%u, bclk=%lu\n",
rate, width, channels, clk_get_rate(data->clk_bclk));
ret = snd_soc_component_read(cmpnt, ABOX_UAIF_CTRL1(id), &ctrl1);
if (ret < 0)
dev_err(dev, "sfr access failed: %d\n", ret);
switch (params_format(hw_params)) {
case SNDRV_PCM_FORMAT_S16:
case SNDRV_PCM_FORMAT_S24:
case SNDRV_PCM_FORMAT_S32:
set_value_by_name(ctrl1, ABOX_SBIT_MAX, (width - 1));
break;
default:
return -EINVAL;
}
switch (channels) {
case 2:
set_value_by_name(ctrl1, ABOX_VALID_STR, 0);
set_value_by_name(ctrl1, ABOX_VALID_END, 0);
break;
case 1:
case 4:
case 6:
case 8:
set_value_by_name(ctrl1, ABOX_VALID_STR, (width - 1));
set_value_by_name(ctrl1, ABOX_VALID_END, (width - 1));
break;
default:
return -EINVAL;
}
set_value_by_name(ctrl1, ABOX_SLOT_MAX, (channels - 1));
set_value_by_name(ctrl1, ABOX_FORMAT, abox_get_format(width, channels));
ret = snd_soc_component_write(cmpnt, ABOX_UAIF_CTRL1(id), ctrl1);
if (ret < 0)
dev_err(dev, "sfr access failed: %d\n", ret);
return 0;
}
static int abox_uaif_trigger(struct snd_pcm_substream *substream,
int trigger, struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct snd_soc_component *cmpnt = data->cmpnt;
int id = data->id;
unsigned long mask, shift;
int ret = 0;
dev_info(dev, "%s[%c](%d)\n", __func__,
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
'C' : 'P', trigger);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
mask = ABOX_MIC_ENABLE_MASK;
shift = ABOX_MIC_ENABLE_L;
} else {
mask = ABOX_SPK_ENABLE_MASK;
shift = ABOX_SPK_ENABLE_L;
}
switch (trigger) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ret = snd_soc_component_update_bits(cmpnt, ABOX_UAIF_CTRL0(id),
mask, 1 << shift);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
ret = snd_soc_component_update_bits(cmpnt,
ABOX_UAIF_CTRL0(id), mask, 0 << shift);
}
break;
default:
ret = -EINVAL;
}
if (ret < 0)
dev_err(dev, "sfr access failed: %d\n", ret);
return ret;
}
static void abox_uaif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct abox_if_data *data = snd_soc_dai_get_drvdata(dai);
struct snd_soc_component *cmpnt = data->cmpnt;
int id = data->id;
dev_dbg(dev, "%s[%c]\n", __func__,
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
'C' : 'P');
/* dpcm_be_dai_trigger doesn't call trigger stop on paused stream. */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_soc_component_update_bits(cmpnt, ABOX_UAIF_CTRL0(id),
ABOX_SPK_ENABLE_MASK, 0 << ABOX_SPK_ENABLE_L);
abox_if_shutdown(substream, dai);
}
static const struct snd_soc_dai_ops abox_dsif_dai_ops = {
.set_bclk_ratio = abox_dsif_set_bclk_ratio,
.set_fmt = abox_dsif_set_fmt,
.set_channel_map = abox_dsif_set_channel_map,
.startup = abox_if_startup,
.shutdown = abox_if_shutdown,
.hw_params = abox_dsif_hw_params,
.hw_free = abox_if_hw_free,
.trigger = abox_dsif_trigger,
};
static struct snd_soc_dai_driver abox_dsif_dai_drv = {
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = ABOX_SAMPLING_RATES,
.rate_min = 8000,
.rate_max = 384000,
.formats = SNDRV_PCM_FMTBIT_S32,
},
.ops = &abox_dsif_dai_ops,
};
static const struct snd_soc_dai_ops abox_uaif_dai_ops = {
.set_fmt = abox_uaif_set_fmt,
.set_tristate = abox_uaif_set_tristate,
.startup = abox_if_startup,
.shutdown = abox_uaif_shutdown,
.hw_params = abox_uaif_hw_params,
.hw_free = abox_if_hw_free,
.trigger = abox_uaif_trigger,
};
static struct snd_soc_dai_driver abox_uaif_dai_drv = {
.playback = {
.channels_min = 1,
.channels_max = 8,
.rates = ABOX_SAMPLING_RATES,
.rate_min = 8000,
.rate_max = 384000,
.formats = ABOX_SAMPLE_FORMATS,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
.rates = ABOX_SAMPLING_RATES,
.rate_min = 8000,
.rate_max = 384000,
.formats = ABOX_SAMPLE_FORMATS,
},
.ops = &abox_uaif_dai_ops,
.symmetric_rates = 1,
.symmetric_channels = 1,
.symmetric_samplebits = 1,
};
static int abox_if_config_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct device *dev = cmpnt->dev;
struct abox_if_data *data = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int val = data->config[reg];
dev_dbg(dev, "%s(0x%08x): %u\n", __func__, reg, val);
ucontrol->value.integer.value[0] = val;
return 0;
}
static int abox_if_config_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
struct device *dev = cmpnt->dev;
struct abox_if_data *data = snd_soc_component_get_drvdata(cmpnt);
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
unsigned int val = ucontrol->value.integer.value[0];
dev_info(dev, "%s(0x%08x, %u)\n", __func__, reg, val);
data->config[reg] = val;
return 0;
}
static const struct snd_kcontrol_new abox_if_controls[] = {
SOC_SINGLE_EXT("%s width", ABOX_IF_WIDTH, 0, 32, 0,
abox_if_config_get, abox_if_config_put),
SOC_SINGLE_EXT("%s channel", ABOX_IF_CHANNEL, 0, 8, 0,
abox_if_config_get, abox_if_config_put),
SOC_SINGLE_EXT("%s rate", ABOX_IF_RATE, 0, 384000, 0,
abox_if_config_get, abox_if_config_put),
};
static int abox_if_cmpnt_probe(struct snd_soc_component *cmpnt)
{
struct device *dev = cmpnt->dev;
struct abox_if_data *data = snd_soc_component_get_drvdata(cmpnt);
struct snd_kcontrol_new (*controls)[3];
struct snd_kcontrol_new *control;
int i;
dev_info(dev, "%s\n", __func__);
controls = devm_kmemdup(dev, abox_if_controls,
sizeof(abox_if_controls), GFP_KERNEL);
if (!controls)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(*controls); i++) {
control = &(*controls)[i];
control->name = devm_kasprintf(dev, GFP_KERNEL, control->name,
data->of_data->get_dai_name(data->id));
}
snd_soc_add_component_controls(cmpnt, *controls, ARRAY_SIZE(*controls));
data->cmpnt = cmpnt;
snd_soc_component_init_regmap(cmpnt, data->abox_data->regmap);
abox_register_if(data->abox_data->pdev, to_platform_device(dev),
data->id, snd_soc_component_get_dapm(cmpnt),
data->of_data->get_dai_name(data->id),
!!data->dai_drv->playback.formats,
!!data->dai_drv->capture.formats);
return 0;
}
static void abox_if_cmpnt_remove(struct snd_soc_component *cmpnt)
{
struct device *dev = cmpnt->dev;
dev_info(dev, "%s\n", __func__);
}
static const struct snd_soc_component_driver abox_if_cmpnt = {
.probe = abox_if_cmpnt_probe,
.remove = abox_if_cmpnt_remove,
};
enum abox_dai abox_dsif_get_dai_id(int id)
{
return ABOX_DSIF;
}
const char *abox_dsif_get_dai_name(int id)
{
return "DSIF";
}
const char *abox_dsif_get_str_name(int id, int stream)
{
return (stream == SNDRV_PCM_STREAM_PLAYBACK) ?
"DSIF Playback" : "DSIF Capture";
}
enum abox_dai abox_uaif_get_dai_id(int id)
{
return ABOX_UAIF0 + id;
}
const char *abox_uaif_get_dai_name(int id)
{
static const char * const names[] = {
"UAIF0", "UAIF1", "UAIF2", "UAIF3", "UAIF4",
"UAIF5", "UAIF6", "UAIF7", "UAIF8", "UAIF9",
};
return (id < ARRAY_SIZE(names)) ? names[id] : ERR_PTR(-EINVAL);
}
const char *abox_uaif_get_str_name(int id, int stream)
{
static const char * const names_pla[] = {
"UAIF0 Playback", "UAIF1 Playback", "UAIF2 Playback",
"UAIF3 Playback", "UAIF4 Playback", "UAIF5 Playback",
"UAIF6 Playback", "UAIF7 Playback", "UAIF8 Playback",
"UAIF9 Playback",
};
static const char * const names_cap[] = {
"UAIF0 Capture", "UAIF1 Capture", "UAIF2 Capture",
"UAIF3 Capture", "UAIF4 Capture", "UAIF5 Capture",
"UAIF6 Capture", "UAIF7 Capture", "UAIF8 Capture",
"UAIF9 Capture",
};
const char *ret;
if (stream == SNDRV_PCM_STREAM_PLAYBACK && id < ARRAY_SIZE(names_pla))
ret = names_pla[id];
else if (stream == SNDRV_PCM_STREAM_CAPTURE &&
id < ARRAY_SIZE(names_cap))
ret = names_cap[id];
else
ret = ERR_PTR(-EINVAL);
return ret;
}
static const struct of_device_id samsung_abox_if_match[] = {
{
.compatible = "samsung,abox-uaif",
.data = (void *)&(struct abox_if_of_data){
.get_dai_id = abox_uaif_get_dai_id,
.get_dai_name = abox_uaif_get_dai_name,
.get_str_name = abox_uaif_get_str_name,
.base_dai_drv = &abox_uaif_dai_drv,
},
},
{
.compatible = "samsung,abox-dsif",
.data = (void *)&(struct abox_if_of_data){
.get_dai_id = abox_dsif_get_dai_id,
.get_dai_name = abox_dsif_get_dai_name,
.get_str_name = abox_dsif_get_str_name,
.base_dai_drv = &abox_dsif_dai_drv,
},
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_abox_if_match);
static int samsung_abox_if_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device *dev_abox = dev->parent;
struct device_node *np = dev->of_node;
struct abox_if_data *data;
int ret;
dev_dbg(dev, "%s\n", __func__);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
data->sfr_base = devm_not_request_and_map(pdev, "sfr", 0, NULL, NULL);
if (IS_ERR(data->sfr_base))
return PTR_ERR(data->sfr_base);
ret = of_property_read_u32_index(np, "id", 0, &data->id);
if (ret < 0) {
dev_err(dev, "id property reading fail\n");
return ret;
}
data->clk_bclk = devm_clk_get_and_prepare(pdev, "bclk");
if (IS_ERR(data->clk_bclk))
return PTR_ERR(data->clk_bclk);
data->clk_bclk_gate = devm_clk_get_and_prepare(pdev, "bclk_gate");
if (IS_ERR(data->clk_bclk_gate))
return PTR_ERR(data->clk_bclk_gate);
data->of_data = of_match_node(samsung_abox_if_match,
pdev->dev.of_node)->data;
data->abox_data = dev_get_drvdata(dev_abox);
data->dai_drv = devm_kzalloc(dev, sizeof(struct snd_soc_dai_driver),
GFP_KERNEL);
if (!data->dai_drv)
return -ENOMEM;
memcpy(data->dai_drv, data->of_data->base_dai_drv,
sizeof(struct snd_soc_dai_driver));
data->dai_drv->id = data->of_data->get_dai_id(data->id);
data->dai_drv->name = data->of_data->get_dai_name(data->id);
if (data->dai_drv->capture.formats)
data->dai_drv->capture.stream_name =
data->of_data->get_str_name(data->id,
SNDRV_PCM_STREAM_CAPTURE);
if (data->dai_drv->playback.formats)
data->dai_drv->playback.stream_name =
data->of_data->get_str_name(data->id,
SNDRV_PCM_STREAM_PLAYBACK);
ret = devm_snd_soc_register_component(dev, &abox_if_cmpnt,
data->dai_drv, 1);
if (ret < 0)
return ret;
pm_runtime_enable(dev);
pm_runtime_no_callbacks(dev);
return ret;
}
static int samsung_abox_if_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
dev_dbg(dev, "%s\n", __func__);
return 0;
}
static struct platform_driver samsung_abox_if_driver = {
.probe = samsung_abox_if_probe,
.remove = samsung_abox_if_remove,
.driver = {
.name = "samsung-abox-if",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_abox_if_match),
},
};
module_platform_driver(samsung_abox_if_driver);
int abox_if_hw_params_fixup_by_dai(struct snd_soc_dai *dai,
struct snd_pcm_hw_params *params, int stream)
{
struct device *dev = dai->dev;
struct abox_if_data *data = dev_get_drvdata(dev);
unsigned int rate, channels, width;
int ret = 0;
if (dev->driver != &samsung_abox_if_driver.driver)
return -EINVAL;
dev_dbg(dev, "%s[%s](%d)\n", __func__, dai->name, stream);
rate = data->config[ABOX_IF_RATE];
channels = data->config[ABOX_IF_CHANNEL];
width = data->config[ABOX_IF_WIDTH];
/* don't break symmetric limitation */
if (dai->driver->symmetric_rates && dai->rate && dai->rate != rate)
rate = dai->rate;
if (dai->driver->symmetric_channels && dai->channels &&
dai->channels != channels)
channels = dai->channels;
if (dai->driver->symmetric_samplebits && dai->sample_width &&
dai->sample_width != width)
width = dai->sample_width;
if (rate)
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = rate;
if (channels)
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min =
channels;
if (width) {
unsigned int format = 0;
switch (width) {
case 8:
format = SNDRV_PCM_FORMAT_S8;
break;
case 16:
format = SNDRV_PCM_FORMAT_S16;
break;
case 24:
format = SNDRV_PCM_FORMAT_S24;
break;
case 32:
format = SNDRV_PCM_FORMAT_S32;
break;
default:
width = format = 0;
break;
}
if (format) {
struct snd_mask *mask;
mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
snd_mask_none(mask);
snd_mask_set(mask, format);
}
}
if (rate || channels || width)
dev_info(dev, "%s: %s: %d bit, %u channel, %uHz\n",
__func__, dai->name, width, channels, rate);
else
ret = -EINVAL;
return ret;
}
int abox_if_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params, int stream)
{
struct snd_soc_dai *dai = rtd->cpu_dai;
return abox_if_hw_params_fixup_by_dai(dai, params, stream);
}
/* Module information */
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box UAIF/DSIF Driver");
MODULE_ALIAS("platform:samsung-abox-if");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,63 @@
/* sound/soc/samsung/abox/abox_if.h
*
* ALSA SoC - Samsung Abox UAIF/DSIF driver
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_IF_H
#define __SND_SOC_ABOX_IF_H
#include "abox.h"
enum abox_if_config {
ABOX_IF_WIDTH,
ABOX_IF_CHANNEL,
ABOX_IF_RATE,
ABOX_IF_FMT_COUNT,
};
struct abox_if_of_data {
enum abox_dai (*get_dai_id)(int id);
const char *(*get_dai_name)(int id);
const char *(*get_str_name)(int id, int stream);
struct snd_soc_dai_driver *base_dai_drv;
};
struct abox_if_data {
int id;
void __iomem *sfr_base;
struct clk *clk_bclk;
struct clk *clk_bclk_gate;
struct snd_soc_component *cmpnt;
struct snd_soc_dai_driver *dai_drv;
struct abox_data *abox_data;
const struct abox_if_of_data *of_data;
unsigned int config[ABOX_IF_FMT_COUNT];
};
/**
* UAIF/DSIF hw params fixup helper by dai
* @param[in] dai snd_soc_dai
* @param[out] params snd_pcm_hw_params
* @param[in] stream SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE
* @return error code if any
*/
extern int abox_if_hw_params_fixup_by_dai(struct snd_soc_dai *dai,
struct snd_pcm_hw_params *params, int stream);
/**
* UAIF/DSIF hw params fixup helper
* @param[in] rtd snd_soc_pcm_runtime
* @param[out] params snd_pcm_hw_params
* @param[in] stream SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE
* @return error code if any
*/
extern int abox_if_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params, int stream);
#endif /* __SND_SOC_ABOX_IF_H */

View file

@ -0,0 +1,443 @@
/* sound/soc/samsung/abox/abox_log.c
*
* ALSA SoC Audio Layer - Samsung Abox Log driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
#include <sound/samsung/abox.h>
#include "abox_util.h"
#include "abox.h"
#include "abox_dbg.h"
#include "abox_log.h"
#undef VERBOSE_LOG
#undef TEST
#ifdef TEST
#define SIZE_OF_BUFFER (SZ_128)
#else
#define SIZE_OF_BUFFER (SZ_2M)
#endif
#define S_IRWUG (0660)
struct abox_log_kernel_buffer {
char *buffer;
unsigned int index;
bool wrap;
bool updated;
wait_queue_head_t wq;
};
struct abox_log_buffer_info {
struct list_head list;
struct device *dev;
int id;
bool file_created;
atomic_t opened;
ssize_t file_index;
struct mutex lock;
struct ABOX_LOG_BUFFER *log_buffer;
struct abox_log_kernel_buffer kernel_buffer;
};
static LIST_HEAD(abox_log_list_head);
static u32 abox_log_auto_save;
static void abox_log_memcpy(struct device *dev,
struct abox_log_kernel_buffer *kernel_buffer,
const char *src, size_t size)
{
size_t left_size = SIZE_OF_BUFFER - kernel_buffer->index;
dev_dbg(dev, "%s(%zu)\n", __func__, size);
if (left_size < size) {
#ifdef VERBOSE_LOG
dev_dbg(dev, "0: %s\n", src);
#endif
memcpy(kernel_buffer->buffer + kernel_buffer->index, src,
left_size);
src += left_size;
size -= left_size;
kernel_buffer->index = 0;
kernel_buffer->wrap = true;
}
#ifdef VERBOSE_LOG
dev_dbg(dev, "1: %s\n", src);
#endif
memcpy(kernel_buffer->buffer + kernel_buffer->index, src, size);
kernel_buffer->index += (unsigned int)size;
}
static void abox_log_file_name(struct device *dev,
struct abox_log_buffer_info *info, char *name, size_t size)
{
snprintf(name, size, "/data/calliope-%02d.log", info->id);
}
static void abox_log_file_save(struct device *dev,
struct abox_log_buffer_info *info)
{
struct ABOX_LOG_BUFFER *log_buffer = info->log_buffer;
unsigned int index_writer = log_buffer->index_writer;
char name[32];
struct file *filp;
mm_segment_t old_fs;
dev_dbg(dev, "%s(%d)\n", __func__, info->id);
abox_log_file_name(dev, info, name, sizeof(name));
old_fs = get_fs();
set_fs(KERNEL_DS);
if (likely(info->file_created)) {
filp = filp_open(name, O_RDWR | O_APPEND | O_CREAT, S_IRWUG);
dev_dbg(dev, "appended\n");
} else {
filp = filp_open(name, O_RDWR | O_TRUNC | O_CREAT, S_IRWUG);
info->file_created = true;
dev_dbg(dev, "created\n");
}
if (IS_ERR(filp)) {
dev_warn(dev, "%s: saving log fail\n", __func__);
goto out;
}
if (log_buffer->index_reader > index_writer) {
vfs_write(filp, log_buffer->buffer + log_buffer->index_reader,
log_buffer->size - log_buffer->index_reader,
&filp->f_pos);
vfs_write(filp, log_buffer->buffer, index_writer, &filp->f_pos);
} else {
vfs_write(filp, log_buffer->buffer + log_buffer->index_reader,
index_writer - log_buffer->index_reader, &filp->f_pos);
}
vfs_fsync(filp, 0);
filp_close(filp, NULL);
out:
set_fs(old_fs);
}
static void abox_log_flush(struct device *dev,
struct abox_log_buffer_info *info)
{
struct ABOX_LOG_BUFFER *log_buffer = info->log_buffer;
unsigned int index_writer = log_buffer->index_writer;
struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer;
if (log_buffer->index_reader == index_writer)
return;
dev_dbg(dev, "%s(%d): index_writer=%u, index_reader=%u, size=%u\n",
__func__, info->id, index_writer,
log_buffer->index_reader, log_buffer->size);
mutex_lock(&info->lock);
if (abox_log_auto_save)
abox_log_file_save(dev, info);
if (log_buffer->index_reader > index_writer) {
abox_log_memcpy(info->dev, kernel_buffer,
log_buffer->buffer + log_buffer->index_reader,
log_buffer->size - log_buffer->index_reader);
log_buffer->index_reader = 0;
}
abox_log_memcpy(info->dev, kernel_buffer,
log_buffer->buffer + log_buffer->index_reader,
index_writer - log_buffer->index_reader);
log_buffer->index_reader = index_writer;
mutex_unlock(&info->lock);
kernel_buffer->updated = true;
wake_up_interruptible(&kernel_buffer->wq);
#ifdef TEST
dev_dbg(dev, "shared_buffer: %s\n", log_buffer->buffer);
dev_dbg(dev, "kernel_buffer: %s\n", info->kernel_buffer.buffer);
#endif
}
void abox_log_flush_all(struct device *dev)
{
struct abox_log_buffer_info *info;
dev_dbg(dev, "%s\n", __func__);
list_for_each_entry(info, &abox_log_list_head, list) {
abox_log_flush(info->dev, info);
}
}
EXPORT_SYMBOL(abox_log_flush_all);
static unsigned long abox_log_flush_all_work_rearm_self;
static void abox_log_flush_all_work_func(struct work_struct *work);
static DECLARE_DEFERRABLE_WORK(abox_log_flush_all_work,
abox_log_flush_all_work_func);
static void abox_log_flush_all_work_func(struct work_struct *work)
{
abox_log_flush_all(NULL);
schedule_delayed_work(&abox_log_flush_all_work, msecs_to_jiffies(3000));
set_bit(0, &abox_log_flush_all_work_rearm_self);
}
void abox_log_schedule_flush_all(struct device *dev)
{
if (test_and_clear_bit(0, &abox_log_flush_all_work_rearm_self))
cancel_delayed_work(&abox_log_flush_all_work);
schedule_delayed_work(&abox_log_flush_all_work, msecs_to_jiffies(100));
}
EXPORT_SYMBOL(abox_log_schedule_flush_all);
void abox_log_drain_all(struct device *dev)
{
cancel_delayed_work(&abox_log_flush_all_work);
abox_log_flush_all(dev);
}
EXPORT_SYMBOL(abox_log_drain_all);
static int abox_log_file_open(struct inode *inode, struct file *file)
{
struct abox_log_buffer_info *info = inode->i_private;
dev_dbg(info->dev, "%s\n", __func__);
if (atomic_cmpxchg(&info->opened, 0, 1))
return -EBUSY;
info->file_index = -1;
file->private_data = info;
return 0;
}
static int abox_log_file_release(struct inode *inode, struct file *file)
{
struct abox_log_buffer_info *info = inode->i_private;
dev_dbg(info->dev, "%s\n", __func__);
atomic_cmpxchg(&info->opened, 1, 0);
return 0;
}
static ssize_t abox_log_file_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct abox_log_buffer_info *info = file->private_data;
struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer;
unsigned int index;
size_t end, size;
bool first = (info->file_index < 0);
int ret;
dev_dbg(info->dev, "%s(%zu, %lld)\n", __func__, count, *ppos);
mutex_lock(&info->lock);
if (first) {
info->file_index = likely(kernel_buffer->wrap) ?
kernel_buffer->index : 0;
}
do {
index = kernel_buffer->index;
end = ((info->file_index < index) ||
((info->file_index == index) && !first)) ?
index : SIZE_OF_BUFFER;
size = min(end - info->file_index, count);
if (size == 0) {
mutex_unlock(&info->lock);
if (file->f_flags & O_NONBLOCK) {
dev_dbg(info->dev, "non block\n");
return -EAGAIN;
}
kernel_buffer->updated = false;
ret = wait_event_interruptible(kernel_buffer->wq,
kernel_buffer->updated);
if (ret != 0) {
dev_dbg(info->dev, "interrupted\n");
return ret;
}
mutex_lock(&info->lock);
}
#ifdef VERBOSE_LOG
dev_dbg(info->dev, "loop %zu, %zu, %zd, %zu\n", size, end,
info->file_index, count);
#endif
} while (size == 0);
dev_dbg(info->dev, "start=%zd, end=%zd size=%zd\n", info->file_index,
end, size);
if (copy_to_user(buf, kernel_buffer->buffer + info->file_index,
size)) {
mutex_unlock(&info->lock);
return -EFAULT;
}
info->file_index += size;
if (info->file_index >= SIZE_OF_BUFFER)
info->file_index = 0;
mutex_unlock(&info->lock);
dev_dbg(info->dev, "%s: size = %zd\n", __func__, size);
return size;
}
static unsigned int abox_log_file_poll(struct file *file, poll_table *wait)
{
struct abox_log_buffer_info *info = file->private_data;
struct abox_log_kernel_buffer *kernel_buffer = &info->kernel_buffer;
dev_dbg(info->dev, "%s\n", __func__);
poll_wait(file, &kernel_buffer->wq, wait);
return POLLIN | POLLRDNORM;
}
static const struct file_operations abox_log_fops = {
.open = abox_log_file_open,
.release = abox_log_file_release,
.read = abox_log_file_read,
.poll = abox_log_file_poll,
.llseek = generic_file_llseek,
.owner = THIS_MODULE,
};
static struct abox_log_buffer_info abox_log_buffer_info_new;
void abox_log_register_buffer_work_func(struct work_struct *work)
{
struct device *dev;
int id;
struct ABOX_LOG_BUFFER *buffer;
struct abox_log_buffer_info *info;
char name[16];
dev = abox_log_buffer_info_new.dev;
id = abox_log_buffer_info_new.id;
buffer = abox_log_buffer_info_new.log_buffer;
abox_log_buffer_info_new.dev = NULL;
abox_log_buffer_info_new.id = 0;
abox_log_buffer_info_new.log_buffer = NULL;
dev_info(dev, "%s(%d)\n", __func__, id);
info = vmalloc(sizeof(*info));
mutex_init(&info->lock);
info->id = id;
info->file_created = false;
atomic_set(&info->opened, 0);
info->kernel_buffer.buffer = vzalloc(SIZE_OF_BUFFER);
info->kernel_buffer.index = 0;
info->kernel_buffer.wrap = false;
init_waitqueue_head(&info->kernel_buffer.wq);
info->dev = dev;
info->log_buffer = buffer;
list_add_tail(&info->list, &abox_log_list_head);
snprintf(name, sizeof(name), "log-%02d", id);
debugfs_create_file(name, 0664, abox_dbg_get_root_dir(), info,
&abox_log_fops);
}
static DECLARE_WORK(abox_log_register_buffer_work,
abox_log_register_buffer_work_func);
int abox_log_register_buffer(struct device *dev, int id,
struct ABOX_LOG_BUFFER *buffer)
{
struct abox_log_buffer_info *info;
dev_dbg(dev, "%s(%d)\n", __func__, id);
if (abox_log_buffer_info_new.dev != NULL ||
abox_log_buffer_info_new.id > 0 ||
abox_log_buffer_info_new.log_buffer != NULL) {
return -EBUSY;
}
list_for_each_entry(info, &abox_log_list_head, list) {
if (info->id == id) {
dev_dbg(dev, "already registered log: %d\n", id);
return 0;
}
}
abox_log_buffer_info_new.dev = dev;
abox_log_buffer_info_new.id = id;
abox_log_buffer_info_new.log_buffer = buffer;
schedule_work(&abox_log_register_buffer_work);
return 0;
}
EXPORT_SYMBOL(abox_log_register_buffer);
#ifdef TEST
static struct ABOX_LOG_BUFFER *abox_log_test_buffer;
static void abox_log_test_work_func(struct work_struct *work);
DECLARE_DELAYED_WORK(abox_log_test_work, abox_log_test_work_func);
static void abox_log_test_work_func(struct work_struct *work)
{
struct ABOX_LOG_BUFFER *log = abox_log_test_buffer;
static unsigned int i;
char buffer[32];
char *buffer_index = buffer;
int size, left;
pr_debug("%s: %d\n", __func__, i);
size = snprintf(buffer, sizeof(buffer), "%d ", i++);
if (log->index_writer + size > log->size) {
left = log->size - log->index_writer;
memcpy(&log->buffer[log->index_writer], buffer_index, left);
log->index_writer = 0;
buffer_index += left;
}
left = size - (buffer_index - buffer);
memcpy(&log->buffer[log->index_writer], buffer_index, left);
log->index_writer += left;
abox_log_flush_all(NULL);
schedule_delayed_work(&abox_log_test_work, msecs_to_jiffies(1000));
}
#endif
static int __init samsung_abox_log_late_initcall(void)
{
pr_info("%s\n", __func__);
debugfs_create_u32("log_auto_save", S_IRWUG, abox_dbg_get_root_dir(),
&abox_log_auto_save);
#ifdef TEST
abox_log_test_buffer = vzalloc(SZ_128);
abox_log_test_buffer->size = SZ_64;
abox_log_register_buffer(NULL, 0, abox_log_test_buffer);
schedule_delayed_work(&abox_log_test_work, msecs_to_jiffies(1000));
#endif
return 0;
}
late_initcall(samsung_abox_log_late_initcall);

View file

@ -0,0 +1,53 @@
/* sound/soc/samsung/abox/abox_log.h
*
* ALSA SoC - Samsung Abox Log driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_LOG_H
#define __SND_SOC_ABOX_LOG_H
#include <linux/device.h>
#include <sound/samsung/abox.h>
/**
* Flush log from all shared memories to kernel memory
* @param[in] dev pointer to abox device
*/
extern void abox_log_flush_all(struct device *dev);
/**
* Schedule log flush from all shared memories to kernel memory
* @param[in] dev pointer to abox device
*/
extern void abox_log_schedule_flush_all(struct device *dev);
/**
* drain log and stop scheduling log flush
* @param[in] dev pointer to abox device
*/
extern void abox_log_drain_all(struct device *dev);
/**
* Flush log from specific shared memory to kernel memory
* @param[in] dev pointer to abox device
* @param[in] id unique buffer id
*/
extern void abox_log_flush_by_id(struct device *dev, int id);
/**
* Register abox log buffer
* @param[in] dev pointer to abox device
* @param[in] id unique buffer id
* @param[in] buffer pointer to shared buffer
* @return error code if any
*/
extern int abox_log_register_buffer(struct device *dev, int id,
struct ABOX_LOG_BUFFER *buffer);
#endif /* __SND_SOC_ABOX_LOG_H */

View file

@ -0,0 +1,200 @@
/* sound/soc/samsung/abox/abox_mmap_fd.c
*
* ALSA SoC Audio Layer - Samsung Abox mmap_fd driver
*
* Copyright (c) 2018 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <sound/samsung/abox.h>
#include <sound/sounddev_abox.h>
#include <linux/dma-buf.h>
#include <uapi/linux/dma-buf.h>
#include <linux/exynos_ion.h>
#if defined(CONFIG_VIDEOBUF2_CMA_PHYS)
#include <media/videobuf2-cma-phys.h>
#elif defined(CONFIG_VIDEOBUF2_ION)
#include <media/videobuf2-ion.h>
#endif
#include "../../../../drivers/iommu/exynos-iommu.h"
#include "../../../../drivers/staging/android/uapi/ion.h"
#include "abox.h"
int abox_mmap_fd(struct abox_platform_data *data,
struct snd_pcm_mmap_fd *mmap_fd)
{
struct device *dev = &data->pdev->dev;
int id = data->id;
struct abox_ion_buf *buf = &data->ion_buf;
struct dma_buf *temp_buf;
int ret = 0;
dev_dbg(dev, "%s id(%d)\n", __func__, id);
if (buf->fd >= 0) {
mmap_fd->dir = SNDRV_PCM_STREAM_PLAYBACK;
mmap_fd->size = buf->size;
mmap_fd->actual_size = buf->size;
mmap_fd->fd = buf->fd;
} else {
mmap_fd->fd = dma_buf_fd(buf->dma_buf, O_CLOEXEC);
if (mmap_fd->fd >= 0) {
mmap_fd->dir = SNDRV_PCM_STREAM_PLAYBACK;
mmap_fd->size = buf->size;
mmap_fd->actual_size = buf->size;
} else {
ret = -EFAULT;
dev_err(dev, "%s dma_buf_fd is failed\n", __func__);
dma_buf_put(buf->dma_buf);
goto error_get_fd;
}
buf->fd = mmap_fd->fd;
dev_dbg(dev, "%s fd(%d)\n", __func__, buf->fd);
temp_buf = dma_buf_get(buf->fd);
data->mmap_fd_state = true;
dev_dbg(dev, "%s-(%p)(%p)\n", __func__, buf->dma_buf, temp_buf);
}
dev_info(dev, "%s id(%d) fd(%d)\n", __func__, id, buf->fd);
error_get_fd:
return ret;
}
int abox_ion_alloc(struct abox_platform_data *data,
struct abox_ion_buf *buf,
unsigned long iova,
size_t size,
size_t align)
{
struct device *dev = &data->pdev->dev;
struct device *dev_abox = &data->abox_data->pdev->dev;
int heapflags = EXYNOS_ION_HEAP_SYSTEM_MASK;
int ret = 0;
if (!buf)
return -ENOMEM;
size = PAGE_ALIGN(size);
buf->client = data->abox_data->client;
buf->alignment = SZ_4K;
buf->flags = ION_FLAG_SYNC_FORCE;
buf->handle = ion_alloc(buf->client, size, buf->alignment,
heapflags, buf->flags);
if (IS_ERR(buf->handle)) {
ret = -ENOMEM;
goto error_alloc;
}
/* ion_share_dma_buf will call dam_buf_get */
buf->dma_buf = ion_share_dma_buf(buf->client, buf->handle);
if (IS_ERR(buf->dma_buf)) {
ret = PTR_ERR(buf->dma_buf);
goto error_share;
}
buf->attachment = dma_buf_attach(buf->dma_buf, dev_abox);
if (IS_ERR(buf->attachment)) {
ret = -ENOMEM;
goto error_attach;
}
buf->cookie.sgt = dma_buf_map_attachment(
buf->attachment, DMA_BIDIRECTIONAL);
if (IS_ERR(buf->cookie.sgt)) {
ret = -ENOMEM;
goto error_map_dmabuf;
}
if (!buf->kva)
buf->kva = ion_map_kernel(buf->client, buf->handle);
if (IS_ERR_OR_NULL(buf->kva)) {
ret = -ENOMEM;
goto error_dma_buf_vmap;
}
buf->size = size;
buf->cookie.ioaddr = iova;
buf->direction = DMA_BIDIRECTIONAL;
ret = abox_iommu_map_sg(&data->abox_data->pdev->dev,
buf->cookie.ioaddr,
buf->cookie.sgt->sgl,
buf->cookie.sgt->nents,
DMA_BIDIRECTIONAL,
buf->size,
buf->kva);
if (ret < 0) {
dev_err(dev, "Failed to iommu_map: %d\n", ret);
ret = -ENOMEM;
goto error_iommu_map_sg;
}
dev_info(dev, "%s buf(0x%lx, 0x%llx, %p)\n",
__func__,
buf->size,
buf->cookie.ioaddr,
buf->kva);
return ret;
error_iommu_map_sg:
ion_unmap_kernel(buf->client, buf->handle);
error_dma_buf_vmap:
dma_buf_unmap_attachment(buf->attachment, buf->cookie.sgt,
DMA_BIDIRECTIONAL);
error_map_dmabuf:
dma_buf_detach(buf->dma_buf, buf->attachment);
error_attach:
dma_buf_put(buf->dma_buf);
error_share:
ion_free(buf->client, buf->handle);
error_alloc:
dev_err(dev, "%s: Error occured while allocating\n", __func__);
return ret;
}
int abox_ion_free(struct abox_platform_data *data)
{
struct device *dev = &data->pdev->dev;
int ret = 0;
ret = abox_iommu_unmap(&data->abox_data->pdev->dev,
data->ion_buf.cookie.ioaddr,
data->ion_buf.cookie.paddr,
data->ion_buf.size);
if (ret < 0)
dev_err(dev, "Failed to iommu_unmap: %d\n", ret);
if (data->ion_buf.kva) {
ion_unmap_kernel(data->ion_buf.client,
data->ion_buf.handle);
if (data->mmap_fd_state == true)
dma_buf_put(data->ion_buf.dma_buf);
}
dma_buf_unmap_attachment(data->ion_buf.attachment,
data->ion_buf.cookie.sgt,
DMA_BIDIRECTIONAL);
dma_buf_detach(data->ion_buf.dma_buf,
data->ion_buf.attachment);
dma_buf_put(data->ion_buf.dma_buf);
return ret;
}

View file

@ -0,0 +1,29 @@
/* sound/soc/samsung/abox/abox_mmap_fd.h
*
* ALSA SoC - Samsung Abox MMAP Exclusive mode driver
*
* Copyright (c) 2018 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_MMAPFD_H
#define __SND_SOC_ABOX_MMAPFD_H
#include "abox.h"
#define BUFFER_ION_BYTES_MAX (SZ_512K)
extern int abox_mmap_fd(struct abox_platform_data *data,
struct snd_pcm_mmap_fd *mmap_fd);
extern int abox_ion_alloc(struct abox_platform_data *data,
struct abox_ion_buf *buf,
unsigned long iova,
size_t size,
size_t align);
extern int abox_ion_free(struct abox_platform_data *data);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,177 @@
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <sound/pcm.h>
#include "abox_util.h"
void __iomem *devm_not_request_and_map(struct platform_device *pdev,
const char *name, unsigned int num, phys_addr_t *phys_addr,
size_t *size)
{
struct resource *res;
void __iomem *ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, num);
if (IS_ERR_OR_NULL(res)) {
dev_err(&pdev->dev, "Failed to get %s\n", name);
return ERR_PTR(-EINVAL);
}
if (phys_addr)
*phys_addr = res->start;
if (size)
*size = resource_size(res);
ret = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (IS_ERR_OR_NULL(ret)) {
dev_err(&pdev->dev, "Failed to map %s\n", name);
return ERR_PTR(-EFAULT);
}
return ret;
}
void __iomem *devm_request_and_map(struct platform_device *pdev,
const char *name, unsigned int num, phys_addr_t *phys_addr,
size_t *size)
{
struct resource *res;
void __iomem *ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, num);
if (IS_ERR_OR_NULL(res)) {
dev_err(&pdev->dev, "Failed to get %s\n", name);
return ERR_PTR(-EINVAL);
}
if (phys_addr)
*phys_addr = res->start;
if (size)
*size = resource_size(res);
res = devm_request_mem_region(&pdev->dev, res->start,
resource_size(res), name);
if (IS_ERR_OR_NULL(res)) {
dev_err(&pdev->dev, "Failed to request %s\n", name);
return ERR_PTR(-EFAULT);
}
ret = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (IS_ERR_OR_NULL(ret)) {
dev_err(&pdev->dev, "Failed to map %s\n", name);
return ERR_PTR(-EFAULT);
}
return ret;
}
void __iomem *devm_request_and_map_byname(struct platform_device *pdev,
const char *name, phys_addr_t *phys_addr, size_t *size)
{
struct resource *res;
void __iomem *ret;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
if (IS_ERR_OR_NULL(res)) {
dev_err(&pdev->dev, "Failed to get %s\n", name);
return ERR_PTR(-EINVAL);
}
if (phys_addr)
*phys_addr = res->start;
if (size)
*size = resource_size(res);
res = devm_request_mem_region(&pdev->dev, res->start,
resource_size(res), name);
if (IS_ERR_OR_NULL(res)) {
dev_err(&pdev->dev, "Failed to request %s\n", name);
return ERR_PTR(-EFAULT);
}
ret = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (IS_ERR_OR_NULL(ret)) {
dev_err(&pdev->dev, "Failed to map %s\n", name);
return ERR_PTR(-EFAULT);
}
return ret;
}
struct clk *devm_clk_get_and_prepare(struct platform_device *pdev,
const char *name)
{
struct device *dev = &pdev->dev;
struct clk *clk;
int ret;
clk = devm_clk_get(dev, name);
if (IS_ERR(clk)) {
dev_err(dev, "Failed to get clock %s\n", name);
goto error;
}
ret = clk_prepare(clk);
if (ret < 0) {
dev_err(dev, "Failed to prepare clock %s\n", name);
goto error;
}
error:
return clk;
}
u32 readl_phys(phys_addr_t addr)
{
u32 ret;
void __iomem *virt = ioremap(addr, 0x4);
ret = readl(virt);
pr_debug("%pa = %08x\n", &addr, ret);
iounmap(virt);
return ret;
}
void writel_phys(unsigned int val, phys_addr_t addr)
{
void __iomem *virt = ioremap(addr, 0x4);
writel(val, virt);
pr_debug("%pa <= %08x\n", &addr, val);
iounmap(virt);
}
bool is_secure_gic(void)
{
pr_debug("%s: %08x, %08x\n", __func__, readl_phys(0x10000000),
readl_phys(0x10000010));
return (readl_phys(0x10000000) == 0xE8895000) &&
(readl_phys(0x10000010) == 0x0);
}
u64 width_range_to_bits(unsigned int width_min, unsigned int width_max)
{
static const struct {
unsigned int width;
u64 format;
} map[] = {
{ 8, SNDRV_PCM_FMTBIT_S8 },
{ 16, SNDRV_PCM_FMTBIT_S16 },
{ 24, SNDRV_PCM_FMTBIT_S24 },
{ 32, SNDRV_PCM_FMTBIT_S32 },
};
int i;
u64 fmt = 0;
for (i = 0; i < ARRAY_SIZE(map); i++) {
if (map[i].width >= width_min && map[i].width <= width_max)
fmt |= map[i].format;
}
return fmt;
}
char substream_to_char(struct snd_pcm_substream *substream)
{
return (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 'p' : 'c';
}

View file

@ -0,0 +1,137 @@
/* sound/soc/samsung/abox/abox_util.h
*
* ALSA SoC - Samsung Abox utility
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_UTIL_H
#define __SND_SOC_ABOX_UTIL_H
#include <sound/pcm.h>
/**
* ioremap to virtual address but not request
* @param[in] pdev pointer to platform device structure
* @param[in] name name of resource
* @param[in] num index of resource
* @param[out] phys_addr physical address of the resource
* @param[out] size size of the resource
* @return virtual address
*/
extern void __iomem *devm_not_request_and_map(struct platform_device *pdev,
const char *name, unsigned int num, phys_addr_t *phys_addr,
size_t *size);
/**
* Request memory resource and map to virtual address
* @param[in] pdev pointer to platform device structure
* @param[in] name name of resource
* @param[in] num index of resource
* @param[out] phys_addr physical address of the resource
* @param[out] size size of the resource
* @return virtual address
*/
extern void __iomem *devm_request_and_map(struct platform_device *pdev,
const char *name, unsigned int num, phys_addr_t *phys_addr,
size_t *size);
/**
* Request memory resource and map to virtual address
* @param[in] pdev pointer to platform device structure
* @param[in] name name of resource
* @param[out] phys_addr physical address of the resource
* @param[out] size size of the resource
* @return virtual address
*/
extern void __iomem *devm_request_and_map_byname(struct platform_device *pdev,
const char *name, phys_addr_t *phys_addr, size_t *size);
/**
* Request clock and prepare
* @param[in] pdev pointer to platform device structure
* @param[in] name name of clock
* @return pointer to clock
*/
extern struct clk *devm_clk_get_and_prepare(struct platform_device *pdev,
const char *name);
/**
* Read single long physical address (sleeping function)
* @param[in] addr physical address
* @return value of the physical address
*/
extern u32 readl_phys(phys_addr_t addr);
/**
* Write single long physical address (sleeping function)
* @param[in] val value
* @param[in] addr physical address
*/
extern void writel_phys(unsigned int val, phys_addr_t addr);
/**
* Atomically increments @v, if @v was @r, set to 0.
* @param[in] v pointer of type atomic_t
* @param[in] r maximum range of @v.
* @return Returns old value
*/
static inline int atomic_inc_unless_in_range(atomic_t *v, int r)
{
int ret;
while ((ret = __atomic_add_unless(v, 1, r)) == r) {
ret = atomic_cmpxchg(v, r, 0);
if (ret == r)
break;
}
return ret;
}
/**
* Atomically decrements @v, if @v was 0, set to @r.
* @param[in] v pointer of type atomic_t
* @param[in] r maximum range of @v.
* @return Returns old value
*/
static inline int atomic_dec_unless_in_range(atomic_t *v, int r)
{
int ret;
while ((ret = __atomic_add_unless(v, -1, 0)) == 0) {
ret = atomic_cmpxchg(v, 0, r);
if (ret == 0)
break;
}
return ret;
}
/**
* Check whether the GIC is secure (sleeping function)
* @return true if the GIC is secure, false on otherwise
*/
extern bool is_secure_gic(void);
/**
* Get SNDRV_PCM_FMTBIT_* within width_min and width_max.
* @param[in] width_min minimum bit width
* @param[in] width_max maximum bit width
* @return Bitwise and of SNDRV_PCM_FMTBIT_*
*/
extern u64 width_range_to_bits(unsigned int width_min,
unsigned int width_max);
/**
* Get character from substream direction
* @param[in] substream substream
* @return 'p' if direction is playback. 'c' if not.
*/
extern char substream_to_char(struct snd_pcm_substream *substream);
#endif /* __SND_SOC_ABOX_UTIL_H */

View file

@ -0,0 +1,744 @@
/* sound/soc/samsung/abox/abox_vdma.c
*
* ALSA SoC Audio Layer - Samsung Abox Virtual DMA driver
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
#undef DEBUG
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/memblock.h>
#include <linux/iommu.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include "abox_util.h"
#include "abox.h"
#include "abox_vdma.h"
#undef TEST
#define VDMA_COUNT_MAX SZ_32
#define NAME_LENGTH SZ_32
struct abox_vdma_rtd {
struct snd_dma_buffer buffer;
struct snd_pcm_hardware hardware;
struct snd_pcm_substream *substream;
unsigned long iova;
size_t pointer;
bool ack_enabled;
bool iommu_mapped;
};
struct abox_vdma_info {
struct device *dev;
int id;
char name[NAME_LENGTH];
struct abox_vdma_rtd rtd[SNDRV_PCM_STREAM_LAST + 1];
};
static struct device *abox_vdma_dev_abox;
static struct abox_vdma_info abox_vdma_list[VDMA_COUNT_MAX];
static int abox_vdma_get_idx(int id)
{
return id - PCMTASK_VDMA_ID_BASE;
}
static unsigned long abox_vdma_get_iova(int id, int stream)
{
int idx = abox_vdma_get_idx(id);
long ret;
switch (stream) {
case SNDRV_PCM_STREAM_PLAYBACK:
case SNDRV_PCM_STREAM_CAPTURE:
ret = IOVA_VDMA_BUFFER(idx) + (SZ_512K * stream);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static struct abox_vdma_info *abox_vdma_get_info(int id)
{
int idx = abox_vdma_get_idx(id);
if (idx < 0 || idx >= ARRAY_SIZE(abox_vdma_list))
return NULL;
return &abox_vdma_list[idx];
}
static struct abox_vdma_rtd *abox_vdma_get_rtd(struct abox_vdma_info *info,
int stream)
{
if (!info || stream < 0 || stream >= ARRAY_SIZE(info->rtd))
return NULL;
return &info->rtd[stream];
}
static int abox_vdma_request_ipc(ABOX_IPC_MSG *msg, int atomic, int sync)
{
return abox_request_ipc(abox_vdma_dev_abox, msg->ipcid, msg,
sizeof(*msg), atomic, sync);
}
int abox_vdma_period_elapsed(struct abox_vdma_info *info,
struct abox_vdma_rtd *rtd, size_t pointer)
{
dev_dbg(info->dev, "%s[%d:%c](%zx)\n", __func__, info->id,
substream_to_char(rtd->substream), pointer);
rtd->pointer = pointer - rtd->iova;
snd_pcm_period_elapsed(rtd->substream);
return 0;
}
static int abox_vdma_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
struct snd_soc_platform *platform = soc_rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform);
struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream);
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream));
snd_soc_set_runtime_hwparams(substream, &rtd->hardware);
msg.ipcid = abox_stream_to_ipcid(substream->stream);
msg.task_id = pcmtask_msg->channel_id = id;
pcmtask_msg->msgtype = PCM_PLTDAI_OPEN;
return abox_vdma_request_ipc(&msg, 0, 0);
}
static int abox_vdma_close(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
struct snd_soc_platform *platform = soc_rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream));
msg.ipcid = abox_stream_to_ipcid(substream->stream);
msg.task_id = pcmtask_msg->channel_id = id;
pcmtask_msg->msgtype = PCM_PLTDAI_CLOSE;
return abox_vdma_request_ipc(&msg, 0, 0);
}
static int abox_vdma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
struct snd_soc_platform *platform = soc_rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform);
struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream);
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
int ret;
dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream));
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (ret < 0)
return ret;
msg.ipcid = abox_stream_to_ipcid(substream->stream);
msg.task_id = pcmtask_msg->channel_id = id;
pcmtask_msg->msgtype = PCM_SET_BUFFER;
pcmtask_msg->param.setbuff.phyaddr = rtd->iova;
pcmtask_msg->param.setbuff.size = params_period_bytes(params);
pcmtask_msg->param.setbuff.count = params_periods(params);
ret = abox_vdma_request_ipc(&msg, 0, 0);
if (ret < 0)
return ret;
pcmtask_msg->msgtype = PCM_PLTDAI_HW_PARAMS;
pcmtask_msg->param.hw_params.sample_rate = params_rate(params);
pcmtask_msg->param.hw_params.bit_depth = params_width(params);
pcmtask_msg->param.hw_params.channels = params_channels(params);
ret = abox_vdma_request_ipc(&msg, 0, 0);
if (ret < 0)
return ret;
return ret;
}
static int abox_vdma_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
struct snd_soc_platform *platform = soc_rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream));
msg.ipcid = abox_stream_to_ipcid(substream->stream);
msg.task_id = pcmtask_msg->channel_id = id;
pcmtask_msg->msgtype = PCM_PLTDAI_HW_FREE;
abox_vdma_request_ipc(&msg, 0, 0);
return snd_pcm_lib_free_pages(substream);
}
static int abox_vdma_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
struct snd_soc_platform *platform = soc_rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform);
struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream);
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream));
rtd->pointer = 0;
msg.ipcid = abox_stream_to_ipcid(substream->stream);
msg.task_id = pcmtask_msg->channel_id = id;
pcmtask_msg->msgtype = PCM_PLTDAI_PREPARE;
return abox_vdma_request_ipc(&msg, 0, 0);
}
static int abox_vdma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
struct snd_soc_platform *platform = soc_rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
struct platform_device *pdev_abox;
int ret;
dev_info(dev, "%s[%d:%c](%d)\n", __func__, id,
substream_to_char(substream), cmd);
pdev_abox = to_platform_device(abox_vdma_dev_abox);
msg.ipcid = abox_stream_to_ipcid(substream->stream);
msg.task_id = pcmtask_msg->channel_id = id;
pcmtask_msg->msgtype = PCM_PLTDAI_TRIGGER;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
pcmtask_msg->param.trigger = 1;
ret = abox_vdma_request_ipc(&msg, 1, 0);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pcmtask_msg->param.trigger = 0;
ret = abox_vdma_request_ipc(&msg, 1, 0);
break;
default:
dev_err(dev, "invalid command: %d\n", cmd);
ret = -EINVAL;
}
return ret;
}
static snd_pcm_uframes_t abox_vdma_pointer(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
struct snd_soc_platform *platform = soc_rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform);
struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream);
dev_dbg(dev, "%s[%d:%c]\n", __func__, id, substream_to_char(substream));
return bytes_to_frames(substream->runtime, rtd->pointer);
}
static int abox_vdma_ack(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *pcm_rtd = substream->runtime;
struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
struct snd_soc_platform *platform = soc_rtd->platform;
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
struct abox_vdma_info *info = snd_soc_platform_get_drvdata(platform);
struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, substream->stream);
snd_pcm_uframes_t appl_ptr = pcm_rtd->control->appl_ptr;
snd_pcm_uframes_t appl_ofs = appl_ptr % pcm_rtd->buffer_size;
ssize_t appl_bytes = frames_to_bytes(pcm_rtd, appl_ofs);
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
if (!rtd->ack_enabled)
return 0;
dev_dbg(dev, "%s[%d:%c]: %zd\n", __func__, id,
substream_to_char(substream), appl_bytes);
msg.ipcid = abox_stream_to_ipcid(substream->stream);
msg.task_id = pcmtask_msg->channel_id = id;
pcmtask_msg->msgtype = PCM_PLTDAI_ACK;
pcmtask_msg->param.pointer = (unsigned int)appl_bytes;
return abox_vdma_request_ipc(&msg, 0, 0);
}
static struct snd_pcm_ops abox_vdma_platform_ops = {
.open = abox_vdma_open,
.close = abox_vdma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = abox_vdma_hw_params,
.hw_free = abox_vdma_hw_free,
.prepare = abox_vdma_prepare,
.trigger = abox_vdma_trigger,
.pointer = abox_vdma_pointer,
.ack = abox_vdma_ack,
};
static int abox_vdma_platform_probe(struct snd_soc_platform *platform)
{
struct device *dev = platform->dev;
int id = to_platform_device(dev)->id;
dev_dbg(dev, "%s[%d]\n", __func__, id);
snd_soc_platform_set_drvdata(platform, abox_vdma_get_info(id));
return 0;
}
static int abox_vdma_platform_new(struct snd_soc_pcm_runtime *soc_rtd)
{
struct device *dev = soc_rtd->platform->dev;
struct device *dev_abox = abox_vdma_dev_abox;
struct snd_pcm *pcm = soc_rtd->pcm;
int id = to_platform_device(dev)->id;
struct abox_vdma_info *info = abox_vdma_get_info(id);
int i, ret;
dev_dbg(dev, "%s[%d]\n", __func__, id);
for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) {
struct snd_pcm_substream *substream = pcm->streams[i].substream;
if (!substream)
continue;
if (info->rtd[i].iova == 0)
info->rtd[i].iova = abox_vdma_get_iova(id, i);
if (info->rtd[i].buffer.bytes == 0)
info->rtd[i].buffer.bytes = BUFFER_BYTES_MAX;
if (info->rtd[i].buffer.addr) {
substream->dma_buffer = info->rtd[i].buffer;
} else {
size_t size = info->rtd[i].buffer.bytes;
ret = snd_pcm_lib_preallocate_pages(substream,
SNDRV_DMA_TYPE_DEV, dev_abox,
size, size);
if (ret < 0)
return ret;
}
if (abox_iova_to_phys(dev_abox, info->rtd[i].iova) == 0) {
ret = abox_iommu_map(dev_abox, info->rtd[i].iova,
substream->dma_buffer.addr,
substream->dma_buffer.bytes);
if (ret < 0)
return ret;
info->rtd[i].iommu_mapped = true;
}
info->rtd[i].substream = substream;
}
return 0;
}
static void abox_vdma_platform_free(struct snd_pcm *pcm)
{
struct snd_soc_pcm_runtime *soc_rtd = pcm->private_data;
struct device *dev = soc_rtd->platform->dev;
struct device *dev_abox = abox_vdma_dev_abox;
int id = to_platform_device(dev)->id;
struct abox_vdma_info *info = abox_vdma_get_info(id);
int i;
dev_dbg(dev, "%s[%d]\n", __func__, id);
for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) {
struct snd_pcm_substream *substream = pcm->streams[i].substream;
if (!substream)
continue;
info->rtd[i].substream = NULL;
if (info->rtd[i].iommu_mapped) {
abox_iommu_unmap(dev_abox, info->rtd[i].iova,
substream->dma_buffer.addr,
substream->dma_buffer.bytes);
info->rtd[i].iommu_mapped = false;
}
if (info->rtd[i].buffer.addr) {
substream->dma_buffer.area = NULL;
} else {
snd_pcm_lib_preallocate_free(substream);
}
}
}
struct snd_soc_platform_driver abox_vdma_platform = {
.probe = abox_vdma_platform_probe,
.ops = &abox_vdma_platform_ops,
.pcm_new = abox_vdma_platform_new,
.pcm_free = abox_vdma_platform_free,
};
static irqreturn_t abox_vdma_irq_handler(int ipc_id, void *dev_id,
ABOX_IPC_MSG *msg)
{
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask;
int id = pcmtask_msg->channel_id;
int stream = abox_ipcid_to_stream(ipc_id);
struct abox_vdma_info *info = abox_vdma_get_info(id);
struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, stream);
if (!info || !rtd)
return IRQ_NONE;
switch (pcmtask_msg->msgtype) {
case PCM_PLTDAI_POINTER:
abox_vdma_period_elapsed(info, rtd, pcmtask_msg->param.pointer);
break;
case PCM_PLTDAI_ACK:
rtd->ack_enabled = !!pcmtask_msg->param.trigger;
break;
case PCM_PLTDAI_REGISTER:
{
struct PCMTASK_HARDWARE *hardware;
struct device *dev_abox = dev_id;
struct abox_data *data = dev_get_drvdata(dev_abox);
hardware = &pcmtask_msg->param.hardware;
abox_vdma_register(dev_abox, id, stream,
abox_addr_to_kernel_addr(data, hardware->addr),
abox_addr_to_phys_addr(data, hardware->addr),
hardware);
break;
}
default:
return IRQ_NONE;
}
return IRQ_HANDLED;
}
static struct snd_soc_dai_link abox_vdma_dai_links[VDMA_COUNT_MAX];
static struct snd_soc_card abox_vdma_card = {
.name = "abox_vdma",
.owner = THIS_MODULE,
.dai_link = abox_vdma_dai_links,
.num_links = 0,
};
void abox_vdma_register_work_func(struct work_struct *work)
{
int id;
struct abox_vdma_info *info;
dev_dbg(abox_vdma_dev_abox, "%s\n", __func__);
if (!abox_vdma_card.dev) {
platform_device_register_data(abox_vdma_dev_abox,
"samsung-abox-vdma", -1, NULL, 0);
}
for (info = abox_vdma_list; (info - abox_vdma_list) <
ARRAY_SIZE(abox_vdma_list); info++) {
id = info->id;
if (info->dev == abox_vdma_dev_abox) {
dev_dbg(info->dev, "%s[%d]\n", __func__, id);
platform_device_register_data(info->dev,
"samsung-abox-vdma", id, NULL, 0);
}
}
}
static DECLARE_WORK(abox_vdma_register_work, abox_vdma_register_work_func);
int abox_vdma_register(struct device *dev, int id, int stream,
void *area, phys_addr_t addr,
const struct PCMTASK_HARDWARE *pcm_hardware)
{
struct abox_vdma_info *info = abox_vdma_get_info(id);
struct abox_vdma_rtd *rtd = abox_vdma_get_rtd(info, stream);
struct snd_dma_buffer *buffer = &rtd->buffer;
struct snd_pcm_hardware *hardware = &rtd->hardware;
if (!info || !rtd)
return -EINVAL;
if (info->dev && rtd->iova)
return -EEXIST;
dev_info(dev, "%s(%d, %s, %d, %u)\n", __func__, id, pcm_hardware->name,
stream, pcm_hardware->buffer_bytes_max);
info->id = id;
strncpy(info->name, pcm_hardware->name, sizeof(info->name) - 1);
rtd->iova = pcm_hardware->addr;
buffer->dev.type = SNDRV_DMA_TYPE_DEV;
buffer->dev.dev = dev;
buffer->area = area;
buffer->addr = addr;
buffer->bytes = pcm_hardware->buffer_bytes_max;
hardware->info = SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_BLOCK_TRANSFER
| SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_MMAP_VALID;
hardware->formats = width_range_to_bits(pcm_hardware->width_min,
pcm_hardware->width_max);
hardware->rates = (pcm_hardware->rate_max > 192000) ?
SNDRV_PCM_RATE_KNOT : snd_pcm_rate_range_to_bits(
pcm_hardware->rate_min, pcm_hardware->rate_max);
hardware->rate_min = pcm_hardware->rate_min;
hardware->rate_max = pcm_hardware->rate_max;
hardware->channels_min = pcm_hardware->channels_min;
hardware->channels_max = pcm_hardware->channels_max;
hardware->buffer_bytes_max = pcm_hardware->buffer_bytes_max;
hardware->period_bytes_min = pcm_hardware->period_bytes_min;
hardware->period_bytes_max = pcm_hardware->period_bytes_max;
hardware->periods_min = pcm_hardware->periods_min;
hardware->periods_max = pcm_hardware->periods_max;
abox_vdma_dev_abox = info->dev = dev;
schedule_work(&abox_vdma_register_work);
return 0;
}
static void abox_vdma_register_card_work_func(struct work_struct *work)
{
int i;
dev_dbg(abox_vdma_dev_abox, "%s\n", __func__);
snd_soc_unregister_card(&abox_vdma_card);
for (i = 0; i < abox_vdma_card.num_links; i++) {
struct snd_soc_dai_link *link = &abox_vdma_card.dai_link[i];
if (link->name)
continue;
link->name = link->stream_name =
kasprintf(GFP_KERNEL, "dummy%d", i);
link->stream_name = link->name;
link->cpu_name = "snd-soc-dummy";
link->cpu_dai_name = "snd-soc-dummy-dai";
link->codec_name = "snd-soc-dummy";
link->codec_dai_name = "snd-soc-dummy-dai";
link->no_pcm = 1;
}
snd_soc_register_card(&abox_vdma_card);
}
DECLARE_DELAYED_WORK(abox_vdma_register_card_work,
abox_vdma_register_card_work_func);
static int abox_vdma_add_dai_link(struct device *dev)
{
int id = to_platform_device(dev)->id;
int idx = abox_vdma_get_idx(id);
struct abox_vdma_info *info = abox_vdma_get_info(id);
struct snd_soc_dai_link *link = &abox_vdma_dai_links[idx];
struct abox_vdma_rtd *playback, *capture;
dev_dbg(dev, "%s[%d]\n", __func__, id);
if (idx > ARRAY_SIZE(abox_vdma_dai_links)) {
dev_err(dev, "Too many request\n");
return -ENOMEM;
}
cancel_delayed_work_sync(&abox_vdma_register_card_work);
kfree(link->name);
link->name = link->stream_name = kstrdup(info->name, GFP_KERNEL);
link->cpu_name = "snd-soc-dummy";
link->cpu_dai_name = "snd-soc-dummy-dai";
link->platform_name = dev_name(dev);
link->codec_name = "snd-soc-dummy";
link->codec_dai_name = "snd-soc-dummy-dai";
link->ignore_suspend = 1;
link->ignore_pmdown_time = 1;
link->no_pcm = 0;
playback = &info->rtd[SNDRV_PCM_STREAM_PLAYBACK];
capture = &info->rtd[SNDRV_PCM_STREAM_CAPTURE];
link->playback_only = playback->buffer.area && !capture->buffer.area;
link->capture_only = !playback->buffer.area && capture->buffer.area;
if (abox_vdma_card.num_links <= idx)
abox_vdma_card.num_links = idx + 1;
schedule_delayed_work(&abox_vdma_register_card_work, HZ);
return 0;
}
static int samsung_abox_vdma_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int id = to_platform_device(dev)->id;
struct abox_vdma_info *info = abox_vdma_get_info(id);
int ret = 0;
dev_dbg(dev, "%s[%d]\n", __func__, id);
if (id <= 0) {
abox_vdma_card.dev = &pdev->dev;
} else {
info->dev = dev;
pm_runtime_no_callbacks(dev);
pm_runtime_enable(dev);
devm_snd_soc_register_platform(dev, &abox_vdma_platform);
ret = abox_vdma_add_dai_link(dev);
}
return ret;
}
static int samsung_abox_vdma_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int id = to_platform_device(dev)->id;
dev_dbg(dev, "%s[%d]\n", __func__, id);
return 0;
}
static const struct platform_device_id samsung_abox_vdma_driver_ids[] = {
{
.name = "samsung-abox-vdma",
},
{},
};
MODULE_DEVICE_TABLE(platform, samsung_abox_vdma_driver_ids);
static struct platform_driver samsung_abox_vdma_driver = {
.probe = samsung_abox_vdma_probe,
.remove = samsung_abox_vdma_remove,
.driver = {
.name = "samsung-abox-vdma",
.owner = THIS_MODULE,
},
.id_table = samsung_abox_vdma_driver_ids,
};
module_platform_driver(samsung_abox_vdma_driver);
#ifdef TEST
static unsigned char test_buf[4096];
static void test_work_func(struct work_struct *work)
{
struct abox_vdma_info *info = &abox_vdma_list[2];
static unsigned char i;
int j;
pr_debug("%s: %d\n", __func__, i);
for (j = 0; j < 1024; j++, i++) {
test_buf[i % ARRAY_SIZE(test_buf)] = i;
}
abox_vdma_period_elapsed(info, &info->rtd[0], i % ARRAY_SIZE(test_buf));
abox_vdma_period_elapsed(info, &info->rtd[1], i % ARRAY_SIZE(test_buf));
schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(1000));
}
DECLARE_DELAYED_WORK(test_work, test_work_func);
static const struct PCMTASK_HARDWARE test_hardware = {
.name = "test01",
.addr = 0x12345678,
.width_min = 16,
.width_max = 24,
.rate_min = 48000,
.rate_max = 192000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 4096,
.period_bytes_min = 1024,
.period_bytes_max = 2048,
.periods_min = 2,
.periods_max = 4,
};
#endif
static int __init samsung_abox_vdma_initcall(void)
{
struct abox_data *data = abox_get_abox_data();
struct device *dev_abox;
if (!data)
return 0;
pr_info("%s\n", __func__);
dev_abox = &abox_get_abox_data()->pdev->dev;
abox_register_irq_handler(dev_abox, IPC_PCMPLAYBACK,
abox_vdma_irq_handler, dev_abox);
abox_register_irq_handler(dev_abox, IPC_PCMCAPTURE,
abox_vdma_irq_handler, dev_abox);
#ifdef TEST
abox_vdma_register(dev_abox, 102, SNDRV_PCM_STREAM_PLAYBACK, test_buf,
virt_to_phys(test_buf), &test_hardware);
abox_vdma_register(dev_abox, 102, SNDRV_PCM_STREAM_CAPTURE, test_buf,
virt_to_phys(test_buf), &test_hardware);
schedule_delayed_work(&test_work, HZ * 5);
#endif
return 0;
}
late_initcall(samsung_abox_vdma_initcall);
/* Module information */
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box Virtual DMA Driver");
MODULE_ALIAS("platform:samsung-abox-vdma");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,29 @@
/* sound/soc/samsung/abox/abox_vdma.h
*
* ALSA SoC Audio Layer - Samsung Abox Virtual DMA driver
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_VDMA_H
#define __SND_SOC_ABOX_VDMA_H
/**
* Register abox dump buffer
* @param[in] dev pointer to abox device
* @param[in] id unique buffer id
* @param[in] stream SNDRV_PCM_STREAM_*
* @param[in] area virtual address of the buffer
* @param[in] addr pysical address of the buffer
* @param[in] pcm_hardware hardware information of virtual DMA
* @return error code if any
*/
extern int abox_vdma_register(struct device *dev, int id, int stream,
void *area, phys_addr_t addr,
const struct PCMTASK_HARDWARE *pcm_hardware);
#endif /* __SND_SOC_ABOX_VDMA_H */

View file

@ -0,0 +1,96 @@
/* sound/soc/samsung/abox/abox_vss.c
*
* ALSA SoC Audio Layer - Samsung Abox VSS driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/shm_ipc.h>
#include <linux/io.h>
#include "abox.h"
static unsigned int VSS_MAGIC_OFFSET = 0x500000;
static const int E9810_INT_FREQ = 178000;
static const int E9810_INT_FREQ_SPK = 400000;
static const unsigned int E9810_INT_ID = ABOX_CPU_GEAR_CALL_KERNEL;
int abox_vss_notify_call(struct device *dev, struct abox_data *data, int en)
{
int ret = 0;
dev_info(dev, "%s(%d)\n", __func__, en);
if (en) {
if (IS_ENABLED(CONFIG_SOC_EXYNOS9810)) {
if (data->sound_type == SOUND_TYPE_SPEAKER)
ret = abox_request_int_freq(dev, data,
E9810_INT_ID,
E9810_INT_FREQ_SPK);
else
ret = abox_request_int_freq(dev, data,
E9810_INT_ID,
E9810_INT_FREQ);
}
} else {
if (IS_ENABLED(CONFIG_SOC_EXYNOS9810))
ret = abox_request_int_freq(dev, data, E9810_INT_ID, 0);
}
return ret;
}
static int samsung_abox_vss_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
void __iomem *magic_addr;
dev_dbg(dev, "%s\n", __func__);
of_property_read_u32(np, "magic_offset", &VSS_MAGIC_OFFSET);
dev_info(dev, "magic_offset = 0x%08X\n", VSS_MAGIC_OFFSET);
magic_addr = phys_to_virt(shm_get_vss_base() + VSS_MAGIC_OFFSET);
writel(0, magic_addr);
return 0;
}
static int samsung_abox_vss_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
dev_dbg(dev, "%s\n", __func__);
return 0;
}
static const struct of_device_id samsung_abox_vss_match[] = {
{
.compatible = "samsung,abox-vss",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_abox_vss_match);
static struct platform_driver samsung_abox_vss_driver = {
.probe = samsung_abox_vss_probe,
.remove = samsung_abox_vss_remove,
.driver = {
.name = "samsung-abox-vss",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_abox_vss_match),
},
};
module_platform_driver(samsung_abox_vss_driver);
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box VSS Driver");
MODULE_ALIAS("platform:samsung-abox-vss");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,25 @@
/* sound/soc/samsung/abox/abox_vss.h
*
* ALSA SoC - Samsung Abox VSS
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_ABOX_VSS_H
#define __SND_SOC_ABOX_VSS_H
/**
* Notify Call start or stop
* @param[in] dev pointer to calling device
* @param[in] data abox_data
* @param[in] en if enable 1, if not 0
* @return 0 or error code
*/
extern int abox_vss_notify_call(struct device *dev, struct abox_data *data,
int en);
#endif /* __SND_SOC_ABOX_VSS_H */

View file

@ -0,0 +1,798 @@
/* sound/soc/samsung/abox/abox_wdma.c
*
* ALSA SoC Audio Layer - Samsung Abox WDMA driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
/* #define DEBUG */
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include <linux/firmware.h>
#include <linux/regmap.h>
#include <linux/iommu.h>
#include <linux/delay.h>
#include <linux/memblock.h>
#include <sound/hwdep.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/sounddev_abox.h>
#include "../../../../drivers/iommu/exynos-iommu.h"
#include <sound/samsung/abox.h>
#include "abox_util.h"
#include "abox_gic.h"
#include "abox_dbg.h"
#include "abox_vss.h"
#include "abox_mmapfd.h"
#include "abox.h"
#define USE_FIXED_MEMORY
static const struct snd_pcm_hardware abox_wdma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_BLOCK_TRANSFER
| SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_MMAP_VALID,
.formats = ABOX_WDMA_SAMPLE_FORMATS,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = BUFFER_BYTES_MAX,
.period_bytes_min = PERIOD_BYTES_MIN,
.period_bytes_max = PERIOD_BYTES_MAX,
.periods_min = BUFFER_BYTES_MAX / PERIOD_BYTES_MAX,
.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
};
static int abox_wdma_request_ipc(struct abox_platform_data *data,
ABOX_IPC_MSG *msg, int atomic, int sync)
{
struct device *dev_abox = &data->pdev_abox->dev;
return abox_request_ipc(dev_abox, msg->ipcid, msg, sizeof(*msg),
atomic, sync);
}
static irqreturn_t abox_wdma_irq_handler(int irq, void *dev_id,
ABOX_IPC_MSG *msg)
{
struct platform_device *pdev = dev_id;
struct device *dev = &pdev->dev;
struct abox_platform_data *data = platform_get_drvdata(pdev);
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask;
int id = data->id;
if (id != pcmtask_msg->channel_id)
return IRQ_NONE;
dev_dbg(dev, "%s[%d]: ipcid=%d, msgtype=%d\n", __func__, id,
msg->ipcid, pcmtask_msg->msgtype);
switch (pcmtask_msg->msgtype) {
case PCM_PLTDAI_POINTER:
snd_pcm_period_elapsed(data->substream);
break;
default:
dev_warn(dev, "Unknown pcmtask message: %d\n",
pcmtask_msg->msgtype);
break;
}
return IRQ_HANDLED;
}
static int abox_wdma_enabled(struct abox_platform_data *data)
{
return readl(data->sfr_base + ABOX_WDMA_CTRL) & ABOX_WDMA_ENABLE_MASK;
}
static void abox_wdma_disable_barrier(struct device *dev,
struct abox_platform_data *data)
{
int id = data->id;
struct abox_data *abox_data = data->abox_data;
u64 timeout = local_clock() + ABOX_DMA_TIMEOUT_NS;
while (abox_wdma_enabled(data)) {
if (local_clock() <= timeout) {
cond_resched();
continue;
}
dev_warn_ratelimited(dev, "WDMA disable timeout[%d]\n", id);
abox_dbg_dump_simple(dev, abox_data, "WDMA disable timeout");
break;
}
}
static int abox_wdma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
struct abox_data *abox_data = data->abox_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int id = data->id;
size_t buffer_bytes = PAGE_ALIGN(params_buffer_bytes(params));
int ret;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d]\n", __func__, id);
if (data->buf_type == BUFFER_TYPE_DMA) {
ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
if (ret < 0) {
dev_err(dev, "Memory allocation failed (size:%u)\n",
params_buffer_bytes(params));
return ret;
}
#ifndef USE_FIXED_MEMORY
ret = iommu_map(abox_data->iommu_domain, IOVA_WDMA_BUFFER(id),
runtime->dma_addr, round_up(runtime->dma_bytes,
PAGE_SIZE), 0);
if (ret < 0) {
dev_err(dev, "dma buffer iommu map failed\n");
return ret;
}
#endif
} else if (data->buf_type == BUFFER_TYPE_ION) {
dev_info(dev, "ion_buffer %s bytes(%ld) size(%ld)\n",
__func__,
buffer_bytes, data->ion_buf.size);
data->dmab.bytes = buffer_bytes;
snd_pcm_set_runtime_buffer(substream, &data->dmab);
} else {
dev_err(dev, "buf_type is not defined\n");
}
pcmtask_msg->channel_id = id;
msg.ipcid = IPC_PCMCAPTURE;
msg.task_id = pcmtask_msg->channel_id = id;
pcmtask_msg->msgtype = PCM_SET_BUFFER;
pcmtask_msg->param.setbuff.phyaddr = IOVA_WDMA_BUFFER(id);
pcmtask_msg->param.setbuff.size = params_period_bytes(params);
pcmtask_msg->param.setbuff.count = params_periods(params);
ret = abox_wdma_request_ipc(data, &msg, 0, 0);
if (ret < 0)
return ret;
pcmtask_msg->msgtype = PCM_PLTDAI_HW_PARAMS;
pcmtask_msg->param.hw_params.sample_rate = params_rate(params);
pcmtask_msg->param.hw_params.bit_depth = params_width(params);
pcmtask_msg->param.hw_params.channels = params_channels(params);
ret = abox_wdma_request_ipc(data, &msg, 0, 0);
if (ret < 0)
return ret;
if (params_rate(params) > 48000)
abox_request_cpu_gear_dai(dev, abox_data, rtd->cpu_dai,
abox_data->cpu_gear_min - 1);
dev_info(dev, "%s:Total=%zu PrdSz=%u(%u) #Prds=%u rate=%u, width=%d, channels=%u\n",
snd_pcm_stream_str(substream), runtime->dma_bytes,
params_period_size(params), params_period_bytes(params),
params_periods(params), params_rate(params),
params_width(params), params_channels(params));
return 0;
}
static int abox_wdma_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
int id = data->id;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d]\n", __func__, id);
msg.ipcid = IPC_PCMCAPTURE;
pcmtask_msg->msgtype = PCM_PLTDAI_HW_FREE;
msg.task_id = pcmtask_msg->channel_id = id;
abox_wdma_request_ipc(data, &msg, 0, 0);
if (data->buf_type == BUFFER_TYPE_DMA) {
#ifndef USE_FIXED_MEMORY
iommu_unmap(data->abox_data->iommu_domain, IOVA_WDMA_BUFFER(id),
round_up(substream->runtime->dma_bytes, PAGE_SIZE));
exynos_sysmmu_tlb_invalidate(data->abox_data->iommu_domain,
(dma_addr_t)IOVA_WDMA_BUFFER(id),
round_up(substream->runtime->dma_bytes, PAGE_SIZE));
#endif
}
switch (data->type) {
default:
abox_wdma_disable_barrier(dev, data);
break;
}
if (data->buf_type == BUFFER_TYPE_DMA) {
return snd_pcm_lib_free_pages(substream);
} else if (data->buf_type == BUFFER_TYPE_ION) {
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}
return 0;
}
static int abox_wdma_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
int id = data->id;
int ret;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d]\n", __func__, id);
data->pointer = IOVA_WDMA_BUFFER(id);
switch (data->type) {
case PLATFORM_CALL:
case PLATFORM_VI_SENSING:
break;
default:
ret = abox_try_to_asrc_off(dev, data->abox_data, rtd,
SNDRV_PCM_STREAM_CAPTURE);
if (ret < 0)
dev_warn(dev, "abox_try_to_asrc_off: %d\n", ret);
break;
}
msg.ipcid = IPC_PCMCAPTURE;
pcmtask_msg->msgtype = PCM_PLTDAI_PREPARE;
msg.task_id = pcmtask_msg->channel_id = id;
ret = abox_wdma_request_ipc(data, &msg, 0, 0);
return ret;
}
static int abox_wdma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
int id = data->id;
int ret;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_info(dev, "%s[%d](%d)\n", __func__, id, cmd);
msg.ipcid = IPC_PCMCAPTURE;
pcmtask_msg->msgtype = PCM_PLTDAI_TRIGGER;
msg.task_id = pcmtask_msg->channel_id = id;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
pcmtask_msg->param.trigger = 1;
ret = abox_wdma_request_ipc(data, &msg, 1, 0);
switch (data->type) {
case PLATFORM_REALTIME:
msg.ipcid = IPC_ERAP;
msg.msg.erap.msgtype = REALTIME_START;
ret = abox_wdma_request_ipc(data, &msg, 1, 0);
break;
default:
break;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pcmtask_msg->param.trigger = 0;
ret = abox_wdma_request_ipc(data, &msg, 1, 0);
switch (data->type) {
case PLATFORM_REALTIME:
msg.ipcid = IPC_ERAP;
msg.msg.erap.msgtype = REALTIME_STOP;
ret = abox_wdma_request_ipc(data, &msg, 1, 0);
break;
default:
break;
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static snd_pcm_uframes_t abox_wdma_pointer(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
struct snd_pcm_runtime *runtime = substream->runtime;
int id = data->id;
ssize_t pointer;
u32 status = readl(data->sfr_base + ABOX_WDMA_STATUS);
bool progress = (status & ABOX_WDMA_PROGRESS_MASK) ? true : false;
if (data->pointer >= IOVA_WDMA_BUFFER(id)) {
pointer = data->pointer - IOVA_WDMA_BUFFER(id);
} else if (((data->type == PLATFORM_NORMAL) ||
(data->type == PLATFORM_SYNC)) && progress) {
ssize_t offset, count;
ssize_t buffer_bytes, period_bytes;
buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
period_bytes = snd_pcm_lib_period_bytes(substream);
offset = (((status & ABOX_WDMA_RBUF_OFFSET_MASK) >>
ABOX_WDMA_RBUF_OFFSET_L) << 4);
count = (status & ABOX_WDMA_RBUF_CNT_MASK);
while ((offset % period_bytes) && (buffer_bytes >= 0)) {
buffer_bytes -= period_bytes;
if ((buffer_bytes & offset) == offset)
offset = buffer_bytes;
}
pointer = offset + count;
} else {
pointer = 0;
}
dev_dbg(dev, "%s[%d]: pointer=%08zx\n", __func__, id, pointer);
return bytes_to_frames(runtime, pointer);
}
static int abox_wdma_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
struct abox_data *abox_data = data->abox_data;
int id = data->id;
int ret;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d]\n", __func__, id);
if (data->type == PLATFORM_CALL) {
if (abox_cpu_gear_idle(dev, abox_data, ABOX_CPU_GEAR_CALL_VSS))
abox_request_cpu_gear_sync(dev, abox_data,
ABOX_CPU_GEAR_CALL_KERNEL,
ABOX_CPU_GEAR_MAX);
ret = abox_request_l2c_sync(dev, abox_data, dev, true);
if (ret < 0)
return ret;
ret = abox_vss_notify_call(dev, abox_data, 1);
if (ret < 0)
dev_warn(dev, "call notify failed: %d\n", ret);
}
abox_request_cpu_gear_dai(dev, abox_data, rtd->cpu_dai,
abox_data->cpu_gear_min);
snd_soc_set_runtime_hwparams(substream, &abox_wdma_hardware);
data->substream = substream;
msg.ipcid = IPC_PCMCAPTURE;
pcmtask_msg->msgtype = PCM_PLTDAI_OPEN;
msg.task_id = pcmtask_msg->channel_id = id;
ret = abox_wdma_request_ipc(data, &msg, 0, 0);
return ret;
}
static int abox_wdma_close(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
struct abox_data *abox_data = data->abox_data;
int id = data->id;
int ret;
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
dev_dbg(dev, "%s[%d]\n", __func__, id);
data->substream = NULL;
msg.ipcid = IPC_PCMCAPTURE;
pcmtask_msg->msgtype = PCM_PLTDAI_CLOSE;
msg.task_id = pcmtask_msg->channel_id = id;
ret = abox_wdma_request_ipc(data, &msg, 0, 1);
abox_request_cpu_gear_dai(dev, abox_data, rtd->cpu_dai,
ABOX_CPU_GEAR_MIN);
if (data->type == PLATFORM_CALL) {
abox_request_cpu_gear(dev, abox_data, ABOX_CPU_GEAR_CALL_KERNEL,
ABOX_CPU_GEAR_MIN);
ret = abox_request_l2c(dev, abox_data, dev, false);
if (ret < 0)
return ret;
ret = abox_vss_notify_call(dev, abox_data, 0);
if (ret < 0)
dev_warn(dev, "call notify failed: %d\n", ret);
}
return ret;
}
static int abox_wdma_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
int id = data->id;
struct snd_pcm_runtime *runtime = substream->runtime;
dev_info(dev, "%s[%d]\n", __func__, id);
/* Increased cpu gear for sound camp.
* Only sound camp uses mmap now.
*/
abox_request_cpu_gear_dai(dev, data->abox_data, rtd->cpu_dai,
data->abox_data->cpu_gear_min - 1);
return dma_mmap_writecombine(dev, vma,
runtime->dma_area,
runtime->dma_addr,
runtime->dma_bytes);
}
static int abox_wdma_ack(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
int id = data->id;
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
snd_pcm_uframes_t appl_ofs = appl_ptr % runtime->buffer_size;
ssize_t appl_bytes = frames_to_bytes(runtime, appl_ofs);
ABOX_IPC_MSG msg;
struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask;
if (!data->ack_enabled)
return 0;
dev_dbg(dev, "%s[%d]: %zd\n", __func__, id, appl_bytes);
msg.ipcid = IPC_PCMCAPTURE;
pcmtask_msg->msgtype = PCM_PLTDAI_ACK;
pcmtask_msg->param.pointer = (unsigned int)appl_bytes;
msg.task_id = pcmtask_msg->channel_id = id;
return abox_wdma_request_ipc(data, &msg, 0, 0);
}
static struct snd_pcm_ops abox_wdma_ops = {
.open = abox_wdma_open,
.close = abox_wdma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = abox_wdma_hw_params,
.hw_free = abox_wdma_hw_free,
.prepare = abox_wdma_prepare,
.trigger = abox_wdma_trigger,
.pointer = abox_wdma_pointer,
.mmap = abox_wdma_mmap,
.ack = abox_wdma_ack,
};
static int abox_wdma_fio_ioctl(struct snd_hwdep *hw, struct file *file,
unsigned int cmd, unsigned long _arg);
#ifdef CONFIG_COMPAT
static int abox_wdma_fio_compat_ioctl(struct snd_hwdep *hw,
struct file *file,
unsigned int cmd, unsigned long _arg);
#endif
static int abox_pcm_add_hwdep_dev(struct snd_soc_pcm_runtime *runtime,
struct abox_platform_data *data)
{
struct snd_hwdep *hwdep;
int rc;
char id[] = "ABOX_MMAP_FD_NN";
snprintf(id, sizeof(id), "ABOX_MMAP_FD_%d", SNDRV_PCM_STREAM_CAPTURE);
pr_debug("%s: pcm dev %d\n", __func__, runtime->pcm->device);
rc = snd_hwdep_new(runtime->card->snd_card,
&id[0],
0 + runtime->pcm->device,
&hwdep);
if (!hwdep || rc < 0) {
pr_err("%s: hwdep intf failed to create %s - hwdep\n", __func__,
id);
return rc;
}
hwdep->iface = 0;
hwdep->private_data = data;
hwdep->ops.ioctl = abox_wdma_fio_ioctl;
hwdep->ops.ioctl_compat = abox_wdma_fio_compat_ioctl;
data->hwdep = hwdep;
return 0;
}
static int abox_wdma_new(struct snd_soc_pcm_runtime *runtime)
{
struct snd_pcm *pcm = runtime->pcm;
struct snd_pcm_str *stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
struct snd_pcm_substream *substream = stream->substream;
struct snd_soc_platform *platform = runtime->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
int id = data->id;
size_t buffer_bytes;
int ret;
if (data->buf_type == BUFFER_TYPE_ION) {
buffer_bytes = BUFFER_ION_BYTES_MAX;
data->ion_buf.fd = -2;
ret = abox_ion_alloc(data,
&data->ion_buf,
IOVA_WDMA_BUFFER(id),
buffer_bytes,
0);
if (ret < 0)
return ret;
/* update buffer infomation using ion allocated buffer */
data->dmab.area = data->ion_buf.kva;
data->dmab.addr = data->ion_buf.cookie.ioaddr;
ret = abox_pcm_add_hwdep_dev(runtime, data);
if (ret < 0) {
dev_err(dev, "snd_hwdep_new() failed: %d\n", ret);
return ret;
}
} else {
switch (data->type) {
case PLATFORM_NORMAL:
buffer_bytes = BUFFER_BYTES_MAX;
break;
default:
buffer_bytes = BUFFER_BYTES_MAX >> 2;
break;
}
ret = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV,
runtime->cpu_dai->dev, buffer_bytes, buffer_bytes);
if (ret < 0)
return ret;
#ifdef USE_FIXED_MEMORY
ret = iommu_map(data->abox_data->iommu_domain, IOVA_WDMA_BUFFER(id),
substream->dma_buffer.addr, BUFFER_BYTES_MAX, 0);
#endif
}
return ret;
}
static void abox_wdma_free(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream =
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
struct snd_soc_pcm_runtime *runtime = substream->private_data;
struct snd_soc_platform *platform = runtime->platform;
struct device *dev = platform->dev;
struct abox_platform_data *data = dev_get_drvdata(dev);
int id = data->id;
int ret = 0;
if (data->buf_type == BUFFER_TYPE_ION) {
ret = abox_ion_free(data);
if (ret < 0)
dev_err(dev, "abox_ion_free() failed (%d)\n", ret);
if (data->hwdep) {
snd_device_free(runtime->card->snd_card, data->hwdep);
data->hwdep = NULL;
}
} else {
#ifdef USE_FIXED_MEMORY
iommu_unmap(data->abox_data->iommu_domain, IOVA_WDMA_BUFFER(id),
BUFFER_BYTES_MAX);
#endif
snd_pcm_lib_preallocate_free_for_all(pcm);
}
}
static struct snd_soc_platform_driver abox_wdma = {
.ops = &abox_wdma_ops,
.pcm_new = abox_wdma_new,
.pcm_free = abox_wdma_free,
};
static int abox_wdma_fio_common_ioctl(struct snd_hwdep *hw, struct file *filp,
unsigned int cmd, unsigned long __user *_arg)
{
struct abox_platform_data *data = hw->private_data;
struct device *dev;
struct snd_pcm_mmap_fd mmap_fd;
int ret = 0;
unsigned long arg;
if (!data || (((cmd >> 8) & 0xff) != 'U'))
return -ENOTTY;
dev = &data->pdev->dev;
if (get_user(arg, _arg))
return -EFAULT;
dev_dbg(dev, "%s: ioctl(0x%x)\n", __func__, cmd);
switch (cmd) {
case SNDRV_PCM_IOCTL_MMAP_DATA_FD:
ret = abox_mmap_fd(data, &mmap_fd);
if (ret < 0)
dev_err(dev, "%s MMAP_FD failed: %d\n", __func__, ret);
if (copy_to_user(_arg, &mmap_fd, sizeof(mmap_fd)))
return -EFAULT;
break;
default:
dev_err(dev, "unknown ioctl = 0x%x\n", cmd);
return -EINVAL;
}
return 0;
}
static int abox_wdma_fio_ioctl(struct snd_hwdep *hw, struct file *file,
unsigned int cmd, unsigned long _arg)
{
return abox_wdma_fio_common_ioctl(hw, file,
cmd, (unsigned long __user *)_arg);
}
#ifdef CONFIG_COMPAT
static int abox_wdma_fio_compat_ioctl(struct snd_hwdep *hw,
struct file *file,
unsigned int cmd, unsigned long _arg)
{
return abox_wdma_fio_common_ioctl(hw, file, cmd, compat_ptr(_arg));
}
#endif /* CONFIG_COMPAT */
static int samsung_abox_wdma_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct abox_platform_data *data;
int ret;
const char *type;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
data->pdev = pdev;
data->sfr_base = devm_not_request_and_map(pdev, "sfr", 0, NULL, NULL);
if (IS_ERR(data->sfr_base))
return PTR_ERR(data->sfr_base);
data->pdev_abox = to_platform_device(pdev->dev.parent);
if (!data->pdev_abox) {
dev_err(dev, "Failed to get abox platform device\n");
return -EPROBE_DEFER;
}
data->abox_data = platform_get_drvdata(data->pdev_abox);
abox_register_irq_handler(&data->pdev_abox->dev, IPC_PCMCAPTURE,
abox_wdma_irq_handler, pdev);
ret = of_property_read_u32_index(np, "id", 0, &data->id);
if (ret < 0) {
dev_err(dev, "id property reading fail\n");
return ret;
}
ret = of_property_read_string(np, "type", &type);
if (ret < 0) {
dev_err(dev, "type property reading fail\n");
return ret;
}
if (!strncmp(type, "call", sizeof("call")))
data->type = PLATFORM_CALL;
else if (!strncmp(type, "compress", sizeof("compress")))
data->type = PLATFORM_COMPRESS;
else if (!strncmp(type, "realtime", sizeof("realtime")))
data->type = PLATFORM_REALTIME;
else if (!strncmp(type, "vi-sensing", sizeof("vi-sensing")))
data->type = PLATFORM_VI_SENSING;
else if (!strncmp(type, "sync", sizeof("sync")))
data->type = PLATFORM_SYNC;
else
data->type = PLATFORM_NORMAL;
ret = of_property_read_string(np, "buffer_type", &type);
if (ret < 0)
type = "";
if (!strncmp(type, "ion", sizeof("ion")))
data->buf_type = BUFFER_TYPE_ION;
else if (!strncmp(type, "dma", sizeof("dma")))
data->buf_type = BUFFER_TYPE_DMA;
else
data->buf_type = BUFFER_TYPE_DMA;
abox_register_wdma(data->abox_data->pdev, pdev, data->id);
pm_runtime_no_callbacks(dev);
pm_runtime_enable(dev);
ret = snd_soc_register_platform(&pdev->dev, &abox_wdma);
if (ret < 0)
return ret;
data->hwdep = NULL;
return 0;
}
static int samsung_abox_wdma_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static const struct of_device_id samsung_abox_wdma_match[] = {
{
.compatible = "samsung,abox-wdma",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_abox_wdma_match);
static struct platform_driver samsung_abox_wdma_driver = {
.probe = samsung_abox_wdma_probe,
.remove = samsung_abox_wdma_remove,
.driver = {
.name = "samsung-abox-wdma",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_abox_wdma_match),
},
};
module_platform_driver(samsung_abox_wdma_driver);
/* Module information */
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung ASoC A-Box WDMA Driver");
MODULE_ALIAS("platform:samsung-abox-wdma");
MODULE_LICENSE("GPL");

770
sound/soc/samsung/dp_dma.c Normal file
View file

@ -0,0 +1,770 @@
/*
* dma.c -- ALSA Soc Audio Layer
*
* (c) 2006 Wolfson Microelectronics PLC.
* Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
*
* Copyright 2004-2005 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* 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/slab.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/iommu.h>
#include <linux/dma/dma-pl330.h>
#ifdef CONFIG_SOC_EXYNOS9810
#include <linux/switch.h>
#include <sound/samsung/dp_ado.h>
#endif
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/exynos.h>
#include "dma.h"
#include "dp_dma.h"
#define PERIOD_MIN 4
#define ST_RUNNING (1<<0)
#define ST_OPENED (1<<1)
#define SRAM_END (0x04000000)
#ifdef CONFIG_SOC_EXYNOS8895
#define DP_FIFO (0x11090838)
#elif CONFIG_SOC_EXYNOS9810
#define DP_FIFO (0x11095818)
#endif
#define RX_SRAM_SIZE (0x2000) /* 8 KB */
#define MAX_DEEPBUF_SIZE (0xA000) /* 40 KB */
static struct device *g_debug_dev;
static void __iomem *dp_debug_sfr;
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_U24_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_U20_3LE |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE,
.channels_min = 1,
.channels_max = 8,
.buffer_bytes_max = 1024*1024,
.period_bytes_min = 128,
.period_bytes_max = 256*1024,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
struct s3c_dma_params {
struct s3c2410_dma_client *client; /* stream identifier */
int channel; /* Channel ID */
dma_addr_t dma_addr;
int dma_size; /* Size of the DMA transfer */
#ifdef CONFIG_ARM64
unsigned long ch;
#else
unsigned ch;
#endif
struct samsung_dma_ops *ops;
struct device *sec_dma_dev; /* stream identifier */
char *ch_name;
bool esa_dma;
bool compr_dma;
};
struct runtime_data {
spinlock_t lock;
int state;
unsigned int dma_loaded;
unsigned int dma_period;
dma_addr_t dma_start;
dma_addr_t dma_pos;
dma_addr_t dma_end;
struct s3c_dma_params *params;
struct snd_pcm_hardware hw;
struct displayport_audio_config_data dp_config;
bool cap_dram_used;
dma_addr_t irq_pos;
u32 irq_cnt;
};
#ifdef CONFIG_SND_SAMSUNG_IOMMU
struct dma_iova {
dma_addr_t iova;
dma_addr_t pa;
unsigned char *va;
struct list_head node;
};
static LIST_HEAD(iova_list);
#endif
static void audio_buffdone(void *data);
/* dma_enqueue
*
* place a dma buffer onto the queue for the dma system
* to handle.
*/
static void dma_enqueue(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
dma_addr_t pos = prtd->dma_pos;
unsigned long limit;
struct samsung_dma_prep dma_info;
pr_info("Entered %s\n", __func__);
limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
pr_debug("%s: loaded %d, limit %lu\n",
__func__, prtd->dma_loaded, limit);
dma_info.cap = DMA_CYCLIC;
dma_info.direction = DMA_MEM_TO_DEV;
dma_info.fp = audio_buffdone;
dma_info.fp_param = substream;
dma_info.period = prtd->dma_period;
dma_info.len = prtd->dma_period*limit;
if (prtd->params->esa_dma || samsung_dma_has_infiniteloop()) {
dma_info.buf = prtd->dma_pos;
dma_info.infiniteloop = (unsigned int)limit;
prtd->params->ops->prepare(prtd->params->ch, &dma_info);
} else {
dma_info.infiniteloop = 0;
while (prtd->dma_loaded < limit) {
pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
if ((pos + dma_info.period) > prtd->dma_end) {
dma_info.period = prtd->dma_end - pos;
pr_debug("%s: corrected dma len %ld\n",
__func__, dma_info.period);
}
dma_info.buf = pos;
prtd->params->ops->prepare(prtd->params->ch, &dma_info);
prtd->dma_loaded++;
pos += prtd->dma_period;
if (pos >= prtd->dma_end)
pos = prtd->dma_start;
}
prtd->dma_pos = pos;
}
}
static void audio_buffdone(void *data)
{
struct snd_pcm_substream *substream = data;
struct runtime_data *prtd;
dma_addr_t src, dst, pos;
pr_debug("Entered %s\n", __func__);
if (!substream)
return;
prtd = substream->runtime->private_data;
if (prtd->state & ST_RUNNING) {
prtd->params->ops->getposition(prtd->params->ch, &src, &dst);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
pos = dst - prtd->dma_start;
else
pos = src - prtd->dma_start;
prtd->irq_cnt++;
prtd->irq_pos = pos;
pos /= prtd->dma_period;
pos = prtd->dma_start + (pos * prtd->dma_period);
if (pos >= prtd->dma_end)
pos = prtd->dma_start;
prtd->dma_pos = pos;
snd_pcm_period_elapsed(substream);
if (!prtd->params->esa_dma && !samsung_dma_has_circular()) {
spin_lock(&prtd->lock);
prtd->dma_loaded--;
if (!samsung_dma_has_infiniteloop())
dma_enqueue(substream);
spin_unlock(&prtd->lock);
}
}
}
static int dma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
unsigned long totbytes = params_buffer_bytes(params);
int burst_len;
struct samsung_dma_req req;
struct samsung_dma_config config;
pr_debug("Entered %s\n", __func__);
burst_len = snd_pcm_format_physical_width(params_format(params)) *
params_channels(params) / 32;
/* this may get called several times by oss emulation
* with different params -HW */
if (prtd->params == NULL) {
prtd->params = kzalloc(sizeof(struct s3c_dma_params), GFP_KERNEL);
pr_debug("params %p, client %p, channel %d\n", prtd->params,
prtd->params->client, prtd->params->channel);
prtd->params->ops = samsung_dma_get_ops();
req.cap = DMA_CYCLIC;
req.client = prtd->params->client;
config.direction = DMA_MEM_TO_DEV;
config.width = 4;
config.maxburst = burst_len;
config.fifo = DP_FIFO;
prtd->params->ch = prtd->params->ops->request(prtd->params->channel,
&req, g_debug_dev, "tx");
pr_info("dma_request: ch %d, req %p, dev %p, ch_name [%s]\n",
prtd->params->channel, &req, rtd->cpu_dai->dev,
prtd->params->ch_name);
prtd->params->ops->config(prtd->params->ch, &config);
}
if (params != NULL) {
runtime->access = params_access(params);
runtime->format = params_format(params);
runtime->subformat = params_subformat(params);
runtime->period_size = params_period_bytes(params);
runtime->rate = params_rate(params);
runtime->channels = params_channels(params);
runtime->sample_bits = snd_pcm_format_width(params_format(params));
}
pr_info("[AUDIO] %s: period_size: %lu\n", __func__, runtime->period_size);
pr_info("[AUDIO] %s: rate: %u\n", __func__, runtime->rate);
pr_info("[AUDIO] %s: channels: %u\n", __func__, runtime->channels);
pr_info("[AUDIO] %s: sample_bits: %u\n", __func__, runtime->sample_bits);
pr_info("[AUDIO] %s: burst_len: %u\n", __func__, burst_len);
switch (runtime->rate) {
case 32000:
prtd->dp_config.audio_fs = FS_32KHZ;
break;
case 44100:
prtd->dp_config.audio_fs = FS_44KHZ;
break;
case 48000:
prtd->dp_config.audio_fs = FS_48KHZ;
break;
case 88200:
prtd->dp_config.audio_fs = FS_88KHZ;
break;
case 96000:
prtd->dp_config.audio_fs = FS_96KHZ;
break;
case 176400:
prtd->dp_config.audio_fs = FS_176KHZ;
break;
case 192000:
prtd->dp_config.audio_fs = FS_192KHZ;
break;
default:
pr_debug("[AUDIO] Not supported sample rate: %u\n", runtime->rate);
return -EINVAL;
}
switch (runtime->sample_bits) {
case 16:
prtd->dp_config.audio_bit = AUDIO_16_BIT;
break;
case 20:
prtd->dp_config.audio_bit = AUDIO_20_BIT;
break;
case 24:
prtd->dp_config.audio_bit = AUDIO_24_BIT;
break;
default:
pr_debug("[AUDIO] Not supported sample bits: %u\n", runtime->sample_bits);
return -EINVAL;
}
prtd->dp_config.audio_channel_cnt = runtime->channels;
prtd->dp_config.audio_word_length = burst_len - 1;
if (snd_pcm_format_physical_width(params_format(params)) == 32)
prtd->dp_config.audio_packed_mode = NORMAL_MODE;
else
prtd->dp_config.audio_packed_mode = PACKED_MODE2;
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = totbytes;
spin_lock_irq(&prtd->lock);
prtd->dma_loaded = 0;
prtd->dma_period = params_period_bytes(params);
prtd->dma_start = runtime->dma_addr;
prtd->dma_pos = prtd->dma_start;
prtd->dma_end = prtd->dma_start + totbytes;
prtd->cap_dram_used = runtime->dma_addr < SRAM_END ? false : true;
while ((totbytes / prtd->dma_period) < PERIOD_MIN)
prtd->dma_period >>= 1;
spin_unlock_irq(&prtd->lock);
pr_info("ADMA:%s:DmaAddr=@%pad Total=%d PrdSz=%d(%d) #Prds=%d dma_area=0x%p\n",
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? "P" : "C",
&prtd->dma_start, (u32)runtime->dma_bytes,
params_period_bytes(params),(u32) prtd->dma_period,
params_periods(params), runtime->dma_area);
return 0;
}
static int dma_hw_free(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
pr_debug("Entered %s\n", __func__);
snd_pcm_set_runtime_buffer(substream, NULL);
if (prtd->params) {
prtd->params->ops->flush(prtd->params->ch);
prtd->params->ops->release(prtd->params->ch,
prtd->params->client);
kfree(prtd->params);
prtd->params = NULL;
}
return 0;
}
static int dma_prepare(struct snd_pcm_substream *substream)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_info("Entered %s\n", __func__);
/* return if this is a bufferless transfer e.g.
* codec <--> BT codec or GSM modem -- lg FIXME */
if (!prtd->params)
return 0;
/* flush the DMA channel */
prtd->params->ops->flush(prtd->params->ch);
prtd->dma_loaded = 0;
prtd->dma_pos = prtd->dma_start;
prtd->irq_pos = prtd->dma_start;
prtd->irq_cnt = 0;
/* enqueue dma buffers */
dma_enqueue(substream);
return ret;
}
static int dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct runtime_data *prtd = substream->runtime->private_data;
int ret = 0;
pr_info("[DP Audio] Entered %s ++\n", __func__);
spin_lock(&prtd->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
prtd->state |= ST_RUNNING;
pr_info("%s: Start DP DMA request initial status = 0x%08x\n",
__func__, readl(dp_debug_sfr + 0x580C));
prtd->dp_config.audio_enable = AUDIO_ENABLE;
displayport_audio_config(&prtd->dp_config);
pr_info("%s: Start DP DMA request Low status = 0x%08x\n",
__func__, readl(dp_debug_sfr + 0x580C));
prtd->params->ops->trigger(prtd->params->ch);
pr_info("%s: Start DP DMA request DMA On status = 0x%08x\n",
__func__, readl(dp_debug_sfr + 0x580C));
prtd->dp_config.audio_enable = AUDIO_DMA_REQ_HIGH;
displayport_audio_config(&prtd->dp_config);
pr_info("%s: Start DP DMA request DP Audio En status = 0x%08x\n",
__func__, readl(dp_debug_sfr + 0x580C));
break;
case SNDRV_PCM_TRIGGER_STOP:
prtd->state &= ~ST_RUNNING;
prtd->dp_config.audio_enable = AUDIO_WAIT_BUF_FULL;
pr_info("%s: Stop DP DMA request WAIT BUF FULL status = 0x%08x\n",
__func__, readl(dp_debug_sfr + 0x580C));
displayport_audio_config(&prtd->dp_config);
pr_info("%s: Stop DP DMA request DMA Off status = 0x%08x\n",
__func__, readl(dp_debug_sfr + 0x580C));
prtd->params->ops->stop(prtd->params->ch);
pr_info("%s: Stop DP DMA request DP Audio Dis status = 0x%08x\n",
__func__, readl(dp_debug_sfr + 0x580C));
prtd->dp_config.audio_enable = AUDIO_DISABLE;
displayport_audio_config(&prtd->dp_config);
pr_info("%s: Stop DP DMA request End status = 0x%08x\n",
__func__, readl(dp_debug_sfr + 0x580C));
break;
default:
ret = -EINVAL;
break;
}
spin_unlock(&prtd->lock);
pr_info("[DP Audio] Entered %s --\n", __func__);
return ret;
}
static snd_pcm_uframes_t dma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
unsigned long res;
pr_debug("Entered %s\n", __func__);
res = prtd->dma_pos - prtd->dma_start;
pr_debug("Pointer offset: %lu\n", res);
/* we seem to be getting the odd error from the pcm library due
* to out-of-bounds pointers. this is maybe due to the dma engine
* not having loaded the new values for the channel before being
* called... (todo - fix )
*/
if (res >= snd_pcm_lib_buffer_bytes(substream)) {
if (res == snd_pcm_lib_buffer_bytes(substream))
res = 0;
}
return bytes_to_frames(substream->runtime, res);
}
static int dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd;
pr_debug("Entered %s\n", __func__);
prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
spin_lock_init(&prtd->lock);
memcpy(&prtd->hw, &dma_hardware, sizeof(struct snd_pcm_hardware));
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
runtime->private_data = prtd;
snd_soc_set_runtime_hwparams(substream, &prtd->hw);
pr_info("%s: prtd = %p\n", __func__, prtd);
return 0;
}
static int dma_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct runtime_data *prtd = runtime->private_data;
pr_debug("Entered %s\n", __func__);
if (!prtd) {
pr_debug("dma_close called with prtd == NULL\n");
return 0;
}
pr_info("%s: prtd = %p, irq_cnt %u\n",
__func__, prtd, prtd->irq_cnt);
#if 0
if (prtd->irq_cnt == 0) {
pr_info("=== DisplayPort SFR DUMP ===\n");
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr, 0x30, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x100, 0x10, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x200, 0x8, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x1000, 0x204, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x2000, 0x68, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x3000, 0x24, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x3100, 0x14, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x4000, 0x68, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x4400, 0x94, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x4500, 0x8, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x5000, 0x108, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x5400, 0x70, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x5800, 0xF0, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x5900, 0x4, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x5C00, 0xC0, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x5d00, 0x84, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x6000, 0x108, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x6400, 0x70, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x6800, 0xF0, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x6900, 0x4, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x6C00, 0xC0, false);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
dp_debug_sfr + 0x6D00, 0x84, false);
}
#endif
kfree(prtd);
return 0;
}
static int dma_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
dma_addr_t dma_pa = runtime->dma_addr;
#ifdef CONFIG_SND_SAMSUNG_IOMMU
struct dma_iova *di;
#endif
pr_debug("Entered %s\n", __func__);
#ifdef CONFIG_SND_SAMSUNG_IOMMU
list_for_each_entry(di, &iova_list, node) {
if (di->iova == runtime->dma_addr)
dma_pa = di->pa;
}
#endif
return dma_mmap_writecombine(substream->pcm->card->dev, vma,
runtime->dma_area, dma_pa,
runtime->dma_bytes);
}
static struct snd_pcm_ops pcm_dma_ops = {
.open = dma_open,
.close = dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dma_hw_params,
.hw_free = dma_hw_free,
.prepare = dma_prepare,
.trigger = dma_trigger,
.pointer = dma_pointer,
.mmap = dma_mmap,
};
static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
size_t size = dma_hardware.buffer_bytes_max;
pr_debug("Entered %s\n", __func__);
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = dma_alloc_coherent(pcm->card->dev, size,
&buf->addr, GFP_KERNEL);
if (!buf->area)
return -ENOMEM;
buf->bytes = size;
return 0;
}
static void dma_free_dma_buffers(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
struct runtime_data *prtd;
int stream;
pr_debug("Entered %s\n", __func__);
iounmap(dp_debug_sfr);
for (stream = 0; stream < 2; stream++) {
substream = pcm->streams[stream].substream;
if (!substream)
continue;
buf = &substream->dma_buffer;
if (!buf->area)
continue;
prtd = substream->runtime->private_data;
if (prtd->cap_dram_used) {
dma_free_coherent(pcm->card->dev, buf->bytes,
buf->area, buf->addr);
} else {
iounmap(buf->area);
}
buf->area = NULL;
}
}
#ifdef CONFIG_ZONE_DMA
static u64 dma_mask = DMA_BIT_MASK(32);
#else
static u64 dma_mask = DMA_BIT_MASK(36);
#endif
static int dma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret = 0;
pr_debug("Entered %s\n", __func__);
if (!card->dev->dma_mask)
card->dev->dma_mask = &dma_mask;
if (!card->dev->coherent_dma_mask)
#ifdef CONFIG_ZONE_DMA
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
#else
card->dev->coherent_dma_mask = DMA_BIT_MASK(36);
#endif
dp_debug_sfr = ioremap(0x11090000, 0x7000);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
ret = preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE);
if (ret)
goto out;
}
out:
return ret;
}
static struct snd_soc_platform_driver samsung_display_adma = {
.ops = &pcm_dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
#ifdef CONFIG_SOC_EXYNOS9810
struct switch_dev dp_ado_switch;
void dp_ado_switch_set_state(int state)
{
switch_set_state(&dp_ado_switch, state);
}
#endif
static int samsung_display_adma_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct runtime_data *data;
#ifdef CONFIG_SOC_EXYNOS9810
int ret;
#endif
g_debug_dev = dev;
data = devm_kzalloc(dev, sizeof(struct runtime_data), GFP_KERNEL);
if (!data) {
dev_err(dev, "Failed to allocate memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, data);
spin_lock_init(&data->lock);
#ifdef CONFIG_SOC_EXYNOS9810
dp_ado_switch.name = "ch_hdmi_audio";
ret = switch_dev_register(&dp_ado_switch);
if (ret) {
dev_err(dev, "Failed to register dp audio switch\n");
kfree(data);
return -EINVAL;
}
#endif
return snd_soc_register_platform(&pdev->dev, &samsung_display_adma);
}
static int samsung_display_adma_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static const struct of_device_id samsung_display_adma_match[] = {
{
.compatible = "samsung,displayport-adma",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_display_adma_match);
static struct platform_driver samsung_display_adma_driver = {
.probe = samsung_display_adma_probe,
.remove = samsung_display_adma_remove,
.driver = {
.name = "samsung-displayport-adma",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_display_adma_match),
},
};
module_platform_driver(samsung_display_adma_driver);
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("Samsung Display Port Audio DMA Driver");
MODULE_ALIAS("platform:samsung-display-adma");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,60 @@
#ifndef SAMSUNG_DP_DMA_H
#define SAMSUNG_DP_DMA_H
#define AUDIO_DISABLE 0
#define AUDIO_ENABLE 1
#define AUDIO_WAIT_BUF_FULL 2
#define AUDIO_DMA_REQ_HIGH 3
enum audio_sampling_frequency {
FS_32KHZ = 0,
FS_44KHZ = 1,
FS_48KHZ = 2,
FS_88KHZ = 3,
FS_96KHZ = 4,
FS_176KHZ = 5,
FS_192KHZ = 6,
};
enum audio_bit_per_channel {
AUDIO_16_BIT = 0,
AUDIO_20_BIT,
AUDIO_24_BIT,
};
enum audio_16bit_dma_mode {
NORMAL_MODE = 0,
PACKED_MODE = 1,
PACKED_MODE2 = 2,
};
enum audio_dma_word_length {
WORD_LENGTH_1 = 0,
WORD_LENGTH_2,
WORD_LENGTH_3,
WORD_LENGTH_4,
WORD_LENGTH_5,
WORD_LENGTH_6,
WORD_LENGTH_7,
WORD_LENGTH_8,
};
struct displayport_audio_config_data {
u32 audio_enable;
u32 audio_channel_cnt;
enum audio_sampling_frequency audio_fs;
enum audio_bit_per_channel audio_bit;
enum audio_16bit_dma_mode audio_packed_mode;
enum audio_dma_word_length audio_word_length;
};
#if defined(CONFIG_EXYNOS_MIPI_DISPLAYPORT) || defined (CONFIG_EXYNOS_DISPLAYPORT)
extern int displayport_audio_config(struct displayport_audio_config_data *audio_config_data);
#else
int displayport_audio_config(struct displayport_audio_config_data *audio_config_data)
{
return -ENODEV;
}
#endif
#endif /* SAMSUNG_DP_DMA_H */

View file

@ -0,0 +1,72 @@
/*
* ALSA SoC CP dummy cpu dai driver
*
* This driver provides one dummy dai.
*
* Copyright (c) 2014 Samsung Electronics
* http://www.samsungsemi.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/of.h>
#include <sound/soc.h>
static struct snd_soc_dai_driver dummy_cpu_dai_drv = {
.name = "dummy_cpu_dai",
.playback = {
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_U24_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_U20_3LE |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE,
},
};
static const struct snd_soc_component_driver dummy_cpudai_component = {
.name = "dummy-cpudai",
};
static int dummy_cpu_probe(struct platform_device *pdev)
{
snd_soc_register_component(&pdev->dev, &dummy_cpudai_component,
&dummy_cpu_dai_drv, 1);
return 0;
}
static int dummy_cpu_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
return 0;
}
static const struct of_device_id dummy_cpu_of_match[] = {
{ .compatible = "samsung,dummy-cpu", },
{},
};
MODULE_DEVICE_TABLE(of, dummy_cpu_of_match);
static struct platform_driver dummy_cpu_driver = {
.probe = dummy_cpu_probe,
.remove = dummy_cpu_remove,
.driver = {
.name = "dummy_cpu_dai",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(dummy_cpu_of_match),
.suppress_bind_attrs = true,
},
};
module_platform_driver(dummy_cpu_driver);
MODULE_AUTHOR("Hyunwoong Kim <khw0178.kim@samsung.com>");
MODULE_DESCRIPTION("Dummy dai driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,886 @@
/*
* Driver for Madera CODECs on Exynos8895
*
* Copyright 2013 Wolfson Microelectronics
* Copyright 2016 Cirrus Logic
*
* 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/debugfs.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/mfd/madera/core.h>
#include <linux/extcon/extcon-madera.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <soc/samsung/exynos-pmu.h>
#include <sound/samsung/abox.h>
#include "../codecs/madera.h"
#define EXYNOS_PMU_PMU_DEBUG_OFFSET (0x0A00)
#define MADERA_DAI_OFFSET (13)
/* Used for debugging and test automation */
static u32 voice_trigger_count;
struct clk_conf {
int id;
int source;
int rate;
};
struct exynos8895_drvdata {
struct device *dev;
struct clk_conf fll1_refclk;
struct clk_conf sysclk;
struct clk_conf dspclk;
struct notifier_block nb;
};
static struct exynos8895_drvdata exynos8895_drvdata;
static const struct snd_soc_ops rdma_ops = {
};
static const struct snd_soc_ops wdma_ops = {
};
static const struct snd_soc_ops uaif_ops = {
};
static int dsif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int tx_slot[] = {0, 1};
/* bclk ratio 64 for DSD64, 128 for DSD128 */
snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
/* channel map 0 1 if left is first, 1 0 if right is first */
snd_soc_dai_set_channel_map(cpu_dai, 2, tx_slot, 0, NULL);
return 0;
}
static const struct snd_soc_ops dsif_ops = {
.hw_params = dsif_hw_params,
};
static int exynos8895_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct exynos8895_drvdata *drvdata = card->drvdata;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
struct snd_soc_codec *codec;
int ret;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[MADERA_DAI_OFFSET].name);
codec_dai = rtd->codec_dai;
codec = codec_dai->codec;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
break;
ret = snd_soc_codec_set_pll(codec, drvdata->fll1_refclk.id,
drvdata->fll1_refclk.source,
drvdata->fll1_refclk.rate,
drvdata->sysclk.rate);
if (ret < 0)
dev_err(drvdata->dev, "Failed to start FLL: %d\n", ret);
break;
default:
break;
}
return 0;
}
static int exynos8895_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct exynos8895_drvdata *drvdata = card->drvdata;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
struct snd_soc_codec *codec;
int ret;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[MADERA_DAI_OFFSET].name);
codec_dai = rtd->codec_dai;
codec = codec_dai->codec;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_codec_set_pll(codec, drvdata->fll1_refclk.id,
0, 0, 0);
if (ret < 0) {
dev_err(drvdata->dev, "Failed to stop FLL: %d\n", ret);
return ret;
}
break;
default:
break;
}
return 0;
}
static int exynos8895_madera_notify(struct notifier_block *nb,
unsigned long event, void *data)
{
const struct madera_hpdet_notify_data *hp_inf;
const struct madera_micdet_notify_data *md_inf;
const struct madera_voice_trigger_info *vt_inf;
const struct exynos8895_drvdata *drvdata =
container_of(nb, struct exynos8895_drvdata, nb);
switch (event) {
case MADERA_NOTIFY_VOICE_TRIGGER:
vt_inf = data;
dev_info(drvdata->dev, "Voice Triggered (core_num=%d)\n",
vt_inf->core_num);
++voice_trigger_count;
break;
case MADERA_NOTIFY_HPDET:
hp_inf = data;
dev_info(drvdata->dev, "HPDET val=%d.%02d ohms\n",
hp_inf->impedance_x100 / 100,
hp_inf->impedance_x100 % 100);
break;
case MADERA_NOTIFY_MICDET:
md_inf = data;
dev_info(drvdata->dev, "MICDET present=%c val=%d.%02d ohms\n",
md_inf->present ? 'Y' : 'N',
md_inf->impedance_x100 / 100,
md_inf->impedance_x100 % 100);
break;
default:
dev_info(drvdata->dev, "notifier event=0x%lx data=0x%p\n",
event, data);
break;
}
return NOTIFY_DONE;
}
#ifdef CONFIG_DEBUG_FS
static void exynos8895_init_debugfs(struct snd_soc_card *card)
{
struct dentry *root;
if (!card->debugfs_card_root) {
dev_warn(card->dev, "No card debugfs root\n");
return;
}
root = debugfs_create_dir("test-automation", card->debugfs_card_root);
if (!root) {
dev_warn(card->dev, "Failed to create debugfs dir\n");
return;
}
debugfs_create_u32("voice_trigger_count", S_IRUGO, root,
&voice_trigger_count);
}
#else
static void arndale_init_debugfs(struct snd_soc_card *card)
{
}
#endif
static int exynos8895_late_probe(struct snd_soc_card *card)
{
struct exynos8895_drvdata *drvdata = card->drvdata;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *aif1_dai;
struct snd_soc_codec *codec;
struct snd_soc_component *cpu;
int ret;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
aif1_dai = rtd->cpu_dai;
cpu = aif1_dai->component;
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[MADERA_DAI_OFFSET].name);
aif1_dai = rtd->codec_dai;
codec = aif1_dai->codec;
ret = snd_soc_codec_set_sysclk(codec, drvdata->sysclk.id,
drvdata->sysclk.source,
drvdata->sysclk.rate,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(drvdata->dev, "Failed to set SYSCLK: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_sysclk(codec, drvdata->dspclk.id,
drvdata->dspclk.source,
drvdata->dspclk.rate,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(drvdata->dev, "Failed to set SYSCLK: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif1_dai, drvdata->sysclk.id, 0, 0);
if (ret != 0) {
dev_err(drvdata->dev, "Failed to set AIF1 clock: %d\n", ret);
return ret;
}
snd_soc_dapm_ignore_suspend(&card->dapm, "VOUTPUT");
snd_soc_dapm_ignore_suspend(&card->dapm, "VINPUT1");
snd_soc_dapm_ignore_suspend(&card->dapm, "VINPUT2");
snd_soc_dapm_ignore_suspend(&card->dapm, "VOUTPUTCALL");
snd_soc_dapm_ignore_suspend(&card->dapm, "VINPUTCALL");
snd_soc_dapm_ignore_suspend(&card->dapm, "HEADSETMIC");
snd_soc_dapm_ignore_suspend(&card->dapm, "RECEIVER");
snd_soc_dapm_ignore_suspend(&card->dapm, "HEADPHONE");
snd_soc_dapm_ignore_suspend(&card->dapm, "SPEAKER");
snd_soc_dapm_ignore_suspend(&card->dapm, "BLUETOOTH MIC");
snd_soc_dapm_ignore_suspend(&card->dapm, "BLUETOOTH SPK");
snd_soc_dapm_ignore_suspend(&card->dapm, "DMIC1");
snd_soc_dapm_ignore_suspend(&card->dapm, "DMIC2");
snd_soc_dapm_ignore_suspend(&card->dapm, "VTS Virtual Output");
snd_soc_dapm_sync(&card->dapm);
snd_soc_dapm_ignore_suspend(snd_soc_codec_get_dapm(codec), "AIF1 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_codec_get_dapm(codec), "AIF1 Capture");
snd_soc_dapm_ignore_suspend(snd_soc_codec_get_dapm(codec), "AUXPDM1");
snd_soc_dapm_sync(snd_soc_codec_get_dapm(codec));
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA0 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA1 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA2 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA3 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA4 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA5 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA6 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX RDMA7 Playback");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA0 Capture");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA1 Capture");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA2 Capture");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA3 Capture");
snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(cpu), "ABOX WDMA4 Capture");
snd_soc_dapm_sync(snd_soc_component_get_dapm(cpu));
exynos8895_init_debugfs(card);
drvdata->nb.notifier_call = exynos8895_madera_notify;
madera_register_notifier(codec, &drvdata->nb);
return 0;
}
static struct snd_soc_dai_link exynos8895_dai[] = {
{
.name = "RDMA0",
.stream_name = "RDMA0",
.cpu_dai_name = "RDMA0",
.platform_name = "13e51000.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA1",
.stream_name = "RDMA1",
.cpu_dai_name = "RDMA1",
.platform_name = "13e51100.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA2",
.stream_name = "RDMA2",
.cpu_dai_name = "RDMA2",
.platform_name = "13e51200.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA3",
.stream_name = "RDMA3",
.cpu_dai_name = "RDMA3",
.platform_name = "13e51300.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA4",
.stream_name = "RDMA4",
.cpu_dai_name = "RDMA4",
.platform_name = "13e51400.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA5",
.stream_name = "RDMA5",
.cpu_dai_name = "RDMA5",
.platform_name = "13e51500.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA6",
.stream_name = "RDMA6",
.cpu_dai_name = "RDMA6",
.platform_name = "13e51600.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA7",
.stream_name = "RDMA7",
.cpu_dai_name = "RDMA7",
.platform_name = "13e51700.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "WDMA0",
.stream_name = "WDMA0",
.cpu_dai_name = "WDMA0",
.platform_name = "13e52000.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "WDMA1",
.stream_name = "WDMA1",
.cpu_dai_name = "WDMA1",
.platform_name = "13e52100.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "WDMA2",
.stream_name = "WDMA2",
.cpu_dai_name = "WDMA2",
.platform_name = "13e52200.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "WDMA3",
.stream_name = "WDMA3",
.cpu_dai_name = "WDMA3",
.platform_name = "13e52300.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "WDMA4",
.stream_name = "WDMA4",
.cpu_dai_name = "WDMA4",
.platform_name = "13e52400.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "UAIF0",
.stream_name = "UAIF0",
.cpu_dai_name = "UAIF0",
.platform_name = "snd-soc-dummy",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "UAIF1",
.stream_name = "UAIF1",
.cpu_dai_name = "UAIF1",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "UAIF2",
.stream_name = "UAIF2",
.cpu_dai_name = "UAIF2",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "UAIF3",
.stream_name = "UAIF3",
.cpu_dai_name = "UAIF3",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "UAIF4",
.stream_name = "UAIF4",
.cpu_dai_name = "UAIF4",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "DSIF",
.stream_name = "DSIF",
.cpu_dai_name = "DSIF",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_PDM | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &dsif_ops,
.dpcm_playback = 1,
},
{
.name = "ABOX Internal",
.stream_name = "ABOX Internal",
.cpu_dai_name = "Internal",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_PDM | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "VTS-Trigger",
.stream_name = "VTS-Trigger",
.cpu_dai_name = "vts-tri",
.platform_name = "vts_dma0",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.capture_only = true,
},
{
.name = "VTS-Record",
.stream_name = "VTS-Record",
.cpu_dai_name = "vts-rec",
.platform_name = "vts_dma1",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.capture_only = true,
},
{
.name = "DP Audio",
.stream_name = "DP Audio",
.cpu_dai_name = "snd-soc-dummy-dai",
.platform_name = "15a40000.displayport_adma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
},
};
static const char * const vts_output_texts[] = {
"None",
"DMIC1",
};
static const struct soc_enum vts_output_enum =
SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(vts_output_texts),
vts_output_texts);
static const struct snd_kcontrol_new vts_output_mux[] = {
SOC_DAPM_ENUM("VTS Virtual Output Mux", vts_output_enum),
};
static const struct snd_kcontrol_new exynos8895_controls[] = {
SOC_DAPM_PIN_SWITCH("DMIC1"),
SOC_DAPM_PIN_SWITCH("DMIC2"),
};
static struct snd_soc_dapm_widget exynos8895_widgets[] = {
SND_SOC_DAPM_OUTPUT("VOUTPUT"),
SND_SOC_DAPM_INPUT("VINPUT1"),
SND_SOC_DAPM_INPUT("VINPUT2"),
SND_SOC_DAPM_OUTPUT("VOUTPUTCALL"),
SND_SOC_DAPM_INPUT("VINPUTCALL"),
SND_SOC_DAPM_MIC("DMIC1", NULL),
SND_SOC_DAPM_MIC("DMIC2", NULL),
SND_SOC_DAPM_MIC("HEADSETMIC", NULL),
SND_SOC_DAPM_SPK("RECEIVER", NULL),
SND_SOC_DAPM_HP("HEADPHONE", NULL),
SND_SOC_DAPM_SPK("SPEAKER", NULL),
SND_SOC_DAPM_MIC("BLUETOOTH MIC", NULL),
SND_SOC_DAPM_SPK("BLUETOOTH SPK", NULL),
SND_SOC_DAPM_OUTPUT("VTS Virtual Output"),
SND_SOC_DAPM_MUX("VTS Virtual Output Mux", SND_SOC_NOPM, 0, 0,
&vts_output_mux[0]),
};
static struct snd_soc_codec_conf codec_conf[] = {
{.name_prefix = "ABOX", },
{.name_prefix = "ABOX", },
{.name_prefix = "ABOX", },
{.name_prefix = "ABOX", },
{.name_prefix = "ABOX", },
{.name_prefix = "ABOX", },
{.name_prefix = "ABOX", },
{.name_prefix = "VTS", },
};
static struct snd_soc_aux_dev aux_dev[] = {
{
.name = "EFFECT",
},
};
static struct snd_soc_card exynos8895 = {
.name = "Exynos8895-Madera",
.owner = THIS_MODULE,
.dai_link = exynos8895_dai,
.num_links = ARRAY_SIZE(exynos8895_dai),
.late_probe = exynos8895_late_probe,
.controls = exynos8895_controls,
.num_controls = ARRAY_SIZE(exynos8895_controls),
.dapm_widgets = exynos8895_widgets,
.num_dapm_widgets = ARRAY_SIZE(exynos8895_widgets),
.set_bias_level = exynos8895_set_bias_level,
.set_bias_level_post = exynos8895_set_bias_level_post,
.drvdata = (void *)&exynos8895_drvdata,
.codec_conf = codec_conf,
.num_configs = ARRAY_SIZE(codec_conf),
.aux_dev = aux_dev,
.num_aux_devs = ARRAY_SIZE(aux_dev),
};
static int exynos8895_read_clk_conf(struct device_node *np,
const char * const prop,
struct clk_conf *conf)
{
u32 tmp;
int ret;
ret = of_property_read_u32_index(np, prop, 0, &tmp);
if (ret)
return ret;
conf->id = tmp;
ret = of_property_read_u32_index(np, prop, 1, &tmp);
if (ret)
return ret;
conf->source = tmp;
conf->source--;
ret = of_property_read_u32_index(np, prop, 2, &tmp);
if (ret)
return ret;
conf->rate = tmp;
return 0;
}
static int exynos8895_read_dai(struct device_node *np, const char * const prop,
struct device_node **dai, const char **name)
{
int ret = 0;
np = of_get_child_by_name(np, prop);
if (!np)
return -ENOENT;
*dai = of_parse_phandle(np, "sound-dai", 0);
if (!*dai) {
ret = -ENODEV;
goto out;
}
if (*name == NULL) {
/* Ignoring the return as we don't register DAIs to the platform */
ret = snd_soc_of_get_dai_name(np, name);
if (ret && !*name)
return ret;
}
out:
of_node_put(np);
return ret;
}
static struct clk *xclkout;
static void control_xclkout(bool on)
{
if (on) {
clk_enable(xclkout);
} else {
clk_disable(xclkout);
}
}
static int exynos8895_audio_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &exynos8895;
struct exynos8895_drvdata *drvdata = card->drvdata;
struct device_node *np = pdev->dev.of_node;
struct device_node *dai;
int nlink = 0, n;
int ret;
card->dev = &pdev->dev;
drvdata->dev = card->dev;
xclkout = devm_clk_get(&pdev->dev, "xclkout");
if (IS_ERR(xclkout)) {
dev_err(&pdev->dev, "xclkout get failed\n");
return PTR_ERR(xclkout);
}
ret = clk_prepare(xclkout);
if (ret < 0) {
dev_err(&pdev->dev, "xclkout prepare failed\n");
return ret;
}
ret = exynos8895_read_clk_conf(np, "cirrus,sysclk", &drvdata->sysclk);
if (ret) {
dev_err(card->dev, "Failed to parse sysclk: %d\n", ret);
return ret;
}
ret = exynos8895_read_clk_conf(np, "cirrus,dspclk", &drvdata->dspclk);
if (ret) {
dev_err(card->dev, "Failed to parse dspclk: %d\n", ret);
return ret;
}
ret = exynos8895_read_clk_conf(np, "cirrus,fll1-refclk",
&drvdata->fll1_refclk);
if (ret) {
dev_err(card->dev, "Failed to parse fll1-refclk: %d\n", ret);
return ret;
}
for_each_child_of_node(np, dai) {
ret = exynos8895_read_dai(dai, "cpu",
&exynos8895_dai[nlink].cpu_of_node,
&exynos8895_dai[nlink].cpu_dai_name);
if (ret) {
dev_warn(card->dev,
"Failed to parse cpu DAI for %s: %d\n",
dai->name, ret);
return ret;
}
if (!exynos8895_dai[nlink].platform_name) {
ret = exynos8895_read_dai(dai, "platform",
&exynos8895_dai[nlink].platform_of_node,
&exynos8895_dai[nlink].platform_name);
}
if (!exynos8895_dai[nlink].codec_name) {
ret = exynos8895_read_dai(dai, "codec",
&exynos8895_dai[nlink].codec_of_node,
&exynos8895_dai[nlink].codec_dai_name);
if (ret) {
dev_warn(card->dev,
"Failed to parse codec DAI for %s: %d\n",
dai->name, ret);
return ret;
}
}
if (++nlink == card->num_links)
break;
}
if (!nlink) {
dev_warn(card->dev, "No DAIs specified\n");
}
if (of_property_read_bool(np, "samsung,routing")) {
ret = snd_soc_of_parse_audio_routing(card, "samsung,routing");
if (ret)
return ret;
}
for (n = 0; n < ARRAY_SIZE(codec_conf); n++) {
codec_conf[n].of_node = of_parse_phandle(np, "samsung,codec", n);
if (!codec_conf[n].of_node) {
dev_err(&pdev->dev,
"Property 'samsung,codec' missing\n");
return -EINVAL;
}
}
for (n = 0; n < ARRAY_SIZE(aux_dev); n++) {
aux_dev[n].codec_of_node = of_parse_phandle(np, "samsung,aux", n);
if (!aux_dev[n].codec_of_node) {
dev_err(&pdev->dev,
"Property 'samsung,aux' missing\n");
return -EINVAL;
}
}
control_xclkout(true);
ret = devm_snd_soc_register_card(card->dev, card);
if (ret)
dev_err(card->dev, "snd_soc_register_card() failed:%d\n", ret);
return ret;
}
#ifdef CONFIG_OF
static const struct of_device_id exynos8895_of_match[] = {
{ .compatible = "samsung,exynos8895-madera", },
{},
};
MODULE_DEVICE_TABLE(of, exynos8895_of_match);
#endif /* CONFIG_OF */
static struct platform_driver exynos8895_audio_driver = {
.driver = {
.name = "exynos8895-madera",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(exynos8895_of_match),
},
.probe = exynos8895_audio_probe,
};
module_platform_driver(exynos8895_audio_driver);
MODULE_DESCRIPTION("ALSA SoC Exynos8895 Madera Driver");
MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:exynos8895-madera");

View file

@ -0,0 +1,484 @@
/*
* exynos8895_sound.c
*
* 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/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/samsung/abox.h>
static const struct snd_soc_ops rdma_ops = {
};
static const struct snd_soc_ops wdma_ops = {
};
static const struct snd_soc_ops uaif_ops = {
};
static int dsif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int tx_slot[] = {0, 1};
/* bclk ratio 64 for DSD64, 128 for DSD128 */
snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
/* channel map 0 1 if left is first, 1 0 if right is first */
snd_soc_dai_set_channel_map(cpu_dai, 2, tx_slot, 0, NULL);
return 0;
}
static const struct snd_soc_ops dsif_ops = {
.hw_params = dsif_hw_params,
};
static struct snd_soc_dai_link exynos8895_dai_links[] = {
{
.name = "RDMA0",
.stream_name = "RDMA0",
.cpu_dai_name = "RDMA0",
.platform_name = "13e51000.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA1",
.stream_name = "RDMA1",
.cpu_dai_name = "RDMA1",
.platform_name = "13e51100.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA2",
.stream_name = "RDMA2",
.cpu_dai_name = "RDMA2",
.platform_name = "13e51200.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA3",
.stream_name = "RDMA3",
.cpu_dai_name = "RDMA3",
.platform_name = "13e51300.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA4",
.stream_name = "RDMA4",
.cpu_dai_name = "RDMA4",
.platform_name = "13e51400.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA5",
.stream_name = "RDMA5",
.cpu_dai_name = "RDMA5",
.platform_name = "13e51500.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA6",
.stream_name = "RDMA6",
.cpu_dai_name = "RDMA6",
.platform_name = "13e51600.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "RDMA7",
.stream_name = "RDMA7",
.cpu_dai_name = "RDMA7",
.platform_name = "13e51700.abox_rdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &rdma_ops,
.dpcm_playback = 1,
},
{
.name = "WDMA0",
.stream_name = "WDMA0",
.cpu_dai_name = "WDMA0",
.platform_name = "13e52000.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "WDMA1",
.stream_name = "WDMA1",
.cpu_dai_name = "WDMA1",
.platform_name = "13e52100.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "WDMA2",
.stream_name = "WDMA2",
.cpu_dai_name = "WDMA2",
.platform_name = "13e52200.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "WDMA3",
.stream_name = "WDMA3",
.cpu_dai_name = "WDMA3",
.platform_name = "13e52300.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "WDMA4",
.stream_name = "WDMA4",
.cpu_dai_name = "WDMA4",
.platform_name = "13e52400.abox_wdma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_suspend = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST_PRE, SND_SOC_DPCM_TRIGGER_PRE_POST},
.ops = &wdma_ops,
.dpcm_capture = 1,
},
{
.name = "UAIF0",
.stream_name = "UAIF0",
.cpu_dai_name = "UAIF0",
.platform_name = "snd-soc-dummy",
.codec_dai_name = "cod3033x-aif",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "UAIF1",
.stream_name = "UAIF1",
.cpu_dai_name = "UAIF1",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "UAIF2",
.stream_name = "UAIF2",
.cpu_dai_name = "UAIF2",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "UAIF3",
.stream_name = "UAIF3",
.cpu_dai_name = "UAIF3",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "UAIF4",
.stream_name = "UAIF4",
.cpu_dai_name = "UAIF4",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &uaif_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "DSIF",
.stream_name = "DSIF",
.cpu_dai_name = "DSIF",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_PDM | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = abox_hw_params_fixup_helper,
.ops = &dsif_ops,
.dpcm_playback = 1,
},
{
.name = "ABOX Internal",
.stream_name = "ABOX Internal",
.cpu_dai_name = "Internal",
.platform_name = "snd-soc-dummy",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_PDM | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS,
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "VTS-Trigger",
.stream_name = "VTS-Trigger",
.cpu_dai_name = "vts-tri",
.platform_name = "vts_dma0",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.capture_only = true,
},
{
.name = "VTS-Record",
.stream_name = "VTS-Record",
.cpu_dai_name = "vts-rec",
.platform_name = "vts_dma1",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.capture_only = true,
},
{
.name = "DP Audio",
.stream_name = "DP Audio",
.cpu_dai_name = "snd-soc-dummy-dai",
.platform_name = "15a40000.displayport_adma",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
},
};
static struct snd_soc_dapm_widget exynos8895_widgets[] = {
SND_SOC_DAPM_MIC("DMIC1", NULL),
SND_SOC_DAPM_MIC("DMIC2", NULL),
SND_SOC_DAPM_MIC("DMIC3", NULL),
SND_SOC_DAPM_MIC("EARMIC", NULL),
};
static struct snd_soc_dapm_route exynos8895_routes[] = {
{"VTS PAD DPDM", NULL, "DMIC3"},
};
static struct snd_soc_codec_conf codec_conf[] = {
{
.name_prefix = "ABOX",
},
{
.name_prefix = "VTS",
},
};
static int exynos8895_sound_late_probe(struct snd_soc_card *card)
{
/* force enable mic bias for VTS */
// snd_soc_dapm_force_enable_pin(&card->dapm, "DMIC3");
// snd_soc_dapm_sync(&card->dapm);
return 0;
}
static struct snd_soc_card exynos8895_snd_card = {
.name = "Exynos8895-snd-card",
.owner = THIS_MODULE,
.dai_link = exynos8895_dai_links,
.num_links = ARRAY_SIZE(exynos8895_dai_links),
.dapm_widgets = exynos8895_widgets,
.num_dapm_widgets = ARRAY_SIZE(exynos8895_widgets),
.dapm_routes = exynos8895_routes,
.num_dapm_routes = ARRAY_SIZE(exynos8895_routes),
.codec_conf = codec_conf,
.num_configs = ARRAY_SIZE(codec_conf),
.late_probe = exynos8895_sound_late_probe,
};
static int exynos8895_sound_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &exynos8895_snd_card;
int n, ret;
dev_info(dev, "%s\n", __func__);
card->dev = &pdev->dev;
for (n = 0; np && n < ARRAY_SIZE(exynos8895_dai_links); n++) {
if (!exynos8895_dai_links[n].cpu_dai_name) {
exynos8895_dai_links[n].cpu_of_node = of_parse_phandle(np,
"samsung,audio-cpu", n);
if (!exynos8895_dai_links[n].cpu_of_node) {
dev_err(&pdev->dev, "Property 'samsung,audio-cpu' missing or invalid\n");
ret = -EINVAL;
}
}
if (!exynos8895_dai_links[n].platform_name) {
exynos8895_dai_links[n].platform_of_node = exynos8895_dai_links[n].cpu_of_node;
}
if (!exynos8895_dai_links[n].codec_name) {
exynos8895_dai_links[n].codec_of_node = of_parse_phandle(np,
"samsung,audio-codec", n);
if (!exynos8895_dai_links[n].codec_of_node) {
dev_err(&pdev->dev, "Property 'samsung,audio-codec' missing or invalid\n");
ret = -EINVAL;
}
}
}
for (n = 0; n < ARRAY_SIZE(codec_conf); n++) {
codec_conf[n].of_node = of_parse_phandle(np, "samsung,codec", n);
if (!codec_conf[n].of_node) {
dev_err(&pdev->dev,
"Property 'samsung,codec' missing\n");
return -EINVAL;
}
}
ret = devm_snd_soc_register_card(dev, card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);
return ret;
}
static int exynos8895_sound_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id exynos8895_sound_of_match[] = {
{ .compatible = "samsung,exynos8895-sound", },
{},
};
MODULE_DEVICE_TABLE(of, exynos8895_sound_of_match);
#endif /* CONFIG_OF */
static struct platform_driver exynos8895_sound_driver = {
.driver = {
.name = "exynos8895-sound",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = of_match_ptr(exynos8895_sound_of_match),
},
.probe = exynos8895_sound_probe,
.remove = exynos8895_sound_remove,
};
module_platform_driver(exynos8895_sound_driver);
MODULE_DESCRIPTION("ALSA SoC EXYNOS8895 sound driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:exynos8895-sound");

File diff suppressed because it is too large Load diff

View file

@ -250,6 +250,8 @@ static int idma_mmap(struct snd_pcm_substream *substream,
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
size = vma->vm_end - vma->vm_start;
offset = vma->vm_pgoff << PAGE_SHIFT;
if (offset > runtime->dma_bytes || size > runtime->dma_bytes - offset)
return -EINVAL;
ret = io_remap_pfn_range(vma, vma->vm_start,
(runtime->dma_addr + offset) >> PAGE_SHIFT,
size, vma->vm_page_prot);

View file

@ -0,0 +1,125 @@
/*
* jack_madera_sysfs_cb.c
* Copyright (c) Samsung Electronics
*
* 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/extcon.h>
#include <linux/input.h>
#include <linux/extcon/extcon-madera.h>
#include <linux/mfd/madera/core.h>
#include <sound/soc.h>
#include <sound/samsung/sec_audio_sysfs.h>
#include "jack_madera_sysfs_cb.h"
static struct snd_soc_codec *madera_codec;
int madera_jack_det;
int madera_ear_mic;
static int select_jack(int state)
{
struct snd_soc_codec *codec = madera_codec;
dev_info(codec->dev, "%s: %d\n", __func__, state);
/* To be developed */
return 0;
}
static int get_jack_status(void)
{
struct snd_soc_codec *codec = madera_codec;
dev_info(codec->dev, "%s: %d\n", __func__, madera_jack_det);
return (madera_jack_det || madera_ear_mic);
}
static int get_key_status(void)
{
struct snd_soc_codec *codec = madera_codec;
struct madera *madera = dev_get_drvdata(codec->dev->parent);
unsigned int val, lvl;
int ret, key;
ret = regmap_read(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_3, &val);
if (ret != 0) {
dev_err(codec->dev, "Failed to read MICDET: %d\n", ret);
return 0;
}
dev_info(codec->dev, "MICDET: %x\n", val);
ret = 0;
if (val & MADERA_MICD_LVL_0_TO_7) {
dev_info(codec->dev, "val LVL_0_TO_7\n");
if (madera_ear_mic) {
lvl = val & MADERA_MICD_LVL_MASK;
lvl >>= MADERA_MICD_LVL_SHIFT;
WARN_ON(!lvl);
WARN_ON(ffs(lvl) - 1 >= madera->pdata.accdet[0].num_micd_ranges);
if (lvl && ffs(lvl) - 1 < madera->pdata.accdet[0].num_micd_ranges) {
key = madera->pdata.accdet[0].micd_ranges[ffs(lvl) - 1].key;
if (key == KEY_MEDIA)
ret = 1;
}
}
}
dev_info(codec->dev, "%s: %d\n", __func__, ret);
return ret;
}
static int get_mic_adc(void)
{
struct snd_soc_codec *codec = madera_codec;
struct madera *madera = dev_get_drvdata(codec->dev->parent);
struct madera_extcon *info = madera->extcon_info;
int adc;
adc = madera_extcon_manual_mic_reading(info);
dev_info(codec->dev, "%s: %d\n", __func__, adc);
return adc;
}
#ifdef CONFIG_EXTCON_PTT
static int get_ptt_state(void)
{
struct snd_soc_codec *codec = madera_codec;
struct madera *madera = dev_get_drvdata(codec->dev->parent);
struct madera_extcon *info = madera->extcon_info;
int ptt_state;
ptt_state = info->ptt_state;
dev_info(codec->dev, "%s: %d\n", __func__, ptt_state);
return ptt_state;
}
#endif
void register_madera_jack_cb(struct snd_soc_codec *codec)
{
madera_codec = codec;
audio_register_jack_select_cb(select_jack);
audio_register_jack_state_cb(get_jack_status);
audio_register_key_state_cb(get_key_status);
audio_register_mic_adc_cb(get_mic_adc);
#ifdef CONFIG_EXTCON_PTT
audio_register_ptt_state_cb(get_ptt_state);
#endif
}
EXPORT_SYMBOL_GPL(register_madera_jack_cb);

View file

@ -0,0 +1,9 @@
#ifndef _JACK_MADERA_SYSFS_CB_H
#define _JACK_MADERA_SYSFS_CB_H
extern int madera_jack_det;
extern int madera_ear_mic;
void register_madera_jack_cb(struct snd_soc_codec *codec);
#endif /*_JACK_MADERA_SYSFS_CB_H */

View file

@ -0,0 +1,685 @@
/*
* sec_audio_debug.c
*
* Copyright (c) 2018 Samsung Electronics
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/debugfs.h>
#include <linux/io.h>
#include <linux/iommu.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/sched.h>
#include <sound/samsung/sec_audio_debug.h>
#include <linux/sec_debug.h>
#include <sound/samsung/abox.h>
#include "abox/abox.h"
#define DBG_STR_BUFF_SZ 256
#define LOG_MSG_BUFF_SZ 512
#define SEC_AUDIO_DEBUG_STRING_WQ_NAME "sec_audio_dbg_str_wq"
struct sec_audio_debug_data {
char *dbg_str_buf;
unsigned long long mode_time;
unsigned long mode_nanosec_t;
struct mutex dbg_lock;
struct workqueue_struct *debug_string_wq;
struct work_struct debug_string_work;
enum abox_debug_err_type debug_err_type;
unsigned int *abox_dbg_addr;
};
static struct sec_audio_debug_data *p_debug_data;
static struct dentry *audio_debugfs;
static struct sec_audio_log_data *p_debug_log_data;
static struct sec_audio_log_data *p_debug_bootlog_data;
static struct sec_audio_log_data *p_debug_pmlog_data;
static unsigned int debug_buff_switch;
int is_abox_rdma_enabled(int id)
{
struct abox_data *data = abox_get_abox_data();
return (readl(data->sfr_base + ABOX_RDMA_BASE + (ABOX_RDMA_INTERVAL * id)
+ ABOX_RDMA_CTRL0) & ABOX_RDMA_ENABLE_MASK);
}
int is_abox_wdma_enabled(int id)
{
struct abox_data *data = abox_get_abox_data();
return (readl(data->sfr_base + ABOX_WDMA_BASE + (ABOX_WDMA_INTERVAL * id)
+ ABOX_WDMA_CTRL) & ABOX_WDMA_ENABLE_MASK);
}
static void abox_debug_string_update_workfunc(struct work_struct *wk)
{
struct abox_data *data = abox_get_abox_data();
int i;
unsigned int len = 0;
/*
struct sec_audio_debug_data *dbg_data = container_of(wk,
struct sec_audio_debug_data, debug_string_work);
*/
if (!p_debug_data)
return;
mutex_lock(&p_debug_data->dbg_lock);
p_debug_data->mode_time = data->audio_mode_time;
p_debug_data->mode_nanosec_t = do_div(p_debug_data->mode_time, NSEC_PER_SEC);
p_debug_data->dbg_str_buf = kzalloc(sizeof(char) * DBG_STR_BUFF_SZ, GFP_KERNEL);
if (!p_debug_data->dbg_str_buf) {
pr_err("%s: no memory\n", __func__);
mutex_unlock(&p_debug_data->dbg_lock);
return;
}
switch (p_debug_data->debug_err_type) {
case TYPE_ABOX_DATAABORT:
case TYPE_ABOX_PREFETCHABORT:
len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len,
"ABOXERR%1d ", p_debug_data->debug_err_type);
if (!abox_is_on()) {
len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "Abox disabled");
goto buff_done;
}
if (p_debug_data->abox_dbg_addr == NULL) {
len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "GPR NULL");
goto buff_done;
}
for (i = 0; i <= 14; i++) {
len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len,
"%08x ", *p_debug_data->abox_dbg_addr++);
if (len >= DBG_STR_BUFF_SZ - 1)
goto buff_done;
}
len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len,
"PC:%08x ", *p_debug_data->abox_dbg_addr++);
if (len >= DBG_STR_BUFF_SZ - 1)
goto buff_done;
len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len,
"m%d:%05lu", data->audio_mode, (unsigned long) p_debug_data->mode_time);
break;
case TYPE_ABOX_OSERROR:
case TYPE_ABOX_UNDEFEXCEPTION:
case TYPE_ABOX_UNKNOWNERROR:
len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len,
"ABOXERR%1d ", p_debug_data->debug_err_type);
if (!abox_is_on()) {
len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "Abox disabled");
goto buff_done;
}
for (i = 0; i <= 14; i++) {
len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len,
"%08x ", readl(data->sfr_base + ABOX_CPU_R(i)));
if (len >= DBG_STR_BUFF_SZ - 1)
goto buff_done;
}
len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len,
"PC:%08x ", readl(data->sfr_base + ABOX_CPU_PC));
if (len >= DBG_STR_BUFF_SZ - 1)
goto buff_done;
len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len,
"L2C:%08x ", readl(data->sfr_base + ABOX_CPU_L2C_STATUS));
if (len >= DBG_STR_BUFF_SZ - 1)
goto buff_done;
len += snprintf(p_debug_data->dbg_str_buf + len, DBG_STR_BUFF_SZ - len,
"m%d:%05lu", data->audio_mode, (unsigned long) p_debug_data->mode_time);
break;
case TYPE_ABOX_VSSERROR:
len += snprintf(p_debug_data->dbg_str_buf, DBG_STR_BUFF_SZ - len, "VSSERROR");
break;
default:
pr_err("%s: unknown type %d\n", __func__, p_debug_data->debug_err_type);
break;
}
buff_done:
pr_info("%s: %s\n", __func__, p_debug_data->dbg_str_buf);
sec_debug_set_extra_info_rvd1(p_debug_data->dbg_str_buf);
kfree(p_debug_data->dbg_str_buf);
p_debug_data->dbg_str_buf = NULL;
mutex_unlock(&p_debug_data->dbg_lock);
}
void abox_debug_string_update(enum abox_debug_err_type type, void *addr)
{
p_debug_data->debug_err_type = type;
p_debug_data->abox_dbg_addr = (unsigned int*) addr;
queue_work(p_debug_data->debug_string_wq, &p_debug_data->debug_string_work);
}
EXPORT_SYMBOL_GPL(abox_debug_string_update);
void adev_err(struct device *dev, const char *fmt, ...)
{
va_list args;
char temp_buf[LOG_MSG_BUFF_SZ];
va_start(args, fmt);
vsnprintf(temp_buf, sizeof(temp_buf), fmt, args);
va_end(args);
dev_printk(KERN_ERR, dev, "%s", temp_buf);
sec_audio_log(3, dev, "%s", temp_buf);
}
void adev_warn(struct device *dev, const char *fmt, ...)
{
va_list args;
char temp_buf[LOG_MSG_BUFF_SZ];
va_start(args, fmt);
vsnprintf(temp_buf, sizeof(temp_buf), fmt, args);
va_end(args);
dev_printk(KERN_WARNING, dev, "%s", temp_buf);
sec_audio_log(4, dev, "%s", temp_buf);
}
void adev_info(struct device *dev, const char *fmt, ...)
{
va_list args;
char temp_buf[LOG_MSG_BUFF_SZ];
va_start(args, fmt);
vsnprintf(temp_buf, sizeof(temp_buf), fmt, args);
va_end(args);
dev_printk(KERN_INFO, dev, "%s", temp_buf);
sec_audio_log(6, dev, "%s", temp_buf);
}
void adev_dbg(struct device *dev, const char *fmt, ...)
{
va_list args;
char temp_buf[LOG_MSG_BUFF_SZ];
va_start(args, fmt);
vsnprintf(temp_buf, sizeof(temp_buf), fmt, args);
va_end(args);
dev_printk(KERN_DEBUG, dev, "%s", temp_buf);
sec_audio_log(7, dev, "%s", temp_buf);
}
static int get_debug_buffer_switch(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = debug_buff_switch;
return 0;
}
static int set_debug_buffer_switch(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
unsigned int val;
int ret = 0;
val = (unsigned int)ucontrol->value.integer.value[0];
if (val) {
alloc_sec_audio_log(p_debug_log_data, SZ_1M);
debug_buff_switch = SZ_1M;
} else {
alloc_sec_audio_log(p_debug_log_data, 0);
debug_buff_switch = 0;
}
return ret;
}
static const struct snd_kcontrol_new debug_controls[] = {
SOC_SINGLE_BOOL_EXT("Debug Buffer Switch", 0,
get_debug_buffer_switch,
set_debug_buffer_switch),
};
int register_debug_mixer(struct snd_soc_card *card)
{
return snd_soc_add_card_controls(card, debug_controls,
ARRAY_SIZE(debug_controls));
}
EXPORT_SYMBOL_GPL(register_debug_mixer);
static int audio_log_open_file(struct inode *inode, struct file *file)
{
struct sec_audio_log_data *p_dbg_log_data;
if (inode->i_private)
file->private_data = inode->i_private;
p_dbg_log_data = file->private_data;
pr_info("%s: %s\n", __func__, p_dbg_log_data->name);
p_dbg_log_data->read_idx = 0;
return 0;
}
static ssize_t audio_log_read_file(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
size_t num_msg;
ssize_t ret;
loff_t pos = *ppos;
size_t copy_len;
struct sec_audio_log_data *p_dbg_log_data = file->private_data;
if (*ppos < 0 || !count)
return -EINVAL;
if (p_dbg_log_data->full)
num_msg = p_dbg_log_data->sz_log_buff - p_dbg_log_data->read_idx;
else
num_msg = (size_t) p_dbg_log_data->buff_idx - p_dbg_log_data->read_idx;
if (num_msg < 0) {
pr_err("%s: buff idx invalid for %s\n", __func__, p_dbg_log_data->name);
return -EINVAL;
}
if (pos > num_msg) {
pr_err("%s: invalid offset for %s\n", __func__, p_dbg_log_data->name);
return -EINVAL;
}
copy_len = min(count, num_msg);
ret = copy_to_user(user_buf, p_dbg_log_data->audio_log_buffer + p_dbg_log_data->read_idx, copy_len);
if (ret) {
pr_err("%s: %s copy fail %d\n", __func__, p_dbg_log_data->name, (int) ret);
return -EFAULT;
}
p_dbg_log_data->read_idx += copy_len;
return copy_len;
}
static const struct file_operations audio_log_fops = {
.open = audio_log_open_file,
.read = audio_log_read_file,
.llseek = default_llseek,
};
static void free_sec_audio_log(struct sec_audio_log_data *p_dbg_log_data)
{
p_dbg_log_data->sz_log_buff = 0;
if (p_dbg_log_data->virtual)
vfree(p_dbg_log_data->audio_log_buffer);
else
kfree(p_dbg_log_data->audio_log_buffer);
p_dbg_log_data->audio_log_buffer = NULL;
}
int alloc_sec_audio_log(struct sec_audio_log_data *p_dbg_log_data, size_t buffer_len)
{
if (p_dbg_log_data->sz_log_buff) {
p_dbg_log_data->sz_log_buff = 0;
free_sec_audio_log(p_dbg_log_data);
}
p_dbg_log_data->buff_idx = 0;
p_dbg_log_data->full = 0;
if (buffer_len <= 0) {
pr_err("%s: Invalid buffer_len for %s %d\n", __func__, p_dbg_log_data->name, (int) buffer_len);
p_dbg_log_data->sz_log_buff = 0;
return 0;
}
if (p_dbg_log_data->virtual) {
p_dbg_log_data->audio_log_buffer = vzalloc(buffer_len);
} else {
p_dbg_log_data->audio_log_buffer = kzalloc(buffer_len, GFP_KERNEL);
}
if (p_dbg_log_data->audio_log_buffer == NULL) {
pr_err("%s: Failed to alloc audio_log_buffer for %s\n", __func__, p_dbg_log_data->name);
p_dbg_log_data->sz_log_buff = 0;
return -ENOMEM;
}
p_dbg_log_data->sz_log_buff = buffer_len;
return p_dbg_log_data->sz_log_buff;
}
EXPORT_SYMBOL_GPL(alloc_sec_audio_log);
static ssize_t log_enable_read_file(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[16];
int len;
struct sec_audio_log_data *p_dbg_log_data = file->private_data;
len = snprintf(buf, 16, "%d\n", (int) p_dbg_log_data->sz_log_buff);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t log_enable_write_file(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[16];
size_t size;
u64 value;
struct sec_audio_log_data *p_dbg_log_data = file->private_data;
size = min(count, (sizeof(buf) - 1));
if (copy_from_user(buf, user_buf, size)) {
pr_err("%s: copy_from_user err\n", __func__);
return -EFAULT;
}
buf[size] = 0;
if (kstrtou64(buf, 10, &value)) {
pr_err("%s: Invalid value\n", __func__);
return -EINVAL;
}
/* do not alloc over 2MB */
if (value > SZ_2M)
value = SZ_2M;
alloc_sec_audio_log(p_dbg_log_data, (size_t) value);
return size;
}
static const struct file_operations log_enable_fops = {
.open = simple_open,
.read = log_enable_read_file,
.write = log_enable_write_file,
.llseek = default_llseek,
};
static ssize_t make_prefix_msg(char *buff, int level, struct device *dev)
{
unsigned long long time = local_clock();
unsigned long nanosec_t = do_div(time, 1000000000);
ssize_t msg_size = 0;
msg_size = scnprintf(buff, LOG_MSG_BUFF_SZ, "<%d> [%5lu.%06lu] %s %s: ",
level, (unsigned long) time, nanosec_t / 1000,
(dev)? dev_driver_string(dev): "NULL", (dev)? dev_name(dev): "NULL");
return msg_size;
}
static void copy_msgs(char *buff, struct sec_audio_log_data *p_dbg_log_data)
{
if (p_dbg_log_data->buff_idx + strlen(buff) > p_dbg_log_data->sz_log_buff - 1) {
p_dbg_log_data->full = 1;
p_dbg_log_data->buff_idx = 0;
}
p_dbg_log_data->buff_idx +=
scnprintf(p_dbg_log_data->audio_log_buffer + p_dbg_log_data->buff_idx,
(strlen(buff) + 1), "%s", buff);
}
void sec_audio_log(int level, struct device *dev, const char *fmt, ...)
{
va_list args;
char temp_buf[LOG_MSG_BUFF_SZ];
ssize_t temp_buff_idx = 0;
struct sec_audio_log_data *p_dbg_log_data = p_debug_log_data;
if (!p_dbg_log_data->sz_log_buff) {
return;
}
temp_buff_idx = make_prefix_msg(temp_buf, level, dev);
va_start(args, fmt);
temp_buff_idx +=
vsnprintf(temp_buf + temp_buff_idx,
LOG_MSG_BUFF_SZ - temp_buff_idx, fmt, args);
va_end(args);
copy_msgs(temp_buf, p_dbg_log_data);
}
EXPORT_SYMBOL_GPL(sec_audio_log);
void sec_audio_bootlog(int level, struct device *dev, const char *fmt, ...)
{
va_list args;
char temp_buf[LOG_MSG_BUFF_SZ];
ssize_t temp_buff_idx = 0;
struct sec_audio_log_data *p_dbg_log_data = p_debug_bootlog_data;
if (!p_dbg_log_data->sz_log_buff) {
return;
}
temp_buff_idx = make_prefix_msg(temp_buf, level, dev);
va_start(args, fmt);
temp_buff_idx +=
vsnprintf(temp_buf + temp_buff_idx,
LOG_MSG_BUFF_SZ - (temp_buff_idx + 1), fmt, args);
va_end(args);
copy_msgs(temp_buf, p_dbg_log_data);
}
EXPORT_SYMBOL_GPL(sec_audio_bootlog);
void sec_audio_pmlog(int level, struct device *dev, const char *fmt, ...)
{
va_list args;
char temp_buf[LOG_MSG_BUFF_SZ];
ssize_t temp_buff_idx = 0;
struct sec_audio_log_data *p_dbg_log_data = p_debug_pmlog_data;
if (!p_dbg_log_data->sz_log_buff) {
return;
}
temp_buff_idx = make_prefix_msg(temp_buf, level, dev);
va_start(args, fmt);
temp_buff_idx +=
vsnprintf(temp_buf + temp_buff_idx,
LOG_MSG_BUFF_SZ - (temp_buff_idx + 1), fmt, args);
va_end(args);
copy_msgs(temp_buf, p_dbg_log_data);
}
EXPORT_SYMBOL_GPL(sec_audio_pmlog);
static int __init sec_audio_debug_init(void)
{
struct sec_audio_debug_data *data;
struct sec_audio_log_data *log_data;
struct sec_audio_log_data *bootlog_data;
struct sec_audio_log_data *pmlog_data;
int ret = 0;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data) {
pr_err("%s: failed to alloc data\n", __func__);
return -ENOMEM;
}
p_debug_data = data;
mutex_init(&p_debug_data->dbg_lock);
audio_debugfs = debugfs_create_dir("audio", NULL);
if (!audio_debugfs) {
pr_err("Failed to create audio debugfs\n");
ret = -EPERM;
goto err_data;
}
log_data = kzalloc(sizeof(*log_data), GFP_KERNEL);
if (!log_data) {
pr_err("%s: failed to alloc log_data\n", __func__);
ret = -ENOMEM;
goto err_debugfs;
}
p_debug_log_data = log_data;
/*
p_debug_log_data->audio_log_buffer = NULL;
p_debug_log_data->buff_idx = 0;
p_debug_log_data->full = 0;
p_debug_log_data->read_idx = 0;
p_debug_log_data->sz_log_buff = 0;
*/
p_debug_log_data->virtual = 1;
p_debug_log_data->name = kasprintf(GFP_KERNEL, "runtime");
bootlog_data = kzalloc(sizeof(*bootlog_data), GFP_KERNEL);
if (!bootlog_data) {
pr_err("%s: failed to alloc bootlog_data\n", __func__);
ret = -ENOMEM;
goto err_log_data;
}
p_debug_bootlog_data = bootlog_data;
p_debug_bootlog_data->name = kasprintf(GFP_KERNEL, "boot");
pmlog_data = kzalloc(sizeof(*pmlog_data), GFP_KERNEL);
if (!pmlog_data) {
pr_err("%s: failed to alloc pmlog_data\n", __func__);
ret = -ENOMEM;
goto err_bootlog_data;
}
p_debug_pmlog_data = pmlog_data;
p_debug_pmlog_data->name = kasprintf(GFP_KERNEL, "pm");
alloc_sec_audio_log(p_debug_bootlog_data, SZ_1K);
alloc_sec_audio_log(p_debug_pmlog_data, SZ_4K);
p_debug_data->debug_string_wq = create_singlethread_workqueue(
SEC_AUDIO_DEBUG_STRING_WQ_NAME);
if (p_debug_data->debug_string_wq == NULL) {
pr_err("%s: failed to creat debug_string_wq\n", __func__);
ret = -ENOENT;
goto err_pmlog_data;
}
INIT_WORK(&p_debug_data->debug_string_work, abox_debug_string_update_workfunc);
debugfs_create_file("log_enable",
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
audio_debugfs, p_debug_log_data, &log_enable_fops);
debugfs_create_file("log",
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
audio_debugfs, p_debug_log_data, &audio_log_fops);
debugfs_create_file("bootlog_enable",
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
audio_debugfs, p_debug_bootlog_data, &log_enable_fops);
debugfs_create_file("bootlog",
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
audio_debugfs, p_debug_bootlog_data, &audio_log_fops);
debugfs_create_file("pmlog_enable",
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
audio_debugfs, p_debug_pmlog_data, &log_enable_fops);
debugfs_create_file("pmlog",
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
audio_debugfs, p_debug_pmlog_data, &audio_log_fops);
return 0;
err_pmlog_data:
kfree_const(p_debug_pmlog_data->name);
kfree(p_debug_pmlog_data);
p_debug_pmlog_data = NULL;
err_bootlog_data:
kfree_const(p_debug_bootlog_data->name);
kfree(p_debug_bootlog_data);
p_debug_bootlog_data = NULL;
err_log_data:
kfree_const(p_debug_log_data->name);
kfree(p_debug_log_data);
p_debug_log_data = NULL;
err_debugfs:
debugfs_remove_recursive(audio_debugfs);
err_data:
kfree(p_debug_data);
p_debug_data = NULL;
return ret;
}
early_initcall(sec_audio_debug_init);
static void __exit sec_audio_debug_exit(void)
{
if (p_debug_pmlog_data->sz_log_buff)
free_sec_audio_log(p_debug_pmlog_data);
kfree_const(p_debug_pmlog_data->name);
if (p_debug_pmlog_data)
kfree(p_debug_pmlog_data);
p_debug_pmlog_data = NULL;
if (p_debug_bootlog_data->sz_log_buff)
free_sec_audio_log(p_debug_bootlog_data);
kfree_const(p_debug_bootlog_data->name);
if (p_debug_bootlog_data)
kfree(p_debug_bootlog_data);
p_debug_bootlog_data = NULL;
if (p_debug_log_data->sz_log_buff)
free_sec_audio_log(p_debug_log_data);
kfree_const(p_debug_log_data->name);
if (p_debug_log_data)
kfree(p_debug_log_data);
p_debug_log_data = NULL;
destroy_workqueue(p_debug_data->debug_string_wq);
p_debug_data->debug_string_wq = NULL;
debugfs_remove_recursive(audio_debugfs);
kfree(p_debug_data);
p_debug_data = NULL;
}
module_exit(sec_audio_debug_exit);
MODULE_DESCRIPTION("Samsung Electronics Audio Debug driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,336 @@
/*
* sec_audio_sysfs.c
*
* Copyright (c) 2017 Samsung Electronics
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/init.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <sound/samsung/sec_audio_sysfs.h>
struct sec_audio_sysfs_data *audio_data;
int audio_register_jack_select_cb(int (*set_jack) (int))
{
if (audio_data->set_jack_state) {
dev_err(audio_data->jack_dev,
"%s: Already registered\n", __func__);
return -EEXIST;
}
audio_data->set_jack_state = set_jack;
return 0;
}
EXPORT_SYMBOL_GPL(audio_register_jack_select_cb);
static ssize_t audio_jack_select_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
if (audio_data->set_jack_state) {
if ((!size) || (buf[0] != '1')) {
dev_info(dev, "%s: Forced remove jack\n", __func__);
audio_data->set_jack_state(0);
} else {
dev_info(dev, "%s: Forced detect jack\n", __func__);
audio_data->set_jack_state(1);
}
} else {
dev_info(dev, "%s: No callback registered\n", __func__);
}
return size;
}
static DEVICE_ATTR(select_jack, 0664,
NULL, audio_jack_select_store);
int audio_register_jack_state_cb(int (*jack_state) (void))
{
if (audio_data->get_jack_state) {
dev_err(audio_data->jack_dev,
"%s: Already registered\n", __func__);
return -EEXIST;
}
audio_data->get_jack_state = jack_state;
return 0;
}
EXPORT_SYMBOL_GPL(audio_register_jack_state_cb);
static ssize_t audio_jack_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int report = 0;
if (audio_data->get_jack_state)
report = audio_data->get_jack_state();
else
dev_info(dev, "%s: No callback registered\n", __func__);
return snprintf(buf, 4, "%d\n", report);
}
static DEVICE_ATTR(state, 0664,
audio_jack_state_show, NULL);
int audio_register_key_state_cb(int (*key_state) (void))
{
if (audio_data->get_key_state) {
dev_err(audio_data->jack_dev,
"%s: Already registered\n", __func__);
return -EEXIST;
}
audio_data->get_key_state = key_state;
return 0;
}
EXPORT_SYMBOL_GPL(audio_register_key_state_cb);
static ssize_t audio_key_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int report = 0;
if (audio_data->get_key_state)
report = audio_data->get_key_state();
else
dev_info(dev, "%s: No callback registered\n", __func__);
return snprintf(buf, 4, "%d\n", report);
}
static DEVICE_ATTR(key_state, 0664,
audio_key_state_show, NULL);
int audio_register_mic_adc_cb(int (*mic_adc) (void))
{
if (audio_data->get_mic_adc) {
dev_err(audio_data->jack_dev,
"%s: Already registered\n", __func__);
return -EEXIST;
}
audio_data->get_mic_adc = mic_adc;
return 0;
}
EXPORT_SYMBOL_GPL(audio_register_mic_adc_cb);
static ssize_t audio_mic_adc_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int report = 0;
if (audio_data->get_mic_adc)
report = audio_data->get_mic_adc();
else
dev_info(dev, "%s: No callback registered\n", __func__);
return snprintf(buf, 16, "%d\n", report);
}
static DEVICE_ATTR(mic_adc, 0664,
audio_mic_adc_show, NULL);
#ifdef CONFIG_EXTCON_PTT
int audio_register_ptt_state_cb(int (*ptt_state) (void))
{
if (audio_data->get_ptt_state) {
dev_err(audio_data->jack_dev,
"%s: Already registered\n", __func__);
return -EEXIST;
}
audio_data->get_ptt_state = ptt_state;
return 0;
}
EXPORT_SYMBOL_GPL(audio_register_ptt_state_cb);
static ssize_t audio_ptt_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int report = 0;
if (audio_data->get_ptt_state)
report = audio_data->get_ptt_state();
else
dev_info(dev, "%s: No callback registered\n", __func__);
return snprintf(buf, 16, "%d\n", report);
}
static DEVICE_ATTR(ptt_state, 0664,
audio_ptt_state_show, NULL);
#endif
static struct attribute *sec_audio_jack_attr[] = {
&dev_attr_select_jack.attr,
&dev_attr_state.attr,
&dev_attr_key_state.attr,
&dev_attr_mic_adc.attr,
#ifdef CONFIG_EXTCON_PTT
&dev_attr_ptt_state.attr,
#endif
NULL,
};
static struct attribute_group sec_audio_jack_attr_group = {
.attrs = sec_audio_jack_attr,
};
int audio_register_codec_id_state_cb(int (*codec_id_state) (void))
{
if (audio_data->get_codec_id_state) {
dev_err(audio_data->codec_dev,
"%s: Already registered\n", __func__);
return -EEXIST;
}
audio_data->get_codec_id_state = codec_id_state;
return 0;
}
EXPORT_SYMBOL_GPL(audio_register_codec_id_state_cb);
static ssize_t audio_check_codec_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int report = 0;
if (audio_data->get_codec_id_state)
report = audio_data->get_codec_id_state();
else
dev_info(dev, "%s: No callback registered\n", __func__);
return snprintf(buf, 4, "%d\n", report);
}
static DEVICE_ATTR(check_codec_id, 0664,
audio_check_codec_id_show, NULL);
static struct attribute *sec_audio_codec_attr[] = {
&dev_attr_check_codec_id.attr,
NULL,
};
static struct attribute_group sec_audio_codec_attr_group = {
.attrs = sec_audio_codec_attr,
};
static int __init sec_audio_sysfs_init(void)
{
int ret = 0;
audio_data = kzalloc(sizeof(struct sec_audio_sysfs_data), GFP_KERNEL);
if (audio_data == NULL)
return -ENOMEM;
audio_data->audio_class = class_create(THIS_MODULE, "audio");
if (IS_ERR(audio_data->audio_class)) {
pr_err("%s: Failed to create audio class\n", __func__);
ret = PTR_ERR(audio_data->audio_class);
goto err_alloc;
}
audio_data->jack_dev =
device_create(audio_data->audio_class,
NULL, 0, NULL, "earjack");
if (IS_ERR(audio_data->jack_dev)) {
pr_err("%s: Failed to create earjack device\n", __func__);
ret = PTR_ERR(audio_data->jack_dev);
goto err_class;
}
ret = sysfs_create_group(&audio_data->jack_dev->kobj,
&sec_audio_jack_attr_group);
if (ret) {
pr_err("%s: Failed to create earjack sysfs\n", __func__);
goto err_jack_device;
}
audio_data->codec_dev =
device_create(audio_data->audio_class,
NULL, 1, NULL, "codec");
if (IS_ERR(audio_data->codec_dev)) {
pr_err("%s: Failed to create codec device\n", __func__);
ret = PTR_ERR(audio_data->codec_dev);
goto err_jack_attr;
}
ret = sysfs_create_group(&audio_data->codec_dev->kobj,
&sec_audio_codec_attr_group);
if (ret) {
pr_err("%s: Failed to create codec sysfs\n", __func__);
goto err_codec_device;
}
return 0;
err_codec_device:
device_destroy(audio_data->audio_class, 1);
audio_data->codec_dev = NULL;
err_jack_attr:
sysfs_remove_group(&audio_data->jack_dev->kobj,
&sec_audio_jack_attr_group);
err_jack_device:
device_destroy(audio_data->audio_class, 0);
audio_data->jack_dev = NULL;
err_class:
class_destroy(audio_data->audio_class);
audio_data->audio_class = NULL;
err_alloc:
kfree(audio_data);
audio_data = NULL;
return ret;
}
subsys_initcall(sec_audio_sysfs_init);
static void __exit sec_audio_sysfs_exit(void)
{
if (audio_data->codec_dev) {
sysfs_remove_group(&audio_data->codec_dev->kobj,
&sec_audio_codec_attr_group);
device_destroy(audio_data->audio_class, 1);
}
if (audio_data->jack_dev) {
sysfs_remove_group(&audio_data->jack_dev->kobj,
&sec_audio_jack_attr_group);
device_destroy(audio_data->audio_class, 0);
}
if (audio_data->audio_class)
class_destroy(audio_data->audio_class);
kfree(audio_data);
}
module_exit(sec_audio_sysfs_exit);
MODULE_DESCRIPTION("Samsung Electronics Audio SYSFS driver");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
config SND_SOC_SAMSUNG_MAILBOX
bool "Samsung MAILBOX"
select GENERIC_IRQ_CHIP
help
Say Y if you want to use mailbox for voice trigger system.
config SND_SOC_SAMSUNG_VTS
bool "Samsung VTS"
depends on SND_SOC_SAMSUNG_MAILBOX
help
Say Y if you want to use voice trigger system.

View file

@ -0,0 +1,2 @@
obj-$(CONFIG_SND_SOC_SAMSUNG_MAILBOX) += mailbox.o
obj-$(CONFIG_SND_SOC_SAMSUNG_VTS) += vts.o vts_dma.o vts_log.o vts_dump.o

View file

@ -0,0 +1,259 @@
/* sound/soc/samsung/vts/mailbox.c
*
* ALSA SoC - Samsung Mailbox driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
//#define DEBUG
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/irqdomain.h>
#include <linux/irq.h>
#include <linux/irqchip/chained_irq.h>
#include <sound/samsung/vts.h>
#include "mailbox.h"
#define DEVICE_NAME "samsung-mailbox-asoc"
struct mailbox_data {
void __iomem *sfr_base;
struct irq_domain *irq_domain;
int irq;
};
int mailbox_generate_interrupt(const struct platform_device *pdev, int hw_irq)
{
struct mailbox_data *data = platform_get_drvdata(pdev);
writel(BIT(hw_irq), data->sfr_base + MAILBOX_INTGR0);
dev_dbg(&pdev->dev, "%s: writel(%lx, %lx)", __func__,
BIT(hw_irq), (unsigned long)virt_to_phys(data->sfr_base + MAILBOX_INTGR0));
return 0;
}
EXPORT_SYMBOL(mailbox_generate_interrupt);
void mailbox_write_shared_register(const struct platform_device *pdev,
const u32 *values, int start, int count)
{
struct mailbox_data *data = platform_get_drvdata(pdev);
u32 __iomem *sfr = data->sfr_base + MAILBOX_ISSR0 + (start * sizeof(u32));
dev_dbg(&pdev->dev, "%s(%p, %p, %d, %d)", __func__, pdev, values, start, count);
while (count--) {
writel_relaxed(*values++, sfr++);
}
}
EXPORT_SYMBOL(mailbox_write_shared_register);
void mailbox_read_shared_register(const struct platform_device *pdev,
u32 *values, int start, int count)
{
struct mailbox_data *data = platform_get_drvdata(pdev);
u32 __iomem *sfr = data->sfr_base + MAILBOX_ISSR0 + (start * sizeof(u32));
dev_dbg(&pdev->dev, "%s(%p, %p, %d, %d)", __func__, pdev, values, start, count);
while (count--) {
*values++ = readl_relaxed(sfr++);
dev_dbg(&pdev->dev, "value=%d\n", *(values - 1));
}
}
EXPORT_SYMBOL(mailbox_read_shared_register);
static void mailbox_handle_irq(struct irq_desc *desc)
{
struct platform_device *pdev = irq_desc_get_handler_data(desc);
struct mailbox_data *data = platform_get_drvdata(pdev);
struct irq_domain *irq_domain = data->irq_domain;
struct irq_chip *chip = irq_desc_get_chip(desc);
struct irq_chip_generic *gc = irq_get_domain_generic_chip(irq_domain, 0);
u32 stat = readl_relaxed(gc->reg_base + MAILBOX_INTSR1);
dev_dbg(&pdev->dev, "%s: stat=%08x\n", __func__, stat);
chained_irq_enter(chip, desc);
while (stat) {
u32 hwirq = __fls(stat);
generic_handle_irq(irq_find_mapping(irq_domain, gc->irq_base + hwirq));
stat &= ~(1 << hwirq);
}
chained_irq_exit(chip, desc);
}
static int mailbox_suspend(struct device *dev)
{
return 0;
}
static int mailbox_resume(struct device *dev)
{
return 0;
}
static const struct of_device_id samsung_mailbox_of_match[] = {
{
.compatible = "samsung,mailbox-asoc",
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_mailbox_of_match);
SIMPLE_DEV_PM_OPS(samsung_mailbox_pm, mailbox_suspend, mailbox_resume);
static void mailbox_irq_enable(struct irq_data *data)
{
if (vts_is_on()) {
irq_gc_mask_clr_bit(data);
} else {
pr_debug("%s(%d): vts is already off\n",
__func__, data->irq);
}
return;
}
static void mailbox_irq_disable(struct irq_data *data)
{
if (vts_is_on()) {
irq_gc_mask_set_bit(data);
} else {
pr_debug("%s(%d): vts is already off\n",
__func__, data->irq);
}
return;
}
static void __iomem *devm_request_and_ioremap(struct platform_device *pdev, const char *name)
{
struct device *dev = &pdev->dev;
struct resource *res;
void __iomem *result;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
if (IS_ERR_OR_NULL(res)) {
dev_err(dev, "Failed to get %s\n", name);
return ERR_PTR(-EINVAL);
}
res = devm_request_mem_region(dev, res->start, resource_size(res), name);
if (IS_ERR_OR_NULL(res)) {
dev_err(dev, "Failed to request %s\n", name);
return ERR_PTR(-EFAULT);
}
result = devm_ioremap(dev, res->start, resource_size(res));
if (IS_ERR_OR_NULL(result)) {
dev_err(dev, "Failed to map %s\n", name);
return ERR_PTR(-EFAULT);
}
return result;
}
static int samsung_mailbox_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct mailbox_data *data;
struct irq_chip_generic *gc;
int result;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (IS_ERR_OR_NULL(data)) {
dev_err(dev, "Failed to allocate memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, data);
data->sfr_base = devm_request_and_ioremap(pdev, "sfr");
if (IS_ERR(data->sfr_base)) {
return PTR_ERR(data->sfr_base);
}
result = platform_get_irq(pdev, 0);
if (result < 0) {
dev_err(dev, "Failed to get irq resource\n");
return result;
}
data->irq = result;
data->irq_domain = irq_domain_add_linear(np, MAILBOX_INT1_OFFSET + MAILBOX_INT1_SIZE,
&irq_generic_chip_ops, NULL);
if (IS_ERR_OR_NULL(data->irq_domain)) {
dev_err(dev, "Failed to add irq domain\n");
return -ENOMEM;
}
result = irq_alloc_domain_generic_chips(data->irq_domain, MAILBOX_INT1_OFFSET + MAILBOX_INT1_SIZE,
1, "mailbox_irq_chip", handle_level_irq, 0, 0, IRQ_GC_INIT_MASK_CACHE);
if (result < 0) {
dev_err(dev, "Failed to allocation generic irq chips\n");
return result;
}
gc = irq_get_domain_generic_chip(data->irq_domain, 0);
gc->reg_base = data->sfr_base;
gc->chip_types[0].chip.irq_enable = mailbox_irq_enable;
gc->chip_types[0].chip.irq_disable = mailbox_irq_disable;
gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit;
gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit;
gc->chip_types[0].regs.mask = MAILBOX_INTMR1;
gc->chip_types[0].regs.ack = MAILBOX_INTCR1;
#ifdef CONFIG_SOC_EXYNOS8895
gc->wake_enabled = 0x0000FFFF;
#else
gc->wake_enabled = 0xFFFF0000;
#endif
gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake;
irq_set_handler_data(data->irq, pdev);
irq_set_chained_handler(data->irq, mailbox_handle_irq);
dev_info(dev, "Probed successfully\n");
return 0;
}
static int samsung_mailbox_remove(struct platform_device *pdev)
{
struct mailbox_data *mailbox_data = platform_get_drvdata(pdev);
dev_info(&pdev->dev, "%s\n", __func__);
irq_remove_generic_chip(irq_get_domain_generic_chip(mailbox_data->irq_domain, 0),
IRQ_MSK(MAILBOX_INT1_SIZE), 0, 0);
irq_domain_remove(mailbox_data->irq_domain);
return 0;
}
static struct platform_driver samsung_mailbox_driver = {
.probe = samsung_mailbox_probe,
.remove = samsung_mailbox_remove,
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_mailbox_of_match),
.pm = &samsung_mailbox_pm,
},
};
module_platform_driver(samsung_mailbox_driver);
/* Module information */
MODULE_AUTHOR("Gyeongtaek Lee, <gt82.lee@samsung.com>");
MODULE_DESCRIPTION("Samsung Mailbox");
MODULE_ALIAS("platform:samsung-mailbox");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,88 @@
/* sound/soc/samsung/vts/mailbox.h
*
* ALSA SoC - Samsung Mailbox driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __MAILBOX_H
#define __MAILBOX_H
/* MAILBOX */
#ifdef CONFIG_SOC_EXYNOS8895
#define MAILBOX_MCUCTRL (0x0000)
#define MAILBOX_INTGR0 (0x0008)
#define MAILBOX_INTCR0 (0x000C)
#define MAILBOX_INTMR0 (0x0010)
#define MAILBOX_INTSR0 (0x0014)
#define MAILBOX_INTMSR0 (0x0018)
#define MAILBOX_INTGR1 (0x001C)
#define MAILBOX_INTCR1 (0x0020)
#define MAILBOX_INTMR1 (0x0024)
#define MAILBOX_INTSR1 (0x0028)
#define MAILBOX_INTMSR1 (0x002C)
#define MAILBOX_MIF_INIT (0x004c)
#define MAILBOX_IS_VERSION (0x0050)
#define MAILBOX_ISSR0 (0x0080)
#define MAILBOX_ISSR1 (0x0084)
#define MAILBOX_ISSR2 (0x0088)
#define MAILBOX_ISSR3 (0x008C)
#define MAILBOX_SEMAPHORE0 (0x0180)
#define MAILBOX_SEMAPHORE1 (0x0184)
#define MAILBOX_SEMAPHORE2 (0x0188)
#define MAILBOX_SEMAPHORE3 (0x018C)
#define MAILBOX_SEMA0CON (0x01C0)
#define MAILBOX_SEMA0STATE (0x01C8)
#define MAILBOX_SEMA1CON (0x01E0)
#define MAILBOX_SEMA1STATE (0x01E8)
#elif defined(CONFIG_SOC_EXYNOS9810)
#define MAILBOX_MCUCTRL (0x0000)
#define MAILBOX_INTGR0 (0x001C)
#define MAILBOX_INTCR0 (0x0020)
#define MAILBOX_INTMR0 (0x0024)
#define MAILBOX_INTSR0 (0x0028)
#define MAILBOX_INTMSR0 (0x002C)
#define MAILBOX_INTGR1 (0x0008)
#define MAILBOX_INTCR1 (0x000C)
#define MAILBOX_INTMR1 (0x0010)
#define MAILBOX_INTSR1 (0x0014)
#define MAILBOX_INTMSR1 (0x0018)
#define MAILBOX_MIF_INIT (0x004C)
#define MAILBOX_IS_VERSION (0x0050)
#define MAILBOX_ISSR0 (0x0080)
#define MAILBOX_ISSR1 (0x0084)
#define MAILBOX_ISSR2 (0x0088)
#define MAILBOX_ISSR3 (0x008C)
#define MAILBOX_ISSR4 (0x0090)
#define MAILBOX_ISSR5 (0x0094)
#define MAILBOX_SEMAPHORE0 (0x0180)
#define MAILBOX_SEMAPHORE1 (0x0184)
#define MAILBOX_SEMAPHORE2 (0x0188)
#define MAILBOX_SEMAPHORE3 (0x018C)
#define MAILBOX_SEMA0CON (0x01C0)
#define MAILBOX_SEMA0STATE (0x01C8)
#define MAILBOX_SEMA1CON (0x01E0)
#define MAILBOX_SEMA1STATE (0x01E8)
#endif
/* MAILBOX_MCUCTRL */
#define MAILBOX_MSWRST_OFFSET (0)
#define MAILBOX_MSWRST_SIZE (1)
/* MAILBOX_INT*R0 */
#define MAILBOX_INT0_OFFSET (0)
#define MAILBOX_INT0_SIZE (16)
/* MAILBOX_INT*R1 */
#define MAILBOX_INT1_OFFSET (16)
#define MAILBOX_INT1_SIZE (16)
/* MAILBOX_SEMA?CON */
#define MAILBOX_INT_EN_OFFSET (3)
#define MAILBOX_INT_EN_SIZE (1)
/* MAILBOX_SEMA?STATE */
#define MAILBOX_LOCK_OFFSET (0)
#define MAILBOX_LOCK_SIZE (1)
#endif /* __MAILBOX_H */

2791
sound/soc/samsung/vts/vts.c Normal file

File diff suppressed because it is too large Load diff

342
sound/soc/samsung/vts/vts.h Normal file
View file

@ -0,0 +1,342 @@
/* sound/soc/samsung/vts/vts.h
*
* ALSA SoC - Samsung VTS driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_VTS_H
#define __SND_SOC_VTS_H
#include <sound/memalloc.h>
#include <linux/wakelock.h>
/* SYSREG_VTS */
#define VTS_DEBUG (0x0404)
#define VTS_DMIC_CLK_CTRL (0x0408)
#define VTS_HWACG_CM4_CLKREQ (0x0428)
#define VTS_DMIC_CLK_CON (0x0434)
#define VTS_SYSPOWER_CTRL (0x0500)
#define VTS_SYSPOWER_STATUS (0x0504)
/* VTS_DEBUG */
#define VTS_NMI_EN_BY_WDT_OFFSET (0)
#define VTS_NMI_EN_BY_WDT_SIZE (1)
/* VTS_DMIC_CLK_CTRL */
#define VTS_CG_STATUS_OFFSET (5)
#define VTS_CG_STATUS_SIZE (1)
#define VTS_CLK_ENABLE_OFFSET (4)
#define VTS_CLK_ENABLE_SIZE (1)
#define VTS_CLK_SEL_OFFSET (0)
#define VTS_CLK_SEL_SIZE (1)
/* VTS_HWACG_CM4_CLKREQ */
#define VTS_MASK_OFFSET (0)
#define VTS_MASK_SIZE (32)
/* VTS_DMIC_CLK_CON */
#define VTS_ENABLE_CLK_GEN_OFFSET (0)
#define VTS_ENABLE_CLK_GEN_SIZE (1)
#define VTS_SEL_EXT_DMIC_CLK_OFFSET (1)
#define VTS_SEL_EXT_DMIC_CLK_SIZE (1)
#define VTS_ENABLE_CLK_CLK_GEN_OFFSET (14)
#define VTS_ENABLE_CLK_CLK_GEN_SIZE (1)
/* VTS_SYSPOWER_CTRL */
#define VTS_SYSPOWER_CTRL_OFFSET (0)
#define VTS_SYSPOWER_CTRL_SIZE (1)
/* VTS_SYSPOWER_STATUS */
#define VTS_SYSPOWER_STATUS_OFFSET (0)
#define VTS_SYSPOWER_STATUS_SIZE (1)
#define VTS_DMIC_ENABLE_DMIC_IF (0x0000)
#define VTS_DMIC_CONTROL_DMIC_IF (0x0004)
/* VTS_DMIC_ENABLE_DMIC_IF */
#define VTS_DMIC_ENABLE_DMIC_IF_OFFSET (31)
#define VTS_DMIC_ENABLE_DMIC_IF_SIZE (1)
#define VTS_DMIC_PERIOD_DATA2REQ_OFFSET (16)
#define VTS_DMIC_PERIOD_DATA2REQ_SIZE (2)
/* VTS_DMIC_CONTROL_DMIC_IF */
#define VTS_DMIC_HPF_EN_OFFSET (31)
#define VTS_DMIC_HPF_EN_SIZE (1)
#define VTS_DMIC_HPF_SEL_OFFSET (28)
#define VTS_DMIC_HPF_SEL_SIZE (1)
#define VTS_DMIC_CPS_SEL_OFFSET (27)
#define VTS_DMIC_CPS_SEL_SIZE (1)
#define VTS_DMIC_GAIN_OFFSET (24)
#define VTS_DMIC_GAIN_SIZE (3)
#define VTS_DMIC_DMIC_SEL_OFFSET (18)
#define VTS_DMIC_DMIC_SEL_SIZE (1)
#define VTS_DMIC_RCH_EN_OFFSET (17)
#define VTS_DMIC_RCH_EN_SIZE (1)
#define VTS_DMIC_LCH_EN_OFFSET (16)
#define VTS_DMIC_LCH_EN_SIZE (1)
#define VTS_DMIC_SYS_SEL_OFFSET (12)
#define VTS_DMIC_SYS_SEL_SIZE (2)
#define VTS_DMIC_POLARITY_CLK_OFFSET (10)
#define VTS_DMIC_POLARITY_CLK_SIZE (1)
#define VTS_DMIC_POLARITY_OUTPUT_OFFSET (9)
#define VTS_DMIC_POLARITY_OUTPUT_SIZE (1)
#define VTS_DMIC_POLARITY_INPUT_OFFSET (8)
#define VTS_DMIC_POLARITY_INPUT_SIZE (1)
#define VTS_DMIC_OVFW_CTRL_OFFSET (4)
#define VTS_DMIC_OVFW_CTRL_SIZE (1)
#define VTS_DMIC_CIC_SEL_OFFSET (0)
#define VTS_DMIC_CIC_SEL_SIZE (1)
/* CM4 */
#define VTS_CM4_R(x) (0x0010 + (x * 0x4))
#define VTS_CM4_PC (0x0004)
#define VTS_IRQ_VTS_ERROR (16)
#define VTS_IRQ_VTS_BOOT_COMPLETED (17)
#define VTS_IRQ_VTS_IPC_RECEIVED (18)
#define VTS_IRQ_VTS_VOICE_TRIGGERED (19)
#define VTS_IRQ_VTS_PERIOD_ELAPSED (20)
#define VTS_IRQ_VTS_REC_PERIOD_ELAPSED (21)
#define VTS_IRQ_VTS_DBGLOG_BUFZERO (22)
#define VTS_IRQ_VTS_DBGLOG_BUFONE (23)
#define VTS_IRQ_VTS_AUDIO_DUMP (24)
#define VTS_IRQ_VTS_LOG_DUMP (25)
#define VTS_IRQ_COUNT (26)
#define VTS_IRQ_AP_IPC_RECEIVED (0)
#define VTS_IRQ_AP_SET_DRAM_BUFFER (1)
#define VTS_IRQ_AP_START_RECOGNITION (2)
#define VTS_IRQ_AP_STOP_RECOGNITION (3)
#define VTS_IRQ_AP_START_COPY (4)
#define VTS_IRQ_AP_STOP_COPY (5)
#define VTS_IRQ_AP_SET_MODE (6)
#define VTS_IRQ_AP_POWER_DOWN (7)
#define VTS_IRQ_AP_TARGET_SIZE (8)
#define VTS_IRQ_AP_SET_REC_BUFFER (9)
#define VTS_IRQ_AP_START_REC (10)
#define VTS_IRQ_AP_STOP_REC (11)
#define VTS_IRQ_AP_RESTART_RECOGNITION (13)
#define VTS_IRQ_AP_TEST_COMMAND (15)
#define VTS_IRQ_LIMIT (32)
#define VTS_BAAW_BASE (0x60000000)
#define VTS_BAAW_SRC_START_ADDRESS (0x10000)
#define VTS_BAAW_SRC_END_ADDRESS (0x10004)
#define VTS_BAAW_REMAPPED_ADDRESS (0x10008)
#define VTS_BAAW_INIT_DONE (0x1000C)
#define BUFFER_BYTES_MAX (0xa0000)
#define PERIOD_BYTES_MIN (SZ_4)
#define PERIOD_BYTES_MAX (BUFFER_BYTES_MAX / 2)
#define SOUND_MODEL_SIZE_MAX (SZ_32K)
#define SOUND_MODEL_COUNT (3)
/* DRAM for copying VTS firmware logs */
#define LOG_BUFFER_BYTES_MAX (0x2000)
#define VTS_SRAMLOG_MSGS_OFFSET (0x59000)
/* VTS firmware version information offset */
#define VTSFW_VERSION_OFFSET (0x7c)
#define DETLIB_VERSION_OFFSET (0x78)
/* svoice net(0x8000) & grammar(0x300) binary sizes defined in firmware */
#define SOUND_MODEL_SVOICE_SIZE_MAX (0x8000 + 0x300)
/* google binary size defined in firmware (It is same with VTSDRV_MISC_MODEL_BIN_MAXSZ) */
#define SOUND_MODEL_GOOGLE_SIZE_MAX (0xB500)
/* VTS Model Binary Max buffer sizes */
#define VTS_MODEL_SVOICE_BIN_MAXSZ (SOUND_MODEL_SVOICE_SIZE_MAX)
#define VTS_MODEL_GOOGLE_BIN_MAXSZ (SOUND_MODEL_GOOGLE_SIZE_MAX)
enum ipc_state {
IDLE,
SEND_MSG,
SEND_MSG_OK,
SEND_MSG_FAIL,
};
enum trigger {
TRIGGER_NONE = -1,
TRIGGER_SVOICE = 0,
TRIGGER_GOOGLE = 1,
TRIGGER_SENSORY = 2,
TRIGGER_COUNT,
};
enum vts_platform_type {
PLATFORM_VTS_NORMAL_RECORD,
PLATFORM_VTS_TRIGGER_RECORD,
};
enum executionmode {
//default is off
VTS_OFF_MODE = 0,
//voice-trig-mode:Both LPSD & Trigger are enabled
VTS_VOICE_TRIGGER_MODE = 1,
//sound-detect-mode: Low Power sound Detect
VTS_SOUND_DETECT_MODE = 2,
//vt-always-mode: key phrase Detection only(Trigger)
VTS_VT_ALWAYS_ON_MODE = 3,
//google-trigger: key phrase Detection only(Trigger)
VTS_GOOGLE_TRIGGER_MODE = 4,
//sensory-trigger: key phrase Detection only(Trigger)
VTS_SENSORY_TRIGGER_MODE = 5,
//off:voice-trig-mode:Both LPSD & Trigger are enabled
VTS_VOICE_TRIGGER_MODE_OFF = 6,
//off:sound-detect-mode: Low Power sound Detect
VTS_SOUND_DETECT_MODE_OFF = 7,
//off:vt-always-mode: key phrase Detection only(Trigger)
VTS_VT_ALWAYS_ON_MODE_OFF = 8,
//off:google-trigger: key phrase Detection only(Trigger)
VTS_GOOGLE_TRIGGER_MODE_OFF = 9,
//off:sensory-trigger: key phrase Detection only(Trigger)
VTS_SENSORY_TRIGGER_MODE_OFF = 10,
VTS_MODE_COUNT,
};
enum vts_dump_type {
KERNEL_PANIC_DUMP,
VTS_FW_NOT_READY,
VTS_IPC_TRANS_FAIL,
RUNTIME_SUSPEND_DUMP,
VTS_DUMP_LAST,
};
enum vts_test_command {
VTS_DISABLE_LOGDUMP = 0x01000000,
VTS_ENABLE_LOGDUMP = 0x02000000,
VTS_DISABLE_AUDIODUMP = 0x04000000,
VTS_ENABLE_AUDIODUMP = 0x08000000,
VTS_DISABLE_DEBUGLOG = 0x10000000,
VTS_ENABLE_DEBUGLOG = 0x20000000,
VTS_ENABLE_SRAM_LOG = 0x80000000,
};
struct vts_ipc_msg {
int msg;
u32 values[3];
};
enum vts_micconf_type {
VTS_MICCONF_FOR_RECORD = 0,
VTS_MICCONF_FOR_TRIGGER = 1,
VTS_MICCONF_FOR_GOOGLE = 2,
};
enum vts_state_machine {
VTS_STATE_NONE = 0, /* runtime_suspended state */
VTS_STATE_VOICECALL = 1, /* sram L2Cache call state */
VTS_STATE_RUNTIME_RESUMING = 2, /* runtime_resume started */
VTS_STATE_RUNTIME_RESUMED = 3, /* runtime_resume done */
VTS_STATE_RECOG_STARTED = 4, /* Recognization started */
VTS_STATE_RECOG_TRIGGERED = 5, /* Recognize triggered */
VTS_STATE_SEAMLESS_REC_STARTED = 6, /* seamless record started */
VTS_STATE_SEAMLESS_REC_STOPPED = 7, /* seamless record stopped */
VTS_STATE_RECOG_STOPPED = 8, /* Recognization stopped */
VTS_STATE_RUNTIME_SUSPENDING = 9, /* runtime_suspend started */
VTS_STATE_RUNTIME_SUSPENDED = 10, /* runtime_suspend done */
};
struct vts_model_bin_info {
unsigned char *data;
size_t actual_sz;
size_t max_sz;
bool loaded;
};
struct vts_dbg_dump {
long long time;
enum vts_dump_type dbg_type;
unsigned int gpr[17];
char *sram_log;
char *sram_fw;
};
struct vts_log_buffer {
char *addr;
unsigned int size;
};
struct vts_data {
struct platform_device *pdev;
struct snd_soc_component *cmpnt;
void __iomem *sfr_base;
void __iomem *baaw_base;
void __iomem *sram_base;
void __iomem *dmic_base;
void __iomem *gpr_base;
size_t sram_size;
struct regmap *regmap_dmic;
struct clk *clk_rco;
struct clk *clk_dmic;
struct clk *clk_dmic_if;
struct clk *clk_dmic_sync;
struct pinctrl *pinctrl;
unsigned int vtsfw_version;
unsigned int vtsdetectlib_version;
const struct firmware *firmware;
unsigned int vtsdma_count;
unsigned long syssel_rate;
struct platform_device *pdev_mailbox;
struct platform_device *pdev_vtsdma[2];
struct proc_dir_entry *proc_dir_entry;
int irq[VTS_IRQ_LIMIT];
volatile enum ipc_state ipc_state_ap;
wait_queue_head_t ipc_wait_queue;
spinlock_t ipc_spinlock;
struct mutex ipc_mutex;
u32 dma_area_vts;
struct snd_dma_buffer dmab;
struct snd_dma_buffer dmab_rec;
struct snd_dma_buffer dmab_log;
struct snd_dma_buffer dmab_model;
u32 target_size;
volatile enum trigger active_trigger;
u32 voicerecog_start;
enum executionmode exec_mode;
bool vts_ready;
volatile unsigned long sram_acquired;
volatile bool enabled;
volatile bool running;
bool voicecall_enabled;
struct snd_soc_card *card;
int micclk_init_cnt;
unsigned int mic_ready;
bool irq_state;
u32 lpsdgain;
u32 dmicgain;
u32 amicgain;
char *sramlog_baseaddr;
u32 running_ipc;
struct wake_lock wake_lock;
unsigned int vts_state;
u32 vtslog_enabled;
bool audiodump_enabled;
bool logdump_enabled;
struct vts_model_bin_info svoice_info;
struct vts_model_bin_info google_info;
spinlock_t state_spinlock;
struct vts_dbg_dump p_dump[VTS_DUMP_LAST];
};
struct vts_platform_data {
unsigned int id;
struct platform_device *pdev_vts;
struct vts_data *vts_data;
struct snd_pcm_substream *substream;
enum vts_platform_type type;
volatile unsigned int pointer;
};
extern int vts_start_ipc_transaction(struct device *dev, struct vts_data *data,
int msg, u32 (*values)[3], int atomic, int sync);
extern int vts_send_ipc_ack(struct vts_data *data, u32 result);
extern void vts_register_dma(struct platform_device *pdev_vts,
struct platform_device *pdev_vts_dma, unsigned int id);
extern int vts_set_dmicctrl(struct platform_device *pdev, int micconf_type, bool enable);
extern int vts_sound_machine_drv_register(void);
#endif /* __SND_SOC_VTS_H */

View file

@ -0,0 +1,356 @@
/* sound/soc/samsung/vts/vts-plat.c
*
* ALSA SoC - Samsung VTS platfrom driver
*
* Copyright (c) 2016 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 version 2 as
* published by the Free Software Foundation.
*/
//#define DEBUG
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/pm_runtime.h>
#include <linux/firmware.h>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/wakelock.h>
#include <asm-generic/delay.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include <sound/samsung/mailbox.h>
#include <sound/samsung/vts.h>
#include <soc/samsung/exynos-pmu.h>
#include "vts.h"
static const struct snd_pcm_hardware vts_platform_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_BLOCK_TRANSFER
| SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16,
.rates = SNDRV_PCM_RATE_16000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = BUFFER_BYTES_MAX,
.period_bytes_min = PERIOD_BYTES_MIN,
.period_bytes_max = PERIOD_BYTES_MAX,
.periods_min = BUFFER_BYTES_MAX / PERIOD_BYTES_MAX,
.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
};
static int vts_platform_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
struct snd_pcm_runtime *runtime = substream->runtime;
if (data->type == PLATFORM_VTS_TRIGGER_RECORD) {
snd_pcm_set_runtime_buffer(substream, &data->vts_data->dmab);
} else {
snd_pcm_set_runtime_buffer(substream, &data->vts_data->dmab_rec);
}
dev_info(dev, "%s:%s:DmaAddr=%pad Total=%zu PrdSz=%u(%u) #Prds=%u dma_area=%p\n",
__func__, snd_pcm_stream_str(substream), &runtime->dma_addr,
runtime->dma_bytes, params_period_size(params),
params_period_bytes(params), params_periods(params),
runtime->dma_area);
data->pointer = 0;
return 0;
}
static int vts_platform_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
dev_dbg(dev, "%s\n", __func__);
return 0;
}
static int vts_platform_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
dev_info(dev, "%s\n", __func__);
return 0;
}
static int vts_platform_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
u32 values[3] = {0,0,0};
int result = 0;
dev_info(dev, "%s ++ CMD: %d\n", __func__, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (data->type == PLATFORM_VTS_TRIGGER_RECORD) {
dev_dbg(dev, "%s VTS_IRQ_AP_START_COPY\n", __func__);
result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_START_COPY, &values, 1, 1);
} else {
dev_dbg(dev, "%s VTS_IRQ_AP_START_REC\n", __func__);
result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_START_REC, &values, 1, 1);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (data->type == PLATFORM_VTS_TRIGGER_RECORD) {
dev_dbg(dev, "%s VTS_IRQ_AP_STOP_COPY\n", __func__);
result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_STOP_COPY, &values, 1, 1);
} else {
dev_dbg(dev, "%s VTS_IRQ_AP_STOP_REC\n", __func__);
result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_STOP_REC, &values, 1, 1);
}
break;
default:
result = -EINVAL;
break;
}
dev_info(dev, "%s -- CMD: %d\n", __func__, cmd);
return result;
}
static snd_pcm_uframes_t vts_platform_pointer(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
struct snd_pcm_runtime *runtime = substream->runtime;
dev_dbg(dev, "%s: pointer=%08x\n", __func__, data->pointer);
return bytes_to_frames(runtime, data->pointer);
}
static int vts_platform_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
int result = 0;
dev_info(dev, "%s\n", __func__);
if (data->vts_data->voicecall_enabled) {
dev_warn(dev, "%s VTS SRAM is Used for CP call\n",
__func__);
return -EBUSY;
}
pm_runtime_get_sync(dev);
snd_soc_set_runtime_hwparams(substream, &vts_platform_hardware);
if (data->type == PLATFORM_VTS_NORMAL_RECORD) {
dev_info(dev, "%s open --\n", __func__);
result = vts_set_dmicctrl(data->vts_data->pdev,
VTS_MICCONF_FOR_RECORD, true);
if (result < 0) {
dev_err(dev, "%s: MIC control configuration failed\n", __func__);
pm_runtime_put_sync(dev);
}
}
return result;
}
static int vts_platform_close(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
int result = 0;
dev_info(dev, "%s\n", __func__);
if (data->vts_data->voicecall_enabled) {
dev_warn(dev, "%s VTS SRAM is Used for CP call\n",
__func__);
pm_runtime_get_sync(dev);
return -EBUSY;
}
if (data->type == PLATFORM_VTS_NORMAL_RECORD) {
dev_info(dev, "%s close --\n", __func__);
result = vts_set_dmicctrl(data->vts_data->pdev,
VTS_MICCONF_FOR_RECORD, false);
if (result < 0)
dev_warn(dev, "%s: MIC control configuration failed\n", __func__);
}
pm_runtime_put_sync(dev);
return result;
}
static int vts_platform_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct snd_pcm_runtime *runtime = substream->runtime;
dev_info(dev, "%s\n", __func__);
return dma_mmap_writecombine(dev, vma,
runtime->dma_area,
runtime->dma_addr,
runtime->dma_bytes);
}
static struct snd_pcm_ops vts_platform_ops = {
.open = vts_platform_open,
.close = vts_platform_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = vts_platform_hw_params,
.hw_free = vts_platform_hw_free,
.prepare = vts_platform_prepare,
.trigger = vts_platform_trigger,
.pointer = vts_platform_pointer,
.mmap = vts_platform_mmap,
};
static int vts_platform_new(struct snd_soc_pcm_runtime *runtime)
{
struct snd_soc_platform *platform = runtime->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
struct snd_pcm_substream *substream = runtime->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
dev_info(dev, "%s \n", __func__);
data->substream = substream;
dev_info(dev, "%s Update Soc Card from runtime!!\n", __func__);
data->vts_data->card = runtime->card;
return 0;
}
static void vts_platform_free(struct snd_pcm *pcm)
{
return;
}
static const struct snd_soc_platform_driver vts_dma = {
.ops = &vts_platform_ops,
.pcm_new = vts_platform_new,
.pcm_free = vts_platform_free,
};
static int samsung_vts_dma_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *np_vts;
struct vts_platform_data *data;
int result;
const char *type;
dev_info(dev, "%s \n", __func__);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data) {
dev_err(dev, "Failed to allocate memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, data);
np_vts = of_parse_phandle(np, "vts", 0);
if (!np_vts) {
dev_err(dev, "Failed to get vts device node\n");
return -EPROBE_DEFER;
}
data->pdev_vts = of_find_device_by_node(np_vts);
if (!data->pdev_vts) {
dev_err(dev, "Failed to get vts platform device\n");
return -EPROBE_DEFER;
}
data->vts_data = platform_get_drvdata(data->pdev_vts);
result = of_property_read_u32_index(np, "id", 0, &data->id);
if (result < 0) {
dev_err(dev, "id property reading fail\n");
return result;
}
result = of_property_read_string(np, "type", &type);
if (result < 0) {
dev_err(dev, "type property reading fail\n");
return result;
}
if (!strncmp(type, "vts-record", sizeof("vts-record"))) {
data->type = PLATFORM_VTS_NORMAL_RECORD;
dev_info(dev, "%s - vts-record Probed \n", __func__);
} else {
data->type = PLATFORM_VTS_TRIGGER_RECORD;
dev_info(dev, "%s - vts-trigger-record Probed \n", __func__);
}
vts_register_dma(data->vts_data->pdev, pdev, data->id);
return snd_soc_register_platform(&pdev->dev, &vts_dma);
}
static int samsung_vts_dma_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static const struct of_device_id samsung_vts_dma_match[] = {
{
.compatible = "samsung,vts-dma",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_vts_dma_match);
static struct platform_driver samsung_vts_dma_driver = {
.probe = samsung_vts_dma_probe,
.remove = samsung_vts_dma_remove,
.driver = {
.name = "samsung-vts-dma",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_vts_dma_match),
},
};
module_platform_driver(samsung_vts_dma_driver);
/* Module information */
MODULE_AUTHOR("Palli Satish Kumar Reddy, <palli.satish@samsung.com>");
MODULE_DESCRIPTION("Samsung VTS DMA");
MODULE_ALIAS("platform:samsung-vts-dma");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,183 @@
/* sound/soc/samsung/vts/vts_dump.c
*
* ALSA SoC - Samsung VTS dump driver
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
//#define DEBUG
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
#include <sound/soc.h>
#include "vts_dump.h"
#define S_IRWUG (S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP)
struct vts_dump_info {
struct device *dev;
struct mutex audiolock;
struct mutex loglock;
u32 audioaddr_offset;
u32 audiodump_sz;
u32 logaddr_offset;
u32 logdump_sz;
};
static struct vts_dump_info gdump_info;
static void vts_audiodump_flush_work_func(struct work_struct *work)
{
struct device *dev = gdump_info.dev;
struct vts_dump_info *dump_info = &gdump_info;
struct vts_data *data = dev_get_drvdata(dev);
char filename[SZ_64];
struct file *filp;
dev_dbg(dev, "%s: SRAM Offset: 0x%x Size: %d\n", __func__,
dump_info->audioaddr_offset, dump_info->audiodump_sz);
mutex_lock(&gdump_info.audiolock);
sprintf(filename, "/data/vts_audiodump.raw");
filp = filp_open(filename, O_RDWR | O_APPEND |
O_CREAT, S_IRUSR | S_IWUSR);
dev_info(dev, "AudioDump appended mode\n");
if (!IS_ERR_OR_NULL(filp)) {
void *area = (void *)(data->sram_base +
dump_info->audioaddr_offset);
size_t bytes = (size_t)dump_info->audiodump_sz;
/* dev_dbg(dev, " %p, %zx\n", area, bytes); */
kernel_write(filp, area, bytes, filp->f_pos);
dev_dbg(dev, "kernel_write %p, %zx\n", area, bytes);
vfs_fsync(filp, 1);
filp_close(filp, NULL);
} else {
dev_err(dev, "VTS Audiodump file [%s] open error: %ld\n",
filename, PTR_ERR(filp));
}
vts_send_ipc_ack(data, 1);
mutex_unlock(&gdump_info.audiolock);
}
static void vts_logdump_flush_work_func(struct work_struct *work)
{
struct device *dev = gdump_info.dev;
struct vts_dump_info *dump_info = &gdump_info;
struct vts_data *data = dev_get_drvdata(dev);
char filename[SZ_64];
struct file *filp;
dev_dbg(dev, "%s: SRAM Offset: 0x%x Size: %d\n", __func__,
dump_info->logaddr_offset, dump_info->logdump_sz);
mutex_lock(&gdump_info.loglock);
sprintf(filename, "/data/vts_logdump.txt");
filp = filp_open(filename, O_RDWR | O_APPEND |
O_CREAT, S_IRUSR | S_IWUSR);
dev_info(dev, "LogDump appended mode\n");
if (!IS_ERR_OR_NULL(filp)) {
void *area = (void *)(data->sram_base +
dump_info->logaddr_offset);
size_t bytes = (size_t)dump_info->logdump_sz;
/* dev_dbg(dev, " %p, %zx\n", area, bytes); */
kernel_write(filp, area, bytes, filp->f_pos);
dev_dbg(dev, "kernel_write %p, %zx\n", area, bytes);
vfs_fsync(filp, 1);
filp_close(filp, NULL);
} else {
dev_err(dev, "VTS Logdump file [%s] open error: %ld\n",
filename, PTR_ERR(filp));
}
vts_send_ipc_ack(data, 1);
mutex_unlock(&gdump_info.loglock);
}
static DECLARE_WORK(vts_audiodump_work, vts_audiodump_flush_work_func);
static DECLARE_WORK(vts_logdump_work, vts_logdump_flush_work_func);
void vts_audiodump_schedule_flush(struct device *dev)
{
struct vts_data *data = dev_get_drvdata(dev);
if (gdump_info.audioaddr_offset) {
schedule_work(&vts_audiodump_work);
dev_dbg(dev, "%s: AudioDump Scheduled\n", __func__);
} else {
dev_warn(dev, "%s: AudioDump address not registered\n",
__func__);
/* send ipc ack to unblock vts firmware */
vts_send_ipc_ack(data, 1);
}
}
EXPORT_SYMBOL(vts_audiodump_schedule_flush);
void vts_logdump_schedule_flush(struct device *dev)
{
struct vts_data *data = dev_get_drvdata(dev);
if (gdump_info.logaddr_offset) {
schedule_work(&vts_logdump_work);
dev_dbg(dev, "%s: LogDump Scheduled\n", __func__);
} else {
dev_warn(dev, "%s: LogDump address not registered\n",
__func__);
/* send ipc ack to unblock vts firmware */
vts_send_ipc_ack(data, 1);
}
}
EXPORT_SYMBOL(vts_logdump_schedule_flush);
void vts_dump_addr_register(
struct device *dev,
u32 addroffset,
u32 dumpsz,
u32 dump_mode)
{
struct vts_data *data = dev_get_drvdata(dev);
gdump_info.dev = dev;
if ((addroffset + dumpsz) > data->sram_size) {
dev_warn(dev, "%s: wrong offset[0x%x] or size[0x%x]\n",
__func__, addroffset, dumpsz);
return;
}
if (dump_mode == VTS_AUDIO_DUMP) {
gdump_info.audioaddr_offset = addroffset;
gdump_info.audiodump_sz = dumpsz;
} else if (dump_mode == VTS_LOG_DUMP) {
gdump_info.logaddr_offset = addroffset;
gdump_info.logdump_sz = dumpsz;
} else
dev_warn(dev, "%s: Unknown dump mode\n", __func__);
dev_info(dev, "%s: %sDump offset[0x%x] size [%d]Scheduled\n",
__func__, (dump_mode ? "Log" : "Audio"),
addroffset, dumpsz);
}
EXPORT_SYMBOL(vts_dump_addr_register);
static int __init samsung_vts_dump_late_initcall(void)
{
pr_info("%s\n", __func__);
mutex_init(&gdump_info.audiolock);
mutex_init(&gdump_info.loglock);
gdump_info.audioaddr_offset = 0;
gdump_info.audiodump_sz = 0;
gdump_info.logaddr_offset = 0;
gdump_info.logdump_sz = 0;
return 0;
}
late_initcall(samsung_vts_dump_late_initcall);

View file

@ -0,0 +1,40 @@
/* sound/soc/samsung/vts/vts_dump.h
*
* ALSA SoC - Samsung vts dump driver
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_VTS_DUMP_H
#define __SND_SOC_VTS_DUMP_H
#include <linux/device.h>
#include "vts.h"
#define VTS_ADUIODUMP_AFTER_MINS 2 // VTS will dump 4.4 sec data after every 2 minutes
#define VTS_LOGDUMP_AFTER_MINS 1 // VTS will dump available log after every 1 minute
enum vts_dump_mode {
VTS_AUDIO_DUMP = 0,
VTS_LOG_DUMP = 1,
};
/**
* Schedule pcm dump from sram memory to file
* @param[in] dev pointer to vts device
* @param[in] addroffset SRAM offset for PCM dta
* @param[in] size size of pcm data
*/
extern void vts_audiodump_schedule_flush(struct device *dev);
extern void vts_logdump_schedule_flush(struct device *dev);
extern void vts_dump_addr_register(
struct device *dev,
u32 addroffset,
u32 dumpsz,
u32 dump_mode);
#endif /* __SND_SOC_VTS_DUMP_H */

View file

@ -0,0 +1,302 @@
/* sound/soc/samsung/vts/vts_log.c
*
* ALSA SoC - Samsung VTS Log driver
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
//#define DEBUG
#include <linux/io.h>
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
#include <sound/soc.h>
#include "vts.h"
#include "vts_log.h"
#define VTS_LOG_BUFFER_SIZE (SZ_1M)
#define S_IRWUG (S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP)
struct vts_kernel_log_buffer {
char *buffer;
unsigned int index;
bool wrap;
bool updated;
wait_queue_head_t wq;
};
struct vts_log_buffer_info {
struct device *dev;
struct mutex lock;
struct vts_log_buffer log_buffer;
struct vts_kernel_log_buffer kernel_buffer;
bool registered;
u32 logbuf_index;
};
static struct dentry *vts_dbg_root_dir __read_mostly;
static struct vts_log_buffer_info glogbuf_info;
struct dentry *vts_dbg_get_root_dir(void)
{
pr_debug("%s\n", __func__);
if (vts_dbg_root_dir == NULL)
vts_dbg_root_dir = debugfs_create_dir("vts", NULL);
return vts_dbg_root_dir;
}
static ssize_t vts_log_file_index;
static int vts_log_file_open(struct inode *inode, struct file *file)
{
struct vts_log_buffer_info *info = inode->i_private;
struct vts_data *data = dev_get_drvdata(info->dev);
u32 values[3] = {0, 0, 0};
int result = 0;
dev_dbg(info->dev, "%s\n", __func__);
file->private_data = inode->i_private;
vts_log_file_index = -1;
if (data->vts_ready) {
values[0] = VTS_ENABLE_DEBUGLOG;
values[1] = 0;
values[2] = 0;
result = vts_start_ipc_transaction(info->dev, data, VTS_IRQ_AP_TEST_COMMAND, &values, 0, 1);
if (result < 0)
dev_err(info->dev, "%s Enable_debuglog ipc transaction failed\n", __func__);
}
data->vtslog_enabled = 1;
return 0;
}
static int vts_log_file_close(struct inode *inode, struct file *file)
{
struct vts_log_buffer_info *info = inode->i_private;
struct vts_data *data = dev_get_drvdata(info->dev);
u32 values[3] = {0, 0, 0};
int result = 0;
dev_dbg(info->dev, "%s\n", __func__);
vts_log_file_index = -1;
if (data->vts_ready) {
values[0] = VTS_DISABLE_DEBUGLOG;
values[1] = 0;
values[2] = 0;
result = vts_start_ipc_transaction(info->dev, data, VTS_IRQ_AP_TEST_COMMAND, &values, 0, 1);
if (result < 0)
dev_err(info->dev, "%s Disable_debuglog ipc transaction failed\n", __func__);
/* reset VTS SRAM debug log buffer */
vts_register_log_buffer(info->dev, 0, 0);
}
data->vtslog_enabled = 0;
return 0;
}
static ssize_t vts_log_file_read(
struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
struct vts_log_buffer_info *info = file->private_data;
struct vts_kernel_log_buffer *kernel_buffer = &info->kernel_buffer;
size_t end, size;
bool first = (vts_log_file_index < 0);
int result;
dev_dbg(info->dev, "%s(%zu, %lld)\n", __func__, count, *ppos);
mutex_lock(&info->lock);
if (vts_log_file_index < 0)
vts_log_file_index = likely(kernel_buffer->wrap) ?
kernel_buffer->index : 0;
do {
end = ((vts_log_file_index < kernel_buffer->index) ||
((vts_log_file_index == kernel_buffer->index)
&& !first)) ? kernel_buffer->index
: VTS_LOG_BUFFER_SIZE;
size = min(end - vts_log_file_index, count);
if (size == 0) {
mutex_unlock(&info->lock);
if (file->f_flags & O_NONBLOCK) {
dev_dbg(info->dev, "non block\n");
return -EAGAIN;
}
kernel_buffer->updated = false;
result = wait_event_interruptible(kernel_buffer->wq,
kernel_buffer->updated);
if (result != 0) {
dev_dbg(info->dev, "interrupted\n");
return result;
}
mutex_lock(&info->lock);
}
#ifdef VERBOSE_LOG
dev_dbg(info->dev, "loop %zu, %zu, %zd, %zu\n", size, end, vts_log_file_index, count);
#endif
} while (size == 0);
dev_dbg(info->dev, "start=%zd, end=%zd size=%zd\n", vts_log_file_index, end, size);
if (copy_to_user(buf, kernel_buffer->buffer + vts_log_file_index, size))
return -EFAULT;
vts_log_file_index += size;
if (vts_log_file_index >= VTS_LOG_BUFFER_SIZE)
vts_log_file_index = 0;
mutex_unlock(&info->lock);
dev_dbg(info->dev, "%s: size = %zd\n", __func__, size);
return size;
}
static unsigned int vts_log_file_poll(struct file *file, poll_table *wait)
{
struct vts_log_buffer_info *info = file->private_data;
struct vts_kernel_log_buffer *kernel_buffer = &info->kernel_buffer;
dev_dbg(info->dev, "%s\n", __func__);
poll_wait(file, &kernel_buffer->wq, wait);
return POLLIN | POLLRDNORM;
}
static const struct file_operations vts_log_fops = {
.open = vts_log_file_open,
.release = vts_log_file_close,
.read = vts_log_file_read,
.poll = vts_log_file_poll,
.llseek = generic_file_llseek,
.owner = THIS_MODULE,
};
static void vts_log_memcpy(struct device *dev,
char *src, size_t size)
{
struct vts_kernel_log_buffer *kernel_buffer = NULL;
size_t left_size = 0;
kernel_buffer = &glogbuf_info.kernel_buffer;
left_size = VTS_LOG_BUFFER_SIZE - kernel_buffer->index;
dev_dbg(dev, "%s(%zu)\n", __func__, size);
if (left_size < size) {
#ifdef VERBOSE_LOG
dev_dbg(dev, "0: %s\n", src);
#endif
memcpy_fromio(kernel_buffer->buffer +
kernel_buffer->index, src, left_size);
src += left_size;
size -= left_size;
kernel_buffer->index = 0;
kernel_buffer->wrap = true;
}
#ifdef VERBOSE_LOG
dev_dbg(dev, "1: %s\n", src);
#endif
memcpy_fromio(kernel_buffer->buffer + kernel_buffer->index, src, size);
kernel_buffer->index += size;
}
static void vts_log_flush_work_func(struct work_struct *work)
{
struct device *dev = glogbuf_info.dev;
struct vts_log_buffer *log_buffer = &glogbuf_info.log_buffer;
struct vts_kernel_log_buffer *kernel_buffer = NULL;
int logbuf_index = glogbuf_info.logbuf_index;
kernel_buffer = &glogbuf_info.kernel_buffer;
dev_dbg(dev, "%s: LogBuffer Index: %d\n", __func__,
logbuf_index);
mutex_lock(&glogbuf_info.lock);
vts_log_memcpy(dev, (log_buffer->addr +
log_buffer->size * logbuf_index), log_buffer->size);
/* memory barrier */
wmb();
mutex_unlock(&glogbuf_info.lock);
kernel_buffer->updated = true;
wake_up_interruptible(&kernel_buffer->wq);
}
static DECLARE_WORK(vts_log_work, vts_log_flush_work_func);
void vts_log_schedule_flush(struct device *dev, u32 index)
{
if (glogbuf_info.registered &&
glogbuf_info.log_buffer.size) {
glogbuf_info.logbuf_index = index;
schedule_work(&vts_log_work);
dev_dbg(dev, "%s: VTS Log Buffer[%d] Scheduled\n",
__func__, index);
} else
dev_warn(dev, "%s: VTS Debugging buffer not registered\n",
__func__);
}
EXPORT_SYMBOL(vts_log_schedule_flush);
int vts_register_log_buffer(
struct device *dev,
u32 addroffset,
u32 logsz)
{
struct vts_data *data = dev_get_drvdata(dev);
dev_dbg(dev, "%s(offset 0x%x)\n", __func__, addroffset);
if ((addroffset + logsz) > data->sram_size) {
dev_warn(dev, "%s: wrong offset[0x%x] or size[0x%x]\n",
__func__, addroffset, logsz);
return -EINVAL;
}
if (!glogbuf_info.registered) {
glogbuf_info.dev = dev;
mutex_init(&glogbuf_info.lock);
glogbuf_info.kernel_buffer.buffer =
vzalloc(VTS_LOG_BUFFER_SIZE);
glogbuf_info.kernel_buffer.index = 0;
glogbuf_info.kernel_buffer.wrap = false;
init_waitqueue_head(&glogbuf_info.kernel_buffer.wq);
debugfs_create_file("vts-log", S_IRWUG,
vts_dbg_get_root_dir(),
&glogbuf_info, &vts_log_fops);
glogbuf_info.registered = true;
}
/* Update logging buffer address and size info */
glogbuf_info.log_buffer.addr = data->sram_base + addroffset;
glogbuf_info.log_buffer.size = logsz;
return 0;
}
EXPORT_SYMBOL(vts_register_log_buffer);
static int __init samsung_vts_log_late_initcall(void)
{
pr_info("%s\n", __func__);
return 0;
}
late_initcall(samsung_vts_log_late_initcall);

View file

@ -0,0 +1,36 @@
/* sound/soc/samsung/vts/vts_log.h
*
* ALSA SoC - Samsung vts Log driver
*
* Copyright (c) 2017 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 version 2 as
* published by the Free Software Foundation.
*/
#ifndef __SND_SOC_VTS_LOG_H
#define __SND_SOC_VTS_LOG_H
#include <linux/device.h>
#include "vts.h"
/**
* Schedule log flush sram memory to kernel memory
* @param[in] dev pointer to vts device
*/
extern void vts_log_schedule_flush(struct device *dev, u32 index);
/**
* Register abox log buffer
* @param[in] dev pointer to abox device
* @param[in] addroffset Sram log buffer offset
* @param[in] logsz log buffer size
* @return error code if any
*/
extern int vts_register_log_buffer(
struct device *dev,
u32 addroffset,
u32 logsz);
#endif /* __SND_SOC_VTS_LOG_H */

View file

@ -131,7 +131,9 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream)
fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN;
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
mutex_lock_nested(&fe->pcm_mutex, fe->pcm_subclass);
snd_soc_runtime_activate(fe, stream);
mutex_unlock(&fe->pcm_mutex);
mutex_unlock(&fe->card->mutex);
@ -247,7 +249,9 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream)
else
stream = SNDRV_PCM_STREAM_CAPTURE;
mutex_lock_nested(&fe->pcm_mutex, fe->pcm_subclass);
snd_soc_runtime_deactivate(fe, stream);
mutex_unlock(&fe->pcm_mutex);
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
@ -314,6 +318,7 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd)
{
struct snd_soc_pcm_runtime *fe = cstream->private_data;
struct snd_soc_platform *platform = fe->platform;
enum snd_soc_dpcm_trigger trigger;
int ret = 0, stream;
if (cmd == SND_COMPR_TRIGGER_PARTIAL_DRAIN ||
@ -330,13 +335,46 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd)
else
stream = SNDRV_PCM_STREAM_CAPTURE;
trigger = fe->dai_link->trigger[stream];
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
switch (trigger) {
case SND_SOC_DPCM_TRIGGER_PRE_POST:
trigger = SND_SOC_DPCM_TRIGGER_PRE;
break;
case SND_SOC_DPCM_TRIGGER_POST_PRE:
trigger = SND_SOC_DPCM_TRIGGER_POST;
break;
default:
break;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
switch (trigger) {
case SND_SOC_DPCM_TRIGGER_PRE_POST:
trigger = SND_SOC_DPCM_TRIGGER_POST;
break;
case SND_SOC_DPCM_TRIGGER_POST_PRE:
trigger = SND_SOC_DPCM_TRIGGER_PRE;
break;
default:
break;
}
break;
}
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
if (platform->driver->compr_ops && platform->driver->compr_ops->trigger) {
ret = platform->driver->compr_ops->trigger(cstream, cmd);
if (ret < 0)
goto out;
if (trigger != SND_SOC_DPCM_TRIGGER_POST) {
if (platform->driver->compr_ops && platform->driver->compr_ops->trigger) {
ret = platform->driver->compr_ops->trigger(cstream, cmd);
if (ret < 0)
goto out;
}
}
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
@ -358,6 +396,13 @@ static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd)
break;
}
if (trigger == SND_SOC_DPCM_TRIGGER_POST) {
if (platform->driver->compr_ops && platform->driver->compr_ops->trigger) {
ret = platform->driver->compr_ops->trigger(cstream, cmd);
if (ret < 0)
goto out;
}
}
out:
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO;
mutex_unlock(&fe->card->mutex);
@ -447,6 +492,13 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream,
memset(&fe->dpcm[fe_substream->stream].hw_params, 0,
sizeof(struct snd_pcm_hw_params));
if (platform->driver->compr_ops && platform->driver->compr_ops->get_hw_params) {
ret = platform->driver->compr_ops->get_hw_params(cstream,
&fe->dpcm[fe_substream->stream].hw_params);
if (ret < 0)
goto out;
}
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
ret = dpcm_be_dai_hw_params(fe, stream);

View file

@ -1172,6 +1172,18 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
is_connected_input_ep, custom_stop_condition);
}
int snd_soc_dapm_connected_output_ep(struct snd_soc_dapm_widget *widget,
struct list_head *list)
{
return is_connected_output_ep(widget, list, NULL);
}
int snd_soc_dapm_connected_input_ep(struct snd_soc_dapm_widget *widget,
struct list_head *list)
{
return is_connected_input_ep(widget, list, NULL);
}
/**
* snd_soc_dapm_get_connected_widgets - query audio path and it's widgets.
* @dai: the soc DAI.

View file

@ -31,6 +31,9 @@
#include <sound/soc.h>
#include <sound/soc-dpcm.h>
#include <sound/initval.h>
#if defined(CONFIG_SEC_ABC)
#include <linux/sti/abc_common.h>
#endif
#define DPCM_MAX_BE_USERS 8
@ -459,13 +462,13 @@ static int soc_pcm_open(struct snd_pcm_substream *substream)
const char *codec_dai_name = "multicodec";
int i, ret = 0;
pinctrl_pm_select_default_state(cpu_dai->dev);
for (i = 0; i < rtd->num_codecs; i++)
pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev);
pm_runtime_get_sync(cpu_dai->dev);
for (i = 0; i < rtd->num_codecs; i++)
pm_runtime_get_sync(rtd->codec_dais[i]->dev);
pm_runtime_get_sync(platform->dev);
pinctrl_pm_select_default_state(cpu_dai->dev);
for (i = 0; i < rtd->num_codecs; i++)
pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev);
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
@ -581,6 +584,10 @@ dynamic:
return 0;
config_err:
#if defined(CONFIG_SEC_ABC)
dev_err(platform->dev, "ASoC:Notify sec abc driver of soc_pcm_open_fail\n");
sec_abc_send_event("MODULE=sound@ERROR=soc_pcm_open_fail");
#endif
if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
rtd->dai_link->ops->shutdown(substream);
@ -603,6 +610,12 @@ platform_err:
out:
mutex_unlock(&rtd->pcm_mutex);
for (i = 0; i < rtd->num_codecs; i++) {
if (!rtd->codec_dais[i]->active)
pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev);
}
if (!cpu_dai->active)
pinctrl_pm_select_sleep_state(cpu_dai->dev);
pm_runtime_mark_last_busy(platform->dev);
pm_runtime_put_autosuspend(platform->dev);
for (i = 0; i < rtd->num_codecs; i++) {
@ -612,12 +625,6 @@ out:
pm_runtime_mark_last_busy(cpu_dai->dev);
pm_runtime_put_autosuspend(cpu_dai->dev);
for (i = 0; i < rtd->num_codecs; i++) {
if (!rtd->codec_dais[i]->active)
pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev);
}
if (!cpu_dai->active)
pinctrl_pm_select_sleep_state(cpu_dai->dev);
return ret;
}
@ -715,6 +722,13 @@ static int soc_pcm_close(struct snd_pcm_substream *substream)
mutex_unlock(&rtd->pcm_mutex);
for (i = 0; i < rtd->num_codecs; i++) {
if (!rtd->codec_dais[i]->active)
pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev);
}
if (!cpu_dai->active)
pinctrl_pm_select_sleep_state(cpu_dai->dev);
pm_runtime_mark_last_busy(platform->dev);
pm_runtime_put_autosuspend(platform->dev);
@ -726,13 +740,6 @@ static int soc_pcm_close(struct snd_pcm_substream *substream)
pm_runtime_mark_last_busy(cpu_dai->dev);
pm_runtime_put_autosuspend(cpu_dai->dev);
for (i = 0; i < rtd->num_codecs; i++) {
if (!rtd->codec_dais[i]->active)
pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev);
}
if (!cpu_dai->active)
pinctrl_pm_select_sleep_state(cpu_dai->dev);
return 0;
}
@ -909,6 +916,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
codec_dai->channels = params_channels(&codec_params);
codec_dai->sample_bits = snd_pcm_format_physical_width(
params_format(&codec_params));
codec_dai->sample_width = params_width(&codec_params);
}
ret = soc_dai_hw_params(substream, params, cpu_dai);
@ -929,6 +937,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
cpu_dai->channels = params_channels(params);
cpu_dai->sample_bits =
snd_pcm_format_physical_width(params_format(params));
cpu_dai->sample_width = params_width(params);
out:
mutex_unlock(&rtd->pcm_mutex);
@ -975,6 +984,7 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
cpu_dai->rate = 0;
cpu_dai->channels = 0;
cpu_dai->sample_bits = 0;
cpu_dai->sample_width = 0;
}
for (i = 0; i < rtd->num_codecs; i++) {
@ -983,6 +993,7 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
codec_dai->rate = 0;
codec_dai->channels = 0;
codec_dai->sample_bits = 0;
codec_dai->sample_width = 0;
}
}
@ -1160,9 +1171,11 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe,
stream ? "<-" : "->", be->dai_link->name);
#ifdef CONFIG_DEBUG_FS
#ifndef CONFIG_SND_SOC_SAMSUNG_ABOX
if (fe->debugfs_dpcm_root)
dpcm->debugfs_state = debugfs_create_u32(be->dai_link->name, 0644,
fe->debugfs_dpcm_root, &dpcm->state);
#endif
#endif
return 1;
}
@ -1216,7 +1229,9 @@ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream)
dpcm_be_reparent(fe, dpcm->be, stream);
#ifdef CONFIG_DEBUG_FS
#ifndef CONFIG_SND_SOC_SAMSUNG_ABOX
debugfs_remove(dpcm->debugfs_state);
#endif
#endif
list_del(&dpcm->list_be);
list_del(&dpcm->list_fe);
@ -1924,7 +1939,7 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream)
/* perform any hw_params fixups */
if (be->dai_link->be_hw_params_fixup) {
ret = be->dai_link->be_hw_params_fixup(be,
&dpcm->hw_params);
&dpcm->hw_params, stream);
if (ret < 0) {
dev_err(be->dev,
"ASoC: hw_params BE fixup failed %d\n",
@ -1957,6 +1972,10 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream)
return 0;
unwind:
#if defined(CONFIG_SEC_ABC)
dev_err(dpcm->be->dev, "ASoC:Notify sec abc driver of dpcm_be_dai_hw_params_fail\n");
sec_abc_send_event("MODULE=sound@ERROR=dpcm_be_dai_hw_params_fail");
#endif
/* disable any enabled and non active backends */
list_for_each_entry_continue_reverse(dpcm, &fe->dpcm[stream].be_clients, list_be) {
struct snd_soc_pcm_runtime *be = dpcm->be;
@ -2051,7 +2070,8 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream,
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP))
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED))
continue;
ret = dpcm_do_trigger(dpcm, be_substream, cmd);
@ -2081,7 +2101,8 @@ int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream,
be->dpcm[stream].state = SND_SOC_DPCM_STATE_START;
break;
case SNDRV_PCM_TRIGGER_STOP:
if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START)
if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED))
continue;
if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream))
@ -2134,6 +2155,37 @@ static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd)
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
switch (trigger) {
case SND_SOC_DPCM_TRIGGER_PRE_POST:
trigger = SND_SOC_DPCM_TRIGGER_PRE;
break;
case SND_SOC_DPCM_TRIGGER_POST_PRE:
trigger = SND_SOC_DPCM_TRIGGER_POST;
break;
default:
break;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
switch (trigger) {
case SND_SOC_DPCM_TRIGGER_PRE_POST:
trigger = SND_SOC_DPCM_TRIGGER_POST;
break;
case SND_SOC_DPCM_TRIGGER_POST_PRE:
trigger = SND_SOC_DPCM_TRIGGER_PRE;
break;
default:
break;
}
break;
}
switch (trigger) {
case SND_SOC_DPCM_TRIGGER_PRE:
/* call trigger on the frontend before the backend. */
@ -2832,10 +2884,10 @@ EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_set_state);
int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe,
struct snd_soc_pcm_runtime *be, int stream)
{
struct snd_soc_dpcm *dpcm;
struct snd_soc_dpcm *dpcm, *tmp;
int state;
list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) {
list_for_each_entry_safe(dpcm, tmp, &be->dpcm[stream].fe_clients, list_fe) {
if (dpcm->fe == fe)
continue;

View file

@ -98,6 +98,8 @@ static struct snd_soc_codec_driver dummy_codec;
SNDRV_PCM_FMTBIT_U16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_U24_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_U20_3LE | \
SNDRV_PCM_FMTBIT_S32_LE | \
SNDRV_PCM_FMTBIT_U32_LE | \
SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE)

View file

@ -22,6 +22,12 @@ config SND_USB_AUDIO
To compile this driver as a module, choose M here: the module
will be called snd-usb-audio.
config SND_EXYNOS_USB_AUDIO
bool "EXYNOS USB Audio offloading"
depends on SND_USB_AUDIO && SND_SOC_SAMSUNG_ABOX
help
Say Y here to include support for Exynos USB Audio ABOX offloading.
config SND_USB_UA101
tristate "Edirol UA-101/UA-1000 driver"
select SND_PCM

View file

@ -19,7 +19,7 @@ snd-usbmidi-lib-objs := midi.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o
obj-$(CONFIG_SND_EXYNOS_USB_AUDIO) += exynos_usb_audio.o
obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o

View file

@ -46,6 +46,7 @@
#include <linux/usb/audio-v2.h>
#include <linux/module.h>
#include <linux/usb/exynos_usb_audio.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/info.h>
@ -548,6 +549,12 @@ static int usb_audio_probe(struct usb_interface *intf,
int ifnum;
u32 id;
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
exynos_usb_audio_set_device(dev);
exynos_usb_audio_hcd(dev);
exynos_usb_audio_desc(dev);
exynos_usb_audio_map_buf(dev);
#endif
alts = &intf->altsetting[0];
ifnum = get_iface_desc(alts)->bInterfaceNumber;
id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
@ -640,13 +647,20 @@ static int usb_audio_probe(struct usb_interface *intf,
usb_set_intfdata(intf, chip);
atomic_dec(&chip->active);
mutex_unlock(&register_mutex);
if (dev->do_remote_wakeup)
usb_enable_autosuspend(dev);
return 0;
__error:
if (chip) {
/* chip->active is inside the chip->card object,
* decrement before memory is possibly returned.
*/
atomic_dec(&chip->active);
if (!chip->num_interfaces)
snd_card_free(chip->card);
atomic_dec(&chip->active);
}
mutex_unlock(&register_mutex);
return err;

View file

@ -23,6 +23,7 @@
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <linux/usb/exynos_usb_audio.h>
#include <sound/core.h>
#include <sound/info.h>
@ -366,6 +367,29 @@ static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface,
int clock;
bool writeable;
struct uac_clock_source_descriptor *cs_desc;
unsigned char ep;
unsigned char numEndpoints;
int direction;
int i;
numEndpoints = get_iface_desc(alts)->bNumEndpoints;
if (numEndpoints < 1)
return -EINVAL;
if (numEndpoints == 1) {
ep = get_endpoint(alts, 0)->bEndpointAddress;
} else {
for (i = 0; i < numEndpoints; i++) {
ep = get_endpoint(alts, i)->bmAttributes;
if (!(ep & 0x30)) {
ep = get_endpoint(alts, i)->bEndpointAddress;
break;
}
}
}
if (ep & 0x80)
direction = 1;
else
direction = 0;
clock = snd_usb_clock_find_source(chip, fmt->clock, true);
if (clock < 0)
@ -412,8 +436,14 @@ static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface,
* interface is active. */
if (rate != prev_rate) {
usb_set_interface(dev, iface, 0);
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
exynos_usb_audio_setintf(dev, fmt->iface, 0, direction);
#endif
snd_usb_set_interface_quirk(dev);
usb_set_interface(dev, iface, fmt->altsetting);
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
exynos_usb_audio_setintf(dev, fmt->iface, fmt->altsetting, direction);
#endif
snd_usb_set_interface_quirk(dev);
}

View file

@ -0,0 +1,651 @@
/*
* USB Audio offloading Driver for Exynos
*
* Copyright (c) 2017 by Kyounghye Yun <k-hye.yun@samsung.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.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/bitops.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/completion.h>
#include <sound/samsung/abox.h>
#include <linux/usb/exynos_usb_audio.h>
#define DEBUG 1
struct exynos_usb_audio *usb_audio;
void exynos_usb_audio_set_device(struct usb_device *udev)
{
usb_audio->udev = udev;
usb_audio->is_audio = 1;
}
int exynos_usb_audio_map_buf(struct usb_device *udev)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
struct hcd_hw_info *hwinfo = &udev->hwinfo;
int ret;
if (DEBUG)
pr_info("USB_AUDIO_IPC : %s ", __func__);
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_PCM_BUF;
usb_audio->in_buf_addr = hwinfo->in_dma;
if (DEBUG) {
pr_info("pcm in data buffer pa addr : %#08x %08x\n",
upper_32_bits(le64_to_cpu(hwinfo->in_dma)),
lower_32_bits(le64_to_cpu(hwinfo->in_dma)));
}
ret = abox_iommu_map(dev, USB_AUDIO_PCM_INBUF, hwinfo->in_dma, PAGE_SIZE * 15);
if (ret) {
pr_err("abox iommu mapping for pcm buf is failed\n");
return ret;
}
return 0;
}
int exynos_usb_audio_pcmbuf(struct usb_device *udev)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
struct hcd_hw_info *hwinfo = &udev->hwinfo;
u64 out_dma;
int ret;
if (DEBUG)
pr_info("USB_AUDIO_IPC : %s\n", __func__);
out_dma = abox_iova_to_phys(dev, USB_AUDIO_PCM_OUTBUF);
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_PCM_BUF;
erap_usb->param1 = lower_32_bits(le64_to_cpu(out_dma));
erap_usb->param2 = upper_32_bits(le64_to_cpu(out_dma));
erap_usb->param3 = lower_32_bits(le64_to_cpu(hwinfo->in_dma));
erap_usb->param4 = upper_32_bits(le64_to_cpu(hwinfo->in_dma));
if (DEBUG) {
pr_info("pcm out data buffer pa addr : %#08x %08x\n",
upper_32_bits(le64_to_cpu(out_dma)),
lower_32_bits(le64_to_cpu(out_dma)));
pr_info("pcm in data buffer pa addr : %#08x %08x\n",
upper_32_bits(le64_to_cpu(hwinfo->in_dma)),
lower_32_bits(le64_to_cpu(hwinfo->in_dma)));
pr_info("erap param2 : %#08x param1 : %08x\n",
erap_usb->param2, erap_usb->param1);
pr_info("erap param4 : %#08x param3 : %08x\n",
erap_usb->param4, erap_usb->param3);
}
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
dev_err(&usb_audio->udev->dev, "erap usb transfer pcm buffer is failed\n");
return -1;
}
return 0;
}
int exynos_usb_audio_setrate(int iface, int rate, int alt)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
int ret;
if (DEBUG)
pr_info("USB_AUDIO_IPC : %s\n", __func__);
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_SAMPLE_RATE;
erap_usb->param1 = iface;
erap_usb->param2 = rate;
erap_usb->param3 = alt;
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
dev_err(&usb_audio->udev->dev, "erap usb transfer sample rate is failed\n");
return -1;
}
return 0;
}
int exynos_usb_audio_setintf(struct usb_device *udev, int iface, int alt, int direction)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
struct hcd_hw_info *hwinfo = &udev->hwinfo;
u64 offset, in_offset, out_offset;
int ret;
if (DEBUG)
dev_info(&udev->dev, "USB_AUDIO_IPC : %s\n", __func__);
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_SET_INTF;
erap_usb->param1 = alt;
erap_usb->param2 = iface;
mutex_lock(&usb_audio->lock);
if (direction) {
/* IN EP */
dev_dbg(&udev->dev, "in deq : %#08llx\n", hwinfo->in_deq);
offset = hwinfo->old_in_deq % PAGE_SIZE;
ret = abox_iommu_unmap(dev, USB_AUDIO_IN_DEQ, (hwinfo->old_in_deq - offset), PAGE_SIZE);
if (ret) {
pr_err("abox iommu un-mapping for in buf is failed\n");
return ret;
}
in_offset = hwinfo->in_deq % PAGE_SIZE;
ret = abox_iommu_map(dev, USB_AUDIO_IN_DEQ, (hwinfo->in_deq - in_offset), PAGE_SIZE);
if (ret) {
pr_err("abox iommu mapping for in buf is failed\n");
return ret;
}
erap_usb->param3 = lower_32_bits(le64_to_cpu(hwinfo->in_deq));
erap_usb->param4 = upper_32_bits(le64_to_cpu(hwinfo->in_deq));
} else {
/* OUT EP */
dev_dbg(&udev->dev, "out deq : %#08llx\n", hwinfo->out_deq);
offset = hwinfo->old_out_deq % PAGE_SIZE;
ret = abox_iommu_unmap(dev, USB_AUDIO_OUT_DEQ, (hwinfo->old_out_deq - offset), PAGE_SIZE);
if (ret) {
pr_err("abox iommu un-mapping for in buf is failed\n");
return ret;
}
out_offset = hwinfo->out_deq % PAGE_SIZE;
ret = abox_iommu_map(dev, USB_AUDIO_OUT_DEQ, (hwinfo->out_deq - out_offset), PAGE_SIZE);
if (ret) {
pr_err("abox iommu mapping for out buf is failed\n");
return ret;
}
erap_usb->param3 = lower_32_bits(le64_to_cpu(hwinfo->out_deq));
erap_usb->param4 = upper_32_bits(le64_to_cpu(hwinfo->out_deq));
}
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
dev_err(&usb_audio->udev->dev, "erap usb hcd control failed\n");
return -1;
}
mutex_unlock(&usb_audio->lock);
if (DEBUG) {
dev_info(&udev->dev, "Alt#%d / Intf#%d / Direction %s / EP DEQ : %#08x %08x\n",
erap_usb->param1, erap_usb->param2,
direction ? "IN" : "OUT",
erap_usb->param4, erap_usb->param3);
}
return 0;
}
int exynos_usb_audio_hcd(struct usb_device *udev)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
struct hcd_hw_info *hwinfo = &udev->hwinfo;
int ret;
if (DEBUG) {
dev_info(&udev->dev, "USB_AUDIO_IPC : %s\n", __func__);
dev_info(&udev->dev, "=======[Check HW INFO] ========\n");
dev_info(&udev->dev, "slot_id : %d\n", hwinfo->slot_id);
dev_info(&udev->dev, "dcbaa : %#08llx\n", hwinfo->dcbaa_dma);
dev_info(&udev->dev, "save : %#08llx\n", hwinfo->save_dma);
dev_info(&udev->dev, "in_ctx : %#08llx\n", hwinfo->in_ctx);
dev_info(&udev->dev, "out_ctx : %#08llx\n", hwinfo->out_ctx);
dev_info(&udev->dev, "erst : %#08x %08x\n",
upper_32_bits(le64_to_cpu(hwinfo->erst_addr)),
lower_32_bits(le64_to_cpu(hwinfo->erst_addr)));
dev_info(&udev->dev, "===============================\n");
}
/* back up each address for unmap */
usb_audio->dcbaa_dma = hwinfo->dcbaa_dma;
usb_audio->save_dma = hwinfo->save_dma;
usb_audio->in_ctx = hwinfo->in_ctx;
usb_audio->out_ctx = hwinfo->out_ctx;
usb_audio->erst_addr = hwinfo->erst_addr;
usb_audio->speed = hwinfo->speed;
if (DEBUG)
dev_info(dev, "USB_AUDIO_IPC : SFR MAPPING!\n");
mutex_lock(&usb_audio->lock);
ret = abox_iommu_map(dev, USB_AUDIO_XHCI_BASE, USB_AUDIO_XHCI_BASE, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu mapping for in buf is failed\n");
return ret;
}
/*DCBAA mapping*/
ret = abox_iommu_map(dev, USB_AUDIO_SAVE_RESTORE, hwinfo->save_dma, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu mapping for save_restore buffer is failed\n");
return ret;
}
/*Device Context mapping*/
ret = abox_iommu_map(dev, USB_AUDIO_DEV_CTX, hwinfo->out_ctx, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu mapping for device ctx is failed\n");
return ret;
}
/*Input Context mapping*/
ret = abox_iommu_map(dev, USB_AUDIO_INPUT_CTX, hwinfo->in_ctx, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu mapping for input ctx is failed\n");
return ret;
}
/*ERST mapping*/
ret = abox_iommu_map(dev, USB_AUDIO_ERST, hwinfo->erst_addr, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu mapping for erst is failed\n");
return ret;
}
/* notify to Abox descriptor is ready*/
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_XHCI;
erap_usb->param1 = 1;
erap_usb->param2 = hwinfo->slot_id;
erap_usb->param3 = lower_32_bits(le64_to_cpu(hwinfo->erst_addr));
erap_usb->param4 = upper_32_bits(le64_to_cpu(hwinfo->erst_addr));
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
dev_err(&usb_audio->udev->dev, "erap usb hcd control failed\n");
return -1;
}
mutex_unlock(&usb_audio->lock);
return 0;
}
int exynos_usb_audio_desc(struct usb_device *udev)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
struct usb_host_config *host_cfg = udev->config;
struct usb_config_descriptor cfg_desc = host_cfg->desc;
int ret;
int cfgno = cfg_desc.bConfigurationValue;
unsigned char *buffer;
unsigned int len = udev->rawdesc_length;
u64 desc_addr;
u64 offset;
if (DEBUG)
dev_info(&udev->dev, "USB_AUDIO_IPC : %s\n", __func__);
/* need to memory mapping for usb descriptor */
buffer = udev->rawdescriptors[cfgno-1];
desc_addr = virt_to_phys(buffer);
offset = desc_addr % PAGE_SIZE;
/* store address information */
usb_audio->desc_addr = desc_addr;
usb_audio->offset = offset;
desc_addr -= offset;
mutex_lock(&usb_audio->lock);
ret = abox_iommu_map(dev, USB_AUDIO_DESC, desc_addr, (PAGE_SIZE * 2));
if (ret) {
dev_err(&udev->dev, "USB AUDIO: abox iommu mapping for usb descriptor is failed\n");
return ret;
}
/* notify to Abox descriptor is ready*/
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_DESC;
erap_usb->param1 = 1;
erap_usb->param2 = len;
erap_usb->param3 = offset;
erap_usb->param4 = usb_audio->speed;
if (DEBUG)
dev_info(&udev->dev, "paddr : %#08llx / offset : %#08llx / len : %d / speed : %d\n",
desc_addr + offset , offset, len, usb_audio->speed);
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
dev_err(&usb_audio->udev->dev, "erap usb desc control failed\n");
return -1;
}
mutex_unlock(&usb_audio->lock);
dev_info(&udev->dev, "USB AUDIO: Mapping descriptor for using on Abox USB F/W & Nofity mapping is done!");
return 0;
}
int exynos_usb_audio_conn(int is_conn)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
int ret;
if (DEBUG)
pr_info("USB_AUDIO_IPC : %s\n", __func__);
pr_info("USB DEVICE IS %s\n", is_conn? "CONNECTION" : "DISCONNECTION");
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_CONN;
erap_usb->param1 = is_conn;
if (!is_conn) {
if (usb_audio->is_audio) {
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
pr_err("erap usb dis_conn control failed\n");
return -1;
}
wait_for_completion_timeout(&usb_audio->out_task_done,
msecs_to_jiffies(1000));
/* free mapped iommu & memory */
ret = exynos_usb_audio_exit();
if (ret) {
pr_err("ERROR : usb audio exit is failed\n");
return -1;
}
usb_audio->is_audio = 0;
} else {
pr_err("Is not USB Audio device\n");
}
} else {
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
pr_err("erap usb conn control failed\n");
return -1;
}
}
return 0;
}
int exynos_usb_audio_pcm(int is_open, int direction)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
int ret;
if (DEBUG)
dev_info(dev, "USB_AUDIO_IPC : %s\n", __func__);
dev_info(dev, "PCM %s\n", is_open? "OPEN" : "CLOSE");
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_PCM_OPEN;
erap_usb->param1 = is_open;
erap_usb->param2 = direction;
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
dev_err(&usb_audio->udev->dev, "ERAP USB PCM control failed\n");
return -1;
}
return 0;
}
int exynos_usb_audio_l2(int is_l2)
{
ABOX_IPC_MSG msg;
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
struct IPC_ERAP_MSG *erap_msg = &msg.msg.erap;
struct ERAP_USB_AUDIO_PARAM *erap_usb = &erap_msg->param.usbaudio;
int ret;
if (!usb_audio->is_audio)
return 0;
if (DEBUG)
dev_info(dev, "USB_AUDIO_IPC : %s\n", __func__);
dev_info(dev, " USB Audio %s the L2 power mode\n", is_l2 ? "ENTER" : "EXIT");
msg.ipcid = IPC_ERAP;
erap_msg->msgtype = REALTIME_USB;
erap_usb->type = IPC_USB_L2;
/* 1: device entered L2 , 0: device exited L2 */
erap_usb->param1 = is_l2;
ret = abox_start_ipc_transaction(dev, msg.ipcid, &msg, sizeof(msg), 0, 1);
if (ret) {
dev_err(&usb_audio->udev->dev, "ERAP USB L2 control failed\n");
return -1;
}
return 0;
}
irqreturn_t exynos_usb_audio_irq_handler(int irq, void *dev_id, ABOX_IPC_MSG *msg)
{
struct IPC_ERAP_MSG *erap_msg = &msg->msg.erap;
if (DEBUG)
pr_info("%s: IRQ = %d (5 = ERAP)\n", __func__, irq);
if ( irq == IPC_ERAP ) {
if (erap_msg->msgtype == REALTIME_USB) {
switch (erap_msg->param.usbaudio.type) {
case IPC_USB_TASK:
if (erap_msg->param.usbaudio.param1) {
pr_info("irq : %d /* param1 : 1 , IN EP task done */\n", irq);
complete(&usb_audio->in_task_done);
} else {
pr_info("irq : %d /* param0 : 0 , OUT EP task done */\n", irq);
complete(&usb_audio->out_task_done);
}
break;
default:
pr_err("%s: unknown usb msg type\n", __func__);
break;
}
} else {
pr_err("%s: unknown message type\n", __func__);
}
} else {
pr_err("%s: unknown command\n", __func__);
}
/* FIXME */
return IRQ_HANDLED;
}
int exynos_usb_audio_init(struct device *dev, struct platform_device *pdev)
{
struct device_node *np = dev->of_node;
struct device_node *np_abox;
struct platform_device *pdev_abox;
if (DEBUG)
dev_info(dev, "USB_AUDIO_IPC : %s\n", __func__);
usb_audio = kmalloc(sizeof(struct exynos_usb_audio), GFP_KERNEL);
np_abox =of_parse_phandle(np, "abox", 0);
if(!np_abox) {
dev_err(dev, "Failed to get abox device node\n");
return -EPROBE_DEFER;
}
pdev_abox = of_find_device_by_node(np_abox);
if (!pdev_abox) {
dev_err(&usb_audio->udev->dev, "Failed to get abox platform device\n");
return -EPROBE_DEFER;
}
mutex_init(&usb_audio->lock);
init_completion(&usb_audio->out_task_done);
init_completion(&usb_audio->in_task_done);
usb_audio->abox = pdev_abox;
usb_audio->hcd_pdev = pdev;
usb_audio->udev = NULL;
usb_audio->is_audio = 0;
abox_register_irq_handler(&pdev_abox->dev, IPC_ERAP,
exynos_usb_audio_irq_handler, &usb_audio);
return 0;
}
int exynos_usb_audio_exit(void)
{
struct platform_device *pdev = usb_audio->abox;
struct device *dev = &pdev->dev;
int ret;
u64 addr;
u64 offset;
if (DEBUG)
dev_info(dev, "USB_AUDIO_IPC : %s\n", __func__);
/* unmapping in pcm buffer */
addr = usb_audio->in_buf_addr;
if (DEBUG)
pr_info("PCM IN BUFFER FREE: paddr = %#08llx\n", addr);
ret = abox_iommu_unmap(dev, USB_AUDIO_PCM_INBUF, addr, PAGE_SIZE * 15);
if (ret) {
pr_err("abox iommu unmapping for pcm buf is failed\n");
return ret;
}
/* unmapping usb descriptor */
addr = usb_audio->desc_addr;
offset = usb_audio->offset;
if (DEBUG)
pr_info("DESC BUFFER :: paddr : %#08llx / offset : %#08llx\n",
addr, offset);
ret = abox_iommu_unmap(dev, USB_AUDIO_DESC, addr - offset, (PAGE_SIZE * 2));
if (ret) {
pr_err("USB AUDIO: abox iommu unmapping for usb descriptor is failed\n");
return ret;
}
ret = abox_iommu_unmap(dev, USB_AUDIO_SAVE_RESTORE, usb_audio->save_dma, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu unmapping for dcbaa is failed\n");
return ret;
}
/*Device Context unmapping*/
ret = abox_iommu_unmap(dev, USB_AUDIO_DEV_CTX, usb_audio->out_ctx, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu unmapping for device ctx is failed\n");
return ret;
}
/*Input Context unmapping*/
ret = abox_iommu_unmap(dev, USB_AUDIO_INPUT_CTX, usb_audio->in_ctx, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu unmapping for input ctx is failed\n");
return ret;
}
/*ERST unmapping*/
ret = abox_iommu_unmap(dev, USB_AUDIO_ERST, usb_audio->erst_addr, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu Un-mapping for erst is failed\n");
return ret;
}
if ( DEBUG ) {
dev_info(dev, "USB_AUDIO_IPC : SFR MAPPING!\n");
}
ret = abox_iommu_unmap(dev, USB_AUDIO_XHCI_BASE, USB_AUDIO_XHCI_BASE, PAGE_SIZE);
if (ret) {
pr_err(" abox iommu Un-mapping for in buf is failed\n");
return ret;
}
return 0;
}

View file

@ -190,6 +190,7 @@ static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audiof
if (rate == 48000 && nr_rates == 1 &&
(chip->usb_id == USB_ID(0x0d8c, 0x0201) ||
chip->usb_id == USB_ID(0x0d8c, 0x0102) ||
chip->usb_id == USB_ID(0x0d8c, 0x0078) ||
chip->usb_id == USB_ID(0x0ccd, 0x00b1)) &&
fp->altsetting == 5 && fp->maxpacksize == 392)
rate = 96000;

View file

@ -21,6 +21,7 @@
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <linux/usb/exynos_usb_audio.h>
#include <sound/core.h>
#include <sound/pcm.h>
@ -257,7 +258,7 @@ static int start_endpoints(struct snd_usb_substream *subs)
}
}
dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep);
dev_info(&subs->dev->dev, "Starting sync EP @%p\n", ep);
ep->sync_slave = subs->data_endpoint;
err = snd_usb_endpoint_start(ep);
@ -524,6 +525,9 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
fmt->iface, fmt->altsetting, err);
return -EIO;
}
dev_info(&dev->dev, "setting usb interface %d:%d\n",
fmt->iface, fmt->altsetting);
subs->interface = -1;
subs->altset_idx = 0;
}
@ -543,8 +547,14 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
fmt->iface, fmt->altsetting, err);
return -EIO;
}
dev_dbg(&dev->dev, "setting usb interface %d:%d\n",
dev_info(&dev->dev, "setting usb interface %d:%d\n",
fmt->iface, fmt->altsetting);
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
exynos_usb_audio_setintf(dev, fmt->iface, fmt->altsetting, subs->direction);
dev_info(&dev->dev, "Endpoint #%x / Direction : %d \n",
fmt->endpoint, subs->direction);
#endif
subs->interface = fmt->iface;
subs->altset_idx = fmt->altset_idx;
@ -573,6 +583,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
return 0;
}
#ifndef CONFIG_SND_EXYNOS_USB_AUDIO
/*
* Return the score of matching two audioformats.
* Veto the audioformat if:
@ -718,6 +729,7 @@ static int configure_endpoint(struct snd_usb_substream *subs)
return ret;
}
#endif
/*
* hw_params callback
@ -792,6 +804,28 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream)
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
static int snd_usb_audio_setrate(struct snd_usb_audio *chip, int iface,
struct usb_host_interface *alts,
struct audioformat *fmt, int rate)
{
struct usb_device *dev = chip->dev;
unsigned int ep;
int err;
ep = get_endpoint(alts, 0)->bEndpointAddress;
err = exynos_usb_audio_setrate(iface, rate, fmt->altsetting);
if (err) {
dev_err(&dev->dev, "can not transfer sample rate to abox \n");
return err;
}
dev_info(&dev->dev, "[%s] iface : %d / alt : %d / set freq %d to ep #%x\n", __func__,
iface, fmt->altsetting, rate, ep);
return 0;
}
#endif
/*
* prepare callback
*
@ -805,6 +839,14 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
struct usb_interface *iface;
int ret;
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
ret = exynos_usb_audio_pcmbuf(subs->dev);
if (ret < 0) {
dev_err(&subs->dev->dev, "pcm buf transfer failed\n");
return ret;
}
#endif
if (! subs->cur_audiofmt) {
dev_err(&subs->dev->dev, "no format is specified!\n");
return -ENXIO;
@ -817,10 +859,10 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
ret = -EIO;
goto unlock;
}
#ifndef CONFIG_SND_EXYNOS_USB_AUDIO
snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint);
snd_usb_endpoint_sync_pending_stop(subs->data_endpoint);
#endif
ret = set_format(subs, subs->cur_audiofmt);
if (ret < 0)
goto unlock;
@ -834,7 +876,17 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
subs->cur_rate);
if (ret < 0)
goto unlock;
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
ret = snd_usb_audio_setrate(subs->stream->chip,
subs->cur_audiofmt->iface,
alts,
subs->cur_audiofmt,
subs->cur_rate);
if (ret < 0)
goto unlock;
#endif
#ifndef CONFIG_SND_EXYNOS_USB_AUDIO
if (subs->need_setup_ep) {
ret = configure_endpoint(subs);
if (ret < 0)
@ -859,7 +911,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
* updates for all URBs would happen at the same time when starting */
if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK)
ret = start_endpoints(subs);
#endif
unlock:
snd_usb_unlock_shutdown(subs->stream->chip);
return ret;
@ -1241,6 +1293,9 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream, int direction)
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usb_substream *subs = &as->substream[direction];
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
exynos_usb_audio_pcm(1, direction);
#endif
subs->interface = -1;
subs->altset_idx = 0;
runtime->hw = snd_usb_hardware;
@ -1261,11 +1316,19 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream, int direction)
struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
struct snd_usb_substream *subs = &as->substream[direction];
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
exynos_usb_audio_pcm(0, direction);
#endif
stop_endpoints(subs, true);
if (subs->interface >= 0 &&
!snd_usb_lock_shutdown(subs->stream->chip)) {
usb_set_interface(subs->dev, subs->interface, 0);
#ifdef CONFIG_SND_EXYNOS_USB_AUDIO
dev_info(&subs->dev->dev, "setting usb interface %d:%d, Direction: %d\n",
subs->interface, 0, direction);
exynos_usb_audio_setintf(subs->dev, subs->interface, 0, direction);
#endif
subs->interface = -1;
snd_usb_unlock_shutdown(subs->stream->chip);
}