| // SPDX-License-Identifier: GPL-2.0 | |
| // | |
| // rk3328 ALSA SoC Audio driver | |
| // | |
| // Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd All rights reserved. | |
| #include <linux/clk.h> | |
| #include <linux/delay.h> | |
| #include <linux/device.h> | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/regmap.h> | |
| #include <linux/mfd/syscon.h> | |
| #include <sound/dmaengine_pcm.h> | |
| #include <sound/pcm_params.h> | |
| #include "rk3328_codec.h" | |
| /* | |
| * volume setting | |
| * 0: -39dB | |
| * 26: 0dB | |
| * 31: 6dB | |
| * Step: 1.5dB | |
| */ | |
| #define OUT_VOLUME (0x18) | |
| #define RK3328_GRF_SOC_CON2 (0x0408) | |
| #define RK3328_GRF_SOC_CON10 (0x0428) | |
| #define INITIAL_FREQ (11289600) | |
| struct rk3328_codec_priv { | |
| struct regmap *regmap; | |
| struct gpio_desc *mute; | |
| struct clk *mclk; | |
| struct clk *pclk; | |
| unsigned int sclk; | |
| int spk_depop_time; /* msec */ | |
| }; | |
| static const struct reg_default rk3328_codec_reg_defaults[] = { | |
| { CODEC_RESET, 0x03 }, | |
| { DAC_INIT_CTRL1, 0x00 }, | |
| { DAC_INIT_CTRL2, 0x50 }, | |
| { DAC_INIT_CTRL3, 0x0e }, | |
| { DAC_PRECHARGE_CTRL, 0x01 }, | |
| { DAC_PWR_CTRL, 0x00 }, | |
| { DAC_CLK_CTRL, 0x00 }, | |
| { HPMIX_CTRL, 0x00 }, | |
| { HPOUT_CTRL, 0x00 }, | |
| { HPOUTL_GAIN_CTRL, 0x00 }, | |
| { HPOUTR_GAIN_CTRL, 0x00 }, | |
| { HPOUT_POP_CTRL, 0x11 }, | |
| }; | |
| static int rk3328_codec_reset(struct rk3328_codec_priv *rk3328) | |
| { | |
| regmap_write(rk3328->regmap, CODEC_RESET, 0x00); | |
| mdelay(10); | |
| regmap_write(rk3328->regmap, CODEC_RESET, 0x03); | |
| return 0; | |
| } | |
| static int rk3328_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
| { | |
| struct rk3328_codec_priv *rk3328 = | |
| snd_soc_component_get_drvdata(dai->component); | |
| unsigned int val; | |
| switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { | |
| case SND_SOC_DAIFMT_CBC_CFC: | |
| val = PIN_DIRECTION_IN | DAC_I2S_MODE_SLAVE; | |
| break; | |
| case SND_SOC_DAIFMT_CBP_CFP: | |
| val = PIN_DIRECTION_OUT | DAC_I2S_MODE_MASTER; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| regmap_update_bits(rk3328->regmap, DAC_INIT_CTRL1, | |
| PIN_DIRECTION_MASK | DAC_I2S_MODE_MASK, val); | |
| switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
| case SND_SOC_DAIFMT_DSP_A: | |
| case SND_SOC_DAIFMT_DSP_B: | |
| val = DAC_MODE_PCM; | |
| break; | |
| case SND_SOC_DAIFMT_I2S: | |
| val = DAC_MODE_I2S; | |
| break; | |
| case SND_SOC_DAIFMT_RIGHT_J: | |
| val = DAC_MODE_RJM; | |
| break; | |
| case SND_SOC_DAIFMT_LEFT_J: | |
| val = DAC_MODE_LJM; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| regmap_update_bits(rk3328->regmap, DAC_INIT_CTRL2, | |
| DAC_MODE_MASK, val); | |
| return 0; | |
| } | |
| static int rk3328_mute_stream(struct snd_soc_dai *dai, int mute, int direction) | |
| { | |
| struct rk3328_codec_priv *rk3328 = | |
| snd_soc_component_get_drvdata(dai->component); | |
| unsigned int val; | |
| if (mute) | |
| val = HPOUTL_MUTE | HPOUTR_MUTE; | |
| else | |
| val = HPOUTL_UNMUTE | HPOUTR_UNMUTE; | |
| regmap_update_bits(rk3328->regmap, HPOUT_CTRL, | |
| HPOUTL_MUTE_MASK | HPOUTR_MUTE_MASK, val); | |
| return 0; | |
| } | |
| static int rk3328_codec_power_on(struct rk3328_codec_priv *rk3328, int wait_ms) | |
| { | |
| regmap_update_bits(rk3328->regmap, DAC_PRECHARGE_CTRL, | |
| DAC_CHARGE_XCHARGE_MASK, DAC_CHARGE_PRECHARGE); | |
| mdelay(10); | |
| regmap_update_bits(rk3328->regmap, DAC_PRECHARGE_CTRL, | |
| DAC_CHARGE_CURRENT_ALL_MASK, | |
| DAC_CHARGE_CURRENT_ALL_ON); | |
| mdelay(wait_ms); | |
| return 0; | |
| } | |
| static int rk3328_codec_power_off(struct rk3328_codec_priv *rk3328, int wait_ms) | |
| { | |
| regmap_update_bits(rk3328->regmap, DAC_PRECHARGE_CTRL, | |
| DAC_CHARGE_XCHARGE_MASK, DAC_CHARGE_DISCHARGE); | |
| mdelay(10); | |
| regmap_update_bits(rk3328->regmap, DAC_PRECHARGE_CTRL, | |
| DAC_CHARGE_CURRENT_ALL_MASK, | |
| DAC_CHARGE_CURRENT_ALL_ON); | |
| mdelay(wait_ms); | |
| return 0; | |
| } | |
| static const struct rk3328_reg_msk_val playback_open_list[] = { | |
| { DAC_PWR_CTRL, DAC_PWR_MASK, DAC_PWR_ON }, | |
| { DAC_PWR_CTRL, DACL_PATH_REFV_MASK | DACR_PATH_REFV_MASK, | |
| DACL_PATH_REFV_ON | DACR_PATH_REFV_ON }, | |
| { DAC_PWR_CTRL, HPOUTL_ZERO_CROSSING_MASK | HPOUTR_ZERO_CROSSING_MASK, | |
| HPOUTL_ZERO_CROSSING_ON | HPOUTR_ZERO_CROSSING_ON }, | |
| { HPOUT_POP_CTRL, HPOUTR_POP_MASK | HPOUTL_POP_MASK, | |
| HPOUTR_POP_WORK | HPOUTL_POP_WORK }, | |
| { HPMIX_CTRL, HPMIXL_MASK | HPMIXR_MASK, HPMIXL_EN | HPMIXR_EN }, | |
| { HPMIX_CTRL, HPMIXL_INIT_MASK | HPMIXR_INIT_MASK, | |
| HPMIXL_INIT_EN | HPMIXR_INIT_EN }, | |
| { HPOUT_CTRL, HPOUTL_MASK | HPOUTR_MASK, HPOUTL_EN | HPOUTR_EN }, | |
| { HPOUT_CTRL, HPOUTL_INIT_MASK | HPOUTR_INIT_MASK, | |
| HPOUTL_INIT_EN | HPOUTR_INIT_EN }, | |
| { DAC_CLK_CTRL, DACL_REFV_MASK | DACR_REFV_MASK, | |
| DACL_REFV_ON | DACR_REFV_ON }, | |
| { DAC_CLK_CTRL, DACL_CLK_MASK | DACR_CLK_MASK, | |
| DACL_CLK_ON | DACR_CLK_ON }, | |
| { DAC_CLK_CTRL, DACL_MASK | DACR_MASK, DACL_ON | DACR_ON }, | |
| { DAC_CLK_CTRL, DACL_INIT_MASK | DACR_INIT_MASK, | |
| DACL_INIT_ON | DACR_INIT_ON }, | |
| { DAC_SELECT, DACL_SELECT_MASK | DACR_SELECT_MASK, | |
| DACL_SELECT | DACR_SELECT }, | |
| { HPMIX_CTRL, HPMIXL_INIT2_MASK | HPMIXR_INIT2_MASK, | |
| HPMIXL_INIT2_EN | HPMIXR_INIT2_EN }, | |
| { HPOUT_CTRL, HPOUTL_MUTE_MASK | HPOUTR_MUTE_MASK, | |
| HPOUTL_UNMUTE | HPOUTR_UNMUTE }, | |
| }; | |
| static int rk3328_codec_open_playback(struct rk3328_codec_priv *rk3328) | |
| { | |
| int i; | |
| regmap_update_bits(rk3328->regmap, DAC_PRECHARGE_CTRL, | |
| DAC_CHARGE_CURRENT_ALL_MASK, | |
| DAC_CHARGE_CURRENT_I); | |
| for (i = 0; i < ARRAY_SIZE(playback_open_list); i++) { | |
| regmap_update_bits(rk3328->regmap, | |
| playback_open_list[i].reg, | |
| playback_open_list[i].msk, | |
| playback_open_list[i].val); | |
| mdelay(1); | |
| } | |
| msleep(rk3328->spk_depop_time); | |
| gpiod_set_value(rk3328->mute, 0); | |
| regmap_update_bits(rk3328->regmap, HPOUTL_GAIN_CTRL, | |
| HPOUTL_GAIN_MASK, OUT_VOLUME); | |
| regmap_update_bits(rk3328->regmap, HPOUTR_GAIN_CTRL, | |
| HPOUTR_GAIN_MASK, OUT_VOLUME); | |
| return 0; | |
| } | |
| static const struct rk3328_reg_msk_val playback_close_list[] = { | |
| { HPMIX_CTRL, HPMIXL_INIT2_MASK | HPMIXR_INIT2_MASK, | |
| HPMIXL_INIT2_DIS | HPMIXR_INIT2_DIS }, | |
| { DAC_SELECT, DACL_SELECT_MASK | DACR_SELECT_MASK, | |
| DACL_UNSELECT | DACR_UNSELECT }, | |
| { HPOUT_CTRL, HPOUTL_MUTE_MASK | HPOUTR_MUTE_MASK, | |
| HPOUTL_MUTE | HPOUTR_MUTE }, | |
| { HPOUT_CTRL, HPOUTL_INIT_MASK | HPOUTR_INIT_MASK, | |
| HPOUTL_INIT_DIS | HPOUTR_INIT_DIS }, | |
| { HPOUT_CTRL, HPOUTL_MASK | HPOUTR_MASK, HPOUTL_DIS | HPOUTR_DIS }, | |
| { HPMIX_CTRL, HPMIXL_MASK | HPMIXR_MASK, HPMIXL_DIS | HPMIXR_DIS }, | |
| { DAC_CLK_CTRL, DACL_MASK | DACR_MASK, DACL_OFF | DACR_OFF }, | |
| { DAC_CLK_CTRL, DACL_CLK_MASK | DACR_CLK_MASK, | |
| DACL_CLK_OFF | DACR_CLK_OFF }, | |
| { DAC_CLK_CTRL, DACL_REFV_MASK | DACR_REFV_MASK, | |
| DACL_REFV_OFF | DACR_REFV_OFF }, | |
| { HPOUT_POP_CTRL, HPOUTR_POP_MASK | HPOUTL_POP_MASK, | |
| HPOUTR_POP_XCHARGE | HPOUTL_POP_XCHARGE }, | |
| { DAC_PWR_CTRL, DACL_PATH_REFV_MASK | DACR_PATH_REFV_MASK, | |
| DACL_PATH_REFV_OFF | DACR_PATH_REFV_OFF }, | |
| { DAC_PWR_CTRL, DAC_PWR_MASK, DAC_PWR_OFF }, | |
| { HPMIX_CTRL, HPMIXL_INIT_MASK | HPMIXR_INIT_MASK, | |
| HPMIXL_INIT_DIS | HPMIXR_INIT_DIS }, | |
| { DAC_CLK_CTRL, DACL_INIT_MASK | DACR_INIT_MASK, | |
| DACL_INIT_OFF | DACR_INIT_OFF }, | |
| }; | |
| static int rk3328_codec_close_playback(struct rk3328_codec_priv *rk3328) | |
| { | |
| size_t i; | |
| gpiod_set_value(rk3328->mute, 1); | |
| regmap_update_bits(rk3328->regmap, HPOUTL_GAIN_CTRL, | |
| HPOUTL_GAIN_MASK, 0); | |
| regmap_update_bits(rk3328->regmap, HPOUTR_GAIN_CTRL, | |
| HPOUTR_GAIN_MASK, 0); | |
| for (i = 0; i < ARRAY_SIZE(playback_close_list); i++) { | |
| regmap_update_bits(rk3328->regmap, | |
| playback_close_list[i].reg, | |
| playback_close_list[i].msk, | |
| playback_close_list[i].val); | |
| mdelay(1); | |
| } | |
| /* Workaround for silence when changed Fs 48 -> 44.1kHz */ | |
| rk3328_codec_reset(rk3328); | |
| regmap_update_bits(rk3328->regmap, DAC_PRECHARGE_CTRL, | |
| DAC_CHARGE_CURRENT_ALL_MASK, | |
| DAC_CHARGE_CURRENT_ALL_ON); | |
| return 0; | |
| } | |
| static int rk3328_hw_params(struct snd_pcm_substream *substream, | |
| struct snd_pcm_hw_params *params, | |
| struct snd_soc_dai *dai) | |
| { | |
| struct rk3328_codec_priv *rk3328 = | |
| snd_soc_component_get_drvdata(dai->component); | |
| unsigned int val = 0; | |
| switch (params_format(params)) { | |
| case SNDRV_PCM_FORMAT_S16_LE: | |
| val = DAC_VDL_16BITS; | |
| break; | |
| case SNDRV_PCM_FORMAT_S20_3LE: | |
| val = DAC_VDL_20BITS; | |
| break; | |
| case SNDRV_PCM_FORMAT_S24_LE: | |
| val = DAC_VDL_24BITS; | |
| break; | |
| case SNDRV_PCM_FORMAT_S32_LE: | |
| val = DAC_VDL_32BITS; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| regmap_update_bits(rk3328->regmap, DAC_INIT_CTRL2, DAC_VDL_MASK, val); | |
| val = DAC_WL_32BITS | DAC_RST_DIS; | |
| regmap_update_bits(rk3328->regmap, DAC_INIT_CTRL3, | |
| DAC_WL_MASK | DAC_RST_MASK, val); | |
| return 0; | |
| } | |
| static int rk3328_pcm_startup(struct snd_pcm_substream *substream, | |
| struct snd_soc_dai *dai) | |
| { | |
| struct rk3328_codec_priv *rk3328 = | |
| snd_soc_component_get_drvdata(dai->component); | |
| return rk3328_codec_open_playback(rk3328); | |
| } | |
| static void rk3328_pcm_shutdown(struct snd_pcm_substream *substream, | |
| struct snd_soc_dai *dai) | |
| { | |
| struct rk3328_codec_priv *rk3328 = | |
| snd_soc_component_get_drvdata(dai->component); | |
| rk3328_codec_close_playback(rk3328); | |
| } | |
| static const struct snd_soc_dai_ops rk3328_dai_ops = { | |
| .hw_params = rk3328_hw_params, | |
| .set_fmt = rk3328_set_dai_fmt, | |
| .mute_stream = rk3328_mute_stream, | |
| .startup = rk3328_pcm_startup, | |
| .shutdown = rk3328_pcm_shutdown, | |
| .no_capture_mute = 1, | |
| }; | |
| static struct snd_soc_dai_driver rk3328_dai[] = { | |
| { | |
| .name = "rk3328-hifi", | |
| .id = RK3328_HIFI, | |
| .playback = { | |
| .stream_name = "HIFI Playback", | |
| .channels_min = 1, | |
| .channels_max = 2, | |
| .rates = SNDRV_PCM_RATE_8000_96000, | |
| .formats = (SNDRV_PCM_FMTBIT_S16_LE | | |
| SNDRV_PCM_FMTBIT_S20_3LE | | |
| SNDRV_PCM_FMTBIT_S24_LE | | |
| SNDRV_PCM_FMTBIT_S32_LE), | |
| }, | |
| .capture = { | |
| .stream_name = "HIFI Capture", | |
| .channels_min = 2, | |
| .channels_max = 8, | |
| .rates = SNDRV_PCM_RATE_8000_96000, | |
| .formats = (SNDRV_PCM_FMTBIT_S16_LE | | |
| SNDRV_PCM_FMTBIT_S20_3LE | | |
| SNDRV_PCM_FMTBIT_S24_LE | | |
| SNDRV_PCM_FMTBIT_S32_LE), | |
| }, | |
| .ops = &rk3328_dai_ops, | |
| }, | |
| }; | |
| static int rk3328_codec_probe(struct snd_soc_component *component) | |
| { | |
| struct rk3328_codec_priv *rk3328 = | |
| snd_soc_component_get_drvdata(component); | |
| rk3328_codec_reset(rk3328); | |
| rk3328_codec_power_on(rk3328, 0); | |
| return 0; | |
| } | |
| static void rk3328_codec_remove(struct snd_soc_component *component) | |
| { | |
| struct rk3328_codec_priv *rk3328 = | |
| snd_soc_component_get_drvdata(component); | |
| rk3328_codec_close_playback(rk3328); | |
| rk3328_codec_power_off(rk3328, 0); | |
| } | |
| static const struct snd_soc_component_driver soc_codec_rk3328 = { | |
| .probe = rk3328_codec_probe, | |
| .remove = rk3328_codec_remove, | |
| }; | |
| static bool rk3328_codec_write_read_reg(struct device *dev, unsigned int reg) | |
| { | |
| switch (reg) { | |
| case CODEC_RESET: | |
| case DAC_INIT_CTRL1: | |
| case DAC_INIT_CTRL2: | |
| case DAC_INIT_CTRL3: | |
| case DAC_PRECHARGE_CTRL: | |
| case DAC_PWR_CTRL: | |
| case DAC_CLK_CTRL: | |
| case HPMIX_CTRL: | |
| case DAC_SELECT: | |
| case HPOUT_CTRL: | |
| case HPOUTL_GAIN_CTRL: | |
| case HPOUTR_GAIN_CTRL: | |
| case HPOUT_POP_CTRL: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| static bool rk3328_codec_volatile_reg(struct device *dev, unsigned int reg) | |
| { | |
| switch (reg) { | |
| case CODEC_RESET: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| static const struct regmap_config rk3328_codec_regmap_config = { | |
| .reg_bits = 32, | |
| .reg_stride = 4, | |
| .val_bits = 32, | |
| .max_register = HPOUT_POP_CTRL, | |
| .writeable_reg = rk3328_codec_write_read_reg, | |
| .readable_reg = rk3328_codec_write_read_reg, | |
| .volatile_reg = rk3328_codec_volatile_reg, | |
| .reg_defaults = rk3328_codec_reg_defaults, | |
| .num_reg_defaults = ARRAY_SIZE(rk3328_codec_reg_defaults), | |
| .cache_type = REGCACHE_FLAT, | |
| }; | |
| static int rk3328_platform_probe(struct platform_device *pdev) | |
| { | |
| struct device_node *rk3328_np = pdev->dev.of_node; | |
| struct rk3328_codec_priv *rk3328; | |
| struct regmap *grf; | |
| void __iomem *base; | |
| rk3328 = devm_kzalloc(&pdev->dev, sizeof(*rk3328), GFP_KERNEL); | |
| if (!rk3328) | |
| return -ENOMEM; | |
| grf = syscon_regmap_lookup_by_phandle(rk3328_np, | |
| "rockchip,grf"); | |
| if (IS_ERR(grf)) { | |
| dev_err(&pdev->dev, "missing 'rockchip,grf'\n"); | |
| return PTR_ERR(grf); | |
| } | |
| /* enable i2s_acodec_en */ | |
| regmap_write(grf, RK3328_GRF_SOC_CON2, | |
| (BIT(14) << 16 | BIT(14))); | |
| if (of_property_read_u32(rk3328_np, "spk-depop-time-ms", | |
| &rk3328->spk_depop_time)) { | |
| dev_info(&pdev->dev, "spk_depop_time use default value.\n"); | |
| rk3328->spk_depop_time = 200; | |
| } | |
| rk3328->mute = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_HIGH); | |
| if (IS_ERR(rk3328->mute)) | |
| return PTR_ERR(rk3328->mute); | |
| /* | |
| * Rock64 is the only supported platform to have widely relied on | |
| * this; if we do happen to come across an old DTB, just leave the | |
| * external mute forced off. | |
| */ | |
| if (!rk3328->mute && of_machine_is_compatible("pine64,rock64")) { | |
| dev_warn(&pdev->dev, "assuming implicit control of GPIO_MUTE; update devicetree if possible\n"); | |
| regmap_write(grf, RK3328_GRF_SOC_CON10, BIT(17) | BIT(1)); | |
| } | |
| rk3328->mclk = devm_clk_get_enabled(&pdev->dev, "mclk"); | |
| if (IS_ERR(rk3328->mclk)) | |
| return PTR_ERR(rk3328->mclk); | |
| clk_set_rate(rk3328->mclk, INITIAL_FREQ); | |
| rk3328->pclk = devm_clk_get_enabled(&pdev->dev, "pclk"); | |
| if (IS_ERR(rk3328->pclk)) | |
| return dev_err_probe(&pdev->dev, PTR_ERR(rk3328->pclk), | |
| "failed to get or enable acodec pclk\n"); | |
| base = devm_platform_ioremap_resource(pdev, 0); | |
| if (IS_ERR(base)) | |
| return PTR_ERR(base); | |
| rk3328->regmap = devm_regmap_init_mmio(&pdev->dev, base, | |
| &rk3328_codec_regmap_config); | |
| if (IS_ERR(rk3328->regmap)) | |
| return PTR_ERR(rk3328->regmap); | |
| platform_set_drvdata(pdev, rk3328); | |
| return devm_snd_soc_register_component(&pdev->dev, &soc_codec_rk3328, | |
| rk3328_dai, | |
| ARRAY_SIZE(rk3328_dai)); | |
| } | |
| static const struct of_device_id rk3328_codec_of_match[] __maybe_unused = { | |
| { .compatible = "rockchip,rk3328-codec", }, | |
| {}, | |
| }; | |
| MODULE_DEVICE_TABLE(of, rk3328_codec_of_match); | |
| static struct platform_driver rk3328_codec_driver = { | |
| .driver = { | |
| .name = "rk3328-codec", | |
| .of_match_table = of_match_ptr(rk3328_codec_of_match), | |
| }, | |
| .probe = rk3328_platform_probe, | |
| }; | |
| module_platform_driver(rk3328_codec_driver); | |
| MODULE_AUTHOR("Sugar Zhang <sugar.zhang@rock-chips.com>"); | |
| MODULE_DESCRIPTION("ASoC rk3328 codec driver"); | |
| MODULE_LICENSE("GPL v2"); |