mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
ASoC: Add Loongson-2K0300 I2S controller and sound card support
Binbin Zhou <zhoubinbin@loongson.cn> says: This series adds ASoC support for the Loongson-2K0300 SoC and its associated development boards, including the CTCISZ Forever Pi and the ATK-DL2K0300B. Key changes: - Extend DT bindings to support ls2k0300-i2s and new audio card compatibles. - Refactor the platform I2S driver to handle SoC differences via per-device configuration (rev_id, optional APB DMA config, reset sequence). - Refactor the audio machine driver to support board-specific DAI formats, GPIO-based headphone detection/control, speaker enable, and DAPM routing. - Add jack detection and automatic switching between headphones and speakers for the DL2K0300B board. The patchset also cleans up the existing audio card binding by referencing the common sound-card properties, and adds new compatibles for the Loongson-2K0300 variants with proper DAI format handling. All changes have been tested on Loongson-2K2000 (PCI), Loongson-2K0300 Forever Pi and Loongson-2K0300 DL2K0300B boards. Link: https://patch.msgid.link/cover.1782439646.git.zhoubinbin@loongson.cn
This commit is contained in:
commit
8d01573c48
|
|
@ -8,19 +8,22 @@ title: Loongson 7axxx/2kxxx ASoC audio sound card driver
|
|||
|
||||
maintainers:
|
||||
- Yingkun Meng <mengyingkun@loongson.cn>
|
||||
- Binbin Zhou <zhoubinbin@loongson.cn>
|
||||
|
||||
description:
|
||||
The binding describes the sound card present in loongson
|
||||
7axxx/2kxxx platform. The sound card is an ASoC component
|
||||
which uses Loongson I2S controller to transfer the audio data.
|
||||
|
||||
allOf:
|
||||
- $ref: sound-card-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: loongson,ls-audio-card
|
||||
|
||||
model:
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
description: User specified audio sound card name
|
||||
enum:
|
||||
- loongson,ls-audio-card # Loongson-2K1000/Loongson-2K2000/LS7A
|
||||
- loongson,ls2k0300-forever-pi-audio-card # CTCISZ Forever Pi
|
||||
- loongson,ls2k0300-dl2k0300b-audio-card # ATK-DL2K0300B
|
||||
|
||||
mclk-fs:
|
||||
$ref: simple-card.yaml#/definitions/mclk-fs
|
||||
|
|
@ -45,14 +48,25 @@ properties:
|
|||
required:
|
||||
- sound-dai
|
||||
|
||||
spkr-en-gpios:
|
||||
maxItems: 1
|
||||
description: The GPIO that enables the speakers
|
||||
|
||||
hp-ctl-gpios:
|
||||
maxItems: 1
|
||||
description: The GPIO that control the headphones
|
||||
|
||||
hp-det-gpios:
|
||||
maxItems: 1
|
||||
description: The GPIO that detect headphones are plugged in
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- model
|
||||
- mclk-fs
|
||||
- cpu
|
||||
- codec
|
||||
|
||||
additionalProperties: false
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
|
|
@ -68,3 +82,28 @@ examples:
|
|||
sound-dai = <&es8323>;
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
sound {
|
||||
compatible = "loongson,ls2k0300-dl2k0300b-audio-card";
|
||||
model = "loongson-audio";
|
||||
mclk-fs = <512>;
|
||||
hp-det-gpios = <&gpio 81 GPIO_ACTIVE_HIGH>;
|
||||
spkr-en-gpios = <&gpio 86 GPIO_ACTIVE_HIGH>;
|
||||
hp-ctl-gpios = <&gpio 87 GPIO_ACTIVE_HIGH>;
|
||||
audio-routing =
|
||||
"Headphone", "LOUT1",
|
||||
"Headphone", "ROUT1",
|
||||
"Speaker", "LOUT2",
|
||||
"Speaker", "ROUT2";
|
||||
|
||||
cpu {
|
||||
sound-dai = <&i2s>;
|
||||
};
|
||||
|
||||
codec {
|
||||
sound-dai = <&es8388>;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,9 +14,12 @@ allOf:
|
|||
|
||||
properties:
|
||||
compatible:
|
||||
const: loongson,ls2k1000-i2s
|
||||
enum:
|
||||
- loongson,ls2k0300-i2s
|
||||
- loongson,ls2k1000-i2s
|
||||
|
||||
reg:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: Loongson I2S controller Registers.
|
||||
- description: APB DMA config register for Loongson I2S controller.
|
||||
|
|
@ -49,6 +52,23 @@ required:
|
|||
|
||||
unevaluatedProperties: false
|
||||
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- loongson,ls2k1000-i2s
|
||||
|
||||
then:
|
||||
properties:
|
||||
reg:
|
||||
minItems: 2
|
||||
|
||||
else:
|
||||
properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/loongson,ls2k-clk.h>
|
||||
|
|
|
|||
|
|
@ -405,6 +405,11 @@ static const struct snd_soc_dapm_route es8328_dapm_routes[] = {
|
|||
|
||||
{ "Mic Bias", NULL, "Mic Bias Gen" },
|
||||
|
||||
{ "LINPUT1", NULL, "Mic Bias" },
|
||||
{ "RINPUT1", NULL, "Mic Bias" },
|
||||
{ "LINPUT2", NULL, "Mic Bias" },
|
||||
{ "RINPUT2", NULL, "Mic Bias" },
|
||||
|
||||
{ "Left Mixer", NULL, "Left DAC" },
|
||||
{ "Left Mixer", "Left Bypass Switch", "Left Line Mux" },
|
||||
{ "Left Mixer", "Right Playback Switch", "Right DAC" },
|
||||
|
|
|
|||
|
|
@ -2,24 +2,131 @@
|
|||
//
|
||||
// Loongson ASoC Audio Machine driver
|
||||
//
|
||||
// Copyright (C) 2023 Loongson Technology Corporation Limited
|
||||
// Copyright (C) 2023-2026 Loongson Technology Corporation Limited
|
||||
// Author: Yingkun Meng <mengyingkun@loongson.cn>
|
||||
// Binbin Zhou <zhoubinbin@loongson.cn>
|
||||
//
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-acpi.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/pci.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
static char codec_name[SND_ACPI_I2C_ID_LEN];
|
||||
|
||||
struct loongson_card_data {
|
||||
struct snd_soc_card snd_card;
|
||||
unsigned int mclk_fs;
|
||||
struct gpio_desc *gpiod_hp_det;
|
||||
struct gpio_desc *gpiod_hp_ctl;
|
||||
struct gpio_desc *gpiod_spkr_en;
|
||||
const struct loongson_card_config *cfg;
|
||||
};
|
||||
|
||||
struct loongson_card_config {
|
||||
unsigned int fmt;
|
||||
bool add_hp_jack;
|
||||
bool add_dapm_widgets;
|
||||
bool add_dapm_routes;
|
||||
};
|
||||
|
||||
static const struct loongson_card_config ls2k1000_card_config = {
|
||||
.fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBC_CFC,
|
||||
.add_hp_jack = false,
|
||||
.add_dapm_widgets = false,
|
||||
.add_dapm_routes = false,
|
||||
};
|
||||
|
||||
static const struct loongson_card_config ls2k0300_forever_pi_card_config = {
|
||||
.fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC,
|
||||
.add_hp_jack = false,
|
||||
.add_dapm_widgets = false,
|
||||
.add_dapm_routes = false,
|
||||
};
|
||||
|
||||
static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = {
|
||||
.fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC,
|
||||
.add_hp_jack = true,
|
||||
.add_dapm_widgets = true,
|
||||
.add_dapm_routes = true,
|
||||
};
|
||||
|
||||
static int tegra_machine_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm);
|
||||
struct loongson_card_data *priv = snd_soc_card_get_drvdata(card);
|
||||
|
||||
if (!snd_soc_dapm_widget_name_cmp(w, "Speaker"))
|
||||
gpiod_set_value_cansleep(priv->gpiod_spkr_en,
|
||||
SND_SOC_DAPM_EVENT_ON(event));
|
||||
|
||||
if (!snd_soc_dapm_widget_name_cmp(w, "Headphone"))
|
||||
gpiod_set_value_cansleep(priv->gpiod_hp_ctl,
|
||||
SND_SOC_DAPM_EVENT_ON(event));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone", tegra_machine_event),
|
||||
SND_SOC_DAPM_SPK("Speaker", tegra_machine_event),
|
||||
};
|
||||
|
||||
/* Headphones Jack */
|
||||
|
||||
static struct snd_soc_jack loongson_asoc_hp_jack;
|
||||
|
||||
static struct snd_soc_jack_pin loongson_asoc_hp_jack_pins[] = {
|
||||
{
|
||||
.pin = "Headphone",
|
||||
.mask = SND_JACK_HEADPHONE
|
||||
},
|
||||
{
|
||||
.pin = "Speaker",
|
||||
.mask = SND_JACK_HEADPHONE,
|
||||
.invert = 1
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_jack_gpio loongson_asoc_hp_jack_gpio = {
|
||||
.name = "Headphones detection",
|
||||
.report = SND_JACK_HEADPHONE,
|
||||
.debounce_time = 150,
|
||||
};
|
||||
|
||||
static int loongson_asoc_machine_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct loongson_card_data *ls_priv = snd_soc_card_get_drvdata(card);
|
||||
int ret = 0;
|
||||
|
||||
if (!ls_priv->cfg->add_hp_jack || !ls_priv->gpiod_hp_det)
|
||||
return 0;
|
||||
|
||||
ret = snd_soc_card_jack_new_pins(card, "Headphones Jack",
|
||||
SND_JACK_HEADPHONE,
|
||||
&loongson_asoc_hp_jack,
|
||||
loongson_asoc_hp_jack_pins,
|
||||
ARRAY_SIZE(loongson_asoc_hp_jack_pins));
|
||||
if (ret) {
|
||||
dev_err(rtd->dev, "Headphones Jack creation failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
loongson_asoc_hp_jack_gpio.desc = ls_priv->gpiod_hp_det;
|
||||
|
||||
ret = snd_soc_jack_add_gpios(&loongson_asoc_hp_jack, 1, &loongson_asoc_hp_jack_gpio);
|
||||
if (ret)
|
||||
dev_err(rtd->dev, "Headphone GPIO not added: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int loongson_card_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
|
|
@ -45,7 +152,7 @@ static int loongson_card_hw_params(struct snd_pcm_substream *substream,
|
|||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return snd_soc_runtime_set_dai_fmt(rtd, ls_card->cfg->fmt);
|
||||
}
|
||||
|
||||
static const struct snd_soc_ops loongson_ops = {
|
||||
|
|
@ -61,8 +168,7 @@ static struct snd_soc_dai_link loongson_dai_links[] = {
|
|||
{
|
||||
.name = "Loongson Audio Port",
|
||||
.stream_name = "Loongson Audio",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF
|
||||
| SND_SOC_DAIFMT_CBC_CFC,
|
||||
.init = loongson_asoc_machine_init,
|
||||
SND_SOC_DAILINK_REG(analog),
|
||||
.ops = &loongson_ops,
|
||||
},
|
||||
|
|
@ -91,7 +197,7 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data)
|
|||
const char *codec_dai_name;
|
||||
struct acpi_device *adev;
|
||||
struct device *phy_dev;
|
||||
int i;
|
||||
int i, ret;
|
||||
|
||||
/* fixup platform name based on reference node */
|
||||
adev = loongson_card_acpi_find_device(card, "cpu");
|
||||
|
|
@ -108,7 +214,9 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data)
|
|||
return -ENOENT;
|
||||
snprintf(codec_name, sizeof(codec_name), "i2c-%s", acpi_dev_name(adev));
|
||||
|
||||
device_property_read_string(card->dev, "codec-dai-name", &codec_dai_name);
|
||||
ret = device_property_read_string(card->dev, "codec-dai-name", &codec_dai_name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < card->num_links; i++) {
|
||||
loongson_dai_links[i].platforms->name = dev_name(phy_dev);
|
||||
|
|
@ -121,16 +229,35 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data)
|
|||
|
||||
static int loongson_card_parse_of(struct loongson_card_data *data)
|
||||
{
|
||||
struct device_node *cpu, *codec;
|
||||
struct snd_soc_card *card = &data->snd_card;
|
||||
struct device_node *cpu, *codec;
|
||||
struct device *dev = card->dev;
|
||||
int ret, i;
|
||||
|
||||
data->gpiod_hp_det = devm_gpiod_get_optional(dev, "hp-det", GPIOD_IN);
|
||||
if (IS_ERR(data->gpiod_hp_det))
|
||||
return PTR_ERR(data->gpiod_hp_det);
|
||||
|
||||
data->gpiod_hp_ctl = devm_gpiod_get_optional(dev, "hp-ctl", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(data->gpiod_hp_ctl))
|
||||
return PTR_ERR(data->gpiod_hp_ctl);
|
||||
|
||||
data->gpiod_spkr_en = devm_gpiod_get_optional(dev, "spkr-en", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(data->gpiod_spkr_en))
|
||||
return PTR_ERR(data->gpiod_spkr_en);
|
||||
|
||||
if (data->cfg->add_dapm_routes) {
|
||||
ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
cpu = of_get_child_by_name(dev->of_node, "cpu");
|
||||
if (!cpu) {
|
||||
dev_err(dev, "platform property missing or invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec = of_get_child_by_name(dev->of_node, "codec");
|
||||
if (!codec) {
|
||||
dev_err(dev, "audio-codec property missing or invalid\n");
|
||||
|
|
@ -175,12 +302,22 @@ static int loongson_asoc_card_probe(struct platform_device *pdev)
|
|||
if (!ls_priv)
|
||||
return -ENOMEM;
|
||||
|
||||
ls_priv->cfg = (const struct loongson_card_config *)device_get_match_data(dev);
|
||||
if (!ls_priv->cfg)
|
||||
return -EINVAL;
|
||||
|
||||
card = &ls_priv->snd_card;
|
||||
|
||||
card->dev = dev;
|
||||
card->owner = THIS_MODULE;
|
||||
card->dai_link = loongson_dai_links;
|
||||
card->num_links = ARRAY_SIZE(loongson_dai_links);
|
||||
|
||||
if (ls_priv->cfg->add_dapm_widgets) {
|
||||
card->dapm_widgets = loongson_aosc_dapm_widgets;
|
||||
card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets);
|
||||
}
|
||||
|
||||
snd_soc_card_set_drvdata(card, ls_priv);
|
||||
|
||||
ret = device_property_read_string(dev, "model", &card->name);
|
||||
|
|
@ -200,7 +337,19 @@ static int loongson_asoc_card_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
static const struct of_device_id loongson_asoc_dt_ids[] = {
|
||||
{ .compatible = "loongson,ls-audio-card" },
|
||||
/* Loongson-2K1000/Loongson-2K2000/LS7A */
|
||||
{
|
||||
.compatible = "loongson,ls-audio-card",
|
||||
.data = &ls2k1000_card_config
|
||||
},
|
||||
{
|
||||
.compatible = "loongson,ls2k0300-forever-pi-audio-card",
|
||||
.data = &ls2k0300_forever_pi_card_config
|
||||
},
|
||||
{
|
||||
.compatible = "loongson,ls2k0300-dl2k0300b-audio-card",
|
||||
.data = &ls2k0300_dl2k0300b_card_config
|
||||
},
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// Loongson I2S controller master mode dirver(platform device)
|
||||
//
|
||||
// Copyright (C) 2023-2024 Loongson Technology Corporation Limited
|
||||
// Copyright (C) 2023-2026 Loongson Technology Corporation Limited
|
||||
//
|
||||
// Author: Yingkun Meng <mengyingkun@loongson.cn>
|
||||
// Binbin Zhou <zhoubinbin@loongson.cn>
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
#include "loongson_i2s.h"
|
||||
#include "loongson_dma.h"
|
||||
|
||||
/* Loongson-2K1000 APBDMA routing */
|
||||
#define LOONGSON_I2S_RX_DMA_OFFSET 21
|
||||
#define LOONGSON_I2S_TX_DMA_OFFSET 18
|
||||
|
||||
|
|
@ -30,6 +31,11 @@
|
|||
#define LOONGSON_DMA3_CONF 0x3
|
||||
#define LOONGSON_DMA4_CONF 0x4
|
||||
|
||||
struct loongson_i2s_plat_config {
|
||||
int rev_id;
|
||||
int (*i2s_dma_config)(struct platform_device *pdev);
|
||||
};
|
||||
|
||||
static int loongson_i2s_apbdma_config(struct platform_device *pdev)
|
||||
{
|
||||
int val;
|
||||
|
|
@ -47,8 +53,18 @@ static int loongson_i2s_apbdma_config(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct loongson_i2s_plat_config ls2k0300_i2s_plat_config = {
|
||||
.rev_id = 1,
|
||||
};
|
||||
|
||||
static const struct loongson_i2s_plat_config ls2k1000_i2s_plat_config = {
|
||||
.rev_id = 0,
|
||||
.i2s_dma_config = loongson_i2s_apbdma_config,
|
||||
};
|
||||
|
||||
static int loongson_i2s_plat_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct loongson_i2s_plat_config *plat_config;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct loongson_i2s *i2s;
|
||||
struct resource *res;
|
||||
|
|
@ -59,12 +75,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev)
|
|||
if (!i2s)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = loongson_i2s_apbdma_config(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
plat_config = device_get_match_data(dev);
|
||||
if (!plat_config)
|
||||
return -EINVAL;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
i2s->reg_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (plat_config->i2s_dma_config) {
|
||||
ret = plat_config->i2s_dma_config(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2s->reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
||||
if (IS_ERR(i2s->reg_base))
|
||||
return dev_err_probe(dev, PTR_ERR(i2s->reg_base),
|
||||
"devm_ioremap_resource failed\n");
|
||||
|
|
@ -87,11 +108,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(i2s_clk))
|
||||
return dev_err_probe(dev, PTR_ERR(i2s_clk), "clock property invalid\n");
|
||||
i2s->clk_rate = clk_get_rate(i2s_clk);
|
||||
i2s->rev_id = plat_config->rev_id;
|
||||
|
||||
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
|
||||
dev_set_name(dev, LS_I2S_DRVNAME);
|
||||
dev_set_drvdata(dev, i2s);
|
||||
|
||||
if (i2s->rev_id == 1) {
|
||||
regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET, I2S_CTRL_RESET);
|
||||
fsleep(200);
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_component(dev, &loongson_i2s_edma_component,
|
||||
&loongson_i2s_dai, 1);
|
||||
if (ret)
|
||||
|
|
@ -102,7 +129,8 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
static const struct of_device_id loongson_i2s_ids[] = {
|
||||
{ .compatible = "loongson,ls2k1000-i2s" },
|
||||
{ .compatible = "loongson,ls2k0300-i2s", .data = &ls2k0300_i2s_plat_config },
|
||||
{ .compatible = "loongson,ls2k1000-i2s", .data = &ls2k1000_i2s_plat_config },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, loongson_i2s_ids);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user