Linux Sound subsystem development
 help / color / mirror / Atom feed
From: Pierre-Louis Bossart <pierre-louis.bossart@linux.dev>
To: shumingf@realtek.com, broonie@kernel.org, lgirdwood@gmail.com
Cc: linux-sound@vger.kernel.org, lars@metafoo.de, flove@realtek.com,
	oder_chiou@realtek.com, jack.yu@realtek.com,
	derek.fang@realtek.com,
	Charles Keepax <ckeepax@opensource.cirrus.com>
Subject: Re: [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver
Date: Mon, 20 Jul 2026 16:38:38 +0200	[thread overview]
Message-ID: <fff6c488-295d-47c3-819a-478b6dc3b24b@linux.dev> (raw)
In-Reply-To: <20260720090613.2239891-1-shumingf@realtek.com>

On 7/20/26 11:06, shumingf@realtek.com wrote:
> From: Shuming Fan <shumingf@realtek.com>
> 
> This patch adds the initial SDCA multi-function codec driver for the RT766 and RT767.
> 
> Signed-off-by: Shuming Fan <shumingf@realtek.com>

Looks mostly good, I only have minor comments, see below.


> +static bool rt766_sdca_readable_register(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4:
> +	case RT766_VERSION_ID ... RT766_BOND_LATCH_ID:
> +	case 0xc344 ... 0xc345:
> +	case 0xc900:
> +	case 0xc920:
> +	case 0xd540 ... 0xd542:
> +	case 0xf01e:
> +	case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1:
> +	case 0x310100:
> +	case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END:
> +	case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END:
> +	case RT766_MUTE_REG(UAJ, USER_FU41, 1):
> +	case RT766_MUTE_REG(UAJ, USER_FU41, 2):
> +	case RT766_VOLUME_REG(UAJ, USER_FU41, 1):
> +	case RT766_VOLUME_REG(UAJ, USER_FU41, 2):
> +	case RT766_MUTE_REG(UAJ, USER_FU36, 1):
> +	case RT766_MUTE_REG(UAJ, USER_FU36, 2):
> +	case RT766_VOLUME_REG(UAJ, USER_FU36, 1):
> +	case RT766_VOLUME_REG(UAJ, USER_FU36, 2):
> +	case RT766_PDE_REQ_REG(UAJ, PDE47):
> +	case RT766_PDE_REQ_REG(UAJ, PDE34):
> +	case RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX):
> +	case RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX):
> +	/* 0x40480000 */
> +	case RT766_FUNC_STATUS_REG(UAJ):
> +	/* 0x40481400 */
> +	case RT766_PDE_ACTUAL_REG(UAJ, PDE47):
> +	/* 0x40481480 */

these single line registers listed in comments confuse me, not clear for
example if this one applies to the following line...

> +	case RT766_PDE_ACTUAL_REG(UAJ, PDE34):

but then why not adde the values for the next 3?

> +	case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1):
> +	case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2):
> +	case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE):
> +	/* 0x40600490 */
> +	case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE):
> +	case RT766_PDE_REQ_REG(MIC, PDE11):
> +	case RT766_MUTE_REG(MIC, USER_FU113, 1):
> +	case RT766_MUTE_REG(MIC, USER_FU113, 2):
> +	case RT766_MUTE_REG(MIC, USER_FU113, 3):
> +	case RT766_MUTE_REG(MIC, USER_FU113, 4):
> +	case RT766_VOLUME_REG(MIC, USER_FU113, 1):
> +	case RT766_VOLUME_REG(MIC, USER_FU113, 2):
> +	case RT766_VOLUME_REG(MIC, USER_FU113, 3):
> +	case RT766_VOLUME_REG(MIC, USER_FU113, 4):
> +	/* 0x40880000 */
> +	case RT766_FUNC_STATUS_REG(MIC):

same here, are 0x40880000  and RT766_FUNC_STATUS_REG equivalent? If yes
maybe move the comment to the right side?

> +	case RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX):
> +	/* 0x40881500 */
> +	case RT766_PDE_ACTUAL_REG(MIC, PDE11):
> +	/* 0x40c80000 */
> +	case RT766_FUNC_STATUS_REG(HID):
> +	/* 0x40c80080 - 0x40c80098 */
> +	case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ...
> +		RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH):
> +	case RT766_MUTE_REG(AMP, USER_FU21, 1):
> +	case RT766_MUTE_REG(AMP, USER_FU21, 2):
> +	case RT766_VOLUME_REG(AMP, USER_FU21, 1):
> +	case RT766_VOLUME_REG(AMP, USER_FU21, 2):
> +	case RT766_PDE_REQ_REG(AMP, PDE23):
> +	/* 0x41080000 */
> +	case RT766_FUNC_STATUS_REG(AMP):
> +	case RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER):
> +	case RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX):
> +	/* 0x41081980 */
> +	case RT766_PDE_ACTUAL_REG(AMP, PDE23):
> +	case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}

