root/sound/soc/sdca/sdca_class_function.c
// 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"

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_probe(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 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 = {
        .probe                  = class_function_component_probe,
        .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_device_data *data = &core->sdw->sdca_data;
        struct sdca_function_desc *desc;
        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;
        int i;

        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;

        for (i = 0; i < data->num_functions; i++) {
                desc = &data->function[i];

                if (desc->type == aux_dev_id->driver_data)
                        break;
        }
        if (i == core->sdw->sdca_data.num_functions) {
                dev_err(dev, "failed to locate function\n");
                return -EINVAL;
        }

        drv->function = &core->functions[i];

        ret = sdca_parse_function(dev, core->sdw, desc, 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");

        if (desc->type == SDCA_FUNCTION_TYPE_UAJ)
                cmp_drv->set_jack = class_function_set_jack;

        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 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;
        }

        sdca_irq_disable(drv->function, drv->core->irq_info);

        ret = pm_runtime_force_suspend(dev);
        if (ret) {
                dev_err(dev, "failed to force suspend: %d\n", ret);
                return ret;
        }

        pm_runtime_put_noidle(dev);

        return 0;
}

static int class_function_resume(struct device *dev)
{
        int ret;

        ret = pm_runtime_force_resume(dev);
        if (ret) {
                dev_err(dev, "failed to force resume: %d\n", ret);
                return ret;
        }

        return 0;
}

static const struct dev_pm_ops class_function_pm_ops = {
        SYSTEM_SLEEP_PM_OPS(class_function_suspend, class_function_resume)
        RUNTIME_PM_OPS(class_function_runtime_suspend,
                       class_function_runtime_resume, NULL)
};

static const struct auxiliary_device_id class_function_id_table[] = {
        {
                .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SMART_AMP_NAME,
                .driver_data = SDCA_FUNCTION_TYPE_SMART_AMP,
        },
        {
                .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SMART_MIC_NAME,
                .driver_data = SDCA_FUNCTION_TYPE_SMART_MIC,
        },
        {
                .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_UAJ_NAME,
                .driver_data = SDCA_FUNCTION_TYPE_UAJ,
        },
        {
                .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_HID_NAME,
                .driver_data = SDCA_FUNCTION_TYPE_HID,
        },
        {},
};
MODULE_DEVICE_TABLE(auxiliary, class_function_id_table);

static struct auxiliary_driver class_function_drv = {
        .driver = {
                .name           = "sdca_function",
                .pm             = pm_ptr(&class_function_pm_ops),
        },

        .probe          = class_function_probe,
        .id_table       = class_function_id_table
};
module_auxiliary_driver(class_function_drv);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SDCA Class Function Driver");
MODULE_IMPORT_NS("SND_SOC_SDCA");