From 979e825fe81be4d5f3d5f854568ac6486dcfebdb Mon Sep 17 00:00:00 2001 From: David Wu Date: Fri, 25 May 2018 15:13:28 +0800 Subject: [PATCH] pwm: pwm-rockchip-i2s: Add the PWM driver for rockchip i2s controller Make the i2s controller to implement the PWM continuous mode, use I2S's Left/right two channel, each channel is only 32bits, so the precision of duty percent is 1/64. Change-Id: Ie134d964cdf33573e918b2f79cc8e53118f50a1e Signed-off-by: David Wu --- drivers/pwm/Kconfig | 7 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-rockchip-i2s.c | 504 +++++++++++++++++++++++++++++++++ 3 files changed, 512 insertions(+) create mode 100644 drivers/pwm/pwm-rockchip-i2s.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 68b07223486d..d12c22deceac 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -349,6 +349,13 @@ config PWM_ROCKCHIP Generic PWM framework driver for the PWM controller found on Rockchip SoCs. +config PWM_ROCKCHIP_I2S + tristate "Rockchip I2S PWM support" + depends on ARCH_ROCKCHIP + help + Generic PWM framework driver for the I2S controller found on + Rockchip SoCs, use the I2S controller to implement PWM function. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG || ARCH_EXYNOS diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 5ad6c7ba3099..47af4e77840e 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o +obj-$(CONFIG_PWM_ROCKCHIP_I2S) += pwm-rockchip-i2s.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_STI) += pwm-sti.o diff --git a/drivers/pwm/pwm-rockchip-i2s.c b/drivers/pwm/pwm-rockchip-i2s.c new file mode 100644 index 000000000000..30b39509bf6e --- /dev/null +++ b/drivers/pwm/pwm-rockchip-i2s.c @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * PWM-I2S driver for Rockchip SoCs + * + * Copyright (c) 2018 Rockchip Electronics Co. Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* transmit operation control register */ +#define I2S_TXCR_FBM_MSB 0 +#define I2S_TXCR_FBM_LSB BIT(11) +#define I2S_TXCR_IBM_NORMAL 0 +#define I2S_TXCR_IBM_LSJM BIT(9) +#define I2S_TXCR_IBM_RSJM BIT(10) +#define I2S_TXCR_IBM_MASK GENMASK(10, 9) +#define I2S_TXCR_VDW(x) ((x) - 1) +#define I2S_TXCR_VDW_MASK GENMASK(4, 0) + +/* clock generation register */ +#define I2S_CKR_TSD(x) ((x) - 1) +#define I2S_CKR_TSD_MASK GENMASK(7, 0) + +/* DMA control register */ +#define I2S_DMACR_TDE_DISABLE 0 +#define I2S_DMACR_TDE_ENABLE BIT(8) +#define I2S_DMACR_TDL(x) (x) +#define I2S_DMACR_TDL_MASK GENMASK(4, 0) + +/* Transfer start register */ +#define I2S_XFER_TXS_STOP 0 +#define I2S_XFER_TXS_START BIT(0) + +/* clear SCLK domain logic register */ +#define I2S_CLR_TXC BIT(0) + +/* Mclk div register */ +#define I2S_CLKDIV_TXM(x) ((x) - 1) + +/* I2S REGS */ +#define I2S_TXCR (0x0000) +#define I2S_RXCR (0x0004) +#define I2S_CKR (0x0008) +#define I2S_FIFOLR (0x000c) +#define I2S_DMACR (0x0010) +#define I2S_INTCR (0x0014) +#define I2S_INTSR (0x0018) +#define I2S_XFER (0x001c) +#define I2S_CLR (0x0020) +#define I2S_TXDR (0x0024) +#define I2S_RXDR (0x0028) +#define I2S_TDM_TXCR (0x0030) +#define I2S_TDM_RXCR (0x0034) +#define I2S_CLKDIV (0x0038) + +/* Hardware Param */ +#define I2S_FORMAT_BITS 32 +#define I2S_CHANNEL_NUM 2 +#define I2S_FRAME_BITS (I2S_FORMAT_BITS * I2S_CHANNEL_NUM) +#define I2S_FRAME_BYTES (I2S_FRAME_BITS / 8) +#define I2S_FIFO_WATERMARK_LEVEL 30 + +#define I2S_DMA_BUFFER_SIZE 256 +#define I2S_DMA_BUFFER_FRAME_SIZE (I2S_DMA_BUFFER_SIZE / I2S_FRAME_BYTES) + +struct rockchip_i2s_pwm_dma { + struct dma_chan *chan_tx; + dma_addr_t tx_addr; + char *tx_buff; + dma_cookie_t tx_cookie; +}; + +struct rockchip_i2s_pwm_chip { + struct pwm_chip chip; + struct clk *hclk; + struct clk *mclk; + void __iomem *base; + struct rockchip_i2s_pwm_dma dma; + const struct rockchip_i2s_pwm_data *data; + + struct pwm_state pwm_state; +}; + +struct rockchip_i2s_pwm_data { + unsigned int reg_clkdiv; + unsigned int bit_clkdiv; + unsigned int mask_clkdiv; +}; + +static inline +struct rockchip_i2s_pwm_chip *to_rockchip_i2s_pwm_chip(struct pwm_chip *c) +{ + return container_of(c, struct rockchip_i2s_pwm_chip, chip); +} + +static void rockchip_i2s_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_i2s_pwm_chip *pc = to_rockchip_i2s_pwm_chip(chip); + u32 ctrl; + int ret; + + ret = clk_enable(pc->hclk); + if (ret) + return; + + memcpy(state, &pc->pwm_state, sizeof(struct pwm_state)); + + ctrl = readl_relaxed(pc->base + I2S_XFER); + if (ctrl & I2S_XFER_TXS_START) + state->enabled = true; + else + state->enabled = false; + + clk_disable(pc->hclk); +} + +static int rockchip_i2s_pwm_config(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_i2s_pwm_chip *pc = to_rockchip_i2s_pwm_chip(chip); + unsigned long div_bclk; + unsigned long flags; + u64 mclk_rate, period_div, duty, duty_div; + unsigned int div_val; + int ret, i; + + ret = clk_enable(pc->hclk); + if (ret) + return ret; + /* + * Assume the time of a frame is a period of pwm, so a frame is the unit + * of the pwm, we have to config the buffer per frame. + */ + mclk_rate = clk_get_rate(pc->mclk); + period_div = mclk_rate * state->period; + div_bclk = DIV_ROUND_CLOSEST(period_div, I2S_FRAME_BITS * NSEC_PER_SEC); + + /* + * The duty pecent is equal to the bits percent at whole frame, as the + * time of a frame is a period. + */ + duty_div = DIV_ROUND_CLOSEST(I2S_FRAME_BITS * state->duty_cycle, + state->period); + if (duty_div > 0) + duty = GENMASK_ULL(duty_div - 1, 0); + else + duty = 0; + + if (state->polarity == PWM_POLARITY_INVERSED) + duty = ~duty; + + local_irq_save(flags); + + div_val = readl_relaxed(pc->base + pc->data->reg_clkdiv); + div_val &= ~pc->data->mask_clkdiv; + writel_relaxed((I2S_CLKDIV_TXM(div_bclk) << pc->data->bit_clkdiv) + | div_val, pc->base + pc->data->reg_clkdiv); + + for (i = 0; i < I2S_DMA_BUFFER_FRAME_SIZE; i++) + memcpy((u64 *)pc->dma.tx_buff + i, &duty, sizeof(u64)); + + pc->pwm_state.period = state->period; + pc->pwm_state.duty_cycle = state->duty_cycle; + pc->pwm_state.polarity = state->polarity; + + local_irq_restore(flags); + clk_disable(pc->hclk); + + return ret; +} + +static int rockchip_i2s_pwm_enable(struct pwm_chip *chip, + struct pwm_device *pwm, + bool enable) +{ + struct rockchip_i2s_pwm_chip *pc = to_rockchip_i2s_pwm_chip(chip); + struct rockchip_i2s_pwm_dma *dma = &pc->dma; + struct dma_async_tx_descriptor *tx_desc; + int ret, retry = 10; + u32 val; + + if (enable) { + ret = clk_enable(pc->hclk); + if (ret) + return ret; + + ret = clk_enable(pc->mclk); + if (ret) + goto err_mclk; + + tx_desc = dmaengine_prep_dma_cyclic(dma->chan_tx, dma->tx_addr, + I2S_DMA_BUFFER_SIZE, + I2S_DMA_BUFFER_SIZE, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx_desc) { + dev_err(chip->dev, "Not able to get tx desc for DMA\n"); + ret = -EBUSY; + goto out; + } + + tx_desc->callback = NULL; + tx_desc->callback_param = NULL; + dma->tx_cookie = dmaengine_submit(tx_desc); + ret = dma_submit_error(dma->tx_cookie); + if (ret) { + dev_err(chip->dev, "DMA submit failed\n"); + goto out; + } + + dma_async_issue_pending(pc->dma.chan_tx); + + val = readl_relaxed(pc->base + I2S_DMACR); + val &= ~I2S_DMACR_TDE_ENABLE; + writel_relaxed(val | I2S_DMACR_TDE_ENABLE, + pc->base + I2S_DMACR); + + val = readl_relaxed(pc->base + I2S_XFER); + val &= ~I2S_XFER_TXS_START; + writel_relaxed(val | I2S_XFER_TXS_START, pc->base + I2S_XFER); + } else { + dmaengine_terminate_all(pc->dma.chan_tx); + + val = readl_relaxed(pc->base + I2S_DMACR); + val &= ~I2S_DMACR_TDE_ENABLE; + writel_relaxed(val | I2S_DMACR_TDE_DISABLE, + pc->base + I2S_DMACR); + + val = readl_relaxed(pc->base + I2S_XFER); + val &= ~I2S_XFER_TXS_START; + writel_relaxed(val | I2S_XFER_TXS_STOP, pc->base + I2S_XFER); + + usleep_range(100, 150); + + val = readl_relaxed(pc->base + I2S_CLR); + val &= ~I2S_CLR_TXC; + writel_relaxed(val | I2S_CLR_TXC, pc->base + I2S_CLR); + + /* Should wait for clear operation to finish */ + do { + val = readl_relaxed(pc->base + I2S_CLR); + if (val) + break; + } while (--retry); + + if (!retry) + dev_warn(chip->dev, "fail to clear\n"); + + clk_disable(pc->mclk); + clk_disable(pc->hclk); + } + + return 0; + +out: + clk_disable(pc->mclk); +err_mclk: + clk_disable(pc->hclk); + + return ret; +} + +static int rockchip_i2s_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_state curstate; + bool enabled; + int ret; + + pwm_get_state(pwm, &curstate); + enabled = curstate.enabled; + + ret = rockchip_i2s_pwm_config(chip, pwm, state); + if (ret) + return ret; + + if (state->enabled != enabled) { + ret = rockchip_i2s_pwm_enable(chip, pwm, state->enabled); + if (ret) + return ret; + } + + rockchip_i2s_pwm_get_state(chip, pwm, state); + + return 0; +} + +static const struct pwm_ops rockchip_i2s_pwm_ops = { + .get_state = rockchip_i2s_pwm_get_state, + .apply = rockchip_i2s_pwm_apply, + .owner = THIS_MODULE, +}; + +static int rockchip_i2s_pwm_dma_request(struct rockchip_i2s_pwm_chip *pc, + struct device *dev, + dma_addr_t phy_addr) +{ + struct rockchip_i2s_pwm_dma *dma = &pc->dma; + struct dma_slave_config dma_sconfig; + int ret; + + dma->chan_tx = dma_request_slave_channel(dev, "tx"); + if (!dma->chan_tx) { + dev_err(dev, "can't request DMA tx channel\n"); + return -ENODEV; + } + + dma_sconfig.direction = DMA_MEM_TO_DEV; + dma_sconfig.dst_addr = phy_addr; + dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_sconfig.dst_maxburst = 2; + + ret = dmaengine_slave_config(dma->chan_tx, &dma_sconfig); + if (ret < 0) { + dev_err(dev, "can't configure tx channel\n"); + goto fail; + } + + dma->tx_buff = dma_alloc_coherent(dev, I2S_DMA_BUFFER_SIZE, + &dma->tx_addr, GFP_KERNEL); + if (!dma->tx_buff) { + ret = -ENOMEM; + goto fail; + } + + return 0; +fail: + dma_release_channel(dma->chan_tx); + return ret; +} + +static void rockchip_i2s_pwm_dma_release(struct rockchip_i2s_pwm_chip *pc) +{ + struct rockchip_i2s_pwm_dma *dma = &pc->dma; + struct device *dev = pc->chip.dev; + + dma_free_coherent(dev, I2S_DMA_BUFFER_SIZE, dma->tx_buff, dma->tx_addr); + dma_release_channel(dma->chan_tx); +} + +static int rockchip_i2s_pwm_hw_params(struct rockchip_i2s_pwm_chip *pc) +{ + unsigned int val = 0; + int ret; + + ret = clk_enable(pc->hclk); + if (ret) + return ret; + + /* Config tx format bits with 32, LSB, left justified. */ + val = readl_relaxed(pc->base + I2S_TXCR); + val &= ~(I2S_TXCR_VDW_MASK | I2S_TXCR_IBM_MASK | I2S_TXCR_FBM_LSB); + writel_relaxed(val | I2S_TXCR_VDW(I2S_FORMAT_BITS) | I2S_TXCR_IBM_LSJM + | I2S_TXCR_FBM_LSB, pc->base + I2S_TXCR); + + val = readl_relaxed(pc->base + I2S_CKR); + val &= ~I2S_CKR_TSD_MASK; + writel_relaxed(val | I2S_CKR_TSD(I2S_FRAME_BITS), pc->base + I2S_CKR); + + /* Config the tx fifo watermark level to 30. */ + val = readl_relaxed(pc->base + I2S_DMACR); + val &= ~I2S_DMACR_TDL_MASK; + writel_relaxed(val | I2S_DMACR_TDL(I2S_FIFO_WATERMARK_LEVEL), + pc->base + I2S_DMACR); + + clk_disable(pc->hclk); + return 0; +} + +static const struct rockchip_i2s_pwm_data i2s_pwm_data_v1 = { + .reg_clkdiv = 0x8, + .bit_clkdiv = 16, + .mask_clkdiv = GENMASK(23, 16), +}; + +static const struct rockchip_i2s_pwm_data i2s_pwm_data_v2 = { + .reg_clkdiv = 0x38, + .bit_clkdiv = 0, + .mask_clkdiv = GENMASK(7, 0), +}; + +static const struct of_device_id rockchip_i2s_pwm_match[] = { + { .compatible = "rockchip,i2s-pwm", .data = &i2s_pwm_data_v1 }, + { .compatible = "rockchip,rk3308-i2s-pwm", .data = &i2s_pwm_data_v2 }, + { /* sentinel */ }, +}; + +static int rockchip_i2s_pwm_probe(struct platform_device *pdev) +{ + struct rockchip_i2s_pwm_chip *pc; + const struct of_device_id *id; + struct resource *res; + int ret; + + id = of_match_device(rockchip_i2s_pwm_match, &pdev->dev); + if (!id) + return -EINVAL; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->hclk = devm_clk_get(&pdev->dev, "hclk"); + if (IS_ERR(pc->hclk)) + return PTR_ERR(pc->hclk); + + pc->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(pc->mclk)) + return PTR_ERR(pc->mclk); + + ret = clk_prepare(pc->hclk); + if (ret) + return ret; + + ret = clk_prepare(pc->mclk); + if (ret) + goto err_hclk; + + pc->chip.dev = &pdev->dev; + platform_set_drvdata(pdev, pc); + + ret = rockchip_i2s_pwm_hw_params(pc); + if (ret) + goto err_mclk; + + ret = rockchip_i2s_pwm_dma_request(pc, &pdev->dev, + res->start + I2S_TXDR); + if (ret) { + ret = -EPROBE_DEFER; + goto err_mclk; + } + + pc->data = id->data; + pc->chip.ops = &rockchip_i2s_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = 1; + pc->chip.of_xlate = of_pwm_xlate_with_flags; + pc->chip.of_pwm_n_cells = 3; + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + rockchip_i2s_pwm_dma_release(pc); + goto err_mclk; + } + + return 0; + +err_mclk: + clk_unprepare(pc->mclk); +err_hclk: + clk_unprepare(pc->hclk); + + return ret; +} + +static int rockchip_i2s_pwm_remove(struct platform_device *pdev) +{ + struct rockchip_i2s_pwm_chip *pc = platform_get_drvdata(pdev); + struct pwm_state curstate; + + pwm_get_state(pc->chip.pwms, &curstate); + if (curstate.enabled) + dmaengine_terminate_all(pc->dma.chan_tx); + + rockchip_i2s_pwm_dma_release(pc); + + clk_unprepare(pc->mclk); + clk_unprepare(pc->hclk); + + return pwmchip_remove(&pc->chip); +} + +static struct platform_driver rockchip_i2s_pwm_driver = { + .driver = { + .name = "rockchip-i2s-pwm", + .of_match_table = rockchip_i2s_pwm_match, + }, + .probe = rockchip_i2s_pwm_probe, + .remove = rockchip_i2s_pwm_remove, +}; +module_platform_driver(rockchip_i2s_pwm_driver); + +MODULE_AUTHOR("David Wu "); +MODULE_DESCRIPTION("ROCKCHIP I2S PWM driver"); +MODULE_LICENSE("GPL v2");