From 4be6d92c15f691cc3bcfcb9bde34a0521ac69cc3 Mon Sep 17 00:00:00 2001 From: Wesley Yao Date: Wed, 8 May 2024 20:39:32 +0800 Subject: [PATCH] iio: dac: Add driver for Rockchip Flexbus DAC opmode Change-Id: Iffce47af2cb3c83f60a6d47416b29484bd01ecb0 Signed-off-by: Wesley Yao --- drivers/iio/dac/Kconfig | 6 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/rockchip-flexbus-dac.c | 414 +++++++++++++++++++++++++ 3 files changed, 421 insertions(+) create mode 100644 drivers/iio/dac/rockchip-flexbus-dac.c diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 80521bd28d0f..b5fdc17a71e8 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -385,6 +385,12 @@ config MCP4922 To compile this driver as a module, choose M here: the module will be called mcp4922. +config ROCKCHIP_FLEXBUS_DAC + tristate "Rockchip Flexbus DAC opmode driver" + depends on MFD_ROCKCHIP_FLEXBUS + help + Select this to get support for Rockchip Flexbus DAC opmode. + config STM32_DAC tristate "STMicroelectronics STM32 DAC" depends on (ARCH_STM32 && OF) || COMPILE_TEST diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index e22d9b4bac80..034035fee77a 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_MAX517) += max517.o obj-$(CONFIG_MAX5821) += max5821.o obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4922) += mcp4922.o +obj-$(CONFIG_ROCKCHIP_FLEXBUS_DAC) += rockchip-flexbus-dac.o obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o obj-$(CONFIG_STM32_DAC) += stm32-dac.o obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o diff --git a/drivers/iio/dac/rockchip-flexbus-dac.c b/drivers/iio/dac/rockchip-flexbus-dac.c new file mode 100644 index 000000000000..3b94e7fa11f9 --- /dev/null +++ b/drivers/iio/dac/rockchip-flexbus-dac.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Rockchip Flexbus DAC opmode + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FLEXBUS_MAX_TX_RATE 200000000 +#define FLEXBUS_DAC_ERR_ISR (FLEXBUS_DMA_TIMEOUT_ISR | FLEXBUS_DMA_ERR_ISR | \ + FLEXBUS_TX_UDF_ISR | FLEXBUS_TX_OVF_ISR) +#define FLEXBUS_DAC_ISR (FLEXBUS_DAC_ERR_ISR | FLEXBUS_TX_DONE_ISR) +#define FLEXBUS_DAC_TIMEOUT msecs_to_jiffies(100) +#define FLEXBUS_DAC_REPEAT 0x40 + +enum flexbus_dac_result { + DAC_DONE = 0, + DAC_ERR, +}; + +struct rockchip_flexbus_dac; + +struct rockchip_flexbus_dac_ops { + int (*write_block)(struct rockchip_flexbus_dac *rkfb_dac, void *src, dma_addr_t src_phys, + u32 dst_len, u32 num_of_dfs); +}; + +struct rockchip_flexbus_dac { + struct device *dev; + struct rockchip_flexbus *rkfb; + u32 dfs; + bool free_sclk; + bool cpol; + bool cpha; + struct mutex lock; + struct completion completion; + enum flexbus_dac_result result; + u32 last_val; + u32 src_buf_len; + dma_addr_t src_buf_phys; + void *src_buf; + const struct rockchip_flexbus_dac_ops *ops; +}; + +static const struct iio_chan_spec rockchip_flexbus_dac_channel[] = { + { + .type = IIO_VOLTAGE, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .shift = 0, + .repeat = FLEXBUS_DAC_REPEAT, + .endianness = IIO_CPU, + }, + }, +}; + +static u16 rockchip_flexbus_get_last_val(struct rockchip_flexbus_dac *rkfb_dac, void *src, + u32 num_of_dfs) +{ + u32 index = num_of_dfs - 1; + u16 val; + + switch (rkfb_dac->dfs) { + case 4: + val = readb(src + index * rkfb_dac->dfs / 8); + if (index % 2) + val >>= 4; + val &= 0xf; + break; + case 8: + val = readb(src + index * rkfb_dac->dfs / 8); + break; + case 16: + default: + val = readw(src + index * rkfb_dac->dfs / 8); + break; + } + + return val; +} + +static int rockchip_flexbus_dac_write_block(struct rockchip_flexbus_dac *rkfb_dac, void *src, + dma_addr_t src_phys, u32 src_len, u32 num_of_dfs) +{ + struct rockchip_flexbus *rkfb = rkfb_dac->rkfb; + u32 val; + int ret = 0; + + if (num_of_dfs > 0x08000000) { + dev_err(rkfb_dac->dev, "num_of_dfs is too large\n"); + return -EINVAL; + } + + mutex_lock(&rkfb_dac->lock); + reinit_completion(&rkfb_dac->completion); + + rockchip_flexbus_writel(rkfb, FLEXBUS_TX_NUM, num_of_dfs); + + val = num_of_dfs * rkfb_dac->dfs / 32 / 2; + if (val < 16) + val = 16; + else if (val > rkfb->config->txwat_start_max) + val = rkfb->config->txwat_start_max; + rockchip_flexbus_writel(rkfb, FLEXBUS_TXWAT_START, val); + + rockchip_flexbus_writel(rkfb, FLEXBUS_DMA_SRC_ADDR0, (ulong)src_phys >> 2); + rockchip_flexbus_writel(rkfb, FLEXBUS_DMA_SRC_LEN0, src_len); + + rockchip_flexbus_writel(rkfb, FLEXBUS_ENR, FLEXBUS_TX_ENR); + if (!wait_for_completion_timeout(&rkfb_dac->completion, FLEXBUS_DAC_TIMEOUT)) { + ret = -ETIMEDOUT; + goto end; + } + + if (rkfb_dac->result != DAC_DONE) { + ret = -1; + goto end; + } + + rkfb_dac->last_val = rockchip_flexbus_get_last_val(rkfb_dac, src, num_of_dfs); + +end: + rockchip_flexbus_writel(rkfb, FLEXBUS_ENR, FLEXBUS_TX_DIS); + mutex_unlock(&rkfb_dac->lock); + + return ret; +} + +static void rockchip_flexbus_dac_isr(struct rockchip_flexbus *rkfb, u32 isr) +{ + struct rockchip_flexbus_dac *rkfb_dac = rkfb->fb0_data; + + if (rkfb->opmode0 != ROCKCHIP_FLEXBUS0_OPMODE_DAC) + return; + + if (isr & FLEXBUS_TX_DONE_ISR) { + rkfb_dac->result = DAC_DONE; + rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_TX_DONE_ISR); + } + + if (isr & FLEXBUS_DAC_ERR_ISR) { + rkfb_dac->result = DAC_ERR; + + if (isr & FLEXBUS_DMA_TIMEOUT_ISR) { + dev_err_ratelimited(rkfb_dac->dev, "dma timeout!\n"); + rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_DMA_TIMEOUT_ISR); + } + if (isr & FLEXBUS_DMA_ERR_ISR) { + dev_err_ratelimited(rkfb_dac->dev, "dma err!\n"); + rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_DMA_ERR_ISR); + } + if (isr & FLEXBUS_TX_UDF_ISR) { + dev_err_ratelimited(rkfb_dac->dev, "tx underflow!\n"); + rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_TX_UDF_ISR); + } + if (isr & FLEXBUS_TX_OVF_ISR) { + dev_err_ratelimited(rkfb_dac->dev, "tx overflow!\n"); + rockchip_flexbus_writel(rkfb, FLEXBUS_ICR, FLEXBUS_TX_OVF_ISR); + } + } + + complete(&rkfb_dac->completion); +} + +static int rockchip_flexbus_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct rockchip_flexbus_dac *rkfb_dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = rkfb_dac->last_val; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = clk_get_rate(rkfb_dac->rkfb->clks[0].clk) / 2; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int rockchip_flexbus_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct rockchip_flexbus_dac *rkfb_dac = iio_priv(indio_dev); + u16 *buf; + u32 clk_rate; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + buf = rkfb_dac->src_buf; + switch (rkfb_dac->dfs) { + case 4: + buf[0] = val & 0xf; + break; + case 8: + buf[0] = val & 0xff; + break; + case 16: + default: + buf[0] = val & 0xffff; + break; + } + + ret = rkfb_dac->ops->write_block(rkfb_dac, rkfb_dac->src_buf, + rkfb_dac->src_buf_phys, rkfb_dac->src_buf_len, 1); + break; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&rkfb_dac->lock); + clk_rate = val * 2; + if (clk_rate > FLEXBUS_MAX_TX_RATE) + clk_rate = FLEXBUS_MAX_TX_RATE; + ret = clk_set_rate(rkfb_dac->rkfb->clks[0].clk, clk_rate); + mutex_unlock(&rkfb_dac->lock); + break; + + default: + return -EINVAL; + } + + return ret; +} + +static const struct iio_info rockchip_flexbus_dac_info = { + .read_raw = rockchip_flexbus_dac_read_raw, + .write_raw = rockchip_flexbus_dac_write_raw, +}; + +static irqreturn_t rockchip_flexbus_dac_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct iio_buffer *buf = indio_dev->buffer; + struct rockchip_flexbus_dac *rkfb_dac = iio_priv(indio_dev); + int ret; + + ret = iio_pop_from_buffer(buf, rkfb_dac->src_buf); + if (ret) { + dev_err(rkfb_dac->dev, "failed to pop_from_buffer\n"); + goto end; + } + + rkfb_dac->ops->write_block(rkfb_dac, rkfb_dac->src_buf, rkfb_dac->src_buf_phys, + rkfb_dac->src_buf_len, indio_dev->channels->scan_type.repeat); + +end: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int rockchip_flexbus_dac_init(struct rockchip_flexbus_dac *rkfb_dac) +{ + struct rockchip_flexbus *rkfb = rkfb_dac->rkfb; + u32 val; + + if (rkfb_dac->free_sclk) + rockchip_flexbus_writel(rkfb, FLEXBUS_FREE_SCLK, FLEXBUS_TX_FREE_MODE); + + switch (rkfb_dac->dfs) { + case 4: + val = FLEXBUS_DFS_4BIT; + break; + case 8: + val = FLEXBUS_DFS_8BIT; + break; + case 16: + val = FLEXBUS_DFS_16BIT; + break; + default: + return -EINVAL; + } + if (rkfb_dac->cpol) + val |= FLEXBUS_CPOL; + if (rkfb_dac->cpha) + val |= FLEXBUS_CPHA; + rockchip_flexbus_writel(rkfb, FLEXBUS_TX_CTL, val); + + rockchip_flexbus_setbits(rkfb, FLEXBUS_IMR, FLEXBUS_DAC_ISR); + + return 0; +} + +static int rockchip_flexbus_dac_parse_dt(struct rockchip_flexbus_dac *rkfb_dac) +{ + rkfb_dac->cpol = device_property_read_bool(rkfb_dac->dev, "rockchip,cpol"); + rkfb_dac->cpha = device_property_read_bool(rkfb_dac->dev, "rockchip,cpha"); + rkfb_dac->free_sclk = device_property_read_bool(rkfb_dac->dev, "rockchip,free-sclk"); + + if (device_property_read_u32(rkfb_dac->dev, "rockchip,dfs", &rkfb_dac->dfs)) { + dev_err(rkfb_dac->dev, "failed to get dfs\n"); + return -EINVAL; + } + if (rkfb_dac->dfs != 4 && rkfb_dac->dfs != 8 && rkfb_dac->dfs != 16) { + dev_err(rkfb_dac->dev, "invalid dfs\n"); + return -EINVAL; + } + + return 0; +} + +static const struct rockchip_flexbus_dac_ops rockchip_flexbus_dac_ops = { + .write_block = rockchip_flexbus_dac_write_block, +}; + +static const struct of_device_id rockchip_flexbus_dac_of_match[] = { + { .compatible = "rockchip,flexbus-dac" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rockchip_flexbus_dac_of_match); + +static int rockchip_flexbus_dac_probe(struct platform_device *pdev) +{ + struct rockchip_flexbus *rkfb = dev_get_drvdata(pdev->dev.parent); + struct rockchip_flexbus_dac *rkfb_dac = NULL; + struct iio_dev *indio_dev = NULL; + int ret = 0; + + if (rkfb->opmode0 != ROCKCHIP_FLEXBUS0_OPMODE_DAC) { + dev_err(&pdev->dev, "flexbus0 opmode mismatch!\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*rkfb_dac)); + if (!indio_dev) + return -ENOMEM; + rkfb_dac = iio_priv(indio_dev); + + platform_set_drvdata(pdev, rkfb_dac); + rkfb_dac->dev = &pdev->dev; + rkfb_dac->rkfb = rkfb; + rkfb->fb0_data = rkfb_dac; + rkfb->fb0_isr = rockchip_flexbus_dac_isr; + + ret = rockchip_flexbus_dac_parse_dt(rkfb_dac); + if (ret != 0) + return -EINVAL; + + mutex_init(&rkfb_dac->lock); + init_completion(&rkfb_dac->completion); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &rockchip_flexbus_dac_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = rockchip_flexbus_dac_channel; + indio_dev->num_channels = 1; + + rockchip_flexbus_dac_init(rkfb_dac); + rkfb_dac->ops = &rockchip_flexbus_dac_ops; + + rkfb_dac->src_buf_len = DIV_ROUND_UP(indio_dev->channels->scan_type.repeat * rkfb_dac->dfs, + 8); + rkfb_dac->src_buf_len = round_up(rkfb_dac->src_buf_len, 0x40); + if (rkfb_dac->src_buf_len < 0x40 || rkfb_dac->src_buf_len > 0x0fffffc0) { + dev_err(rkfb_dac->dev, "buf_len should >= 0x40 and <= 0x0fffffc0\n"); + return -EINVAL; + } + + rkfb_dac->src_buf = dma_alloc_coherent(rkfb_dac->dev, rkfb_dac->src_buf_len, + &rkfb_dac->src_buf_phys, GFP_KERNEL | __GFP_ZERO); + if (!rkfb_dac->src_buf) + return -ENOMEM; + + ret = devm_iio_triggered_buffer_setup_ext(&pdev->dev, indio_dev, NULL, + &rockchip_flexbus_dac_trigger_handler, + IIO_BUFFER_DIRECTION_OUT, NULL, NULL); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver rockchip_flexbus_dac_driver = { + .probe = rockchip_flexbus_dac_probe, + .driver = { + .name = "rockchip_flexbus_dac", + .of_match_table = rockchip_flexbus_dac_of_match, + }, +}; + +module_platform_driver(rockchip_flexbus_dac_driver); + +MODULE_DESCRIPTION("Rockchip Flexbus DAC driver"); +MODULE_LICENSE("GPL");