mirror of
https://gitee.com/open-loongarch/linux-6.12.git
synced 2026-10-25 20:47:11 +08:00
!4 feat: 增加定时器(ATIM GTIM)的MFD驱动,及基于该MFD驱动的PWM驱动
Merge pull request !4 from Ilikara/feat/timer-mfd-pwm
This commit is contained in:
@@ -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 = <>im2_pin_m1>, <>im3_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 = <>im2_pin_m1>, <>im3_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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user