!4 feat: 增加定时器(ATIM GTIM)的MFD驱动,及基于该MFD驱动的PWM驱动

Merge pull request !4 from Ilikara/feat/timer-mfd-pwm
This commit is contained in:
maoxiaochuan
2025-03-04 04:11:15 +00:00
committed by Gitee
11 changed files with 1361 additions and 785 deletions
+57 -24
View File
@@ -889,30 +889,6 @@
/* Optional for that dr_mode = "host" or dr_mode = "peripheral" */
};
pwm_atim: pwm@16118000{
compatible = "loongson,ls2k-pwm-atim";
reg = <0 0x16118000 0 0x1000>;
interrupt-parent = <&liointc0>;
interrupts = <13 IRQ_TYPE_LEVEL_HIGH>;
#pwm-cells = <2>;
pinctrl-0 = <&atim3_pin_m1>, <&atim3n_pin_m1>;
pinctrl-names = "default";
clock-frequency = <200000000>;
status = "disabled";
};
pwm_gtim: pwm@16119000{
compatible = "loongson,ls2k-pwm-gtim";
reg = <0 0x16119000 0 0x1000>;
interrupt-parent = <&liointc0>;
interrupts = <14 IRQ_TYPE_LEVEL_HIGH>;
#pwm-cells = <2>;
pinctrl-0 = <&gtim2_pin_m1>, <&gtim3_pin_m1>;
pinctrl-names = "default";
clock-frequency = <200000000>;
status = "disabled";
};
pwm0: pwm@0x1611b000 {
compatible = "loongson,ls2k-pwm";
reg = <0 0x1611b000 0 0xf>;
@@ -969,6 +945,63 @@
status = "disabled";
};
timers1: timer@16118000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "loongson,loongson-timers";
reg = <0 0x16118000 0 0x1000>;
interrupt-parent = <&liointc0>;
interrupts = <13 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clocks CLK_APB>;
clock-names = "int";
dmas = <&dma 0 0x400>,
<&dma 1 0x400>,
<&dma 2 0x400>,
<&dma 3 0x400>,
<&dma 4 0x400>,
<&dma 5 0x400>,
<&dma 6 0x400>;
dma-names = "ch1", "ch2", "ch3", "ch4", "com", "up", "trig";
pinctrl-0 = <&atim3_pin_m1>, <&atim3n_pin_m1>;
pinctrl-names = "default";
clock-frequency = <200000000>;
status = "disabled";
pwm {
compatible = "loongson,ls2k-pwm-timer";
#pwm-cells = <2>;
status = "disabled";
};
};
timers2: timer@16119000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "loongson,loongson-timers";
reg = <0 0x16119000 0 0x1000>;
interrupt-parent = <&liointc0>;
interrupts = <14 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clocks CLK_APB>;
clock-names = "int";
dmas = <&dma 0 0x400>,
<&dma 1 0x400>,
<&dma 2 0x400>,
<&dma 3 0x400>,
<&dma 5 0x400>,
<&dma 6 0x400>;
dma-names = "ch1", "ch2", "ch3", "ch4", "up", "trig";
pinctrl-0 = <&gtim2_pin_m1>, <&gtim3_pin_m1>;
pinctrl-names = "default";
clock-frequency = <200000000>;
status = "disabled";
pwm {
compatible = "loongson,ls2k-pwm-timer";
#pwm-cells = <2>;
status = "disabled";
};
};
tsensor: tsensor@0x16001500 {
compatible = "loongson,ls2k300-thermal";
reg = <0 0x16001500 0 0x30>;
+2 -2
View File
@@ -350,12 +350,12 @@ static int lsia_dma_set_xfer_param(struct lsia_dma_chan *chan,
case DMA_MEM_TO_DEV:
dev_width = chan->dma_sconfig.dst_addr_width;
chan->chan_reg.dma_cpar = chan->dma_sconfig.dst_addr;
dma_ccr = DMA_CCR_DIR;
dma_ccr = DMA_CCR_DIR | DMA_CCR_TCIE;
break;
case DMA_DEV_TO_MEM:
dev_width = chan->dma_sconfig.src_addr_width;
chan->chan_reg.dma_cpar = chan->dma_sconfig.src_addr;
dma_ccr = DMA_CCR_MINC;
dma_ccr = DMA_CCR_MINC | DMA_CCR_TCIE;
break;
default:
return -EINVAL;
+11
View File
@@ -2151,6 +2151,17 @@ config MFD_STM32_TIMERS
for PWM and IIO Timer. This driver allow to share the
registers between the others drivers.
config MFD_LS_TIMERS
tristate "Support for LS Timers"
depends on (MACH_LOONGSON64 && OF) || COMPILE_TEST
select MFD_CORE
select REGMAP
select REGMAP_MMIO
help
Select this option to enable Loongson timers driver used
for PWM and IIO Timer. This driver allow to share the
registers between the others drivers.
config MFD_STPMIC1
tristate "Support for STPMIC1 PMIC"
depends on I2C=y && OF
+1
View File
@@ -264,6 +264,7 @@ obj-$(CONFIG_MFD_SUN4I_GPADC) += sun4i-gpadc.o
obj-$(CONFIG_MFD_STM32_LPTIMER) += stm32-lptimer.o
obj-$(CONFIG_MFD_STM32_TIMERS) += stm32-timers.o
obj-$(CONFIG_MFD_LS_TIMERS) += loongson-timers.o
obj-$(CONFIG_MFD_MXS_LRADC) += mxs-lradc.o
obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o
obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o
+327
View File
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics 2016
* Author: Benjamin Gaignard <benjamin.gaignard@st.com>
*
* Copyright (C) 2025 Ilikara <3435193369@qq.com>
* Modified by: Ilikara <3435193369@qq.com>
*/
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/mfd/loongson-timers.h>
#define LS_TIMERS_MAX_REGISTERS 0x3fc
/* DIER register DMA enable bits */
static const u32 loongson_timers_dier_dmaen[LS_TIMERS_MAX_DMAS] = {
TIM_DIER_CC1DE,
TIM_DIER_CC2DE,
TIM_DIER_CC3DE,
TIM_DIER_CC4DE,
TIM_DIER_UIE,
TIM_DIER_TDE,
TIM_DIER_COMDE
};
static void loongson_timers_dma_done(void *p)
{
struct loongson_timers_dma *dma = p;
struct dma_tx_state state;
enum dma_status status;
status = dmaengine_tx_status(dma->chan, dma->chan->cookie, &state);
if (status == DMA_COMPLETE)
complete(&dma->completion);
}
/**
* loongson_timers_dma_burst_read - Read from timers registers using dual DMA channels.
*
* @dev: Loongson timers MFD device
* @buf: Destination buffer (must hold bursts * num_reg * 2 elements)
* @id: DMA event pair identifier (ch[1/2/3/4]_pair)
* @reg: Base register offset for DMA transfers
* @num_reg: Number of consecutive registers per burst
* @bursts: Number of bursts to capture (each burst triggers dual DMA transfers)
* @tmo_ms: Operation timeout in milliseconds
*/
int loongson_timers_dma_burst_read(struct device *dev, u32 *buf,
enum loongson_timers_dmas id, u32 reg,
unsigned int num_reg, unsigned int bursts,
unsigned long tmo_ms)
{
struct loongson_timers *ddata = dev_get_drvdata(dev);
unsigned long timeout = msecs_to_jiffies(tmo_ms);
struct regmap *regmap = ddata->regmap;
struct loongson_timers_dma *dma = &ddata->dma;
size_t len = num_reg * bursts * sizeof(u32);
struct dma_async_tx_descriptor *descs[2];
struct dma_slave_config configs[2];
dma_cookie_t cookie;
dma_addr_t dma_bufs[2];
long err;
int ret;
/* Sanity check */
if (id < LS_TIMERS_DMA_CH1 || id >= LS_TIMERS_MAX_DMAS - 1)
return -EINVAL;
if (!num_reg || !bursts || reg > LS_TIMERS_MAX_REGISTERS ||
(reg + num_reg * sizeof(u32)) > LS_TIMERS_MAX_REGISTERS)
return -EINVAL;
if (!dma->chans[id] || !dma->chans[id + 1])
return -ENODEV;
mutex_lock(&dma->lock);
/* Select DMA channel in use */
dma->chan = dma->chans[id];
for (int i = 0; i < 2; i++) {
dma_bufs[i] = dma_map_single(dev, buf + i * bursts, len / 2,
DMA_FROM_DEVICE);
if (dma_mapping_error(dev, dma_bufs[i])) {
ret = -ENOMEM;
goto unmap;
}
}
/* Configure DMA channels */
for (int i = 0; i < 2; i++) {
memset(&configs[i], 0, sizeof(configs[i]));
configs[i].src_addr = (dma_addr_t)dma->phys_base + reg + i * sizeof(u32);
configs[i].src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
ret = dmaengine_slave_config(dma->chans[id + i], &configs[i]);
if (ret)
goto unmap;
descs[i] = dmaengine_prep_slave_single(
dma->chans[id + i], dma_bufs[i], len / 2,
DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
if (!descs[i]) {
ret = -EBUSY;
goto unmap;
}
descs[i]->callback = loongson_timers_dma_done;
descs[i]->callback_param = dma;
}
for (int i = 0; i < 2; i++) {
cookie = dmaengine_submit(descs[i]);
ret = dma_submit_error(cookie);
if (ret)
goto dma_term;
}
reinit_completion(&dma->completion);
dma_async_issue_pending(dma->chans[id + 0]);
dma_async_issue_pending(dma->chans[id + 1]);
/* Clear pending flags before enabling DMA request */
ret = regmap_write(regmap, TIM_SR, 0);
if (ret)
goto dma_term;
regmap_update_bits(regmap, TIM_DIER,
loongson_timers_dier_dmaen[id] |
loongson_timers_dier_dmaen[id + 1],
loongson_timers_dier_dmaen[id] |
loongson_timers_dier_dmaen[id + 1]);
if (ret)
goto dma_term;
err = wait_for_completion_interruptible_timeout(&dma->completion,
timeout);
if (err == 0)
ret = -ETIMEDOUT;
else if (err < 0)
ret = err;
regmap_update_bits(regmap, TIM_DIER,
loongson_timers_dier_dmaen[id] |
loongson_timers_dier_dmaen[id + 1],
0);
regmap_write(regmap, TIM_SR, 0);
dma_term:
dmaengine_terminate_all(dma->chans[id + 0]);
dmaengine_terminate_all(dma->chans[id + 1]);
unmap:
for (int i = 0; i < 2; i++) {
if (!dma_mapping_error(dev, dma_bufs[i]))
dma_unmap_single(dev, dma_bufs[i], len / 2,
DMA_FROM_DEVICE);
}
unlock:
dma->chan = NULL;
mutex_unlock(&dma->lock);
return ret;
}
EXPORT_SYMBOL_GPL(loongson_timers_dma_burst_read);
static const struct regmap_config loongson_timers_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = sizeof(u32),
.max_register = LS_TIMERS_MAX_REGISTERS,
};
static void loongson_timers_get_arr_size(struct loongson_timers *ddata)
{
u32 arr;
/* Backup ARR to restore it after getting the maximum value */
regmap_read(ddata->regmap, TIM_ARR, &arr);
/*
* Only the available bits will be written so when readback
* we get the maximum value of auto reload register
*/
regmap_write(ddata->regmap, TIM_ARR, ~0L);
regmap_read(ddata->regmap, TIM_ARR, &ddata->max_arr);
regmap_write(ddata->regmap, TIM_ARR, arr);
}
static int loongson_timers_dma_probe(struct device *dev,
struct loongson_timers *ddata)
{
int i;
int ret = 0;
char name[4];
init_completion(&ddata->dma.completion);
mutex_init(&ddata->dma.lock);
/* Optional DMA support: get valid DMA channel(s) or NULL */
for (i = LS_TIMERS_DMA_CH1; i <= LS_TIMERS_DMA_CH4; i++) {
snprintf(name, ARRAY_SIZE(name), "ch%1d", i + 1);
ddata->dma.chans[i] = dma_request_chan(dev, name);
}
ddata->dma.chans[LS_TIMERS_DMA_UP] = dma_request_chan(dev, "up");
ddata->dma.chans[LS_TIMERS_DMA_TRIG] = dma_request_chan(dev, "trig");
ddata->dma.chans[LS_TIMERS_DMA_COM] = dma_request_chan(dev, "com");
for (i = LS_TIMERS_DMA_CH1; i < LS_TIMERS_MAX_DMAS; i++) {
if (IS_ERR(ddata->dma.chans[i])) {
/* Save the first error code to return */
if (PTR_ERR(ddata->dma.chans[i]) != -ENODEV && !ret)
ret = PTR_ERR(ddata->dma.chans[i]);
ddata->dma.chans[i] = NULL;
}
}
return ret;
}
static void loongson_timers_dma_remove(struct device *dev,
struct loongson_timers *ddata)
{
int i;
for (i = LS_TIMERS_DMA_CH1; i < LS_TIMERS_MAX_DMAS; i++)
if (ddata->dma.chans[i])
dma_release_channel(ddata->dma.chans[i]);
}
static int loongson_timers_irq_probe(struct platform_device *pdev,
struct loongson_timers *ddata)
{
int ret;
ret = platform_get_irq_byname_optional(pdev, "global");
if (ret < 0 && ret != -ENXIO) {
return ret;
} else if (ret != -ENXIO) {
ddata->irq[LS_TIMERS_IRQ_GLOBAL] = ret;
ddata->nr_irqs = 1;
return 0;
}
return 0;
}
static int loongson_timers_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct loongson_timers *ddata;
struct resource *res;
void __iomem *mmio;
int ret;
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
mmio = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(mmio))
return PTR_ERR(mmio);
/* Timer physical addr for DMA */
ddata->dma.phys_base = res->start;
ddata->regmap = devm_regmap_init_mmio_clk(dev, "int", mmio,
&loongson_timers_regmap_cfg);
if (IS_ERR(ddata->regmap))
return PTR_ERR(ddata->regmap);
ddata->clk = devm_clk_get(dev, NULL);
if (IS_ERR(ddata->clk))
return PTR_ERR(ddata->clk);
loongson_timers_get_arr_size(ddata);
ret = loongson_timers_irq_probe(pdev, ddata);
if (ret)
return ret;
ret = loongson_timers_dma_probe(dev, ddata);
if (ret) {
loongson_timers_dma_remove(dev, ddata);
return ret;
}
platform_set_drvdata(pdev, ddata);
ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
if (ret)
loongson_timers_dma_remove(dev, ddata);
return ret;
}
static void loongson_timers_remove(struct platform_device *pdev)
{
struct loongson_timers *ddata = platform_get_drvdata(pdev);
/*
* Don't use devm_ here: enfore of_platform_depopulate() happens before
* DMA are released, to avoid race on DMA.
*/
of_platform_depopulate(&pdev->dev);
loongson_timers_dma_remove(&pdev->dev, ddata);
}
static const struct of_device_id loongson_timers_of_match[] = {
{ .compatible = "loongson,loongson-timers", },
{ /* end node */ },
};
MODULE_DEVICE_TABLE(of, loongson_timers_of_match);
static struct platform_driver loongson_timers_driver = {
.probe = loongson_timers_probe,
.remove_new = loongson_timers_remove,
.driver = {
.name = "loongson-timers",
.of_match_table = loongson_timers_of_match,
},
};
module_platform_driver(loongson_timers_driver);
MODULE_DESCRIPTION("Loongson Timers");
MODULE_LICENSE("GPL v2");
+5 -16
View File
@@ -363,25 +363,14 @@ config PWM_LOONGSON
To compile this driver as a module, choose M here: the module
will be called pwm-loongson.
config PWM_LOONGSON_GTIM
tristate "Loongson GTIM PWM support"
depends on MACH_LOONGSON64 || COMPILE_TEST
depends on COMMON_CLK
config PWM_LOONGSON_TIMER
tristate "Loongson Timer PWM support"
depends on MFD_LS_TIMERS || COMPILE_TEST
help
GTIM PWM framework driver for loongson2k.
Timer PWM framework driver for loongson2k.
To compile this driver as a module, choose M here: the module
will be called pwm-ls-gtim.
config PWM_LOONGSON_ATIM
tristate "Loongson ATIM PWM support"
depends on MACH_LOONGSON64 || COMPILE_TEST
depends on COMMON_CLK
help
ATIM PWM framework driver for loongson2k.
To compile this driver as a module, choose M here: the module
will be called pwm-ls-atim.
will be called pwm-ls-timer.
config PWM_LP3943
tristate "TI/National Semiconductor LP3943 PWM support"
+1 -2
View File
@@ -31,8 +31,7 @@ obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o
obj-$(CONFIG_PWM_LOONGSON) += pwm-loongson.o
obj-$(CONFIG_PWM_LOONGSON_ATIM) += pwm-loongson-atim.o
obj-$(CONFIG_PWM_LOONGSON_GTIM) += pwm-loongson-gtim.o
obj-$(CONFIG_PWM_LOONGSON_TIMER) += pwm-ls-timer.o
obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o
obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o
obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
-374
View File
@@ -1,374 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 by ilikara 3435193369@qq.com.
*
* Loongson ATIM PWM driver
*
* For Loongson's ATIM IP block documentation please refer Chapter 18 of
* Reference Manual: https://loongson.cn/uploads/images/2024120211205269050.%E9%BE%99%E8%8A%AF2K0300%E5%A4%84%E7%90%86%E5%99%A8%E7%94%A8%E6%88%B7%E6%89%8B%E5%86%8C_V1.0.pdf
*
* Author: Ilikara <3435193369@qq.com>
*
* Limitations:
* - ATIM_CNT will be set to 0 when ls_pwm_atim_config is called.
* - All the 8 channels share the same period.
* - Channel n and n+4 share the same duty cycle.
*/
#include <linux/acpi.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/units.h>
#include <linux/delay.h>
/* counter offest */
#define ATIM_CR1 0x00
#define ATIM_CR2 0x04
#define ATIM_SMCR 0x08
#define ATIM_DIER 0x0C
#define ATIM_SR 0x10
#define ATIM_EGR 0x14
#define ATIM_CCMR(n) 0x18 + (((n) % 4) / 2) * 0x04
#define ATIM_CCER 0x20
#define ATIM_CNT 0x24
#define ATIM_PSC 0x28
#define ATIM_ARR 0x2C
#define ATIM_RCR 0x30
#define ATIM_CCR(n) 0x34 + ((n) % 4) * 0x04
#define ATIM_BDTR 0x44
#define ATIM_INSTA 0x50
/* CR1 each bit */
#define CR1_CEN BIT(0)
/* EGR each bit */
#define EGR_UG BIT(0)
/* CCMR each bit */
#define CCMR_CCnS(n) BIT((n) % 2 * 8 + 0)
#define CCMR_OCnFE(n) BIT((n) % 2 * 8 + 2)
#define CCMR_OCnPE(n) BIT((n) % 2 * 8 + 3)
#define CCMR_OCnM(n) BIT((n) % 2 * 8 + 4)
#define CCMR_OCnCE(n) BIT((n) % 2 * 8 + 7)
/* CCER each bit */
#define CCER_CCnE(n) BIT(((n) % 4) * 4 + 0 + ((n) / 4) * 2)
#define CCER_CCnP(n) BIT(((n) % 4) * 4 + 1 + ((n) / 4) * 2)
/* BDTR each bit */
#define BDTR_MOE BIT(15)
/* default input clk frequency for the ACPI case */
#define LOONGSON_PWM_FREQ_DEFAULT 50000000 /* Hz */
struct pwm_loongson_suspend_store {
u32 arr_reg;
u32 ccr_reg[4];
u32 ccer_reg;
u32 en_mark;
};
struct pwm_loongson_ddata {
struct clk *clk;
void __iomem *base;
u64 clk_rate;
struct pwm_loongson_suspend_store lss;
};
static inline struct pwm_loongson_ddata *
to_pwm_loongson_ddata(struct pwm_chip *chip)
{
return pwmchip_get_drvdata(chip);
}
static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata,
u32 offset)
{
return readl(ddata->base + offset);
}
static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata,
u32 val, u32 offset)
{
writel(val, ddata->base + offset);
}
static int ls_pwm_atim_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
u16 val;
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
val = pwm_loongson_readl(ddata, ATIM_CCER);
if (polarity == PWM_POLARITY_INVERSED)
/* Duty cycle defines LOW period of PWM */
val &= ~CCER_CCnP(pwm->hwpwm);
else
/* Duty cycle defines HIGH period of PWM */
val |= CCER_CCnP(pwm->hwpwm);
pwm_loongson_writel(ddata, val, ATIM_CCER);
return 0;
}
static void ls_pwm_atim_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
u32 val;
if (pwm->state.polarity == PWM_POLARITY_NORMAL)
pwm_loongson_writel(ddata, ddata->lss.arr_reg,
ATIM_CCR(pwm->hwpwm));
else if (pwm->state.polarity == PWM_POLARITY_INVERSED)
pwm_loongson_writel(ddata, 0, ATIM_CCR(pwm->hwpwm));
val = pwm_loongson_readl(ddata, ATIM_CCMR(pwm->hwpwm));
val &= ~(0b111 * CCMR_OCnM(pwm->hwpwm));
pwm_loongson_writel(ddata, val, ATIM_CCMR(pwm->hwpwm));
val = pwm_loongson_readl(ddata, ATIM_CCER);
val &= ~CCER_CCnE(pwm->hwpwm);
pwm_loongson_writel(ddata, val, ATIM_CCER);
ddata->lss.en_mark &= ~BIT(pwm->hwpwm);
if (ddata->lss.en_mark == 0b00000000) {
val = pwm_loongson_readl(ddata, ATIM_CR1);
val &= ~CR1_CEN;
pwm_loongson_writel(ddata, val, ATIM_CR1);
pwm_loongson_writel(ddata, 0x0, ATIM_CNT);
}
}
static int ls_pwm_atim_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
u32 val;
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[pwm->hwpwm % 4],
ATIM_CCR(pwm->hwpwm));
pwm_loongson_writel(ddata, ddata->lss.arr_reg, ATIM_ARR);
// todo:类似config函数中的特殊处理
val = pwm_loongson_readl(ddata, ATIM_CCMR(pwm->hwpwm));
val |= 0b111 * CCMR_OCnM(pwm->hwpwm);
pwm_loongson_writel(ddata, val, ATIM_CCMR(pwm->hwpwm));
val = pwm_loongson_readl(ddata, ATIM_CCER);
val |= CCER_CCnE(pwm->hwpwm);
pwm_loongson_writel(ddata, val, ATIM_CCER);
if (ddata->lss.en_mark == 0b00000000) {
pwm_loongson_writel(ddata, 0x0, ATIM_CNT);
val = pwm_loongson_readl(ddata, ATIM_CR1);
val |= CR1_CEN;
pwm_loongson_writel(ddata, val, ATIM_CR1);
}
ddata->lss.en_mark |= BIT(pwm->hwpwm);
return 0;
}
static int ls_pwm_atim_config(struct pwm_chip *chip, struct pwm_device *pwm,
u64 duty_ns, u64 period_ns)
{
u32 duty, period;
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
/* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */
duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC);
pwm_loongson_writel(ddata, duty, ATIM_CCR(pwm->hwpwm));
/* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */
period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC);
pwm_loongson_writel(ddata, period, ATIM_ARR);
pwm_loongson_writel(ddata, 0, ATIM_CNT);
ddata->lss.ccr_reg[pwm->hwpwm % 4] = duty;
ddata->lss.arr_reg = period;
return 0;
}
static int ls_pwm_atim_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int ret;
u64 period, duty_cycle;
bool enabled = pwm->state.enabled;
if (!state->enabled) {
if (enabled)
ls_pwm_atim_disable(chip, pwm);
return 0;
}
ret = ls_pwm_atim_set_polarity(chip, pwm, state->polarity);
if (ret)
return ret;
period = min(state->period, NSEC_PER_SEC);
duty_cycle = min(state->duty_cycle, NSEC_PER_SEC);
ret = ls_pwm_atim_config(chip, pwm, state->duty_cycle, state->period);
if (ret)
return ret;
if (!enabled && state->enabled)
ret = ls_pwm_atim_enable(chip, pwm);
return ret;
}
static int ls_pwm_atim_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
u32 arr_reg, ccr_reg, ccer_reg;
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
arr_reg = pwm_loongson_readl(ddata, ATIM_ARR);
ccr_reg = pwm_loongson_readl(ddata, ATIM_CCR(pwm->hwpwm));
ccer_reg = pwm_loongson_readl(ddata, ATIM_CCER);
/* duty & period have a max of 2^32, so we can't overflow */
state->duty_cycle = DIV64_U64_ROUND_UP((u64)ccr_reg * NSEC_PER_SEC,
ddata->clk_rate);
state->period = DIV64_U64_ROUND_UP((u64)arr_reg * NSEC_PER_SEC,
ddata->clk_rate);
state->polarity = (ccer_reg & CCER_CCnP(pwm->hwpwm)) ?
PWM_POLARITY_INVERSED :
PWM_POLARITY_NORMAL;
state->enabled = (ccer_reg & CCER_CCnE(pwm->hwpwm)) ? true : false;
return 0;
}
static const struct pwm_ops ls_pwm_atim_ops = {
.apply = ls_pwm_atim_apply,
.get_state = ls_pwm_atim_get_state,
};
static int ls_pwm_atim_probe(struct platform_device *pdev)
{
int ret;
struct pwm_chip *chip;
struct pwm_loongson_ddata *ddata;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
u32 of_clk_freq = 0;
chip = devm_pwmchip_alloc(dev, 8, sizeof(*ddata));
if (IS_ERR(chip))
return PTR_ERR(chip);
ddata = to_pwm_loongson_ddata(chip);
ddata->lss.en_mark = 0b00000000;
ddata->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ddata->base))
return PTR_ERR(ddata->base);
ddata->clk = devm_clk_get_optional_enabled(dev, NULL);
if (IS_ERR(ddata->clk))
return dev_err_probe(dev, PTR_ERR(ddata->clk),
"failed to get pwm clock\n");
ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT;
if (ddata->clk) {
ret = devm_clk_rate_exclusive_get(dev, ddata->clk);
if (ret)
return ret;
ddata->clk_rate = clk_get_rate(ddata->clk);
} else {
#ifdef CONFIG_OF
if (!of_property_read_u32(np, "clock-frequency", &of_clk_freq))
ddata->clk_rate = of_clk_freq;
#endif
}
/* Explicitly initialize the CCER register */
pwm_loongson_writel(ddata, 0, ATIM_CCER);
/* Additional configuration for ATIM */
pwm_loongson_writel(ddata, EGR_UG, ATIM_EGR);
pwm_loongson_writel(ddata, BDTR_MOE, ATIM_BDTR);
chip->ops = &ls_pwm_atim_ops;
chip->atomic = true;
dev_set_drvdata(dev, chip);
ret = devm_pwmchip_add(dev, chip);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to add PWM chip\n");
return 0;
}
static int ls_pwm_atim_suspend(struct device *dev)
{
struct pwm_chip *chip = dev_get_drvdata(dev);
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
ddata->lss.ccer_reg = pwm_loongson_readl(ddata, ATIM_CCER);
ddata->lss.ccr_reg[0] = pwm_loongson_readl(ddata, ATIM_CCR(0));
ddata->lss.ccr_reg[1] = pwm_loongson_readl(ddata, ATIM_CCR(1));
ddata->lss.ccr_reg[2] = pwm_loongson_readl(ddata, ATIM_CCR(2));
ddata->lss.ccr_reg[3] = pwm_loongson_readl(ddata, ATIM_CCR(3));
ddata->lss.arr_reg = pwm_loongson_readl(ddata, ATIM_ARR);
clk_disable_unprepare(ddata->clk);
return 0;
}
static int ls_pwm_atim_resume(struct device *dev)
{
int ret;
struct pwm_chip *chip = dev_get_drvdata(dev);
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
ret = clk_prepare_enable(ddata->clk);
if (ret)
return ret;
pwm_loongson_writel(ddata, ddata->lss.ccer_reg, ATIM_CCER);
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[0], ATIM_CCR(0));
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[1], ATIM_CCR(1));
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[2], ATIM_CCR(2));
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[3], ATIM_CCR(3));
pwm_loongson_writel(ddata, ddata->lss.arr_reg, ATIM_ARR);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(ls_pwm_atim_pm_ops, ls_pwm_atim_suspend,
ls_pwm_atim_resume);
static struct of_device_id ls_pwm_atim_id_table[] = {
{ .compatible = "loongson,ls2k-pwm-atim" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, ls_pwm_atim_id_table);
static struct platform_driver ls_pwm_atim_driver = {
.driver = {
.name = "ls-pwm-atim",
.pm = pm_ptr(&ls_pwm_atim_pm_ops),
.of_match_table = of_match_ptr(ls_pwm_atim_id_table),
},
.probe = ls_pwm_atim_probe,
};
module_platform_driver(ls_pwm_atim_driver);
MODULE_DESCRIPTION("Loongson Atimer Pwm Driver");
MODULE_AUTHOR("Ilikara <3435193369@qq.com>");
MODULE_LICENSE("GPL");
-367
View File
@@ -1,367 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 by ilikara 3435193369@qq.com.
*
* Loongson GTIM PWM driver
*
* For Loongson's GTIM IP block documentation please refer Chapter 19 of
* Reference Manual: https://loongson.cn/uploads/images/2024120211205269050.%E9%BE%99%E8%8A%AF2K0300%E5%A4%84%E7%90%86%E5%99%A8%E7%94%A8%E6%88%B7%E6%89%8B%E5%86%8C_V1.0.pdf
*
* Author: Ilikara <3435193369@qq.com>
*
* Limitations:
* - GTIM_CNT will be set to 0 when ls_pwm_gtim_config is called.
* - All the 4 channels share the same period.
*/
#include <linux/acpi.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/units.h>
#include <linux/delay.h>
/* counter offest */
#define GTIM_CR1 0x00
#define GTIM_CR2 0x04
#define GTIM_SMCR 0x08
#define GTIM_DIER 0x0C
#define GTIM_SR 0x10
#define GTIM_EGR 0x14
#define GTIM_CCMR(n) 0x18 + ((n) / 2) * 0x04
#define GTIM_CCER 0x20
#define GTIM_CNT 0x24
#define GTIM_PSC 0x28
#define GTIM_ARR 0x2C
#define GTIM_CCR(n) 0x34 + (n) * 0x04
#define GTIM_INSTA 0x50
/* CR1 each bit */
#define CR1_CEN BIT(0)
/* EGR each bit */
#define EGR_UG BIT(0)
/* CCMR each bit */
#define CCMR_CCnS(n) BIT((n) % 2 * 8 + 0)
#define CCMR_OCnFE(n) BIT((n) % 2 * 8 + 2)
#define CCMR_OCnPE(n) BIT((n) % 2 * 8 + 3)
#define CCMR_OCnM(n) BIT((n) % 2 * 8 + 4)
#define CCMR_OCnCE(n) BIT((n) % 2 * 8 + 7)
/* CCER each bit */
#define CCER_CCnE(n) BIT((n) * 4 + 0)
#define CCER_CCnP(n) BIT((n) * 4 + 1)
/* default input clk frequency for the ACPI case */
#define LOONGSON_PWM_FREQ_DEFAULT 50000000 /* Hz */
struct pwm_loongson_suspend_store {
u32 arr_reg;
u32 ccr_reg[4];
u32 ccer_reg;
u32 en_mark;
};
struct pwm_loongson_ddata {
struct clk *clk;
void __iomem *base;
u64 clk_rate;
struct pwm_loongson_suspend_store lss;
};
static inline struct pwm_loongson_ddata *
to_pwm_loongson_ddata(struct pwm_chip *chip)
{
return pwmchip_get_drvdata(chip);
}
static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata,
u32 offset)
{
return readl(ddata->base + offset);
}
static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata,
u32 val, u32 offset)
{
writel(val, ddata->base + offset);
}
static int ls_pwm_gtim_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
u16 val;
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
val = pwm_loongson_readl(ddata, GTIM_CCER);
if (polarity == PWM_POLARITY_INVERSED)
/* Duty cycle defines LOW period of PWM */
val &= ~CCER_CCnP(pwm->hwpwm);
else
/* Duty cycle defines HIGH period of PWM */
val |= CCER_CCnP(pwm->hwpwm);
pwm_loongson_writel(ddata, val, GTIM_CCER);
return 0;
}
static void ls_pwm_gtim_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
u32 val;
if (pwm->state.polarity == PWM_POLARITY_NORMAL)
pwm_loongson_writel(ddata, ddata->lss.arr_reg,
GTIM_CCR(pwm->hwpwm));
else if (pwm->state.polarity == PWM_POLARITY_INVERSED)
pwm_loongson_writel(ddata, 0, GTIM_CCR(pwm->hwpwm));
val = pwm_loongson_readl(ddata, GTIM_CCMR(pwm->hwpwm));
val &= ~(0b111 * CCMR_OCnM(pwm->hwpwm));
pwm_loongson_writel(ddata, val, GTIM_CCMR(pwm->hwpwm));
val = pwm_loongson_readl(ddata, GTIM_CCER);
val &= ~CCER_CCnE(pwm->hwpwm);
pwm_loongson_writel(ddata, val, GTIM_CCER);
ddata->lss.en_mark &= ~BIT(pwm->hwpwm);
if (ddata->lss.en_mark == 0b0000) {
val = pwm_loongson_readl(ddata, GTIM_CR1);
val &= ~CR1_CEN;
pwm_loongson_writel(ddata, val, GTIM_CR1);
pwm_loongson_writel(ddata, 0x0, GTIM_CNT);
}
}
static int ls_pwm_gtim_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
u32 val;
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[pwm->hwpwm % 4],
GTIM_CCR(pwm->hwpwm));
pwm_loongson_writel(ddata, ddata->lss.arr_reg, GTIM_ARR);
// todo:类似config函数中的特殊处理
val = pwm_loongson_readl(ddata, GTIM_CCMR(pwm->hwpwm));
val |= 0b111 * CCMR_OCnM(pwm->hwpwm);
pwm_loongson_writel(ddata, val, GTIM_CCMR(pwm->hwpwm));
val = pwm_loongson_readl(ddata, GTIM_CCER);
val |= CCER_CCnE(pwm->hwpwm);
pwm_loongson_writel(ddata, val, GTIM_CCER);
if (ddata->lss.en_mark == 0b0000) {
pwm_loongson_writel(ddata, 0x0, GTIM_CNT);
val = pwm_loongson_readl(ddata, GTIM_CR1);
val |= CR1_CEN;
pwm_loongson_writel(ddata, val, GTIM_CR1);
}
ddata->lss.en_mark |= BIT(pwm->hwpwm);
return 0;
}
static int ls_pwm_gtim_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
u32 duty, period;
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
/* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */
duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC);
pwm_loongson_writel(ddata, duty, GTIM_CCR(pwm->hwpwm));
/* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */
period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC);
pwm_loongson_writel(ddata, period, GTIM_ARR);
pwm_loongson_writel(ddata, 0, GTIM_CNT);
ddata->lss.ccr_reg[pwm->hwpwm] = duty;
ddata->lss.arr_reg = period;
return 0;
}
static int ls_pwm_gtim_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int ret;
u64 period, duty_cycle;
bool enabled = pwm->state.enabled;
if (!state->enabled) {
if (enabled)
ls_pwm_gtim_disable(chip, pwm);
return 0;
}
ret = ls_pwm_gtim_set_polarity(chip, pwm, state->polarity);
if (ret)
return ret;
period = min(state->period, NSEC_PER_SEC);
duty_cycle = min(state->duty_cycle, NSEC_PER_SEC);
ret = ls_pwm_gtim_config(chip, pwm, state->duty_cycle, state->period);
if (ret)
return ret;
if (!enabled && state->enabled)
ret = ls_pwm_gtim_enable(chip, pwm);
return ret;
}
static int ls_pwm_gtim_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
u32 arr_reg, ccr_reg, ccer_reg;
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
arr_reg = pwm_loongson_readl(ddata, GTIM_ARR);
ccr_reg = pwm_loongson_readl(ddata, GTIM_CCR(pwm->hwpwm));
ccer_reg = pwm_loongson_readl(ddata, GTIM_CCER);
/* duty & period have a max of 2^32, so we can't overflow */
state->duty_cycle = DIV64_U64_ROUND_UP((u64)ccr_reg * NSEC_PER_SEC,
ddata->clk_rate);
state->period = DIV64_U64_ROUND_UP((u64)arr_reg * NSEC_PER_SEC,
ddata->clk_rate);
state->polarity = (ccer_reg & CCER_CCnP(pwm->hwpwm)) ?
PWM_POLARITY_INVERSED :
PWM_POLARITY_NORMAL;
state->enabled = (ccer_reg & CCER_CCnE(pwm->hwpwm)) ? true : false;
return 0;
}
static const struct pwm_ops ls_pwm_gtim_ops = {
.apply = ls_pwm_gtim_apply,
.get_state = ls_pwm_gtim_get_state,
};
static int ls_pwm_gtim_probe(struct platform_device *pdev)
{
int ret;
struct pwm_chip *chip;
struct pwm_loongson_ddata *ddata;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
u32 of_clk_freq = 0;
chip = devm_pwmchip_alloc(dev, 4, sizeof(*ddata));
if (IS_ERR(chip))
return PTR_ERR(chip);
ddata = to_pwm_loongson_ddata(chip);
ddata->lss.en_mark = 0b00000000;
ddata->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ddata->base))
return PTR_ERR(ddata->base);
ddata->clk = devm_clk_get_optional_enabled(dev, NULL);
if (IS_ERR(ddata->clk))
return dev_err_probe(dev, PTR_ERR(ddata->clk),
"failed to get pwm clock\n");
ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT;
if (ddata->clk) {
ret = devm_clk_rate_exclusive_get(dev, ddata->clk);
if (ret)
return ret;
ddata->clk_rate = clk_get_rate(ddata->clk);
} else {
#ifdef CONFIG_OF
if (!of_property_read_u32(np, "clock-frequency", &of_clk_freq))
ddata->clk_rate = of_clk_freq;
#endif
}
/* Explicitly initialize the CCER register */
pwm_loongson_writel(ddata, 0, GTIM_CCER);
/* Additional configuration for GTIM */
pwm_loongson_writel(ddata, EGR_UG, GTIM_EGR);
chip->ops = &ls_pwm_gtim_ops;
chip->atomic = true;
dev_set_drvdata(dev, chip);
ret = devm_pwmchip_add(dev, chip);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to add PWM chip\n");
return 0;
}
static int ls_pwm_gtim_suspend(struct device *dev)
{
struct pwm_chip *chip = dev_get_drvdata(dev);
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
ddata->lss.ccer_reg = pwm_loongson_readl(ddata, GTIM_CCER);
ddata->lss.ccr_reg[0] = pwm_loongson_readl(ddata, GTIM_CCR(0));
ddata->lss.ccr_reg[1] = pwm_loongson_readl(ddata, GTIM_CCR(1));
ddata->lss.ccr_reg[2] = pwm_loongson_readl(ddata, GTIM_CCR(2));
ddata->lss.ccr_reg[3] = pwm_loongson_readl(ddata, GTIM_CCR(3));
ddata->lss.arr_reg = pwm_loongson_readl(ddata, GTIM_ARR);
clk_disable_unprepare(ddata->clk);
return 0;
}
static int ls_pwm_gtim_resume(struct device *dev)
{
int ret;
struct pwm_chip *chip = dev_get_drvdata(dev);
struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip);
ret = clk_prepare_enable(ddata->clk);
if (ret)
return ret;
pwm_loongson_writel(ddata, ddata->lss.ccer_reg, GTIM_CCER);
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[0], GTIM_CCR(0));
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[1], GTIM_CCR(1));
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[2], GTIM_CCR(2));
pwm_loongson_writel(ddata, ddata->lss.ccr_reg[3], GTIM_CCR(3));
pwm_loongson_writel(ddata, ddata->lss.arr_reg, GTIM_ARR);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(ls_pwm_gtim_pm_ops, ls_pwm_gtim_suspend,
ls_pwm_gtim_resume);
static struct of_device_id ls_pwm_gtim_id_table[] = {
{ .compatible = "loongson,ls2k-pwm-gtim" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, ls_pwm_gtim_id_table);
static struct platform_driver ls_pwm_gtim_driver = {
.driver = {
.name = "ls-pwm-gtim",
.pm = pm_ptr(&ls_pwm_gtim_pm_ops),
.of_match_table = of_match_ptr(ls_pwm_gtim_id_table),
},
.probe = ls_pwm_gtim_probe,
};
module_platform_driver(ls_pwm_gtim_driver);
MODULE_DESCRIPTION("Loongson Gtimer Pwm Driver");
MODULE_AUTHOR("Ilikara <3435193369@qq.com>");
MODULE_LICENSE("GPL");
+772
View File
@@ -0,0 +1,772 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics 2016
*
* Author: Gerald Baeza <gerald.baeza@st.com>
*
* Inspired by timer-stm32.c from Maxime Coquelin
* pwm-atmel.c from Bo Shen
*
* Copyright (C) 2025 Ilikara <3435193369@qq.com>
* Modified by: Ilikara <3435193369@qq.com>
*/
#include <linux/bitfield.h>
#include <linux/mfd/loongson-timers.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#define CCMR_CHANNEL_SHIFT 8
#define CCMR_CHANNEL_MASK 0xFF
#define MAX_BREAKINPUT 2
struct loongson_breakinput {
u32 index;
u32 level;
u32 filter;
};
struct loongson_pwm {
struct mutex lock; /* protect pwm config/enable */
struct clk *clk;
struct regmap *regmap;
u32 max_arr;
bool have_complementary_output;
struct loongson_breakinput breakinputs[MAX_BREAKINPUT];
unsigned int num_breakinputs;
u32 capture[6] ____cacheline_aligned; /* DMA'able buffer */
};
static inline struct loongson_pwm *to_loongson_pwm_dev(struct pwm_chip *chip)
{
return pwmchip_get_drvdata(chip);
}
static u32 active_channels(struct loongson_pwm *dev)
{
u32 ccer;
regmap_read(dev->regmap, TIM_CCER, &ccer);
return ccer & TIM_CCER_CCXE;
}
#define TIM_CCER_CC12P (TIM_CCER_CC1P | TIM_CCER_CC2P)
#define TIM_CCER_CC12E (TIM_CCER_CC1E | TIM_CCER_CC2E)
#define TIM_CCER_CC34P (TIM_CCER_CC3P | TIM_CCER_CC4P)
#define TIM_CCER_CC34E (TIM_CCER_CC3E | TIM_CCER_CC4E)
/*
* Capture using PWM input mode:
* ___ ___
* TI[1, 2, 3 or 4]: ........._| |________|
* ^0 ^1 ^2
* . . .
* . . XXXXX
* . . XXXXX |
* . XXXXX . |
* XXXXX . . |
* COUNTER: ______XXXXX . . . |_XXX
* start^ . . . ^stop
* . . . .
* v v . v
* v
* CCR1/CCR3: tx..........t0...........t2
* CCR2/CCR4: tx..............t1.........
*
* DMA burst transfer: | |
* v v
* DMA buffer: { t0, tx } { t2, t1 }
* DMA done: ^
*
* 0: IC1/3 snapchot on rising edge: counter value -> CCR1/CCR3
* + DMA transfer CCR[1/3] & CCR[2/4] values (t0, tx: doesn't care)
* 1: IC2/4 snapchot on falling edge: counter value -> CCR2/CCR4
* 2: IC1/3 snapchot on rising edge: counter value -> CCR1/CCR3
* + DMA transfer CCR[1/3] & CCR[2/4] values (t2, t1)
*
* DMA done, compute:
* - Period = t2 - t0
* - Duty cycle = t1 - t0
*/
static int loongson_pwm_raw_capture(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long tmo_ms, u32 *raw_prd,
u32 *raw_dty)
{
struct loongson_pwm *priv = to_loongson_pwm_dev(chip);
struct device *parent = pwmchip_parent(chip)->parent;
enum loongson_timers_dmas dma_id;
u32 ccen, ccr;
int ret;
u32 capture[4];
/* Ensure registers have been updated, enable counter and capture */
regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG);
regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
/* Use cc1 or cc3 DMA resp for PWM input channels 1 & 2 or 3 & 4 */
dma_id = pwm->hwpwm < 2 ? LS_TIMERS_DMA_CH1 : LS_TIMERS_DMA_CH3;
ccen = pwm->hwpwm < 2 ? TIM_CCER_CC12E : TIM_CCER_CC34E;
ccr = pwm->hwpwm < 2 ? TIM_CCR1 : TIM_CCR3;
regmap_set_bits(priv->regmap, TIM_CCER, ccen);
/*
* Timer DMA burst mode. Request 2 registers, 2 bursts, to get both
* CCR1 & CCR2 (or CCR3 & CCR4) on each capture event.
* We'll get two capture snapchots: { CCR1, CCR2 }, { CCR1, CCR2 }
* or { CCR3, CCR4 }, { CCR3, CCR4 }
*/
ret = loongson_timers_dma_burst_read(parent, priv->capture, dma_id, ccr, 2,
3, tmo_ms);
if (ret)
goto stop;
dev_dbg(parent, "Capture: %u %u | %u %u\n", priv->capture[1],
priv->capture[2], priv->capture[4], priv->capture[5]);
capture[0] = priv->capture[1];
capture[1] = priv->capture[4];
capture[2] = priv->capture[2];
capture[3] = priv->capture[5];
/* Period: t2 - t0 (take care of counter overflow) */
if (capture[0] <= capture[2])
*raw_prd = capture[2] - capture[0];
else
*raw_prd = priv->max_arr - capture[0] + capture[2];
/* Duty cycle capture requires at least two capture units */
if (pwm->chip->npwm < 2)
*raw_dty = 0;
else if (capture[0] <= capture[3])
*raw_dty = capture[3] - capture[0];
else
*raw_dty = priv->max_arr - capture[0] + capture[3];
if (*raw_dty > *raw_prd) {
/*
* Race beetween PWM input and DMA: it may happen
* falling edge triggers new capture on TI2/4 before DMA
* had a chance to read CCR2/4. It means capture[1]
* contains period + duty_cycle. So, subtract period.
*/
*raw_dty -= *raw_prd;
}
stop:
regmap_clear_bits(priv->regmap, TIM_CCER, ccen);
regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
return ret;
}
static int loongson_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_capture *result, unsigned long tmo_ms)
{
struct loongson_pwm *priv = to_loongson_pwm_dev(chip);
unsigned long long prd, div, dty;
unsigned long rate;
unsigned int psc = 0, icpsc, scale;
u32 raw_prd = 0, raw_dty = 0;
int ret = 0;
mutex_lock(&priv->lock);
if (active_channels(priv)) {
ret = -EBUSY;
goto unlock;
}
ret = clk_enable(priv->clk);
if (ret) {
dev_err(pwmchip_parent(chip), "failed to enable counter clock\n");
goto unlock;
}
rate = clk_get_rate(priv->clk);
if (!rate) {
ret = -EINVAL;
goto clk_dis;
}
/* prescaler: fit timeout window provided by upper layer */
div = (unsigned long long)rate * (unsigned long long)tmo_ms;
do_div(div, MSEC_PER_SEC);
prd = div;
while ((div > priv->max_arr) && (psc < MAX_TIM_PSC)) {
psc++;
div = prd;
do_div(div, psc + 1);
}
regmap_write(priv->regmap, TIM_ARR, priv->max_arr);
regmap_write(priv->regmap, TIM_PSC, psc);
/* Reset input selector to its default input and disable slave mode */
regmap_write(priv->regmap, TIM_SMCR, 0x0);
/* Map TI1 or TI2 PWM input to IC1 & IC2 (or TI3/4 to IC3 & IC4) */
regmap_update_bits(priv->regmap,
pwm->hwpwm < 2 ? TIM_CCMR1 : TIM_CCMR2,
TIM_CCMR_CC1S | TIM_CCMR_CC2S, pwm->hwpwm & 0x1 ?
TIM_CCMR_CC1S_TI2 | TIM_CCMR_CC2S_TI2 :
TIM_CCMR_CC1S_TI1 | TIM_CCMR_CC2S_TI1);
/* Capture period on IC1/3 rising edge, duty cycle on IC2/4 falling. */
regmap_update_bits(priv->regmap, TIM_CCER, pwm->hwpwm < 2 ?
TIM_CCER_CC12P : TIM_CCER_CC34P, pwm->hwpwm < 2 ?
TIM_CCER_CC2P : TIM_CCER_CC4P);
ret = loongson_pwm_raw_capture(chip, pwm, tmo_ms, &raw_prd, &raw_dty);
if (ret)
goto stop;
dev_dbg(pwmchip_parent(chip), "Capture: %u %u\n", raw_prd, raw_dty);
/*
* Got a capture. Try to improve accuracy at high rates:
* - decrease counter clock prescaler, scale up to max rate.
* - use input prescaler, capture once every /2 /4 or /8 edges.
*/
if (raw_prd) {
u32 max_arr = priv->max_arr - 0x1000; /* arbitrary margin */
scale = max_arr / min(max_arr, raw_prd);
} else {
scale = priv->max_arr; /* below resolution, use max scale */
}
if (psc && scale > 1) {
/* 2nd measure with new scale */
psc /= scale;
regmap_write(priv->regmap, TIM_PSC, psc);
ret = loongson_pwm_raw_capture(chip, pwm, tmo_ms, &raw_prd,
&raw_dty);
if (ret)
goto stop;
dev_dbg(pwmchip_parent(chip), "Capture: %u %u\n", raw_prd, raw_dty);
}
/* Compute intermediate period not to exceed timeout at low rates */
prd = (unsigned long long)raw_prd * (psc + 1) * NSEC_PER_SEC;
do_div(prd, rate);
for (icpsc = 0; icpsc < MAX_TIM_ICPSC ; icpsc++) {
/* input prescaler: also keep arbitrary margin */
if (raw_prd >= (priv->max_arr - 0x1000) >> (icpsc + 1))
break;
if (prd >= (tmo_ms * NSEC_PER_MSEC) >> (icpsc + 2))
break;
}
if (!icpsc)
goto done;
/* Last chance to improve period accuracy, using input prescaler */
regmap_update_bits(priv->regmap,
pwm->hwpwm < 2 ? TIM_CCMR1 : TIM_CCMR2,
TIM_CCMR_IC1PSC | TIM_CCMR_IC2PSC,
FIELD_PREP(TIM_CCMR_IC1PSC, icpsc) |
FIELD_PREP(TIM_CCMR_IC2PSC, icpsc));
ret = loongson_pwm_raw_capture(chip, pwm, tmo_ms, &raw_prd, &raw_dty);
if (ret)
goto stop;
dev_dbg(pwmchip_parent(chip), "Capture: %u %u\n", raw_prd, raw_dty);
if (raw_dty >= (raw_prd >> icpsc)) {
/*
* We may fall here using input prescaler, when input
* capture starts on high side (before falling edge).
* Example with icpsc to capture on each 4 events:
*
* start 1st capture 2nd capture
* v v v
* ___ _____ _____ _____ _____ ____
* TI1..4 |__| |__| |__| |__| |__|
* v v . . . . . v v
* icpsc1/3: . 0 . 1 . 2 . 3 . 0
* icpsc2/4: 0 1 2 3 0
* v v v v
* CCR1/3 ......t0..............................t2
* CCR2/4 ..t1..............................t1'...
* . . .
* Capture0: .<----------------------------->.
* Capture1: .<-------------------------->. .
* . . .
* Period: .<------> . .
* Low side: .<>.
*
* Result:
* - Period = Capture0 / icpsc
* - Duty = Period - Low side = Period - (Capture0 - Capture1)
*/
raw_dty = (raw_prd >> icpsc) - (raw_prd - raw_dty);
}
done:
prd = (unsigned long long)raw_prd * (psc + 1) * NSEC_PER_SEC;
result->period = DIV_ROUND_UP_ULL(prd, rate << icpsc);
dty = (unsigned long long)raw_dty * (psc + 1) * NSEC_PER_SEC;
result->duty_cycle = DIV_ROUND_UP_ULL(dty, rate);
stop:
regmap_write(priv->regmap, TIM_CCER, 0);
regmap_write(priv->regmap, pwm->hwpwm < 2 ? TIM_CCMR1 : TIM_CCMR2, 0);
regmap_write(priv->regmap, TIM_PSC, 0);
clk_dis:
clk_disable(priv->clk);
unlock:
mutex_unlock(&priv->lock);
return ret;
}
static int loongson_pwm_config(struct loongson_pwm *priv, unsigned int ch,
u64 duty_ns, u64 period_ns)
{
unsigned long long prd, dty;
unsigned long long prescaler;
u32 ccmr, mask, shift;
/*
* .probe() asserted that clk_get_rate() is not bigger than 1 GHz, so
* the calculations here won't overflow.
* First we need to find the minimal value for prescaler such that
*
* period_ns * clkrate
* ------------------------------ < max_arr + 1
* NSEC_PER_SEC * (prescaler + 1)
*
* This equation is equivalent to
*
* period_ns * clkrate
* ---------------------------- < prescaler + 1
* NSEC_PER_SEC * (max_arr + 1)
*
* Using integer division and knowing that the right hand side is
* integer, this is further equivalent to
*
* (period_ns * clkrate) // (NSEC_PER_SEC * (max_arr + 1)) ≤ prescaler
*/
prescaler = mul_u64_u64_div_u64(period_ns, clk_get_rate(priv->clk),
(u64)NSEC_PER_SEC * ((u64)priv->max_arr + 1));
if (prescaler > MAX_TIM_PSC)
return -EINVAL;
prd = mul_u64_u64_div_u64(period_ns, clk_get_rate(priv->clk),
(u64)NSEC_PER_SEC * (prescaler + 1));
if (!prd)
return -EINVAL;
/*
* All channels share the same prescaler and counter so when two
* channels are active at the same time we can't change them
*/
if (active_channels(priv) & ~(1 << ch * 4)) {
u32 psc, arr;
regmap_read(priv->regmap, TIM_PSC, &psc);
regmap_read(priv->regmap, TIM_ARR, &arr);
if ((psc != prescaler) || (arr != prd - 1))
return -EBUSY;
}
regmap_write(priv->regmap, TIM_PSC, prescaler);
regmap_write(priv->regmap, TIM_ARR, prd - 1);
regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE);
/* Calculate the duty cycles */
dty = mul_u64_u64_div_u64(duty_ns, clk_get_rate(priv->clk),
(u64)NSEC_PER_SEC * (prescaler + 1));
regmap_write(priv->regmap, TIM_CCRx(ch + 1), dty);
/* Configure output mode */
shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT;
ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
mask = CCMR_CHANNEL_MASK << shift;
if (ch < 2)
regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr);
else
regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr);
regmap_set_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE);
return 0;
}
static int loongson_pwm_set_polarity(struct loongson_pwm *priv, unsigned int ch,
enum pwm_polarity polarity)
{
u32 mask;
mask = TIM_CCER_CCxP(ch + 1);
if (priv->have_complementary_output)
mask |= TIM_CCER_CCxNP(ch + 1);
regmap_update_bits(priv->regmap, TIM_CCER, mask,
polarity == PWM_POLARITY_NORMAL ? 0 : mask);
return 0;
}
static int loongson_pwm_enable(struct loongson_pwm *priv, unsigned int ch)
{
u32 mask;
int ret;
ret = clk_enable(priv->clk);
if (ret)
return ret;
/* Enable channel */
mask = TIM_CCER_CCxE(ch + 1);
if (priv->have_complementary_output)
mask |= TIM_CCER_CCxNE(ch + 1);
regmap_set_bits(priv->regmap, TIM_CCER, mask);
/* Make sure that registers are updated */
regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG);
/* Enable controller */
regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
return 0;
}
static void loongson_pwm_disable(struct loongson_pwm *priv, unsigned int ch)
{
u32 mask;
/* Disable channel */
mask = TIM_CCER_CCxE(ch + 1);
if (priv->have_complementary_output)
mask |= TIM_CCER_CCxNE(ch + 1);
regmap_clear_bits(priv->regmap, TIM_CCER, mask);
/* When all channels are disabled, we can disable the controller */
if (!active_channels(priv))
regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
clk_disable(priv->clk);
}
static int loongson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
bool enabled;
struct loongson_pwm *priv = to_loongson_pwm_dev(chip);
int ret;
enabled = pwm->state.enabled;
if (!state->enabled) {
if (enabled)
loongson_pwm_disable(priv, pwm->hwpwm);
return 0;
}
if (state->polarity != pwm->state.polarity)
loongson_pwm_set_polarity(priv, pwm->hwpwm, state->polarity);
ret = loongson_pwm_config(priv, pwm->hwpwm,
state->duty_cycle, state->period);
if (ret)
return ret;
if (!enabled && state->enabled)
ret = loongson_pwm_enable(priv, pwm->hwpwm);
return ret;
}
static int loongson_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct loongson_pwm *priv = to_loongson_pwm_dev(chip);
int ret;
/* protect common prescaler for all active channels */
mutex_lock(&priv->lock);
ret = loongson_pwm_apply(chip, pwm, state);
mutex_unlock(&priv->lock);
return ret;
}
static int loongson_pwm_get_state(struct pwm_chip *chip,
struct pwm_device *pwm, struct pwm_state *state)
{
struct loongson_pwm *priv = to_loongson_pwm_dev(chip);
int ch = pwm->hwpwm;
unsigned long rate;
u32 ccer, psc, arr, ccr;
u64 dty, prd;
int ret;
mutex_lock(&priv->lock);
ret = regmap_read(priv->regmap, TIM_CCER, &ccer);
if (ret)
goto out;
state->enabled = ccer & TIM_CCER_CCxE(ch + 1);
state->polarity = (ccer & TIM_CCER_CCxP(ch + 1)) ?
PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
ret = regmap_read(priv->regmap, TIM_PSC, &psc);
if (ret)
goto out;
ret = regmap_read(priv->regmap, TIM_ARR, &arr);
if (ret)
goto out;
ret = regmap_read(priv->regmap, TIM_CCRx(ch + 1), &ccr);
if (ret)
goto out;
rate = clk_get_rate(priv->clk);
prd = (u64)NSEC_PER_SEC * (psc + 1) * (arr + 1);
state->period = DIV_ROUND_UP_ULL(prd, rate);
dty = (u64)NSEC_PER_SEC * (psc + 1) * ccr;
state->duty_cycle = DIV_ROUND_UP_ULL(dty, rate);
out:
mutex_unlock(&priv->lock);
return ret;
}
static const struct pwm_ops ls_timer_pwm_ops = {
.apply = loongson_pwm_apply_locked,
.get_state = loongson_pwm_get_state,
.capture = IS_ENABLED(CONFIG_DMA_ENGINE) ? loongson_pwm_capture : NULL,
};
static int loongson_pwm_set_breakinput(struct loongson_pwm *priv,
const struct loongson_breakinput *bi)
{
u32 shift = TIM_BDTR_BKF_SHIFT(bi->index);
u32 bke = TIM_BDTR_BKE(bi->index);
u32 bkp = TIM_BDTR_BKP(bi->index);
u32 bkf = TIM_BDTR_BKF(bi->index);
u32 mask = bkf | bkp | bke;
u32 bdtr;
bdtr = (bi->filter & TIM_BDTR_BKF_MASK) << shift | bke;
if (bi->level)
bdtr |= bkp;
regmap_update_bits(priv->regmap, TIM_BDTR, mask, bdtr);
regmap_read(priv->regmap, TIM_BDTR, &bdtr);
return (bdtr & bke) ? 0 : -EINVAL;
}
static int loongson_pwm_apply_breakinputs(struct loongson_pwm *priv)
{
unsigned int i;
int ret;
for (i = 0; i < priv->num_breakinputs; i++) {
ret = loongson_pwm_set_breakinput(priv, &priv->breakinputs[i]);
if (ret < 0)
return ret;
}
return 0;
}
static int loongson_pwm_probe_breakinputs(struct loongson_pwm *priv,
struct device_node *np)
{
int nb, ret, array_size;
unsigned int i;
nb = of_property_count_elems_of_size(np, "loongson,breakinput",
sizeof(struct loongson_breakinput));
/*
* Because "loongson,breakinput" parameter is optional do not make probe
* failed if it doesn't exist.
*/
if (nb <= 0)
return 0;
if (nb > MAX_BREAKINPUT)
return -EINVAL;
priv->num_breakinputs = nb;
array_size = nb * sizeof(struct loongson_breakinput) / sizeof(u32);
ret = of_property_read_u32_array(np, "loongson,breakinput",
(u32 *)priv->breakinputs, array_size);
if (ret)
return ret;
for (i = 0; i < priv->num_breakinputs; i++) {
if (priv->breakinputs[i].index > 1 ||
priv->breakinputs[i].level > 1 ||
priv->breakinputs[i].filter > 15)
return -EINVAL;
}
return loongson_pwm_apply_breakinputs(priv);
}
static void loongson_pwm_detect_complementary(struct loongson_pwm *priv)
{
u32 ccer;
/*
* If complementary bit doesn't exist writing 1 will have no
* effect so we can detect it.
*/
regmap_set_bits(priv->regmap, TIM_CCER, TIM_CCER_CC1NE);
regmap_read(priv->regmap, TIM_CCER, &ccer);
regmap_clear_bits(priv->regmap, TIM_CCER, TIM_CCER_CC1NE);
priv->have_complementary_output = (ccer != 0);
}
static unsigned int loongson_pwm_detect_channels(struct regmap *regmap,
unsigned int *num_enabled)
{
u32 ccer, ccer_backup;
/*
* If channels enable bits don't exist writing 1 will have no
* effect so we can detect and count them.
*/
regmap_read(regmap, TIM_CCER, &ccer_backup);
regmap_set_bits(regmap, TIM_CCER, TIM_CCER_CCXE);
regmap_read(regmap, TIM_CCER, &ccer);
regmap_write(regmap, TIM_CCER, ccer_backup);
*num_enabled = hweight32(ccer_backup & TIM_CCER_CCXE);
return hweight32(ccer & TIM_CCER_CCXE);
}
static int loongson_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct loongson_timers *ddata = dev_get_drvdata(pdev->dev.parent);
struct pwm_chip *chip;
struct loongson_pwm *priv;
unsigned int npwm, num_enabled;
unsigned int i;
int ret;
npwm = loongson_pwm_detect_channels(ddata->regmap, &num_enabled);
chip = devm_pwmchip_alloc(dev, npwm, sizeof(*priv));
if (IS_ERR(chip))
return PTR_ERR(chip);
priv = to_loongson_pwm_dev(chip);
mutex_init(&priv->lock);
priv->regmap = ddata->regmap;
priv->clk = ddata->clk;
priv->max_arr = ddata->max_arr;
if (!priv->regmap || !priv->clk)
return dev_err_probe(dev, -EINVAL, "Failed to get %s\n",
priv->regmap ? "clk" : "regmap");
ret = loongson_pwm_probe_breakinputs(priv, np);
if (ret)
return dev_err_probe(dev, ret,
"Failed to configure breakinputs\n");
loongson_pwm_detect_complementary(priv);
ret = devm_clk_rate_exclusive_get(dev, priv->clk);
if (ret)
return dev_err_probe(dev, ret, "Failed to lock clock\n");
/*
* With the clk running with not more than 1 GHz the calculations in
* .apply() won't overflow.
*/
if (clk_get_rate(priv->clk) > 1000000000)
return dev_err_probe(dev, -EINVAL, "Clock freq too high (%lu)\n",
clk_get_rate(priv->clk));
chip->ops = &ls_timer_pwm_ops;
/* Initialize clock refcount to number of enabled PWM channels. */
for (i = 0; i < num_enabled; i++)
clk_enable(priv->clk);
ret = devm_pwmchip_add(dev, chip);
if (ret < 0)
return dev_err_probe(dev, ret,
"Failed to register pwmchip\n");
platform_set_drvdata(pdev, chip);
return 0;
}
static int loongson_pwm_suspend(struct device *dev)
{
struct pwm_chip *chip = dev_get_drvdata(dev);
struct loongson_pwm *priv = to_loongson_pwm_dev(chip);
unsigned int i;
u32 ccer, mask;
/* Look for active channels */
ccer = active_channels(priv);
for (i = 0; i < chip->npwm; i++) {
mask = TIM_CCER_CCxE(i + 1);
if (ccer & mask) {
dev_err(dev, "PWM %u still in use by consumer %s\n",
i, chip->pwms[i].label);
return -EBUSY;
}
}
return pinctrl_pm_select_sleep_state(dev);
}
static int loongson_pwm_resume(struct device *dev)
{
struct pwm_chip *chip = dev_get_drvdata(dev);
struct loongson_pwm *priv = to_loongson_pwm_dev(chip);
int ret;
ret = pinctrl_pm_select_default_state(dev);
if (ret)
return ret;
/* restore breakinput registers that may have been lost in low power */
return loongson_pwm_apply_breakinputs(priv);
}
static DEFINE_SIMPLE_DEV_PM_OPS(loongson_pwm_pm_ops, loongson_pwm_suspend, loongson_pwm_resume);
static const struct of_device_id loongson_pwm_of_match[] = {
{ .compatible = "loongson,ls2k-pwm-timer", },
{ /* end node */ },
};
MODULE_DEVICE_TABLE(of, loongson_pwm_of_match);
static struct platform_driver loongson_pwm_driver = {
.probe = loongson_pwm_probe,
.driver = {
.name = "loongson-pwm-timer",
.of_match_table = loongson_pwm_of_match,
.pm = pm_ptr(&loongson_pwm_pm_ops),
},
};
module_platform_driver(loongson_pwm_driver);
MODULE_ALIAS("platform:loongson-pwm-timer");
MODULE_DESCRIPTION("Loongson loongson2k PWM driver");
MODULE_LICENSE("GPL v2");
+185
View File
@@ -0,0 +1,185 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics 2016
* Author: Benjamin Gaignard <benjamin.gaignard@st.com>
*
* Copyright (C) 2025 Ilikara <3435193369@qq.com>
* Modified by: Ilikara <3435193369@qq.com>
*/
#ifndef _LINUX_LS_GPTIMER_H_
#define _LINUX_LS_GPTIMER_H_
#include <linux/clk.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/regmap.h>
#define TIM_CR1 0x00 /* Control Register 1 */
#define TIM_CR2 0x04 /* Control Register 2 */
#define TIM_SMCR 0x08 /* Slave mode control reg */
#define TIM_DIER 0x0C /* DMA/interrupt register */
#define TIM_SR 0x10 /* Status register */
#define TIM_EGR 0x14 /* Event Generation Reg */
#define TIM_CCMR1 0x18 /* Capt/Comp 1 Mode Reg */
#define TIM_CCMR2 0x1C /* Capt/Comp 2 Mode Reg */
#define TIM_CCER 0x20 /* Capt/Comp Enable Reg */
#define TIM_CNT 0x24 /* Counter */
#define TIM_PSC 0x28 /* Prescaler */
#define TIM_ARR 0x2c /* Auto-Reload Register */
#define TIM_CCRx(x) (0x34 + 4 * ((x) - 1)) /* Capt/Comp Register x (x ∈ {1, .. 4}) */
#define TIM_CCR1 TIM_CCRx(1) /* Capt/Comp Register 1 */
#define TIM_CCR2 TIM_CCRx(2) /* Capt/Comp Register 2 */
#define TIM_CCR3 TIM_CCRx(3) /* Capt/Comp Register 3 */
#define TIM_CCR4 TIM_CCRx(4) /* Capt/Comp Register 4 */
#define TIM_BDTR 0x44 /* Break and Dead-Time Reg */
#define TIM_CR1_CEN BIT(0) /* Counter Enable */
#define TIM_CR1_DIR BIT(4) /* Counter Direction */
#define TIM_CR1_ARPE BIT(7) /* Auto-reload Preload Ena */
#define TIM_CR2_MMS (BIT(4) | BIT(5) | BIT(6)) /* Master mode selection */
#define TIM_CR2_MMS2 GENMASK(23, 20) /* Master mode selection 2 */
#define TIM_SMCR_SMS (BIT(0) | BIT(1) | BIT(2)) /* Slave mode selection */
#define TIM_SMCR_TS (BIT(4) | BIT(5) | BIT(6)) /* Trigger selection */
#define TIM_DIER_UIE BIT(0) /* Update interrupt */
#define TIM_DIER_CCxIE(x) BIT(1 + ((x) - 1)) /* CCx Interrupt Enable (x ∈ {1, .. 4}) */
#define TIM_DIER_CC1IE TIM_DIER_CCxIE(1) /* CC1 Interrupt Enable */
#define TIM_DIER_CC2IE TIM_DIER_CCxIE(2) /* CC2 Interrupt Enable */
#define TIM_DIER_CC3IE TIM_DIER_CCxIE(3) /* CC3 Interrupt Enable */
#define TIM_DIER_CC4IE TIM_DIER_CCxIE(4) /* CC4 Interrupt Enable */
#define TIM_DIER_UDE BIT(8) /* Update DMA request Enable */
#define TIM_DIER_CCxDE(x) BIT(9 + ((x) - 1)) /* CCx DMA request Enable (x ∈ {1, .. 4}) */
#define TIM_DIER_CC1DE TIM_DIER_CCxDE(1) /* CC1 DMA request Enable */
#define TIM_DIER_CC2DE TIM_DIER_CCxDE(2) /* CC2 DMA request Enable */
#define TIM_DIER_CC3DE TIM_DIER_CCxDE(3) /* CC3 DMA request Enable */
#define TIM_DIER_CC4DE TIM_DIER_CCxDE(4) /* CC4 DMA request Enable */
#define TIM_DIER_COMDE BIT(13) /* COM DMA request Enable */
#define TIM_DIER_TDE BIT(14) /* Trigger DMA request Enable */
#define TIM_SR_UIF BIT(0) /* Update interrupt flag */
#define TIM_SR_CC_IF(x) BIT((x) + 1) /* CC1, CC2, CC3, CC4 interrupt flag */
#define TIM_EGR_UG BIT(0) /* Update Generation */
// CCMR Output
#define TIM_CCMR_PE BIT(3) /* Channel Preload Enable */
#define TIM_CCMR_M1 (BIT(6) | BIT(5)) /* Channel PWM Mode 1 */
#define TIM_CCMR_CC1S (BIT(0) | BIT(1)) /* Capture/compare 1 sel */
#define TIM_CCMR_CC2S (BIT(8) | BIT(9)) /* Capture/compare 2 sel */
#define TIM_CCMR_CC1S_TI1 BIT(0) /* IC1/IC3 selects TI1/TI3 */
#define TIM_CCMR_CC1S_TI2 BIT(1) /* IC1/IC3 selects TI2/TI4 */
#define TIM_CCMR_CC2S_TI2 BIT(8) /* IC2/IC4 selects TI2/TI4 */
#define TIM_CCMR_CC2S_TI1 BIT(9) /* IC2/IC4 selects TI1/TI3 */
#define TIM_CCMR_CC3S (BIT(0) | BIT(1)) /* Capture/compare 3 sel */
#define TIM_CCMR_CC4S (BIT(8) | BIT(9)) /* Capture/compare 4 sel */
#define TIM_CCMR_CC3S_TI3 BIT(0) /* IC3 selects TI3 */
#define TIM_CCMR_CC4S_TI4 BIT(8) /* IC4 selects TI4 */
// CCMR Input
#define TIM_CCMR_IC1PSC GENMASK(3, 2) /* Input capture 1 prescaler */
#define TIM_CCMR_IC2PSC GENMASK(11, 10) /* Input capture 2 prescaler */
#define TIM_CCER_CCxE(x) BIT(0 + 4 * ((x) - 1)) /* Capt/Comp x out Ena (x ∈ {1, .. 4}) */
#define TIM_CCER_CCxP(x) BIT(1 + 4 * ((x) - 1)) /* Capt/Comp x Polarity (x ∈ {1, .. 4}) */
#define TIM_CCER_CCxNE(x) BIT(2 + 4 * ((x) - 1)) /* Capt/Comp xN out Ena (x ∈ {1, .. 4}) */
#define TIM_CCER_CCxNP(x) BIT(3 + 4 * ((x) - 1)) /* Capt/Comp xN Polarity (x ∈ {1, .. 4}) */
#define TIM_CCER_CC1E TIM_CCER_CCxE(1) /* Capt/Comp 1 out Ena */
#define TIM_CCER_CC1P TIM_CCER_CCxP(1) /* Capt/Comp 1 Polarity */
#define TIM_CCER_CC1NE TIM_CCER_CCxNE(1) /* Capt/Comp 1N out Ena */
#define TIM_CCER_CC1NP TIM_CCER_CCxNP(1) /* Capt/Comp 1N Polarity */
#define TIM_CCER_CC2E TIM_CCER_CCxE(2) /* Capt/Comp 2 out Ena */
#define TIM_CCER_CC2P TIM_CCER_CCxP(2) /* Capt/Comp 2 Polarity */
#define TIM_CCER_CC2NE TIM_CCER_CCxNE(2) /* Capt/Comp 2N out Ena */
#define TIM_CCER_CC2NP TIM_CCER_CCxNP(2) /* Capt/Comp 2N Polarity */
#define TIM_CCER_CC3E TIM_CCER_CCxE(3) /* Capt/Comp 3 out Ena */
#define TIM_CCER_CC3P TIM_CCER_CCxP(3) /* Capt/Comp 3 Polarity */
#define TIM_CCER_CC3NE TIM_CCER_CCxNE(3) /* Capt/Comp 3N out Ena */
#define TIM_CCER_CC3NP TIM_CCER_CCxNP(3) /* Capt/Comp 3N Polarity */
#define TIM_CCER_CC4E TIM_CCER_CCxE(4) /* Capt/Comp 4 out Ena */
#define TIM_CCER_CC4P TIM_CCER_CCxP(4) /* Capt/Comp 4 Polarity */
#define TIM_CCER_CC4NE TIM_CCER_CCxNE(4) /* Capt/Comp 4N out Ena */
#define TIM_CCER_CC4NP TIM_CCER_CCxNP(4) /* Capt/Comp 4N Polarity */
#define TIM_CCER_CCXE (BIT(0) | BIT(4) | BIT(8) | BIT(12))
#define TIM_BDTR_BKE(x) BIT(12 + (x) * 12) /* Break input enable */
#define TIM_BDTR_BKP(x) BIT(13 + (x) * 12) /* Break input polarity */
#define TIM_BDTR_AOE BIT(14) /* Automatic Output Enable */
#define TIM_BDTR_MOE BIT(15) /* Main Output Enable */
#define TIM_BDTR_BKF(x) (0xf << (16 + (x) * 4))
#define MAX_TIM_PSC 0xFFFF
#define MAX_TIM_ICPSC 0x3
#define TIM_CR2_MMS_SHIFT 4
#define TIM_CR2_MMS2_SHIFT 20
#define TIM_SMCR_SMS_SLAVE_MODE_DISABLED 0 /* counts on internal clock when CEN=1 */
#define TIM_SMCR_SMS_ENCODER_MODE_1 1 /* counts TI1FP1 edges, depending on TI2FP2 level */
#define TIM_SMCR_SMS_ENCODER_MODE_2 2 /* counts TI2FP2 edges, depending on TI1FP1 level */
#define TIM_SMCR_SMS_ENCODER_MODE_3 3 /* counts on both TI1FP1 and TI2FP2 edges */
#define TIM_SMCR_TS_SHIFT 4
#define TIM_BDTR_BKF_MASK 0xF
#define TIM_BDTR_BKF_SHIFT(x) (16 + (x) * 4)
enum loongson_timers_dmas {
LS_TIMERS_DMA_CH1,
LS_TIMERS_DMA_CH2,
LS_TIMERS_DMA_CH3,
LS_TIMERS_DMA_CH4,
LS_TIMERS_DMA_UP,
LS_TIMERS_DMA_TRIG,
LS_TIMERS_DMA_COM,
LS_TIMERS_MAX_DMAS,
};
/* LOONGSON Timer may have either a unique global interrupt or 4 interrupt lines */
enum loongson_timers_irqs {
LS_TIMERS_IRQ_GLOBAL, /* global IRQ */
LS_TIMERS_MAX_IRQS,
};
/**
* struct loongson_timers_dma - LOONGSON timer DMA handling.
* @completion: end of DMA transfer completion
* @phys_base: control registers physical base address
* @lock: protect DMA access
* @chan: DMA channel in use
* @chans: DMA channels available for this timer instance
*/
struct loongson_timers_dma {
struct completion completion;
phys_addr_t phys_base;
struct mutex lock;
struct dma_chan *chan;
struct dma_chan *chans[LS_TIMERS_MAX_DMAS];
};
struct loongson_timers {
struct clk *clk;
struct regmap *regmap;
u32 max_arr;
struct loongson_timers_dma dma; /* Only to be used by the parent */
unsigned int nr_irqs;
int irq[LS_TIMERS_MAX_IRQS];
};
#if IS_REACHABLE(CONFIG_MFD_LS_TIMERS)
int loongson_timers_dma_burst_read(struct device *dev, u32 *buf,
enum loongson_timers_dmas id, u32 reg,
unsigned int num_reg, unsigned int bursts,
unsigned long tmo_ms);
#else
static inline int loongson_timers_dma_burst_read(struct device *dev, u32 *buf,
enum loongson_timers_dmas id,
u32 reg,
unsigned int num_reg,
unsigned int bursts,
unsigned long tmo_ms)
{
return -ENODEV;
}
#endif
#endif