> +static const struct reg_default rt766_sdca_defaults[] = {
> +	/* 0x40400289 - 0x4040028a */
> +	{ RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 },
> +	{ RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 },

things are much clearer/organized here!

> +	/* 0x40400291 - 0x40400292 */
> +	{ RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 },
> +	{ RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 },
> +	/* 0x40400789 - 0x4040078a */
> +	{ RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 },
> +	{ RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 },
> +	/* 0x40400791 - 0x40400792 */
> +	{ RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 },
> +	{ RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 },
> +	/* 0x40401408 */
> +	{ RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 },
> +	/* 0x40401488 */
> +	{ RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 },
> +	/* 0x40480080 */
> +	{ RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 },
> +	/* 0x40480880 */
> +	{ RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 },
> +	/* 0x40600259 - 0x4060025a */
> +	{ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 },
> +	{ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 },
> +	/* 0x40600488 */
> +	{ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), 0x00 },

> +static struct sdca_entity *rt766_find_entity_by_label(struct sdca_function_data *func,
> +	const char *label)
> +{
> +	struct sdca_entity *entity = NULL;

useless initialization?

> +	int idx;
> +
> +	for (idx = 0; idx < func->num_entities; idx++) {
> +		entity = &func->entities[idx];
> +
> +		if (!strcmp(entity->label, label))
> +			return entity;
> +	}
> +
> +	return NULL;
> +}

> +static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766,
> +							  struct sdca_function_data *function,
> +							  struct snd_soc_component *component,
> +							  struct sdca_interrupt_info *info,
> +							  bool enabled)
> +{
> +	struct device *dev = &rt766->slave->dev;
> +	struct sdca_interrupt *interrupt;
> +	struct sdca_control *control;
> +	struct sdca_entity *entity;
> +	irq_handler_t handler;
> +	int i, j, irq, ret;
> +
> +	for (i = 0; i < function->num_entities; i++) {
> +		entity = &function->entities[i];
> +
> +		for (j = 0; j < entity->num_controls; j++) {
> +			control = &entity->controls[j];
> +			irq = control->interrupt_position;
> +
> +			switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
> +			case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
> +				handler = rt766_sdca_irq_jd_handler;
> +				break;
> +			case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER):
> +				handler = rt766_sdca_irq_btn_handler;
> +				break;
> +			default:
> +				continue;
> +			}
> +
> +			interrupt = &info->irqs[irq];
> +
> +			if (enabled) {
> +				ret = sdca_irq_data_populate(dev, rt766->regmap, component,
> +								function, entity, control,
> +								interrupt);
> +				if (ret)
> +					return ret;
> +
> +				interrupt->priv = rt766;
> +				ret = sdca_irq_request(dev, info, irq, interrupt->name,
> +								handler, interrupt);

There was a proposal from Charles to do an irq_populate_early, is there
a conflict between the two patchsets? see "ASoC: SDCA: Populate IRQ data
earlier"

> +				if (ret) {
> +					dev_err(dev, "failed to request irq %s: %d\n",
> +						interrupt->name, ret);
> +					return ret;
> +				}
> +				dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name);
> +			} else {
> +				sdca_irq_free(dev, info, irq, interrupt->name, interrupt);
> +				dev_dbg(dev, "Freeing IRQ %d\n", irq);
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}

> +static int rt766_sdca_set_fu41_playback_ctl(struct rt766_sdca_priv *rt766)
> +{
> +	int err;
> +	unsigned int ch_01, ch_02;

nit-pick: likely copy/paste from other drivers, but flipping the two
lines would be nicer. there are a couple of similar patterns in this driver.

> +
> +	ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ? 0x01 : 0x00;
> +	ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ? 0x01 : 0x00;
> +
> +	err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ, USER_FU41, 1), ch_01);
> +	if (err < 0)
> +		return err;
> +
> +	err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ, USER_FU41, 2), ch_02);
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}

> +static int rt766_sdca_pde47_event(struct snd_soc_dapm_widget *w,
> +	struct snd_kcontrol *kcontrol, int event)
> +{
> +	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> +	struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
> +	const struct sdca_entity *entity = NULL;

useless init?

> +	unsigned char ps0 = 0x0, ps3 = 0x3;
> +	int from_ps, to_ps;
> +	int ret;
> +
> +	switch (event) {
> +	case SND_SOC_DAPM_POST_PMU:
> +		regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE47), ps0);
> +		from_ps = ps3;
> +		to_ps = ps0;
> +		break;
> +	case SND_SOC_DAPM_PRE_PMD:
> +		regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE47), ps3);
> +		from_ps = ps0;
> +		to_ps = ps3;
> +		break;
> +	}
> +
> +	entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 47");
> +	ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
> +				   RT766_FUNC_NUM_UAJ,
> +				   RT766_SDCA_ENT_PDE47,
> +				   from_ps, to_ps,
> +				   entity ? entity->pde.max_delay : NULL,
> +				   entity ? entity->pde.num_max_delay : 0);
> +	if (ret)
> +		dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
> +			__func__, from_ps, to_ps, ret);
> +
> +	return ret;
> +}

