mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-03 17:40:04 +00:00
![Claudiu Beznea](/assets/img/avatar_default.png)
Microchip PDMC IP doesn't filter microphone noises on startup. By default,
it captures data received from digital microphones after
the MCHP_PDMC_MR.EN bits are set. Thus when enable is set on PDMC side the
digital microphones might not be ready yet and PDMC captures data from then
in this time. This data captured is poc noise. To avoid this the software
workaround is to the following:
1/ enable PDMC channel
2/ wait 150ms (on SAMA7G5-EK setup)
3/ execute 16 dummy reads from RHR
4/ clear interrupts
5/ enable interrupts
6/ enable DMA channel
Fixes: 50291652af
("ASoC: atmel: mchp-pdmc: add PDMC driver")
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
Link: https://lore.kernel.org/r/20230228110145.3770525-4-claudiu.beznea@microchip.com
Signed-off-by: Mark Brown <broonie@kernel.org>
1177 lines
30 KiB
C
1177 lines
30 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// Driver for Microchip Pulse Density Microphone Controller (PDMC) interfaces
|
|
//
|
|
// Copyright (C) 2019-2022 Microchip Technology Inc. and its subsidiaries
|
|
//
|
|
// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
|
|
|
|
#include <dt-bindings/sound/microchip,pdmc.h>
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include <sound/core.h>
|
|
#include <sound/dmaengine_pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/tlv.h>
|
|
|
|
/*
|
|
* ---- PDMC Register map ----
|
|
*/
|
|
#define MCHP_PDMC_CR 0x00 /* Control Register */
|
|
#define MCHP_PDMC_MR 0x04 /* Mode Register */
|
|
#define MCHP_PDMC_CFGR 0x08 /* Configuration Register */
|
|
#define MCHP_PDMC_RHR 0x0C /* Receive Holding Register */
|
|
#define MCHP_PDMC_IER 0x14 /* Interrupt Enable Register */
|
|
#define MCHP_PDMC_IDR 0x18 /* Interrupt Disable Register */
|
|
#define MCHP_PDMC_IMR 0x1C /* Interrupt Mask Register */
|
|
#define MCHP_PDMC_ISR 0x20 /* Interrupt Status Register */
|
|
#define MCHP_PDMC_VER 0x50 /* Version Register */
|
|
|
|
/*
|
|
* ---- Control Register (Write-only) ----
|
|
*/
|
|
#define MCHP_PDMC_CR_SWRST BIT(0) /* Software Reset */
|
|
|
|
/*
|
|
* ---- Mode Register (Read/Write) ----
|
|
*/
|
|
#define MCHP_PDMC_MR_PDMCEN_MASK GENMASK(3, 0)
|
|
#define MCHP_PDMC_MR_PDMCEN(ch) (BIT(ch) & MCHP_PDMC_MR_PDMCEN_MASK)
|
|
|
|
#define MCHP_PDMC_MR_OSR_MASK GENMASK(17, 16)
|
|
#define MCHP_PDMC_MR_OSR64 (1 << 16)
|
|
#define MCHP_PDMC_MR_OSR128 (2 << 16)
|
|
#define MCHP_PDMC_MR_OSR256 (3 << 16)
|
|
|
|
#define MCHP_PDMC_MR_SINCORDER_MASK GENMASK(23, 20)
|
|
#define MCHP_PDMC_MR_SINCORDER(order) (((order) << 20) & \
|
|
MCHP_PDMC_MR_SINCORDER_MASK)
|
|
|
|
#define MCHP_PDMC_MR_SINC_OSR_MASK GENMASK(27, 24)
|
|
#define MCHP_PDMC_MR_SINC_OSR_DIS (0 << 24)
|
|
#define MCHP_PDMC_MR_SINC_OSR_8 (1 << 24)
|
|
#define MCHP_PDMC_MR_SINC_OSR_16 (2 << 24)
|
|
#define MCHP_PDMC_MR_SINC_OSR_32 (3 << 24)
|
|
#define MCHP_PDMC_MR_SINC_OSR_64 (4 << 24)
|
|
#define MCHP_PDMC_MR_SINC_OSR_128 (5 << 24)
|
|
#define MCHP_PDMC_MR_SINC_OSR_256 (6 << 24)
|
|
|
|
#define MCHP_PDMC_MR_CHUNK_MASK GENMASK(31, 28)
|
|
#define MCHP_PDMC_MR_CHUNK(chunk) (((chunk) << 28) & \
|
|
MCHP_PDMC_MR_CHUNK_MASK)
|
|
|
|
/*
|
|
* ---- Configuration Register (Read/Write) ----
|
|
*/
|
|
#define MCHP_PDMC_CFGR_BSSEL_MASK (BIT(0) | BIT(2) | BIT(4) | BIT(6))
|
|
#define MCHP_PDMC_CFGR_BSSEL(ch) BIT((ch) * 2)
|
|
|
|
#define MCHP_PDMC_CFGR_PDMSEL_MASK (BIT(16) | BIT(18) | BIT(20) | BIT(22))
|
|
#define MCHP_PDMC_CFGR_PDMSEL(ch) BIT((ch) * 2 + 16)
|
|
|
|
/*
|
|
* ---- Interrupt Enable/Disable/Mask/Status Registers ----
|
|
*/
|
|
#define MCHP_PDMC_IR_RXRDY BIT(0)
|
|
#define MCHP_PDMC_IR_RXEMPTY BIT(1)
|
|
#define MCHP_PDMC_IR_RXFULL BIT(2)
|
|
#define MCHP_PDMC_IR_RXCHUNK BIT(3)
|
|
#define MCHP_PDMC_IR_RXUDR BIT(4)
|
|
#define MCHP_PDMC_IR_RXOVR BIT(5)
|
|
|
|
/*
|
|
* ---- Version Register (Read-only) ----
|
|
*/
|
|
#define MCHP_PDMC_VER_VERSION GENMASK(11, 0)
|
|
|
|
#define MCHP_PDMC_MAX_CHANNELS 4
|
|
#define MCHP_PDMC_DS_NO 2
|
|
#define MCHP_PDMC_EDGE_NO 2
|
|
|
|
struct mic_map {
|
|
int ds_pos;
|
|
int clk_edge;
|
|
};
|
|
|
|
struct mchp_pdmc_chmap {
|
|
struct snd_pcm_chmap_elem *chmap;
|
|
struct mchp_pdmc *dd;
|
|
struct snd_pcm *pcm;
|
|
struct snd_kcontrol *kctl;
|
|
};
|
|
|
|
struct mchp_pdmc {
|
|
struct mic_map channel_mic_map[MCHP_PDMC_MAX_CHANNELS];
|
|
struct device *dev;
|
|
struct snd_dmaengine_dai_dma_data addr;
|
|
struct regmap *regmap;
|
|
struct clk *pclk;
|
|
struct clk *gclk;
|
|
u32 pdmcen;
|
|
u32 suspend_irq;
|
|
u32 startup_delay_us;
|
|
int mic_no;
|
|
int sinc_order;
|
|
bool audio_filter_en;
|
|
};
|
|
|
|
static const char *const mchp_pdmc_sinc_filter_order_text[] = {
|
|
"1", "2", "3", "4", "5"
|
|
};
|
|
|
|
static const unsigned int mchp_pdmc_sinc_filter_order_values[] = {
|
|
1, 2, 3, 4, 5,
|
|
};
|
|
|
|
static const struct soc_enum mchp_pdmc_sinc_filter_order_enum = {
|
|
.items = ARRAY_SIZE(mchp_pdmc_sinc_filter_order_text),
|
|
.texts = mchp_pdmc_sinc_filter_order_text,
|
|
.values = mchp_pdmc_sinc_filter_order_values,
|
|
};
|
|
|
|
static int mchp_pdmc_sinc_order_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *uvalue)
|
|
{
|
|
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
struct mchp_pdmc *dd = snd_soc_component_get_drvdata(component);
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
unsigned int item;
|
|
|
|
item = snd_soc_enum_val_to_item(e, dd->sinc_order);
|
|
uvalue->value.enumerated.item[0] = item;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_pdmc_sinc_order_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *uvalue)
|
|
{
|
|
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
struct mchp_pdmc *dd = snd_soc_component_get_drvdata(component);
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
unsigned int *item = uvalue->value.enumerated.item;
|
|
unsigned int val;
|
|
|
|
if (item[0] >= e->items)
|
|
return -EINVAL;
|
|
|
|
val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l;
|
|
if (val == dd->sinc_order)
|
|
return 0;
|
|
|
|
dd->sinc_order = val;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int mchp_pdmc_af_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *uvalue)
|
|
{
|
|
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
struct mchp_pdmc *dd = snd_soc_component_get_drvdata(component);
|
|
|
|
uvalue->value.integer.value[0] = !!dd->audio_filter_en;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_pdmc_af_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *uvalue)
|
|
{
|
|
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
struct mchp_pdmc *dd = snd_soc_component_get_drvdata(component);
|
|
bool af = uvalue->value.integer.value[0] ? true : false;
|
|
|
|
if (dd->audio_filter_en == af)
|
|
return 0;
|
|
|
|
dd->audio_filter_en = af;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int mchp_pdmc_chmap_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = info->dd->mic_no;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = SNDRV_CHMAP_RR; /* maxmimum 4 channels */
|
|
return 0;
|
|
}
|
|
|
|
static inline struct snd_pcm_substream *
|
|
mchp_pdmc_chmap_substream(struct mchp_pdmc_chmap *info, unsigned int idx)
|
|
{
|
|
struct snd_pcm_substream *s;
|
|
|
|
for (s = info->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; s; s = s->next)
|
|
if (s->number == idx)
|
|
return s;
|
|
return NULL;
|
|
}
|
|
|
|
static struct snd_pcm_chmap_elem *mchp_pdmc_chmap_get(struct snd_pcm_substream *substream,
|
|
struct mchp_pdmc_chmap *ch_info)
|
|
{
|
|
struct snd_pcm_chmap_elem *map;
|
|
|
|
for (map = ch_info->chmap; map->channels; map++) {
|
|
if (map->channels == substream->runtime->channels)
|
|
return map;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int mchp_pdmc_chmap_ctl_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
struct mchp_pdmc *dd = info->dd;
|
|
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
|
struct snd_pcm_substream *substream;
|
|
const struct snd_pcm_chmap_elem *map;
|
|
int i;
|
|
u32 cfgr_val = 0;
|
|
|
|
if (!info->chmap)
|
|
return -EINVAL;
|
|
substream = mchp_pdmc_chmap_substream(info, idx);
|
|
if (!substream)
|
|
return -ENODEV;
|
|
memset(ucontrol->value.integer.value, 0, sizeof(long) * info->dd->mic_no);
|
|
if (!substream->runtime)
|
|
return 0; /* no channels set */
|
|
|
|
map = mchp_pdmc_chmap_get(substream, info);
|
|
if (!map)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < map->channels; i++) {
|
|
int map_idx = map->channels == 1 ? map->map[i] - SNDRV_CHMAP_MONO :
|
|
map->map[i] - SNDRV_CHMAP_FL;
|
|
|
|
/* make sure the reported channel map is the real one, so write the map */
|
|
if (dd->channel_mic_map[map_idx].ds_pos)
|
|
cfgr_val |= MCHP_PDMC_CFGR_PDMSEL(i);
|
|
if (dd->channel_mic_map[map_idx].clk_edge)
|
|
cfgr_val |= MCHP_PDMC_CFGR_BSSEL(i);
|
|
|
|
ucontrol->value.integer.value[i] = map->map[i];
|
|
}
|
|
|
|
regmap_write(dd->regmap, MCHP_PDMC_CFGR, cfgr_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_pdmc_chmap_ctl_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
struct mchp_pdmc *dd = info->dd;
|
|
unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
|
struct snd_pcm_substream *substream;
|
|
struct snd_pcm_chmap_elem *map;
|
|
u32 cfgr_val = 0;
|
|
int i;
|
|
|
|
if (!info->chmap)
|
|
return -EINVAL;
|
|
substream = mchp_pdmc_chmap_substream(info, idx);
|
|
if (!substream)
|
|
return -ENODEV;
|
|
|
|
map = mchp_pdmc_chmap_get(substream, info);
|
|
if (!map)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < map->channels; i++) {
|
|
int map_idx;
|
|
|
|
map->map[i] = ucontrol->value.integer.value[i];
|
|
map_idx = map->channels == 1 ? map->map[i] - SNDRV_CHMAP_MONO :
|
|
map->map[i] - SNDRV_CHMAP_FL;
|
|
|
|
/* configure IP for the desired channel map */
|
|
if (dd->channel_mic_map[map_idx].ds_pos)
|
|
cfgr_val |= MCHP_PDMC_CFGR_PDMSEL(i);
|
|
if (dd->channel_mic_map[map_idx].clk_edge)
|
|
cfgr_val |= MCHP_PDMC_CFGR_BSSEL(i);
|
|
}
|
|
|
|
regmap_write(dd->regmap, MCHP_PDMC_CFGR, cfgr_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mchp_pdmc_chmap_ctl_private_free(struct snd_kcontrol *kcontrol)
|
|
{
|
|
struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
|
|
info->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].chmap_kctl = NULL;
|
|
kfree(info);
|
|
}
|
|
|
|
static int mchp_pdmc_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
|
|
unsigned int size, unsigned int __user *tlv)
|
|
{
|
|
struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
const struct snd_pcm_chmap_elem *map;
|
|
unsigned int __user *dst;
|
|
int c, count = 0;
|
|
|
|
if (!info->chmap)
|
|
return -EINVAL;
|
|
if (size < 8)
|
|
return -ENOMEM;
|
|
if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
|
|
return -EFAULT;
|
|
size -= 8;
|
|
dst = tlv + 2;
|
|
for (map = info->chmap; map->channels; map++) {
|
|
int chs_bytes = map->channels * 4;
|
|
|
|
if (size < 8)
|
|
return -ENOMEM;
|
|
if (put_user(SNDRV_CTL_TLVT_CHMAP_VAR, dst) ||
|
|
put_user(chs_bytes, dst + 1))
|
|
return -EFAULT;
|
|
dst += 2;
|
|
size -= 8;
|
|
count += 8;
|
|
if (size < chs_bytes)
|
|
return -ENOMEM;
|
|
size -= chs_bytes;
|
|
count += chs_bytes;
|
|
for (c = 0; c < map->channels; c++) {
|
|
if (put_user(map->map[c], dst))
|
|
return -EFAULT;
|
|
dst++;
|
|
}
|
|
}
|
|
if (put_user(count, tlv + 1))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new mchp_pdmc_snd_controls[] = {
|
|
SOC_SINGLE_BOOL_EXT("Audio Filter", 0, &mchp_pdmc_af_get, &mchp_pdmc_af_put),
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "SINC Filter Order",
|
|
.info = snd_soc_info_enum_double,
|
|
.get = mchp_pdmc_sinc_order_get,
|
|
.put = mchp_pdmc_sinc_order_put,
|
|
.private_value = (unsigned long)&mchp_pdmc_sinc_filter_order_enum,
|
|
},
|
|
};
|
|
|
|
static int mchp_pdmc_close(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
return snd_soc_add_component_controls(component, mchp_pdmc_snd_controls,
|
|
ARRAY_SIZE(mchp_pdmc_snd_controls));
|
|
}
|
|
|
|
static int mchp_pdmc_open(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
int i;
|
|
|
|
/* remove controls that can't be changed at runtime */
|
|
for (i = 0; i < ARRAY_SIZE(mchp_pdmc_snd_controls); i++) {
|
|
const struct snd_kcontrol_new *control = &mchp_pdmc_snd_controls[i];
|
|
struct snd_ctl_elem_id id;
|
|
struct snd_kcontrol *kctl;
|
|
int err;
|
|
|
|
if (component->name_prefix)
|
|
snprintf(id.name, sizeof(id.name), "%s %s", component->name_prefix,
|
|
control->name);
|
|
else
|
|
strscpy(id.name, control->name, sizeof(id.name));
|
|
|
|
id.numid = 0;
|
|
id.iface = control->iface;
|
|
id.device = control->device;
|
|
id.subdevice = control->subdevice;
|
|
id.index = control->index;
|
|
kctl = snd_ctl_find_id(component->card->snd_card, &id);
|
|
if (!kctl) {
|
|
dev_err(component->dev, "Failed to find %s\n", control->name);
|
|
continue;
|
|
}
|
|
err = snd_ctl_remove(component->card->snd_card, kctl);
|
|
if (err < 0) {
|
|
dev_err(component->dev, "%d: Failed to remove %s\n", err,
|
|
control->name);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver mchp_pdmc_dai_component = {
|
|
.name = "mchp-pdmc",
|
|
.controls = mchp_pdmc_snd_controls,
|
|
.num_controls = ARRAY_SIZE(mchp_pdmc_snd_controls),
|
|
.open = &mchp_pdmc_open,
|
|
.close = &mchp_pdmc_close,
|
|
.legacy_dai_naming = 1,
|
|
.start_dma_last = 1,
|
|
};
|
|
|
|
static const unsigned int mchp_pdmc_1mic[] = {1};
|
|
static const unsigned int mchp_pdmc_2mic[] = {1, 2};
|
|
static const unsigned int mchp_pdmc_3mic[] = {1, 2, 3};
|
|
static const unsigned int mchp_pdmc_4mic[] = {1, 2, 3, 4};
|
|
|
|
static const struct snd_pcm_hw_constraint_list mchp_pdmc_chan_constr[] = {
|
|
{
|
|
.list = mchp_pdmc_1mic,
|
|
.count = ARRAY_SIZE(mchp_pdmc_1mic),
|
|
},
|
|
{
|
|
.list = mchp_pdmc_2mic,
|
|
.count = ARRAY_SIZE(mchp_pdmc_2mic),
|
|
},
|
|
{
|
|
.list = mchp_pdmc_3mic,
|
|
.count = ARRAY_SIZE(mchp_pdmc_3mic),
|
|
},
|
|
{
|
|
.list = mchp_pdmc_4mic,
|
|
.count = ARRAY_SIZE(mchp_pdmc_4mic),
|
|
},
|
|
};
|
|
|
|
static int mchp_pdmc_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
|
|
|
|
regmap_write(dd->regmap, MCHP_PDMC_CR, MCHP_PDMC_CR_SWRST);
|
|
|
|
snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
&mchp_pdmc_chan_constr[dd->mic_no - 1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_pdmc_dai_probe(struct snd_soc_dai *dai)
|
|
{
|
|
struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
|
|
|
|
snd_soc_dai_init_dma_data(dai, NULL, &dd->addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_pdmc_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
{
|
|
unsigned int fmt_master = fmt & SND_SOC_DAIFMT_MASTER_MASK;
|
|
unsigned int fmt_format = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
|
|
|
/* IP needs to be bitclock master */
|
|
if (fmt_master != SND_SOC_DAIFMT_BP_FP &&
|
|
fmt_master != SND_SOC_DAIFMT_BP_FC)
|
|
return -EINVAL;
|
|
|
|
/* IP supports only PDM interface */
|
|
if (fmt_format != SND_SOC_DAIFMT_PDM)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 mchp_pdmc_mr_set_osr(int audio_filter_en, unsigned int osr)
|
|
{
|
|
if (audio_filter_en) {
|
|
switch (osr) {
|
|
case 64:
|
|
return MCHP_PDMC_MR_OSR64;
|
|
case 128:
|
|
return MCHP_PDMC_MR_OSR128;
|
|
case 256:
|
|
return MCHP_PDMC_MR_OSR256;
|
|
}
|
|
} else {
|
|
switch (osr) {
|
|
case 8:
|
|
return MCHP_PDMC_MR_SINC_OSR_8;
|
|
case 16:
|
|
return MCHP_PDMC_MR_SINC_OSR_16;
|
|
case 32:
|
|
return MCHP_PDMC_MR_SINC_OSR_32;
|
|
case 64:
|
|
return MCHP_PDMC_MR_SINC_OSR_64;
|
|
case 128:
|
|
return MCHP_PDMC_MR_SINC_OSR_128;
|
|
case 256:
|
|
return MCHP_PDMC_MR_SINC_OSR_256;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int mchp_pdmc_period_to_maxburst(int period_size)
|
|
{
|
|
if (!(period_size % 8))
|
|
return 8;
|
|
if (!(period_size % 4))
|
|
return 4;
|
|
if (!(period_size % 2))
|
|
return 2;
|
|
return 1;
|
|
}
|
|
|
|
static struct snd_pcm_chmap_elem mchp_pdmc_std_chmaps[] = {
|
|
{ .channels = 1,
|
|
.map = { SNDRV_CHMAP_MONO } },
|
|
{ .channels = 2,
|
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
|
|
{ .channels = 3,
|
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
|
|
SNDRV_CHMAP_RL } },
|
|
{ .channels = 4,
|
|
.map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
|
|
SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
|
|
{ }
|
|
};
|
|
|
|
static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
|
|
struct snd_soc_component *comp = dai->component;
|
|
unsigned long gclk_rate = 0;
|
|
unsigned long best_diff_rate = ~0UL;
|
|
unsigned int channels = params_channels(params);
|
|
unsigned int osr = 0, osr_start;
|
|
unsigned int fs = params_rate(params);
|
|
u32 mr_val = 0;
|
|
u32 cfgr_val = 0;
|
|
int i;
|
|
int ret;
|
|
|
|
dev_dbg(comp->dev, "%s() rate=%u format=%#x width=%u channels=%u\n",
|
|
__func__, params_rate(params), params_format(params),
|
|
params_width(params), params_channels(params));
|
|
|
|
if (channels > dd->mic_no) {
|
|
dev_err(comp->dev, "more channels %u than microphones %d\n",
|
|
channels, dd->mic_no);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dd->pdmcen = 0;
|
|
for (i = 0; i < channels; i++) {
|
|
dd->pdmcen |= MCHP_PDMC_MR_PDMCEN(i);
|
|
if (dd->channel_mic_map[i].ds_pos)
|
|
cfgr_val |= MCHP_PDMC_CFGR_PDMSEL(i);
|
|
if (dd->channel_mic_map[i].clk_edge)
|
|
cfgr_val |= MCHP_PDMC_CFGR_BSSEL(i);
|
|
}
|
|
|
|
for (osr_start = dd->audio_filter_en ? 64 : 8;
|
|
osr_start <= 256 && best_diff_rate; osr_start *= 2) {
|
|
long round_rate;
|
|
unsigned long diff_rate;
|
|
|
|
round_rate = clk_round_rate(dd->gclk,
|
|
(unsigned long)fs * 16 * osr_start);
|
|
if (round_rate < 0)
|
|
continue;
|
|
diff_rate = abs((fs * 16 * osr_start) - round_rate);
|
|
if (diff_rate < best_diff_rate) {
|
|
best_diff_rate = diff_rate;
|
|
osr = osr_start;
|
|
gclk_rate = fs * 16 * osr;
|
|
}
|
|
}
|
|
if (!gclk_rate) {
|
|
dev_err(comp->dev, "invalid sampling rate: %u\n", fs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* CLK is enabled by runtime PM. */
|
|
clk_disable_unprepare(dd->gclk);
|
|
|
|
/* set the rate */
|
|
ret = clk_set_rate(dd->gclk, gclk_rate);
|
|
clk_prepare_enable(dd->gclk);
|
|
if (ret) {
|
|
dev_err(comp->dev, "unable to set rate %lu to GCLK: %d\n",
|
|
gclk_rate, ret);
|
|
return ret;
|
|
}
|
|
|
|
mr_val |= mchp_pdmc_mr_set_osr(dd->audio_filter_en, osr);
|
|
|
|
mr_val |= MCHP_PDMC_MR_SINCORDER(dd->sinc_order);
|
|
|
|
dd->addr.maxburst = mchp_pdmc_period_to_maxburst(snd_pcm_lib_period_bytes(substream));
|
|
mr_val |= MCHP_PDMC_MR_CHUNK(dd->addr.maxburst);
|
|
dev_dbg(comp->dev, "maxburst set to %d\n", dd->addr.maxburst);
|
|
|
|
snd_soc_component_update_bits(comp, MCHP_PDMC_MR,
|
|
MCHP_PDMC_MR_OSR_MASK |
|
|
MCHP_PDMC_MR_SINCORDER_MASK |
|
|
MCHP_PDMC_MR_SINC_OSR_MASK |
|
|
MCHP_PDMC_MR_CHUNK_MASK, mr_val);
|
|
|
|
snd_soc_component_write(comp, MCHP_PDMC_CFGR, cfgr_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mchp_pdmc_noise_filter_workaround(struct mchp_pdmc *dd)
|
|
{
|
|
u32 tmp, steps = 16;
|
|
|
|
/*
|
|
* PDMC doesn't wait for microphones' startup time thus the acquisition
|
|
* may start before the microphones are ready leading to poc noises at
|
|
* the beginning of capture. To avoid this, we need to wait 50ms (in
|
|
* normal startup procedure) or 150 ms (worst case after resume from sleep
|
|
* states) after microphones are enabled and then clear the FIFOs (by
|
|
* reading the RHR 16 times) and possible interrupts before continuing.
|
|
* Also, for this to work the DMA needs to be started after interrupts
|
|
* are enabled.
|
|
*/
|
|
usleep_range(dd->startup_delay_us, dd->startup_delay_us + 5);
|
|
|
|
while (steps--)
|
|
regmap_read(dd->regmap, MCHP_PDMC_RHR, &tmp);
|
|
|
|
/* Clear interrupts. */
|
|
regmap_read(dd->regmap, MCHP_PDMC_ISR, &tmp);
|
|
}
|
|
|
|
static int mchp_pdmc_trigger(struct snd_pcm_substream *substream,
|
|
int cmd, struct snd_soc_dai *dai)
|
|
{
|
|
struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
|
|
struct snd_soc_component *cpu = dai->component;
|
|
#ifdef DEBUG
|
|
u32 val;
|
|
#endif
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
snd_soc_component_update_bits(cpu, MCHP_PDMC_MR,
|
|
MCHP_PDMC_MR_PDMCEN_MASK,
|
|
dd->pdmcen);
|
|
|
|
mchp_pdmc_noise_filter_workaround(dd);
|
|
|
|
/* Enable interrupts. */
|
|
regmap_write(dd->regmap, MCHP_PDMC_IER, dd->suspend_irq |
|
|
MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
|
|
dd->suspend_irq = 0;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
regmap_read(dd->regmap, MCHP_PDMC_IMR, &dd->suspend_irq);
|
|
fallthrough;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
/* Disable overrun and underrun error interrupts */
|
|
regmap_write(dd->regmap, MCHP_PDMC_IDR, dd->suspend_irq |
|
|
MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
|
|
fallthrough;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
snd_soc_component_update_bits(cpu, MCHP_PDMC_MR,
|
|
MCHP_PDMC_MR_PDMCEN_MASK, 0);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
regmap_read(dd->regmap, MCHP_PDMC_MR, &val);
|
|
dev_dbg(dd->dev, "MR (0x%02x): 0x%08x\n", MCHP_PDMC_MR, val);
|
|
regmap_read(dd->regmap, MCHP_PDMC_CFGR, &val);
|
|
dev_dbg(dd->dev, "CFGR (0x%02x): 0x%08x\n", MCHP_PDMC_CFGR, val);
|
|
regmap_read(dd->regmap, MCHP_PDMC_IMR, &val);
|
|
dev_dbg(dd->dev, "IMR (0x%02x): 0x%08x\n", MCHP_PDMC_IMR, val);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = {
|
|
.set_fmt = mchp_pdmc_set_fmt,
|
|
.startup = mchp_pdmc_startup,
|
|
.hw_params = mchp_pdmc_hw_params,
|
|
.trigger = mchp_pdmc_trigger,
|
|
};
|
|
|
|
static int mchp_pdmc_add_chmap_ctls(struct snd_pcm *pcm, struct mchp_pdmc *dd)
|
|
{
|
|
struct mchp_pdmc_chmap *info;
|
|
struct snd_kcontrol_new knew = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_READ |
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
|
|
.info = mchp_pdmc_chmap_ctl_info,
|
|
.get = mchp_pdmc_chmap_ctl_get,
|
|
.put = mchp_pdmc_chmap_ctl_put,
|
|
.tlv.c = mchp_pdmc_chmap_ctl_tlv,
|
|
};
|
|
int err;
|
|
|
|
if (WARN_ON(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].chmap_kctl))
|
|
return -EBUSY;
|
|
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
info->pcm = pcm;
|
|
info->dd = dd;
|
|
info->chmap = mchp_pdmc_std_chmaps;
|
|
knew.name = "Capture Channel Map";
|
|
knew.device = pcm->device;
|
|
knew.count = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count;
|
|
info->kctl = snd_ctl_new1(&knew, info);
|
|
if (!info->kctl) {
|
|
kfree(info);
|
|
return -ENOMEM;
|
|
}
|
|
info->kctl->private_free = mchp_pdmc_chmap_ctl_private_free;
|
|
err = snd_ctl_add(pcm->card, info->kctl);
|
|
if (err < 0)
|
|
return err;
|
|
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].chmap_kctl = info->kctl;
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_pdmc_pcm_new(struct snd_soc_pcm_runtime *rtd,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
|
|
int ret;
|
|
|
|
ret = mchp_pdmc_add_chmap_ctls(rtd->pcm, dd);
|
|
if (ret < 0) {
|
|
dev_err(dd->dev, "failed to add channel map controls: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_dai_driver mchp_pdmc_dai = {
|
|
.probe = mchp_pdmc_dai_probe,
|
|
.capture = {
|
|
.stream_name = "Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 4,
|
|
.rate_min = 8000,
|
|
.rate_max = 192000,
|
|
.rates = SNDRV_PCM_RATE_KNOT,
|
|
.formats = SNDRV_PCM_FMTBIT_S24_LE,
|
|
},
|
|
.ops = &mchp_pdmc_dai_ops,
|
|
.pcm_new = &mchp_pdmc_pcm_new,
|
|
};
|
|
|
|
/* PDMC interrupt handler */
|
|
static irqreturn_t mchp_pdmc_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct mchp_pdmc *dd = (struct mchp_pdmc *)dev_id;
|
|
u32 isr, msr, pending;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
regmap_read(dd->regmap, MCHP_PDMC_ISR, &isr);
|
|
regmap_read(dd->regmap, MCHP_PDMC_IMR, &msr);
|
|
|
|
pending = isr & msr;
|
|
dev_dbg(dd->dev, "ISR (0x%02x): 0x%08x, IMR (0x%02x): 0x%08x, pending: 0x%08x\n",
|
|
MCHP_PDMC_ISR, isr, MCHP_PDMC_IMR, msr, pending);
|
|
if (!pending)
|
|
return IRQ_NONE;
|
|
|
|
if (pending & MCHP_PDMC_IR_RXUDR) {
|
|
dev_warn(dd->dev, "underrun detected\n");
|
|
regmap_write(dd->regmap, MCHP_PDMC_IDR, MCHP_PDMC_IR_RXUDR);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
if (pending & MCHP_PDMC_IR_RXOVR) {
|
|
dev_warn(dd->dev, "overrun detected\n");
|
|
regmap_write(dd->regmap, MCHP_PDMC_IDR, MCHP_PDMC_IR_RXOVR);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* regmap configuration */
|
|
static bool mchp_pdmc_readable_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case MCHP_PDMC_MR:
|
|
case MCHP_PDMC_CFGR:
|
|
case MCHP_PDMC_IMR:
|
|
case MCHP_PDMC_ISR:
|
|
case MCHP_PDMC_RHR:
|
|
case MCHP_PDMC_VER:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool mchp_pdmc_writeable_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case MCHP_PDMC_CR:
|
|
case MCHP_PDMC_MR:
|
|
case MCHP_PDMC_CFGR:
|
|
case MCHP_PDMC_IER:
|
|
case MCHP_PDMC_IDR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool mchp_pdmc_volatile_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case MCHP_PDMC_ISR:
|
|
case MCHP_PDMC_RHR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool mchp_pdmc_precious_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case MCHP_PDMC_RHR:
|
|
case MCHP_PDMC_ISR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const struct regmap_config mchp_pdmc_regmap_config = {
|
|
.reg_bits = 32,
|
|
.reg_stride = 4,
|
|
.val_bits = 32,
|
|
.max_register = MCHP_PDMC_VER,
|
|
.readable_reg = mchp_pdmc_readable_reg,
|
|
.writeable_reg = mchp_pdmc_writeable_reg,
|
|
.precious_reg = mchp_pdmc_precious_reg,
|
|
.volatile_reg = mchp_pdmc_volatile_reg,
|
|
.cache_type = REGCACHE_FLAT,
|
|
};
|
|
|
|
static int mchp_pdmc_dt_init(struct mchp_pdmc *dd)
|
|
{
|
|
struct device_node *np = dd->dev->of_node;
|
|
bool mic_ch[MCHP_PDMC_DS_NO][MCHP_PDMC_EDGE_NO] = {0};
|
|
int i;
|
|
int ret;
|
|
|
|
if (!np) {
|
|
dev_err(dd->dev, "device node not found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dd->mic_no = of_property_count_u32_elems(np, "microchip,mic-pos");
|
|
if (dd->mic_no < 0) {
|
|
dev_err(dd->dev, "failed to get microchip,mic-pos: %d",
|
|
dd->mic_no);
|
|
return dd->mic_no;
|
|
}
|
|
if (!dd->mic_no || dd->mic_no % 2 ||
|
|
dd->mic_no / 2 > MCHP_PDMC_MAX_CHANNELS) {
|
|
dev_err(dd->dev, "invalid array length for microchip,mic-pos: %d",
|
|
dd->mic_no);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dd->mic_no /= 2;
|
|
|
|
dev_info(dd->dev, "%d PDM microphones declared\n", dd->mic_no);
|
|
|
|
/*
|
|
* by default, we consider the order of microphones in
|
|
* microchip,mic-pos to be the same with the channel mapping;
|
|
* 1st microphone channel 0, 2nd microphone channel 1, etc.
|
|
*/
|
|
for (i = 0; i < dd->mic_no; i++) {
|
|
int ds;
|
|
int edge;
|
|
|
|
ret = of_property_read_u32_index(np, "microchip,mic-pos", i * 2,
|
|
&ds);
|
|
if (ret) {
|
|
dev_err(dd->dev,
|
|
"failed to get value no %d value from microchip,mic-pos: %d",
|
|
i * 2, ret);
|
|
return ret;
|
|
}
|
|
if (ds >= MCHP_PDMC_DS_NO) {
|
|
dev_err(dd->dev,
|
|
"invalid DS index in microchip,mic-pos array: %d",
|
|
ds);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32_index(np, "microchip,mic-pos", i * 2 + 1,
|
|
&edge);
|
|
if (ret) {
|
|
dev_err(dd->dev,
|
|
"failed to get value no %d value from microchip,mic-pos: %d",
|
|
i * 2 + 1, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (edge != MCHP_PDMC_CLK_POSITIVE &&
|
|
edge != MCHP_PDMC_CLK_NEGATIVE) {
|
|
dev_err(dd->dev,
|
|
"invalid edge in microchip,mic-pos array: %d", edge);
|
|
return -EINVAL;
|
|
}
|
|
if (mic_ch[ds][edge]) {
|
|
dev_err(dd->dev,
|
|
"duplicated mic (DS %d, edge %d) in microchip,mic-pos array",
|
|
ds, edge);
|
|
return -EINVAL;
|
|
}
|
|
mic_ch[ds][edge] = true;
|
|
dd->channel_mic_map[i].ds_pos = ds;
|
|
dd->channel_mic_map[i].clk_edge = edge;
|
|
}
|
|
|
|
dd->startup_delay_us = 150000;
|
|
of_property_read_u32(np, "microchip,startup-delay-us", &dd->startup_delay_us);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* used to clean the channel index found on RHR's MSB */
|
|
static int mchp_pdmc_process(struct snd_pcm_substream *substream,
|
|
int channel, unsigned long hwoff,
|
|
void *buf, unsigned long bytes)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
u8 *dma_ptr = runtime->dma_area + hwoff +
|
|
channel * (runtime->dma_bytes / runtime->channels);
|
|
u8 *dma_ptr_end = dma_ptr + bytes;
|
|
unsigned int sample_size = samples_to_bytes(runtime, 1);
|
|
|
|
for (; dma_ptr < dma_ptr_end; dma_ptr += sample_size)
|
|
*dma_ptr = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_dmaengine_pcm_config mchp_pdmc_config = {
|
|
.process = mchp_pdmc_process,
|
|
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
|
|
};
|
|
|
|
static int mchp_pdmc_runtime_suspend(struct device *dev)
|
|
{
|
|
struct mchp_pdmc *dd = dev_get_drvdata(dev);
|
|
|
|
regcache_cache_only(dd->regmap, true);
|
|
|
|
clk_disable_unprepare(dd->gclk);
|
|
clk_disable_unprepare(dd->pclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_pdmc_runtime_resume(struct device *dev)
|
|
{
|
|
struct mchp_pdmc *dd = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(dd->pclk);
|
|
if (ret) {
|
|
dev_err(dd->dev,
|
|
"failed to enable the peripheral clock: %d\n", ret);
|
|
return ret;
|
|
}
|
|
ret = clk_prepare_enable(dd->gclk);
|
|
if (ret) {
|
|
dev_err(dd->dev,
|
|
"failed to enable generic clock: %d\n", ret);
|
|
goto disable_pclk;
|
|
}
|
|
|
|
regcache_cache_only(dd->regmap, false);
|
|
regcache_mark_dirty(dd->regmap);
|
|
ret = regcache_sync(dd->regmap);
|
|
if (ret) {
|
|
regcache_cache_only(dd->regmap, true);
|
|
clk_disable_unprepare(dd->gclk);
|
|
disable_pclk:
|
|
clk_disable_unprepare(dd->pclk);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mchp_pdmc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct mchp_pdmc *dd;
|
|
struct resource *res;
|
|
void __iomem *io_base;
|
|
u32 version;
|
|
int irq;
|
|
int ret;
|
|
|
|
dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL);
|
|
if (!dd)
|
|
return -ENOMEM;
|
|
|
|
dd->dev = &pdev->dev;
|
|
ret = mchp_pdmc_dt_init(dd);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
dd->pclk = devm_clk_get(dev, "pclk");
|
|
if (IS_ERR(dd->pclk)) {
|
|
ret = PTR_ERR(dd->pclk);
|
|
dev_err(dev, "failed to get peripheral clock: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
dd->gclk = devm_clk_get(dev, "gclk");
|
|
if (IS_ERR(dd->gclk)) {
|
|
ret = PTR_ERR(dd->gclk);
|
|
dev_err(dev, "failed to get GCK: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
|
if (IS_ERR(io_base)) {
|
|
ret = PTR_ERR(io_base);
|
|
dev_err(dev, "failed to remap register memory: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
dd->regmap = devm_regmap_init_mmio(dev, io_base,
|
|
&mchp_pdmc_regmap_config);
|
|
if (IS_ERR(dd->regmap)) {
|
|
ret = PTR_ERR(dd->regmap);
|
|
dev_err(dev, "failed to init register map: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = devm_request_irq(dev, irq, mchp_pdmc_interrupt, 0,
|
|
dev_name(&pdev->dev), (void *)dd);
|
|
if (ret < 0) {
|
|
dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
|
|
irq, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* by default audio filter is enabled and the SINC Filter order
|
|
* will be set to the recommended value, 3
|
|
*/
|
|
dd->audio_filter_en = true;
|
|
dd->sinc_order = 3;
|
|
|
|
dd->addr.addr = (dma_addr_t)res->start + MCHP_PDMC_RHR;
|
|
platform_set_drvdata(pdev, dd);
|
|
|
|
pm_runtime_enable(dd->dev);
|
|
if (!pm_runtime_enabled(dd->dev)) {
|
|
ret = mchp_pdmc_runtime_resume(dd->dev);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* register platform */
|
|
ret = devm_snd_dmaengine_pcm_register(dev, &mchp_pdmc_config, 0);
|
|
if (ret) {
|
|
dev_err(dev, "could not register platform: %d\n", ret);
|
|
goto pm_runtime_suspend;
|
|
}
|
|
|
|
ret = devm_snd_soc_register_component(dev, &mchp_pdmc_dai_component,
|
|
&mchp_pdmc_dai, 1);
|
|
if (ret) {
|
|
dev_err(dev, "could not register CPU DAI: %d\n", ret);
|
|
goto pm_runtime_suspend;
|
|
}
|
|
|
|
/* print IP version */
|
|
regmap_read(dd->regmap, MCHP_PDMC_VER, &version);
|
|
dev_info(dd->dev, "hw version: %#lx\n",
|
|
version & MCHP_PDMC_VER_VERSION);
|
|
|
|
return 0;
|
|
|
|
pm_runtime_suspend:
|
|
if (!pm_runtime_status_suspended(dd->dev))
|
|
mchp_pdmc_runtime_suspend(dd->dev);
|
|
pm_runtime_disable(dd->dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mchp_pdmc_remove(struct platform_device *pdev)
|
|
{
|
|
struct mchp_pdmc *dd = platform_get_drvdata(pdev);
|
|
|
|
if (!pm_runtime_status_suspended(dd->dev))
|
|
mchp_pdmc_runtime_suspend(dd->dev);
|
|
|
|
pm_runtime_disable(dd->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id mchp_pdmc_of_match[] = {
|
|
{
|
|
.compatible = "microchip,sama7g5-pdmc",
|
|
}, {
|
|
/* sentinel */
|
|
}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mchp_pdmc_of_match);
|
|
|
|
static const struct dev_pm_ops mchp_pdmc_pm_ops = {
|
|
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
|
|
RUNTIME_PM_OPS(mchp_pdmc_runtime_suspend, mchp_pdmc_runtime_resume,
|
|
NULL)
|
|
};
|
|
|
|
static struct platform_driver mchp_pdmc_driver = {
|
|
.driver = {
|
|
.name = "mchp-pdmc",
|
|
.of_match_table = of_match_ptr(mchp_pdmc_of_match),
|
|
.pm = pm_ptr(&mchp_pdmc_pm_ops),
|
|
},
|
|
.probe = mchp_pdmc_probe,
|
|
.remove = mchp_pdmc_remove,
|
|
};
|
|
module_platform_driver(mchp_pdmc_driver);
|
|
|
|
MODULE_DESCRIPTION("Microchip PDMC driver under ALSA SoC architecture");
|
|
MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>");
|
|
MODULE_LICENSE("GPL v2");
|