root/sound/soc/sdca/sdca_regmap.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/bitops.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/types.h>
#include <sound/sdca.h>
#include <sound/sdca_function.h>
#include <sound/sdca_regmap.h>

static struct sdca_entity *
function_find_entity(struct sdca_function_data *function, unsigned int reg)
{
        int i;

        for (i = 0; i < function->num_entities; i++)
                if (SDW_SDCA_CTL_ENT(reg) == function->entities[i].id)
                        return &function->entities[i];

        return NULL;
}

static struct sdca_control *
entity_find_control(struct sdca_entity *entity, unsigned int reg)
{
        int i;

        for (i = 0; i < entity->num_controls; i++) {
                if (SDW_SDCA_CTL_CSEL(reg) == entity->controls[i].sel)
                        return &entity->controls[i];
        }

        return NULL;
}

static struct sdca_control *
function_find_control(struct sdca_function_data *function, unsigned int reg)
{
        struct sdca_entity *entity;

        entity = function_find_entity(function, reg);
        if (!entity)
                return NULL;

        return entity_find_control(entity, reg);
}

/**
 * sdca_regmap_readable - return if a given SDCA Control is readable
 * @function: Pointer to the Function information.
 * @reg: Register address/Control to be processed.
 *
 * Return: Returns true if the register is readable.
 */
bool sdca_regmap_readable(struct sdca_function_data *function, unsigned int reg)
{
        struct sdca_control *control;

        if (!SDW_SDCA_VALID_CTL(reg))
                return false;

        control = function_find_control(function, reg);
        if (!control)
                return false;

        if (!(BIT(SDW_SDCA_CTL_CNUM(reg)) & control->cn_list))
                return false;

        switch (control->mode) {
        case SDCA_ACCESS_MODE_RW:
        case SDCA_ACCESS_MODE_RO:
        case SDCA_ACCESS_MODE_RW1S:
        case SDCA_ACCESS_MODE_RW1C:
                if (SDW_SDCA_NEXT_CTL(0) & reg)
                        return false;
                fallthrough;
        case SDCA_ACCESS_MODE_DUAL:
                /* No access to registers marked solely for device use */
                return control->layers & ~SDCA_ACCESS_LAYER_DEVICE;
        default:
                return false;
        }
}
EXPORT_SYMBOL_NS(sdca_regmap_readable, "SND_SOC_SDCA");

/**
 * sdca_regmap_writeable - return if a given SDCA Control is writeable
 * @function: Pointer to the Function information.
 * @reg: Register address/Control to be processed.
 *
 * Return: Returns true if the register is writeable.
 */
bool sdca_regmap_writeable(struct sdca_function_data *function, unsigned int reg)
{
        struct sdca_control *control;

        if (!SDW_SDCA_VALID_CTL(reg))
                return false;

        control = function_find_control(function, reg);
        if (!control)
                return false;

        if (!(BIT(SDW_SDCA_CTL_CNUM(reg)) & control->cn_list))
                return false;

        switch (control->mode) {
        case SDCA_ACCESS_MODE_RW:
        case SDCA_ACCESS_MODE_RW1S:
        case SDCA_ACCESS_MODE_RW1C:
                if (SDW_SDCA_NEXT_CTL(0) & reg)
                        return false;
                fallthrough;
        case SDCA_ACCESS_MODE_DUAL:
                /* No access to registers marked solely for device use */
                return control->layers & ~SDCA_ACCESS_LAYER_DEVICE;
        default:
                return false;
        }
}
EXPORT_SYMBOL_NS(sdca_regmap_writeable, "SND_SOC_SDCA");

/**
 * sdca_regmap_volatile - return if a given SDCA Control is volatile
 * @function: Pointer to the Function information.
 * @reg: Register address/Control to be processed.
 *
 * Return: Returns true if the register is volatile.
 */
bool sdca_regmap_volatile(struct sdca_function_data *function, unsigned int reg)
{
        struct sdca_control *control;

        if (!SDW_SDCA_VALID_CTL(reg))
                return false;

        control = function_find_control(function, reg);
        if (!control)
                return false;

        return control->is_volatile;
}
EXPORT_SYMBOL_NS(sdca_regmap_volatile, "SND_SOC_SDCA");

/**
 * sdca_regmap_deferrable - return if a given SDCA Control is deferrable
 * @function: Pointer to the Function information.
 * @reg: Register address/Control to be processed.
 *
 * Return: Returns true if the register is deferrable.
 */
bool sdca_regmap_deferrable(struct sdca_function_data *function, unsigned int reg)
{
        struct sdca_control *control;

        if (!SDW_SDCA_VALID_CTL(reg))
                return false;

        control = function_find_control(function, reg);
        if (!control)
                return false;

        return control->deferrable;
}
EXPORT_SYMBOL_NS(sdca_regmap_deferrable, "SND_SOC_SDCA");

/**
 * sdca_regmap_mbq_size - return size in bytes of a given SDCA Control
 * @function: Pointer to the Function information.
 * @reg: Register address/Control to be processed.
 *
 * Return: Returns the size in bytes of the Control.
 */
int sdca_regmap_mbq_size(struct sdca_function_data *function, unsigned int reg)
{
        struct sdca_control *control;

        if (!SDW_SDCA_VALID_CTL(reg))
                return -EINVAL;

        control = function_find_control(function, reg);
        if (!control)
                return -EINVAL;

        return clamp_val(control->nbits / BITS_PER_BYTE, sizeof(u8), sizeof(u32));
}
EXPORT_SYMBOL_NS(sdca_regmap_mbq_size, "SND_SOC_SDCA");

