!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" */ /* 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 { pwm0: pwm@0x1611b000 {
compatible = "loongson,ls2k-pwm"; compatible = "loongson,ls2k-pwm";
reg = <0 0x1611b000 0 0xf>; reg = <0 0x1611b000 0 0xf>;
@@ -969,6 +945,63 @@
status = "disabled"; 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 { tsensor: tsensor@0x16001500 {
compatible = "loongson,ls2k300-thermal"; compatible = "loongson,ls2k300-thermal";
reg = <0 0x16001500 0 0x30>; 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: case DMA_MEM_TO_DEV:
dev_width = chan->dma_sconfig.dst_addr_width; dev_width = chan->dma_sconfig.dst_addr_width;
chan->chan_reg.dma_cpar = chan->dma_sconfig.dst_addr; chan->chan_reg.dma_cpar = chan->dma_sconfig.dst_addr;
dma_ccr = DMA_CCR_DIR; dma_ccr = DMA_CCR_DIR | DMA_CCR_TCIE;
break; break;
case DMA_DEV_TO_MEM: case DMA_DEV_TO_MEM:
dev_width = chan->dma_sconfig.src_addr_width; dev_width = chan->dma_sconfig.src_addr_width;
chan->chan_reg.dma_cpar = chan->dma_sconfig.src_addr; chan->chan_reg.dma_cpar = chan->dma_sconfig.src_addr;
dma_ccr = DMA_CCR_MINC; dma_ccr = DMA_CCR_MINC | DMA_CCR_TCIE;
break; break;
default: default:
return -EINVAL; return -EINVAL;
+11
View File
@@ -2151,6 +2151,17 @@ config MFD_STM32_TIMERS
for PWM and IIO Timer. This driver allow to share the for PWM and IIO Timer. This driver allow to share the
registers between the others drivers. 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 config MFD_STPMIC1
tristate "Support for STPMIC1 PMIC" tristate "Support for STPMIC1 PMIC"
depends on I2C=y && OF 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_LPTIMER) += stm32-lptimer.o
obj-$(CONFIG_MFD_STM32_TIMERS) += stm32-timers.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_MXS_LRADC) += mxs-lradc.o
obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o
obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.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 To compile this driver as a module, choose M here: the module
will be called pwm-loongson. will be called pwm-loongson.
config PWM_LOONGSON_GTIM config PWM_LOONGSON_TIMER
tristate "Loongson GTIM PWM support" tristate "Loongson Timer PWM support"
depends on MACH_LOONGSON64 || COMPILE_TEST depends on MFD_LS_TIMERS || COMPILE_TEST
depends on COMMON_CLK
help 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 To compile this driver as a module, choose M here: the module
will be called pwm-ls-gtim. will be called pwm-ls-timer.
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.
config PWM_LP3943 config PWM_LP3943
tristate "TI/National Semiconductor LP3943 PWM support" 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_JZ4740) += pwm-jz4740.o
obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o
obj-$(CONFIG_PWM_LOONGSON) += pwm-loongson.o obj-$(CONFIG_PWM_LOONGSON) += pwm-loongson.o
obj-$(CONFIG_PWM_LOONGSON_ATIM) += pwm-loongson-atim.o obj-$(CONFIG_PWM_LOONGSON_TIMER) += pwm-ls-timer.o
obj-$(CONFIG_PWM_LOONGSON_GTIM) += pwm-loongson-gtim.o
obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o
obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o
obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.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