[PATCH] Add audio driver for rk817 upstream

Pierre-Louis Bossart pierre-louis.bossart at linux.intel.com
Wed Mar 10 02:10:24 CET 2021



On 3/9/21 5:41 PM, Chris Morgan wrote:
> I'm wondering if you all can help me.  I'm trying to get the rk817
> codec driver working from Rockchip's BSP kernel sources (GPL per
> the license) and I'm struggling with a few parts. The first part

is to Cc: audio maintainers (Takashi Iwai and Mark Brown).

Then you need to make sure there is a Signed-off-by tag from the 
original contribution this code is based on, along with yours. It 
doesn't hurt to have a pointer to that code either in the commit message.

> is I'm not sure if I have my audio paths set up correctly. For example
> the sinks I have set up are for HPOL and HPOR, and the source is for
> MIC. While this does work (audio output seems fine) I'm having issues
> with the GPIO to detect headphone insertion. When I insert headphones
> I expect the audio to output to the headphones, and when I remove
> headphones I expect the audio to output to a speaker. Right now I
> have to manually change the output between the different paths.

You don't necessarily have to do everything at the kernel level, it's 
not uncommon to have the driver set a kcontrol for jack detection, and 
let userspace change settings on a jack detection event. PulseAudio 
relies on UCM JackControl to switch to Headphones and Headset devices.