/**
 * sdca_regmap_count_constants - count the number of DisCo constant Controls
 * @dev: Pointer to the device.
 * @function: Pointer to the Function information, to be parsed.
 *
 * This function returns the number of DisCo constant Controls present
 * in a function. Typically this information will be used to populate
 * the regmap defaults array, allowing drivers to access the values of
 * DisCo constants as any other physical register.
 *
 * Return: Returns number of DisCo constant controls, or a negative error
 * code on failure.
 */
int sdca_regmap_count_constants(struct device *dev,
                                struct sdca_function_data *function)
{
        int nconsts = 0;
        int i, j;

        for (i = 0; i < function->num_entities; i++) {
                struct sdca_entity *entity = &function->entities[i];

                for (j = 0; j < entity->num_controls; j++) {
                        if (entity->controls[j].mode == SDCA_ACCESS_MODE_DC ||
                            entity->controls[j].has_reset)
                                nconsts += hweight64(entity->controls[j].cn_list);
                }
        }

        return nconsts;
}
EXPORT_SYMBOL_NS(sdca_regmap_count_constants, "SND_SOC_SDCA");

/**
 * sdca_regmap_populate_constants - fill an array with DisCo constant values
 * @dev: Pointer to the device.
 * @function: Pointer to the Function information, to be parsed.
 * @consts: Pointer to the array which should be filled with the DisCo
 * constant values.
 *
 * This function will populate a regmap struct reg_default array with
 * the values of the DisCo constants for a given Function. This
 * allows to access the values of DisCo constants the same as any
 * other physical register.
 *
 * Return: Returns the number of constants populated on success, a negative
 * error code on failure.
 */
int sdca_regmap_populate_constants(struct device *dev,
                                   struct sdca_function_data *function,
                                   struct reg_default *consts)
{
        int i, j, k, l;

        for (i = 0, k = 0; i < function->num_entities; i++) {
                struct sdca_entity *entity = &function->entities[i];

                for (j = 0; j < entity->num_controls; j++) {
                        struct sdca_control *control = &entity->controls[j];
                        int cn;

                        if (control->mode != SDCA_ACCESS_MODE_DC &&
                            !control->has_reset)
                                continue;

                        l = 0;
                        for_each_set_bit(cn, (unsigned long *)&control->cn_list,
                                         BITS_PER_TYPE(control->cn_list)) {
                                consts[k].reg = SDW_SDCA_CTL(function->desc->adr,
                                                             entity->id,
                                                             control->sel, cn);
                                if (control->mode == SDCA_ACCESS_MODE_DC)
                                        consts[k].def = control->values[l];
                                else
                                        consts[k].def = control->reset;
                                k++;
                                l++;
                        }
                }
        }

        return k;
}
EXPORT_SYMBOL_NS(sdca_regmap_populate_constants, "SND_SOC_SDCA");

static int populate_control_defaults(struct device *dev, struct regmap *regmap,
                                     struct sdca_function_data *function,
                                     struct sdca_entity *entity,
                                     struct sdca_control *control)
{
        int i, ret;
        int cn;

        if (control->mode == SDCA_ACCESS_MODE_DC)
                return 0;

        if (control->layers & SDCA_ACCESS_LAYER_DEVICE)
                return 0;

        i = 0;
        for_each_set_bit(cn, (unsigned long *)&control->cn_list,
                         BITS_PER_TYPE(control->cn_list)) {
                unsigned int reg, val;

                reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, cn);

                if (control->has_default || control->has_fixed) {
                        ret = regmap_write(regmap, reg, control->values[i]);
                        if (ret) {
                                dev_err(dev, "Failed to write default %#x: %d\n",
                                        reg, ret);
                                return ret;
                        }

                        i++;
                } else if (!control->is_volatile) {
                        if (control->has_reset)
                                regcache_drop_region(regmap, reg, reg);

                        ret = regmap_read(regmap, reg, &val);
                        if (ret) {
                                dev_err(dev, "Failed to read initial %#x: %d\n",
                                        reg, ret);
                                return ret;
                        }
                }
        }

        return 0;
}

/**
 * sdca_regmap_write_defaults - write out DisCo defaults to device
 * @dev: Pointer to the device.
 * @regmap: Pointer to the Function register map.
 * @function: Pointer to the Function information, to be parsed.
 *
 * This function will write out to the hardware all the DisCo default and
 * fixed value controls. This will cause them to be populated into the cache,
 * and subsequent handling can be done through a cache sync. It will also
 * read any non-volatile registers that don't have defaults/fixed values to
 * populate those into the cache, this ensures they are available for reads
 * even when the device is runtime suspended.
 *
 * Return: Returns zero on success, and a negative error code on failure.
 */
int sdca_regmap_write_defaults(struct device *dev, struct regmap *regmap,
                               struct sdca_function_data *function)
{
        int i, j;
        int ret;

        for (i = 0; i < function->num_entities; i++) {
                struct sdca_entity *entity = &function->entities[i];

                for (j = 0; j < entity->num_controls; j++) {
                        struct sdca_control *control = &entity->controls[j];

                        ret = populate_control_defaults(dev, regmap, function,
                                                        entity, control);
                        if (ret)
                                return ret;
                }
        }

        return 0;
}
EXPORT_SYMBOL_NS(sdca_regmap_write_defaults, "SND_SOC_SDCA");

int sdca_regmap_write_init(struct device *dev, struct regmap *regmap,
                           struct sdca_function_data *function)
{
        struct sdca_init_write *init = function->init_table;
        int ret, i;

        for (i = 0; i < function->num_init_table; i++) {
                ret = regmap_write(regmap, init[i].addr, init[i].val);
                if (ret)
                        return ret;
        }

        return 0;
}
EXPORT_SYMBOL_NS(sdca_regmap_write_init, "SND_SOC_SDCA");