linux/sound/soc/rockchip/rk3288_hdmi_analog.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Rockchip machine ASoC driver for RK3288 boards that have an HDMI and analog
   4 * audio output
   5 *
   6 * Copyright (c) 2016, Collabora Ltd.
   7 *
   8 * Authors: Sjoerd Simons <sjoerd.simons@collabora.com>,
   9 *          Romain Perier <romain.perier@collabora.com>
  10 */
  11
  12#include <linux/module.h>
  13#include <linux/platform_device.h>
  14#include <linux/slab.h>
  15#include <linux/gpio/consumer.h>
  16#include <sound/core.h>
  17#include <sound/jack.h>
  18#include <sound/pcm.h>
  19#include <sound/pcm_params.h>
  20#include <sound/soc.h>
  21#include <sound/soc-dapm.h>
  22
  23#include "rockchip_i2s.h"
  24
  25#define DRV_NAME "rk3288-snd-hdmi-analog"
  26
  27struct rk_drvdata {
  28        struct gpio_desc *gpio_hp_en;
  29};
  30
  31static int rk_hp_power(struct snd_soc_dapm_widget *w,
  32                       struct snd_kcontrol *k, int event)
  33{
  34        struct rk_drvdata *machine = snd_soc_card_get_drvdata(w->dapm->card);
  35
  36        gpiod_set_value_cansleep(machine->gpio_hp_en,
  37                                 SND_SOC_DAPM_EVENT_ON(event));
  38
  39        return 0;
  40}
  41
  42static struct snd_soc_jack headphone_jack;
  43static struct snd_soc_jack_pin headphone_jack_pins[] = {
  44        {
  45                .pin = "Analog",
  46                .mask = SND_JACK_HEADPHONE
  47        },
  48};
  49
  50static const struct snd_soc_dapm_widget rk_dapm_widgets[] = {
  51        SND_SOC_DAPM_HP("Analog", rk_hp_power),
  52        SND_SOC_DAPM_LINE("HDMI", NULL),
  53};
  54
  55static const struct snd_kcontrol_new rk_mc_controls[] = {
  56        SOC_DAPM_PIN_SWITCH("Analog"),
  57        SOC_DAPM_PIN_SWITCH("HDMI"),
  58};
  59
  60static int rk_hw_params(struct snd_pcm_substream *substream,
  61                        struct snd_pcm_hw_params *params)
  62{
  63        int ret = 0;
  64        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
  65        struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
  66        struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
  67        int mclk;
  68
  69        switch (params_rate(params)) {
  70        case 8000:
  71        case 16000:
  72        case 24000:
  73        case 32000:
  74        case 48000:
  75        case 64000:
  76        case 96000:
  77                mclk = 12288000;
  78                break;
  79        case 192000:
  80                mclk = 24576000;
  81                break;
  82        case 11025:
  83        case 22050:
  84        case 44100:
  85        case 88200:
  86                mclk = 11289600;
  87                break;
  88        default:
  89                return -EINVAL;
  90        }
  91
  92        ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
  93                                     SND_SOC_CLOCK_OUT);
  94
  95        if (ret && ret != -ENOTSUPP) {
  96                dev_err(codec_dai->dev, "Can't set cpu clock %d\n", ret);
  97                return ret;
  98        }
  99
 100        ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
 101                                     SND_SOC_CLOCK_IN);
 102        if (ret && ret != -ENOTSUPP) {
 103                dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret);
 104                return ret;
 105        }
 106
 107        return 0;
 108}
 109
 110static struct snd_soc_jack_gpio rk_hp_jack_gpio = {
 111        .name = "rockchip,hp-det",
 112        .report = SND_JACK_HEADPHONE,
 113        .debounce_time = 150
 114};
 115
 116static int rk_init(struct snd_soc_pcm_runtime *runtime)
 117{
 118        struct snd_soc_card *card = runtime->card;
 119        struct device *dev = card->dev;
 120
 121        /* Enable optional Headset Jack detection */
 122        if (of_property_present(dev->of_node, "rockchip,hp-det-gpios")) {
 123                rk_hp_jack_gpio.gpiod_dev = dev;
 124                snd_soc_card_jack_new_pins(runtime->card, "Headphone Jack",
 125                                           SND_JACK_HEADPHONE, &headphone_jack,
 126                                           headphone_jack_pins,
 127                                           ARRAY_SIZE(headphone_jack_pins));
 128                snd_soc_jack_add_gpios(&headphone_jack, 1, &rk_hp_jack_gpio);
 129        }
 130
 131        return 0;
 132}
 133
 134static const struct snd_soc_ops rk_ops = {
 135        .hw_params = rk_hw_params,
 136};
 137
 138SND_SOC_DAILINK_DEFS(audio,
 139        DAILINK_COMP_ARRAY(COMP_EMPTY()),
 140        DAILINK_COMP_ARRAY(COMP_CODEC(NULL, NULL),
 141                           COMP_CODEC("hdmi-audio-codec.2.auto", "i2s-hifi")),
 142        DAILINK_COMP_ARRAY(COMP_EMPTY()));
 143
 144static struct snd_soc_dai_link rk_dailink = {
 145        .name = "Codecs",
 146        .stream_name = "Audio",
 147        .init = rk_init,
 148        .ops = &rk_ops,
 149        /* Set codecs as slave */
 150        .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
 151                SND_SOC_DAIFMT_CBC_CFC,
 152        SND_SOC_DAILINK_REG(audio),
 153};
 154
 155static struct snd_soc_card snd_soc_card_rk = {
 156        .name = "ROCKCHIP-I2S",
 157        .dai_link = &rk_dailink,
 158        .num_links = 1,
 159        .num_aux_devs = 0,
 160        .dapm_widgets = rk_dapm_widgets,
 161        .num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets),
 162        .controls = rk_mc_controls,
 163        .num_controls = ARRAY_SIZE(rk_mc_controls),
 164};
 165
 166static int snd_rk_mc_probe(struct platform_device *pdev)
 167{
 168        int ret;
 169        struct snd_soc_card *card = &snd_soc_card_rk;
 170        struct device_node *np = pdev->dev.of_node;
 171        struct rk_drvdata *machine;
 172        struct of_phandle_args args;
 173
 174        machine = devm_kzalloc(&pdev->dev, sizeof(struct rk_drvdata),
 175                               GFP_KERNEL);
 176        if (!machine)
 177                return -ENOMEM;
 178
 179        card->dev = &pdev->dev;
 180
 181        machine->gpio_hp_en = devm_gpiod_get_optional(&pdev->dev, "rockchip,hp-en", GPIOD_OUT_LOW);
 182        if (IS_ERR(machine->gpio_hp_en))
 183                return PTR_ERR(machine->gpio_hp_en);
 184        gpiod_set_consumer_name(machine->gpio_hp_en, "hp_en");
 185
 186        ret = snd_soc_of_parse_card_name(card, "rockchip,model");
 187        if (ret) {
 188                dev_err(card->dev, "SoC parse card name failed %d\n", ret);
 189                return ret;
 190        }
 191
 192        rk_dailink.codecs[0].of_node = of_parse_phandle(np,
 193                                                        "rockchip,audio-codec",
 194                                                        0);
 195        if (!rk_dailink.codecs[0].of_node) {
 196                dev_err(&pdev->dev,
 197                        "Property 'rockchip,audio-codec' missing or invalid\n");
 198                return -EINVAL;
 199        }
 200        ret = of_parse_phandle_with_fixed_args(np, "rockchip,audio-codec",
 201                                               0, 0, &args);
 202        if (ret) {
 203                dev_err(&pdev->dev,
 204                        "Unable to parse property 'rockchip,audio-codec'\n");
 205                return ret;
 206        }
 207
 208        ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name);
 209        if (ret) {
 210                dev_err(&pdev->dev, "Unable to get codec_dai_name\n");
 211                return ret;
 212        }
 213
 214        rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller",
 215                                                  0);
 216        if (!rk_dailink.cpus->of_node) {
 217                dev_err(&pdev->dev,
 218                        "Property 'rockchip,i2s-controller' missing or invalid\n");
 219                return -EINVAL;
 220        }
 221
 222        rk_dailink.platforms->of_node = rk_dailink.cpus->of_node;
 223
 224        ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing");
 225        if (ret) {
 226                dev_err(&pdev->dev,
 227                        "Unable to parse 'rockchip,routing' property\n");
 228                return ret;
 229        }
 230
 231        snd_soc_card_set_drvdata(card, machine);
 232
 233        ret = devm_snd_soc_register_card(&pdev->dev, card);
 234        if (ret)
 235                return dev_err_probe(&pdev->dev, ret,
 236                                     "Soc register card failed\n");
 237
 238        return 0;
 239}
 240
 241static const struct of_device_id rockchip_sound_of_match[] = {
 242        { .compatible = "rockchip,rk3288-hdmi-analog", },
 243        {},
 244};
 245
 246MODULE_DEVICE_TABLE(of, rockchip_sound_of_match);
 247
 248static struct platform_driver rockchip_sound_driver = {
 249        .probe = snd_rk_mc_probe,
 250        .driver = {
 251                .name = DRV_NAME,
 252                .pm = &snd_soc_pm_ops,
 253                .of_match_table = rockchip_sound_of_match,
 254        },
 255};
 256
 257module_platform_driver(rockchip_sound_driver);
 258
 259MODULE_AUTHOR("Sjoerd Simons <sjoerd.simons@collabora.com>");
 260MODULE_DESCRIPTION("Rockchip RK3288 machine ASoC driver");
 261MODULE_LICENSE("GPL v2");
 262MODULE_ALIAS("platform:" DRV_NAME);
 263