> +static int rt766_sdca_pde34_event(struct snd_soc_dapm_widget *w,
> +	struct snd_kcontrol *kcontrol, int event)
> +{
> +	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> +	struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
> +	const struct sdca_entity *entity = NULL;

useless init? Same comment for all PDE events.

> +	unsigned char ps0 = 0x0, ps3 = 0x3;
> +	int from_ps, to_ps;
> +	int ret;
> +
> +	switch (event) {
> +	case SND_SOC_DAPM_POST_PMU:
> +		regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE34), ps0);
> +		from_ps = ps3;
> +		to_ps = ps0;
> +		break;
> +	case SND_SOC_DAPM_PRE_PMD:
> +		regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE34), ps3);
> +		from_ps = ps0;
> +		to_ps = ps3;
> +		break;
> +	}
> +
> +	entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 34");
> +	ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
> +				   RT766_FUNC_NUM_UAJ,
> +				   RT766_SDCA_ENT_PDE34,
> +				   from_ps, to_ps,
> +				   entity ? entity->pde.max_delay : NULL,
> +				   entity ? entity->pde.num_max_delay : 0);
> +	if (ret)
> +		dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
> +			__func__, from_ps, to_ps, ret);
> +
> +	return ret;
> +}


> +static int rt766_sdca_probe(struct snd_soc_component *component)
> +{
> +	struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
> +	struct device *dev = &rt766->slave->dev;
> +	int ret;
> +
> +	rt766->component = component;
> +
> +	ret = pm_runtime_resume(component->dev);
> +	if (ret < 0 && ret != -EACCES)
> +		return ret;
> +
> +	if (rt766->uaj_func_data) {
> +		dev_dbg(dev, "%s : irq %d\n", __func__, rt766->slave->irq);
> +
> +		rt766->irq_info = sdca_irq_allocate(dev, rt766->regmap, rt766->slave->irq);
> +		if (IS_ERR(rt766->irq_info))
> +			return PTR_ERR(rt766->irq_info);
> +
> +		ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data,
> +			component, rt766->irq_info, true);

same question on IRQ allocate/request for Charles.

> +		if (ret < 0) {
> +			dev_err(dev, "Failed to request UAJ SDCA IRQ: %d\n", ret);
> +			return ret;
> +		}
> +
> +		if (rt766->hid_func_data) {
> +			ret = rt766_sdca_irq_ctl(rt766, rt766->hid_func_data,
> +				component, rt766->irq_info, true);
> +			if (ret < 0) {
> +				dev_err(dev, "Failed to request HID SDCA IRQ: %d\n", ret);
> +				return ret;
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}

> +static unsigned int rt766_find_dt_rates(struct device *dev, struct sdca_function_data *function,
> +							const char *label)
> +{
> +	struct sdca_entity *entity = NULL;

useless init?

> +	unsigned int rates;
> +	int i, ret;
> +
> +	for (i = 0; i < function->num_entities; i++) {
> +		entity = &function->entities[i];
> +
> +		if (strcmp(entity->label, label))
> +			continue;
> +
> +		/* Can't check earlier as only terminals have an iot member. */
> +		if (!entity->iot.is_dataport)
> +			continue;
> +
> +		ret = rt766_parse_rates(dev, function, entity, &rates);
> +		if (ret < 0) {
> +			dev_dbg(dev, "%s: failed to parse rates for entity %s\n",
> +				__func__, entity->label);
> +			return 0;
> +		}
> +
> +		dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__, entity->label, rates);
> +	}
> +
> +	return rates;
> +}
> +
> +int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave)
> +{
> +	struct sdca_function_data *func_data_ptr;
> +	struct rt766_sdca_priv *rt766;
> +	unsigned int rates;
> +	int ret;
> +	int i;
> +
> +	rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL);
> +	if (!rt766)
> +		return -ENOMEM;
> +
> +	dev_set_drvdata(dev, rt766);
> +	rt766->slave = slave;
> +	rt766->regmap = regmap;
> +
> +	regcache_cache_only(rt766->regmap, true);
> +
> +	mutex_init(&rt766->disable_irq_lock);
> +
> +	/*
> +	 * Mark hw_init to false
> +	 * HW init will be performed when device reports present
> +	 */
> +	rt766->hw_init = false;
> +	rt766->first_hw_init = false;
> +	rt766->fu41_dapm_mute = true;
> +	rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false;
> +	rt766->fu36_dapm_mute = true;
> +	rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true;
> +	rt766->fu21_dapm_mute = true;
> +	rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false;
> +	rt766->fu113_dapm_mute = true;
> +	rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] =
> +		rt766->fu113_mixer_mute[2] = rt766->fu113_mixer_mute[3] = true;
> +
> +	/* get SDCA function data */
> +	dev_dbg(dev, "SDCA functions found: %d", slave->sdca_data.num_functions);
> +	for (i = 0; i < slave->sdca_data.num_functions; i++) {
> +		func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr), GFP_KERNEL);
> +		if (!func_data_ptr)
> +			return dev_err_probe(dev, -ENOMEM, "failed to allocate function data");
> +
> +		func_data_ptr->desc = &slave->sdca_data.function[i];
> +		ret = sdca_parse_function(dev, slave, func_data_ptr);
> +		if (ret) {
> +			devm_kfree(dev, func_data_ptr);
> +			return ret;
> +		}
> +		dev_dbg(dev, "Function type=%d, num_entities=%d",
> +			slave->sdca_data.function[i].type, func_data_ptr->num_entities);
> +
> +		switch (slave->sdca_data.function[i].type) {
> +		case SDCA_FUNCTION_TYPE_UAJ:
> +			rt766->uaj_func_data = func_data_ptr;
> +			/*
> +			 * Some machines may only support a subset of the sample rates supported by the codec.
> +			 * Therefore, we need to parse the supported sample rates from the DisCo table and
> +			 * configure them in the DAI. If the DisCo table does not provide sample rate information,
> +			 * we will fall back to the default supported rates defined in the codec driver.
> +			 */
> +			rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 41");
> +			if (rates)
> +				rt766_sdca_dai[0].playback.rates = rates;
> +
> +			rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 36");
> +			if (rates)
> +				rt766_sdca_dai[0].capture.rates = rates;
> +			break;
> +		case SDCA_FUNCTION_TYPE_SMART_AMP:
> +			rt766->sa_func_data = func_data_ptr;
> +			rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 21");
> +			if (rates)
> +				rt766_sdca_dai[1].playback.rates = rates;
> +			break;
> +		case SDCA_FUNCTION_TYPE_SMART_MIC:
> +			rt766->sm_func_data = func_data_ptr;
> +			rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 113");
> +			if (rates)
> +				rt766_sdca_dai[2].capture.rates = rates;

are the sdca_dai[index] related to the function? Wondering if we can use
e.g sdca_dai[MIC] instead?

> +			break;
> +		case SDCA_FUNCTION_TYPE_HID:
> +			rt766->hid_func_data = func_data_ptr;
> +			break;
> +		default:
> +			dev_dbg(dev, "Unexpected SDCA function type found: %d",
> +				slave->sdca_data.function[i].type);
> +		}
> +	}
> +
> +	ret =  devm_snd_soc_register_component(dev,
> +			&soc_sdca_dev_rt766, rt766_sdca_dai, ARRAY_SIZE(rt766_sdca_dai));
> +	if (ret < 0)
> +		return ret;
> +
> +	/* set autosuspend parameters */
> +	pm_runtime_set_autosuspend_delay(dev, 3000);
> +	pm_runtime_use_autosuspend(dev);
> +
> +	/* make sure the device does not suspend immediately */
> +	pm_runtime_mark_last_busy(dev);
> +
> +	pm_runtime_enable(dev);
> +
> +	dev_dbg(dev, "%s\n", __func__);
> +
> +	return 0;
> +}



  reply	other threads:[~2026-07-20 14:45 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-20  9:06 [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver shumingf
2026-07-20 14:38 ` Pierre-Louis Bossart [this message]
2026-07-21  7:09   ` Shuming [范書銘]
2026-07-20 16:21 ` Charles Keepax
2026-07-21  7:10   ` Shuming [范書銘]
2026-07-21 10:29     ` Charles Keepax

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=fff6c488-295d-47c3-819a-478b6dc3b24b@linux.dev \
    --to=pierre-louis.bossart@linux.dev \
    --cc=broonie@kernel.org \
    --cc=ckeepax@opensource.cirrus.com \
    --cc=derek.fang@realtek.com \
    --cc=flove@realtek.com \
    --cc=jack.yu@realtek.com \
    --cc=lars@metafoo.de \
    --cc=lgirdwood@gmail.com \
    --cc=linux-sound@vger.kernel.org \
    --cc=oder_chiou@realtek.com \
    --cc=shumingf@realtek.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox