| /* |
| * Copyright (c) 2022 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <zephyr/spinlock.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/logging/log.h> |
| |
| #define DT_DRV_COMPAT intel_alh_dai |
| #define LOG_DOMAIN dai_intel_alh |
| |
| LOG_MODULE_REGISTER(LOG_DOMAIN); |
| |
| #include "alh.h" |
| |
| /* Digital Audio interface formatting */ |
| static int dai_alh_set_config_tplg(struct dai_intel_alh *dp, const void *spec_config) |
| { |
| struct dai_intel_alh_pdata *alh = dai_get_drvdata(dp); |
| const struct dai_intel_ipc3_alh_params *config = spec_config; |
| |
| if (config->channels && config->rate) { |
| alh->params.channels = config->channels; |
| alh->params.rate = config->rate; |
| LOG_INF("%s channels %d rate %d", __func__, config->channels, config->rate); |
| } |
| |
| alh->params.stream_id = config->stream_id; |
| |
| return 0; |
| } |
| |
| static int dai_alh_set_config_blob(struct dai_intel_alh *dp, const void *spec_config) |
| { |
| struct dai_intel_alh_pdata *alh = dai_get_drvdata(dp); |
| const struct dai_intel_ipc4_alh_configuration_blob *blob = spec_config; |
| const struct ipc4_alh_multi_gtw_cfg *alh_cfg = &blob->alh_cfg; |
| |
| alh->params.channels = ALH_CHANNELS_DEFAULT; |
| alh->params.rate = ALH_RATE_DEFAULT; |
| /* the LSB 8bits are for stream id */ |
| alh->params.stream_id = alh_cfg->mapping[0].alh_id & 0xff; |
| |
| return 0; |
| } |
| |
| static int dai_alh_trigger(const struct device *dev, enum dai_dir dir, |
| enum dai_trigger_cmd cmd) |
| { |
| LOG_DBG("cmd %d", cmd); |
| |
| return 0; |
| } |
| |
| static void alh_claim_ownership(void) |
| { |
| #if CONFIG_DAI_ALH_HAS_OWNERSHIP |
| uint32_t ALHASCTL = DT_INST_PROP_BY_IDX(0, reg, 0); |
| uint32_t ALHCSCTL = DT_INST_PROP_BY_IDX(0, reg, 1); |
| |
| sys_write32(sys_read32(ALHASCTL) | ALHASCTL_OSEL(0x3), ALHASCTL); |
| sys_write32(sys_read32(ALHCSCTL) | ALHASCTL_OSEL(0x3), ALHCSCTL); |
| #endif |
| } |
| |
| static void alh_release_ownership(void) |
| { |
| #if CONFIG_DAI_ALH_HAS_OWNERSHIP |
| uint32_t ALHASCTL = DT_INST_PROP_BY_IDX(0, reg, 0); |
| uint32_t ALHCSCTL = DT_INST_PROP_BY_IDX(0, reg, 1); |
| |
| sys_write32(sys_read32(ALHASCTL) | ALHASCTL_OSEL(0), ALHASCTL); |
| sys_write32(sys_read32(ALHCSCTL) | ALHASCTL_OSEL(0), ALHCSCTL); |
| #endif |
| } |
| |
| |
| static const struct dai_config *dai_alh_config_get(const struct device *dev, enum dai_dir dir) |
| { |
| struct dai_config *params = (struct dai_config *)dev->config; |
| struct dai_intel_alh *dp = (struct dai_intel_alh *)dev->data; |
| struct dai_intel_alh_pdata *alh = dai_get_drvdata(dp); |
| |
| params->rate = alh->params.rate; |
| params->channels = alh->params.channels; |
| params->word_size = ALH_WORD_SIZE_DEFAULT; |
| |
| return params; |
| } |
| |
| static int dai_alh_config_set(const struct device *dev, const struct dai_config *cfg, |
| const void *bespoke_cfg) |
| { |
| struct dai_intel_alh *dp = (struct dai_intel_alh *)dev->data; |
| |
| LOG_DBG("%s", __func__); |
| |
| if (cfg->type == DAI_INTEL_ALH) { |
| return dai_alh_set_config_tplg(dp, bespoke_cfg); |
| } else { |
| return dai_alh_set_config_blob(dp, bespoke_cfg); |
| } |
| } |
| |
| static const struct dai_properties *dai_alh_get_properties(const struct device *dev, |
| enum dai_dir dir, int stream_id) |
| { |
| struct dai_intel_alh *dp = (struct dai_intel_alh *)dev->data; |
| struct dai_intel_alh_pdata *alh = dai_get_drvdata(dp); |
| struct dai_properties *prop = &alh->props; |
| uint32_t offset = dir == DAI_DIR_PLAYBACK ? |
| ALH_TXDA_OFFSET : ALH_RXDA_OFFSET; |
| |
| prop->fifo_address = dai_base(dp) + offset + ALH_STREAM_OFFSET * stream_id; |
| prop->dma_hs_id = alh_handshake_map[stream_id]; |
| prop->stream_id = alh->params.stream_id; |
| |
| LOG_DBG("dai_index %u", dp->index); |
| LOG_DBG("fifo %u", prop->fifo_address); |
| LOG_DBG("handshake %u", prop->dma_hs_id); |
| |
| return prop; |
| } |
| |
| static int dai_alh_probe(const struct device *dev) |
| { |
| struct dai_intel_alh *dp = (struct dai_intel_alh *)dev->data; |
| k_spinlock_key_t key; |
| |
| LOG_DBG("%s", __func__); |
| |
| key = k_spin_lock(&dp->lock); |
| |
| if (dp->sref == 0) { |
| alh_claim_ownership(); |
| } |
| |
| dp->sref++; |
| |
| k_spin_unlock(&dp->lock, key); |
| |
| return 0; |
| } |
| |
| static int dai_alh_remove(const struct device *dev) |
| { |
| struct dai_intel_alh *dp = (struct dai_intel_alh *)dev->data; |
| k_spinlock_key_t key; |
| |
| LOG_DBG("%s", __func__); |
| |
| key = k_spin_lock(&dp->lock); |
| |
| if (--dp->sref == 0) { |
| alh_release_ownership(); |
| } |
| |
| k_spin_unlock(&dp->lock, key); |
| |
| return 0; |
| } |
| |
| static int alh_init(const struct device *dev) |
| { |
| return 0; |
| } |
| |
| static const struct dai_driver_api dai_intel_alh_api_funcs = { |
| .probe = dai_alh_probe, |
| .remove = dai_alh_remove, |
| .config_set = dai_alh_config_set, |
| .config_get = dai_alh_config_get, |
| .trigger = dai_alh_trigger, |
| .get_properties = dai_alh_get_properties, |
| }; |
| |
| #define DAI_INTEL_ALH_DEVICE_INIT(n) \ |
| static struct dai_config dai_intel_alh_config_##n; \ |
| static struct dai_intel_alh dai_intel_alh_data_##n = { \ |
| .index = (n / DAI_NUM_ALH_BI_DIR_LINKS_GROUP) << 8 | \ |
| (n % DAI_NUM_ALH_BI_DIR_LINKS_GROUP), \ |
| .plat_data = { \ |
| .base = DT_INST_PROP_BY_IDX(n, reg, 0), \ |
| .fifo_depth[DAI_DIR_PLAYBACK] = ALH_GPDMA_BURST_LENGTH, \ |
| .fifo_depth[DAI_DIR_CAPTURE] = ALH_GPDMA_BURST_LENGTH, \ |
| }, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| alh_init, NULL, \ |
| &dai_intel_alh_data_##n, \ |
| &dai_intel_alh_config_##n, \ |
| POST_KERNEL, 32, \ |
| &dai_intel_alh_api_funcs); |
| |
| DT_INST_FOREACH_STATUS_OKAY(DAI_INTEL_ALH_DEVICE_INIT) |