573 lines · 14.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0// Copyright (C) 2025 Cirrus Logic, Inc. and//                    Cirrus Logic International Semiconductor Ltd. /* * The MIPI SDCA specification is available for public downloads at * https://www.mipi.org/mipi-sdca-v1-0-download */ #include <linux/auxiliary_bus.h>#include <linux/cleanup.h>#include <linux/minmax.h>#include <linux/module.h>#include <linux/pm.h>#include <linux/pm_runtime.h>#include <linux/soundwire/sdw.h>#include <linux/soundwire/sdw_registers.h>#include <sound/pcm.h>#include <sound/sdca_asoc.h>#include <sound/sdca_fdl.h>#include <sound/sdca_function.h>#include <sound/sdca_interrupts.h>#include <sound/sdca_jack.h>#include <sound/sdca_regmap.h>#include <sound/sdw.h>#include <sound/soc-component.h>#include <sound/soc-dai.h>#include <sound/soc.h>#include "sdca_class.h"#include "sdca_function_device.h" struct class_function_drv {	struct device *dev;	struct regmap *regmap;	struct sdca_class_drv *core; 	struct sdca_function_data *function;	bool suspended;}; static void class_function_regmap_lock(void *data){	struct mutex *lock = data; 	mutex_lock(lock);} static void class_function_regmap_unlock(void *data){	struct mutex *lock = data; 	mutex_unlock(lock);} static bool class_function_regmap_writeable(struct device *dev, unsigned int reg){	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 	return sdca_regmap_writeable(drv->function, reg);} static bool class_function_regmap_readable(struct device *dev, unsigned int reg){	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 	return sdca_regmap_readable(drv->function, reg);} static bool class_function_regmap_volatile(struct device *dev, unsigned int reg){	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 	return sdca_regmap_volatile(drv->function, reg);} static const struct regmap_config class_function_regmap_config = {	.name			= "sdca",	.reg_bits		= 32,	.val_bits		= 32,	.reg_format_endian	= REGMAP_ENDIAN_LITTLE,	.val_format_endian	= REGMAP_ENDIAN_LITTLE, 	.max_register		= SDW_SDCA_MAX_REGISTER,	.readable_reg		= class_function_regmap_readable,	.writeable_reg		= class_function_regmap_writeable,	.volatile_reg		= class_function_regmap_volatile, 	.cache_type		= REGCACHE_MAPLE, 	.lock			= class_function_regmap_lock,	.unlock			= class_function_regmap_unlock,}; static int class_function_regmap_mbq_size(struct device *dev, unsigned int reg){	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 	return sdca_regmap_mbq_size(drv->function, reg);} static bool class_function_regmap_deferrable(struct device *dev, unsigned int reg){	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 	return sdca_regmap_deferrable(drv->function, reg);} static const struct regmap_sdw_mbq_cfg class_function_mbq_config = {	.mbq_size		= class_function_regmap_mbq_size,	.deferrable		= class_function_regmap_deferrable,	.retry_us		= 1000,	.timeout_us		= 10000,}; static int class_function_startup(struct snd_pcm_substream *substream,				  struct snd_soc_dai *dai){	struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component); 	return sdca_asoc_set_constraints(drv->dev, drv->regmap, drv->function,					 substream, dai);} static int class_function_sdw_add_peripheral(struct snd_pcm_substream *substream,					     struct snd_pcm_hw_params *params,					     struct snd_soc_dai *dai){	struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);	struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);	struct sdw_slave *sdw = dev_to_sdw_dev(drv->dev->parent);	struct sdw_stream_config sconfig = {0};	struct sdw_port_config pconfig = {0};	int ret; 	if (!sdw_stream)		return -EINVAL; 	snd_sdw_params_to_config(substream, params, &sconfig, &pconfig); 	/*	 * FIXME: As also noted in sdca_asoc_get_port(), currently only	 * a single unshared port is supported for each DAI.	 */	ret = sdca_asoc_get_port(drv->dev, drv->regmap, drv->function, dai);	if (ret < 0)		return ret; 	pconfig.num = ret; 	ret = sdw_stream_add_slave(sdw, &sconfig, &pconfig, 1, sdw_stream);	if (ret) {		dev_err(drv->dev, "failed to add sdw stream: %d\n", ret);		return ret;	} 	return sdca_asoc_hw_params(drv->dev, drv->regmap, drv->function,				   substream, params, dai);} static int class_function_sdw_remove_peripheral(struct snd_pcm_substream *substream,						struct snd_soc_dai *dai){	struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);	struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);	struct sdw_slave *sdw = dev_to_sdw_dev(drv->dev->parent); 	if (!sdw_stream)		return -EINVAL; 	return sdw_stream_remove_slave(sdw, sdw_stream);} static int class_function_sdw_set_stream(struct snd_soc_dai *dai, void *sdw_stream,					 int direction){	snd_soc_dai_dma_data_set(dai, direction, sdw_stream); 	return 0;} static const struct snd_soc_dai_ops class_function_sdw_ops = {	.startup	= class_function_startup,	.shutdown	= sdca_asoc_free_constraints,	.set_stream	= class_function_sdw_set_stream,	.hw_params	= class_function_sdw_add_peripheral,	.hw_free	= class_function_sdw_remove_peripheral,}; static int class_function_component_fixup_controls(struct snd_soc_component *component){	struct class_function_drv *drv = snd_soc_component_get_drvdata(component);	struct sdca_class_drv *core = drv->core; 	return sdca_irq_populate(drv->function, component, core->irq_info);} static void class_function_component_remove(struct snd_soc_component *component){	struct class_function_drv *drv = snd_soc_component_get_drvdata(component);	struct sdca_class_drv *core = drv->core; 	sdca_irq_cleanup(component->dev, drv->function, core->irq_info);} static int class_function_set_jack(struct snd_soc_component *component,				   struct snd_soc_jack *jack, void *d){	struct class_function_drv *drv = snd_soc_component_get_drvdata(component);	struct sdca_class_drv *core = drv->core; 	return sdca_jack_set_jack(core->irq_info, jack);} static const struct snd_soc_component_driver class_function_component_drv = {	.fixup_controls		= class_function_component_fixup_controls,	.remove			= class_function_component_remove,	.endianness		= 1,}; static int class_function_init_device(struct class_function_drv *drv,				      unsigned int status){	int ret; 	if (!(status & SDCA_CTL_ENTITY_0_FUNCTION_HAS_BEEN_RESET)) {		dev_dbg(drv->dev, "reset function device\n"); 		ret = sdca_reset_function(drv->dev, drv->function, drv->regmap);		if (ret)			return ret;	} 	if (status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) {		dev_dbg(drv->dev, "write initialisation\n"); 		ret = sdca_regmap_write_init(drv->dev, drv->core->dev_regmap,					     drv->function);		if (ret)			return ret;	} 	return 0;} static int class_function_boot(struct class_function_drv *drv){	unsigned int reg = SDW_SDCA_CTL(drv->function->desc->adr,					SDCA_ENTITY_TYPE_ENTITY_0,					SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0);	unsigned int val;	int ret; 	guard(mutex)(&drv->core->init_lock); 	ret = regmap_read(drv->regmap, reg, &val);	if (ret < 0) {		dev_err(drv->dev, "failed to read function status: %d\n", ret);		return ret;	} 	ret = class_function_init_device(drv, val);	if (ret)		return ret; 	/* Start FDL process */	ret = sdca_irq_populate_early(drv->dev, drv->regmap, drv->function,				      drv->core->irq_info);	if (ret)		return ret; 	ret = sdca_fdl_sync(drv->dev, drv->function, drv->core->irq_info);	if (ret)		return ret; 	ret = sdca_regmap_write_defaults(drv->dev, drv->regmap, drv->function);	if (ret)		return ret; 	ret = regmap_write(drv->regmap, reg, 0xFF);	if (ret < 0) {		dev_err(drv->dev, "failed to clear function status: %d\n", ret);		return ret;	} 	return 0;} static int class_function_probe(struct auxiliary_device *auxdev,				const struct auxiliary_device_id *aux_dev_id){	struct device *dev = &auxdev->dev;	struct sdca_class_drv *core = dev_get_drvdata(dev->parent);	struct sdca_dev *sdev = auxiliary_dev_to_sdca_dev(auxdev);	struct snd_soc_component_driver *cmp_drv;	struct snd_soc_dai_driver *dais;	struct class_function_drv *drv;	struct regmap_sdw_mbq_cfg *mbq_config;	struct regmap_config *config;	struct reg_default *defaults;	int ndefaults;	int num_dais;	int ret; 	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);	if (!drv)		return -ENOMEM; 	cmp_drv = devm_kmemdup(dev, &class_function_component_drv, sizeof(*cmp_drv),			       GFP_KERNEL);	if (!cmp_drv)		return -ENOMEM; 	config = devm_kmemdup(dev, &class_function_regmap_config, sizeof(*config),			      GFP_KERNEL);	if (!config)		return -ENOMEM; 	mbq_config = devm_kmemdup(dev, &class_function_mbq_config, sizeof(*mbq_config),				  GFP_KERNEL);	if (!mbq_config)		return -ENOMEM; 	drv->dev = dev;	drv->core = core;	drv->function = &sdev->function; 	ret = sdca_parse_function(dev, drv->function);	if (ret)		return ret; 	ndefaults = sdca_regmap_count_constants(dev, drv->function);	if (ndefaults < 0)		return ndefaults; 	defaults = devm_kcalloc(dev, ndefaults, sizeof(*defaults), GFP_KERNEL);	if (!defaults)		return -ENOMEM; 	ret = sdca_regmap_populate_constants(dev, drv->function, defaults);	if (ret < 0)		return ret; 	regcache_sort_defaults(defaults, ndefaults); 	auxiliary_set_drvdata(auxdev, drv); 	config->reg_defaults = defaults;	config->num_reg_defaults = ndefaults;	config->lock_arg = &core->regmap_lock; 	if (drv->function->busy_max_delay) {		mbq_config->timeout_us = drv->function->busy_max_delay;		mbq_config->retry_us = umax(drv->function->busy_max_delay / 10,					    mbq_config->retry_us);	} 	drv->regmap = devm_regmap_init_sdw_mbq_cfg(dev, core->sdw, config, mbq_config);	if (IS_ERR(drv->regmap))		return dev_err_probe(dev, PTR_ERR(drv->regmap),				     "failed to create regmap"); 	switch (drv->function->desc->type) {	case SDCA_FUNCTION_TYPE_UAJ:	case SDCA_FUNCTION_TYPE_RJ:		cmp_drv->set_jack = class_function_set_jack;		break;	default:		break;	} 	ret = sdca_asoc_populate_component(dev, drv->function, cmp_drv,					   &dais, &num_dais,					   &class_function_sdw_ops);	if (ret)		return ret; 	dev_pm_set_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE); 	pm_runtime_set_autosuspend_delay(dev, 200);	pm_runtime_use_autosuspend(dev);	pm_runtime_set_active(dev);	pm_runtime_get_noresume(dev); 	ret = devm_pm_runtime_enable(dev);	if (ret)		return ret; 	ret = class_function_boot(drv);	if (ret)		return ret; 	ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais);	if (ret)		return dev_err_probe(dev, ret, "failed to register component\n"); 	pm_runtime_mark_last_busy(dev);	pm_runtime_put_autosuspend(dev); 	return 0;} static void class_function_remove(struct auxiliary_device *auxdev){	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 	sdca_irq_cleanup_late(drv->dev, drv->function, drv->core->irq_info);} static int class_function_runtime_suspend(struct device *dev){	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); 	/*	 * Whilst the driver doesn't power the chip down here, going into	 * runtime suspend means the driver can't be sure the bus won't	 * power down which would prevent communication with the device.	 */	regcache_cache_only(drv->regmap, true); 	return 0;} static int class_function_runtime_resume(struct device *dev){	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);	int ret; 	guard(mutex)(&drv->core->init_lock); 	regcache_mark_dirty(drv->regmap);	regcache_cache_only(drv->regmap, false); 	if (drv->suspended) {		unsigned int reg = SDW_SDCA_CTL(drv->function->desc->adr,						SDCA_ENTITY_TYPE_ENTITY_0,						SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0);		unsigned int val; 		ret = regmap_read(drv->regmap, reg, &val);		if (ret < 0) {			dev_err(drv->dev, "failed to read function status: %d\n", ret);			goto err;		} 		ret = class_function_init_device(drv, val);		if (ret)			goto err; 		sdca_irq_enable_early(drv->function, drv->core->irq_info); 		ret = sdca_fdl_sync(drv->dev, drv->function, drv->core->irq_info);		if (ret)			goto err; 		sdca_irq_enable(drv->function, drv->core->irq_info); 		ret = regmap_write(drv->regmap, reg, 0xFF);		if (ret < 0) {			dev_err(drv->dev, "failed to clear function status: %d\n", ret);			goto err;		} 		drv->suspended = false;	} 	ret = regcache_sync(drv->regmap);	if (ret) {		dev_err(drv->dev, "failed to restore register cache: %d\n", ret);		goto err;	} 	return 0; err:	regcache_cache_only(drv->regmap, true); 	return ret;} static int class_function_suspend(struct device *dev){	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);	int ret; 	drv->suspended = true; 	/* Ensure runtime resume runs on resume */	ret = pm_runtime_resume_and_get(dev);	if (ret) {		dev_err(dev, "failed to resume for suspend: %d\n", ret);		return ret;	}

Lines 1–500 of 573.