> Additionally, while the codec "technically" has dual channel inputs
> for the microphone, I'm only using the L channel. Should I have a
> stereo mixer? Note that I'm using the simple-audio-card to set my
> paths, widgets, and the gpio pin. They all work, just not seamlessly
> together.
> 
> Basically, I'm wanting to know if and how I should set up my audio
> paths for the speaker and headphones, they use mostly the same pins
> and only really differ in setting the external amp settings.
> Additionally, once I set up my paths how do I ensure that the GPIO
> events will result in switching between the speaker/headphone path?
> 
> Any help you can provide is appreciated. This is my first attempt at
> something of this magnitude (for me it's a big step), even if it's
> just trying to facilitate in getting a vendor's code ready for upstream.

> --- /dev/null
> +++ b/sound/soc/codecs/rk817_codec.c
> @@ -0,0 +1,1148 @@
> +/*
> + * Copyright (c) 2018 Rockchip 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 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.

remove and replace by an SPDX line.

> +static int rk817_playback_path_put(struct snd_kcontrol *kcontrol,
> +				   struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
> +	struct rk817_codec_priv *rk817 = snd_soc_component_get_drvdata(component);
> +	long int pre_path;

it's uncommon to see this, use either int or u32/u64 if your require a 
specific size.

> +
> +	if (rk817->playback_path == ucontrol->value.integer.value[0]) {
> +		return 0;
> +	}
> +
> +	pre_path = rk817->playback_path;

'pre' as in 'previous' or 'preamp' or as opposed to 'post'?

> +	rk817->playback_path = ucontrol->value.integer.value[0];
> +
> +	if (rk817->playback_path != OFF)
> +		clk_prepare_enable(rk817->mclk);
> +	else
> +		clk_disable_unprepare(rk817->mclk);
> +
> +	switch (rk817->playback_path) {
> +	case OFF:
> +		if (pre_path != OFF && pre_path != HP_PATH) {
> +			rk817_codec_power_down(component, RK817_CODEC_PLAYBACK);
> +			if (rk817->capture_path == 0)
> +				rk817_codec_power_down(component, RK817_CODEC_ALL);
> +		}
> +		break;
> +	case SPK_PATH:
> +		if (pre_path == OFF)
> +			rk817_codec_power_up(component, RK817_CODEC_PLAYBACK);
> +		if (!rk817->use_ext_amplifier) {
> +			/* power on dac ibias/l/r */
> +			snd_soc_component_write(component, RK817_CODEC_ADAC_CFG1,
> +						PWD_DACBIAS_ON | PWD_DACD_ON |
> +						PWD_DACL_DOWN | PWD_DACR_DOWN);
> +			/* CLASS D mode, combine LR channels */
> +			snd_soc_component_write(component,
> +						RK817_CODEC_DDAC_MUTE_MIXCTL,
> +						0x10);
> +			/* CLASS D enable */
> +			snd_soc_component_write(component,
> +						RK817_CODEC_ACLASSD_CFG1,
> +						0xa5);
> +			/* restart CLASS D, OCPP/N */
> +			snd_soc_component_write(component,
> +						RK817_CODEC_ACLASSD_CFG2,
> +						0xf7);
> +		} else {
> +			/* HP_CP_EN , CP 2.3V */
> +			snd_soc_component_write(component, RK817_CODEC_AHP_CP,
> +						0x11);
> +			/* power on HP two stage opamp ,HP amplitude 0db */
> +			snd_soc_component_write(component, RK817_CODEC_AHP_CFG0,
> +						0x80);
> +			/* power on dac ibias/l/r */
> +			snd_soc_component_write(component, RK817_CODEC_ADAC_CFG1,
> +						PWD_DACBIAS_ON | PWD_DACD_DOWN |
> +						PWD_DACL_ON | PWD_DACR_ON);
> +			snd_soc_component_update_bits(component,
> +						      RK817_CODEC_DDAC_MUTE_MIXCTL,
> +						      DACMT_ENABLE, DACMT_DISABLE);
> +		}
> +		snd_soc_component_write(component, RK817_CODEC_DDAC_VOLL,
> +					rk817->spk_volume);
> +		snd_soc_component_write(component, RK817_CODEC_DDAC_VOLR,
> +					rk817->spk_volume);
> +		break;
> +	case HP_PATH:
> +		if (pre_path == OFF)
> +			rk817_codec_power_up(component, RK817_CODEC_PLAYBACK);
> +		/* HP_CP_EN , CP 2.3V */
> +		snd_soc_component_write(component, RK817_CODEC_AHP_CP, 0x11);
> +		/* power on HP two stage opamp ,HP amplitude 0db */
> +		snd_soc_component_write(component, RK817_CODEC_AHP_CFG0, 0x80);
> +		/* power on dac ibias/l/r */
> +		snd_soc_component_write(component, RK817_CODEC_ADAC_CFG1,
> +					PWD_DACBIAS_ON | PWD_DACD_DOWN |
> +					PWD_DACL_ON | PWD_DACR_ON);
> +		/* CLASS D mode disable, split LR channels */
> +		snd_soc_component_write(component,
> +					RK817_CODEC_DDAC_MUTE_MIXCTL,
> +					0x00);
> +
> +		snd_soc_component_write(component, RK817_CODEC_DDAC_VOLL,
> +					rk817->hp_volume);
> +		snd_soc_component_write(component, RK817_CODEC_DDAC_VOLR,
> +					rk817->hp_volume);
> +		break;
> +	case SPK_HP:
> +		if (pre_path == OFF)
> +			rk817_codec_power_up(component, RK817_CODEC_PLAYBACK);
> +
> +		/* HP_CP_EN , CP 2.3V  */
> +		snd_soc_component_write(component, RK817_CODEC_AHP_CP, 0x11);
> +		/* power on HP two stage opamp ,HP amplitude 0db */
> +		snd_soc_component_write(component, RK817_CODEC_AHP_CFG0, 0x80);
> +
> +		/* power on dac ibias/l/r */
> +		snd_soc_component_write(component, RK817_CODEC_ADAC_CFG1,
> +					PWD_DACBIAS_ON | PWD_DACD_ON |
> +					PWD_DACL_ON | PWD_DACR_ON);
> +
> +		if (!rk817->use_ext_amplifier) {
> +			/* CLASS D mode, combine LR channels */
> +			snd_soc_component_write(component,
> +						RK817_CODEC_DDAC_MUTE_MIXCTL,
> +						0x10);
> +			/* CLASS D enable */
> +			snd_soc_component_write(component,
> +						RK817_CODEC_ACLASSD_CFG1,
> +						0xa5);
> +			/* restart CLASS D, OCPP/N */
> +			snd_soc_component_write(component,
> +						RK817_CODEC_ACLASSD_CFG2,
> +						0xf7);
> +		}
> +
> +		snd_soc_component_write(component, RK817_CODEC_DDAC_VOLL,
> +					rk817->hp_volume);
> +		snd_soc_component_write(component, RK817_CODEC_DDAC_VOLR,
> +					rk817->hp_volume);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int rk817_capture_path_get(struct snd_kcontrol *kcontrol,
> +				  struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
> +	struct rk817_codec_priv *rk817 = snd_soc_component_get_drvdata(component);
> +
> +	dev_dbg(component->dev, "%s:capture_path %ld\n", __func__, rk817->capture_path);
> +	ucontrol->value.integer.value[0] = rk817->capture_path;
> +	return 0;
> +}
> +
> +static int rk817_capture_path_put(struct snd_kcontrol *kcontrol,
> +				  struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
> +	struct rk817_codec_priv *rk817 = snd_soc_component_get_drvdata(component);
> +	long int pre_path;
> +
> +	if (rk817->capture_path == ucontrol->value.integer.value[0]) {
> +		dev_dbg(component->dev, "%s:capture_path is not changed!\n",
> +			__func__);
> +		return 0;
> +	}
> +
> +	pre_path = rk817->capture_path;
> +	rk817->capture_path = ucontrol->value.integer.value[0];
> +
> +	if (rk817->capture_path != MIC_OFF)
> +		clk_prepare_enable(rk817->mclk);
> +	else
> +		clk_disable_unprepare(rk817->mclk);
> +
> +	switch (rk817->capture_path) {
> +	case MIC_OFF:
> +		if (pre_path != MIC_OFF)
> +			rk817_codec_power_down(component, RK817_CODEC_CAPTURE);
> +		break;
> +	case MIC:
> +		if (pre_path == MIC_OFF)
> +			rk817_codec_power_up(component, RK817_CODEC_CAPTURE);

these sequences look like trying to bypass DAPM or re-invent it with 
custom state machines.

> +
> +		if (rk817->adc_for_loopback) {
> +			/* don't need to gain when adc use for loopback */
> +			snd_soc_component_update_bits(component,
> +						      RK817_CODEC_AMIC_CFG0,
> +						      0xf,
> +						      0x0);
> +			snd_soc_component_write(component,
> +						RK817_CODEC_DMIC_PGA_GAIN,
> +						0x66);
> +			snd_soc_component_write(component,
> +						RK817_CODEC_DADC_VOLL,
> +						0x00);
> +			snd_soc_component_write(component,
> +						RK817_CODEC_DADC_VOLR,
> +						0x00);
> +			break;
> +		}
> +		if (!rk817->mic_in_differential) {
> +			snd_soc_component_write(component,
> +						RK817_CODEC_DADC_VOLR,
> +						0xff);
> +			snd_soc_component_update_bits(component,
> +						      RK817_CODEC_AADC_CFG0,
> +						      ADC_R_PWD_MASK,
> +						      ADC_R_PWD_EN);
> +			snd_soc_component_update_bits(component,
> +						      RK817_CODEC_AMIC_CFG0,
> +						      PWD_PGA_R_MASK,
> +						      PWD_PGA_R_EN);
> +		}
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
[...]

> +static int rk817_codec_parse_dt_property(struct device *dev,
> +					 struct rk817_codec_priv *rk817)
> +{
> +	struct device_node *node = dev->parent->of_node;
> +	int ret;
> +
> +	if (!node) {
> +		dev_err(dev, "%s() dev->parent->of_node is NULL\n",
> +			__func__);
> +		return -ENODEV;
> +	}
> +
> +	node = of_get_child_by_name(dev->parent->of_node, "codec");
> +	if (!node) {
> +		dev_err(dev, "%s() Can not get child: codec\n",
> +			__func__);
> +		return -ENODEV;
> +	}
> +
> +	rk817->hp_ctl_gpio = devm_gpiod_get_optional(dev, "hp-ctl",
> +						  GPIOD_OUT_LOW);
> +
> +	rk817->spk_ctl_gpio = devm_gpiod_get_optional(dev, "spk-ctl",
> +						  GPIOD_OUT_LOW);
> +
> +	ret = of_property_read_u32(node, "spk-mute-delay-ms",
> +				   &rk817->spk_mute_delay);
> +	if (ret < 0) {
> +		rk817->spk_mute_delay = 0;
> +	}
> +
> +	ret = of_property_read_u32(node, "hp-mute-delay-ms",
> +				   &rk817->hp_mute_delay);
> +	if (ret < 0) {
> +		rk817->hp_mute_delay = 0;
> +	}
> +
> +	ret = of_property_read_u32(node, "spk-volume", &rk817->spk_volume);
> +	if (ret < 0) {
> +		rk817->spk_volume = OUT_VOLUME;
> +	}
> +	if (rk817->spk_volume < 3)
> +		rk817->spk_volume = 3;
> +
> +	ret = of_property_read_u32(node, "hp-volume",
> +				   &rk817->hp_volume);
> +	if (ret < 0) {
> +		rk817->hp_volume = OUT_VOLUME;
> +	}
> +	if (rk817->hp_volume < 3)
> +		rk817->hp_volume = 3;
> +
> +	ret = of_property_read_u32(node, "capture-volume",
> +				   &rk817->capture_volume);
> +	if (ret < 0) {
> +		rk817->capture_volume = CAPTURE_VOLUME;
> +	}
> +
> +	rk817->mic_in_differential =
> +			of_property_read_bool(node, "mic-in-differential");
> +
> +	rk817->pdmdata_out_enable =
> +			of_property_read_bool(node, "pdmdata-out-enable");
> +
> +	rk817->use_ext_amplifier =
> +			of_property_read_bool(node, "use-ext-amplifier");
> +
> +	rk817->adc_for_loopback =
> +			of_property_read_bool(node, "adc-for-loopback");

you will need DT bindings for all these properties.

> +
> +	return 0;
> +}
> +
> +static const struct regmap_config rk817_codec_regmap_config = {
> +	.name = "rk817-codec",
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.reg_stride = 1,
> +	.max_register = 0x4f,
> +	.cache_type = REGCACHE_NONE,
> +	.volatile_reg = rk817_volatile_register,
> +	.writeable_reg = rk817_codec_register,
> +	.readable_reg = rk817_codec_register,
> +	.reg_defaults = rk817_reg_defaults,
> +	.num_reg_defaults = ARRAY_SIZE(rk817_reg_defaults),
> +};
> +
> +static int rk817_platform_probe(struct platform_device *pdev)
> +{
> +	struct rk808 *rk817 = dev_get_drvdata(pdev->dev.parent);
> +	struct rk817_codec_priv *rk817_codec_data;
> +	int ret;
> +
> +	if (!rk817) {
> +		dev_err(&pdev->dev, "%s : rk817 is NULL\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	rk817_codec_data = devm_kzalloc(&pdev->dev,
> +					sizeof(struct rk817_codec_priv),
> +					GFP_KERNEL);
> +	if (!rk817_codec_data)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, rk817_codec_data);
> +
> +	ret = rk817_codec_parse_dt_property(&pdev->dev, rk817_codec_data);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "%s() parse device tree property error %d\n",
> +			__func__, ret);
> +		goto err_;
> +	}
> +
> +	rk817_codec_data->regmap = devm_regmap_init_i2c(rk817->i2c,
> +					    &rk817_codec_regmap_config);
> +	if (IS_ERR(rk817_codec_data->regmap)) {
> +		ret = PTR_ERR(rk817_codec_data->regmap);
> +		dev_err(&pdev->dev, "failed to allocate register map: %d\n",
> +			ret);
> +		goto err_;
> +	}
> +
> +	rk817_codec_data->mclk = devm_clk_get(&pdev->dev, "mclk");
> +	if (IS_ERR(rk817_codec_data->mclk)) {
> +		dev_err(&pdev->dev, "Unable to get mclk\n");
> +		ret = -ENXIO;
> +		goto err_;
> +	}
> +
> +	ret = devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_rk817,
> +					      rk817_dai, ARRAY_SIZE(rk817_dai));
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "%s() register codec error %d\n",
> +			__func__, ret);
> +		goto err_;
> +	}
> +
> +	return 0;
> +err_:
> +
> +	return ret;
> +}
> +
> +static int rk817_platform_remove(struct platform_device *pdev)
> +{
> +	snd_soc_unregister_component(&pdev->dev);

humm, that looks like a bug. If you used devm_soc_register_component() 
in the probe, you don't need to release it here manually?


> +++ b/sound/soc/codecs/rk817_codec.h
> @@ -0,0 +1,197 @@
> +/*
> + * Copyright (c) 2018 Rockchip 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 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.

remove and use SPDX




More information about the Alsa-devel mailing list