linuxOS_D21X/source/linux-5.10/drivers/pwm/cap-artinchip.c
2024-11-29 16:33:21 +08:00

426 lines
10 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* cap driver of ArtInChip SoC
*
* Copyright (C) 2020-2023 ArtInChip Technology Co., Ltd.
* Authors: zrq <ruiqi.zheng@artinchip.com>
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/pwm.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/pm.h>
#define AIC_CAP_NAME "aic-cap"
#define AIC_CAP_CH_NUM 3
/* GLB register */
#define GLB_CLK_CTL 0x00
#define GLB_CLK_CTL_CAP_EN(n) (BIT(16) << (n))
/* CAP register */
#define CAP_CNT_V(n) ((((n) & 0x7) << 8) + 0x00)
#define CAP_CNT_PHV(n) ((((n) & 0x7) << 8) + 0x04)
#define CAP_CNT_PRDV(n) ((((n) & 0x7) << 8) + 0x08)
#define CAP_CNT_CMPV(n) ((((n) & 0x7) << 8) + 0x0c)
#define CAP_CNT_PRDV_SH(n) ((((n) & 0x7) << 8) + 0x10)
#define CAP_CNT_CMPV_SH(n) ((((n) & 0x7) << 8) + 0x14)
#define CAP_CONF1(n) ((((n) & 0x7) << 8) + 0x18)
#define CAP_CONF2(n) ((((n) & 0x7) << 8) + 0x1c)
#define CAP_INT_EN(n) ((((n) & 0x7) << 8) + 0x20)
#define CAP_FLG(n) ((((n) & 0x7) << 8) + 0x24)
#define CAP_FLG_CLR(n) ((((n) & 0x7) << 8) + 0x28)
#define CAP_SW_FRC(n) ((((n) & 0x7) << 8) + 0x2c)
#define CAP_IN_FLT(n) ((((n) & 0x7) << 8) + 0x30)
#define CAP_IN_SRC(n) ((((n) & 0x7) << 8) + 0x34)
#define CAP_VER(n) ((((n) & 0x7) << 8) + 0xfc)
/* CAP_CONF1 */
#define CAP_EVNT0_POL_MASK GENMASK(0, 0)
#define CAP_EVNT0_POL_SHIFT 0
#define CAP_EVNT0_RST_MASK GENMASK(1, 1)
#define CAP_EVNT0_RST_SHIFT 1
#define CAP_EVNT1_POL_MASK GENMASK(2, 2)
#define CAP_EVNT1_POL_SHIFT 2
#define CAP_EVNT2_POL_MASK GENMASK(4, 4)
#define CAP_EVNT2_POL_SHIFT 4
#define CAP_EVNT3_POL_MASK GENMASK(6, 6)
#define CAP_EVNT3_POL_SHIFT 6
#define CAP_REG_LD_EN_MASK GENMASK(8, 8)
#define CAP_REG_LD_EN_SHIFT 8
/* CAP_INT_EN */
#define CAP_EVNT3_INT_EN_MASK GENMASK(4, 4)
#define CAP_EVNT3_INT_EN_SHIFT 4
/* CAP_CONF2 */
#define CAP_CNT_EN_MASK GENMASK(4, 4)
#define CAP_CNT_EN_SHIFT 4
#define CAP_EVENT3_FLG BIT(4)
struct aic_cap_data {
bool available;
u32 val[2];
u32 flag;
struct mutex lock;
wait_queue_head_t wait;
};
struct aic_cap_chip {
struct pwm_chip chip;
struct attribute_group attrs;
struct aic_cap_data data[AIC_CAP_CH_NUM];
unsigned long pll_rate;
unsigned long clk_rate;
void __iomem *regs;
void __iomem *glb_regs;
struct clk *clk;
struct reset_control *rst;
u32 irq;
};
static void cap_reg_enable(void __iomem *base, int offset, int bit, int enable)
{
int tmp;
tmp = readl(base + offset);
tmp &= ~bit;
if (enable)
tmp |= bit;
writel(tmp, base + offset);
}
static inline void cap_reg_config(void __iomem *base, u32 offset, u32 mask, u32 shift, u32 val)
{
u32 cur;
cur = readl((base + offset));
setbits(val, mask, shift, cur);
writel(cur, (base + offset));
}
static inline struct aic_cap_chip *to_aic_cap_dev(struct pwm_chip *chip)
{
return container_of(chip, struct aic_cap_chip, chip);
}
static int aic_cap_init(struct aic_cap_chip *cap, u32 ch)
{
/* action configuration */
cap_reg_config(cap->regs, CAP_CONF1(ch), CAP_REG_LD_EN_MASK, CAP_REG_LD_EN_SHIFT, 1);
cap_reg_config(cap->regs, CAP_CONF1(ch), CAP_EVNT3_POL_MASK, CAP_EVNT3_POL_SHIFT, 1);
cap_reg_config(cap->regs, CAP_CONF1(ch), CAP_EVNT2_POL_MASK, CAP_EVNT2_POL_SHIFT, 0);
cap_reg_config(cap->regs, CAP_CONF1(ch), CAP_EVNT1_POL_MASK, CAP_EVNT1_POL_SHIFT, 1);
cap_reg_config(cap->regs, CAP_CONF1(ch), CAP_EVNT0_POL_MASK, CAP_EVNT0_POL_SHIFT, 0);
cap_reg_config(cap->regs, CAP_CONF1(ch), CAP_EVNT0_RST_MASK, CAP_EVNT0_RST_SHIFT, 1);
/* interrupt configuration */
cap_reg_config(cap->regs, CAP_INT_EN(ch), CAP_EVNT3_INT_EN_MASK, CAP_EVNT3_INT_EN_SHIFT, 1);
return 0;
}
static int aic_cap_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
return 0;
}
static int aic_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_capture *result, unsigned long timeout)
{
struct aic_cap_chip *cap = to_aic_cap_dev(chip);
struct device *dev = cap->chip.dev;
int ret;
u32 ch = pwm->hwpwm;
u64 period_temp = 0, duty_clcyle_temp = 0;
if (!cap->data[ch].available) {
dev_err(chip->dev, "%s() ch%d is unavailable!\n", __func__, ch);
return -ENODEV;
}
mutex_lock(&cap->data[ch].lock);
cap->data[ch].flag = 0;
/* Enable capture */
cap_reg_config(cap->regs, CAP_CONF2(ch), CAP_CNT_EN_MASK, CAP_CNT_EN_SHIFT, 1);
ret = wait_event_interruptible_timeout(cap->data[ch].wait, cap->data[ch].flag != 0,
msecs_to_jiffies(timeout));
if (ret == 0) {
ret = -ETIMEDOUT;
dev_err(dev, "wait interrupt time out\n");
goto out;
}
period_temp = (u64)NSEC_PER_SEC * (u64)cap->data[ch].val[1];
duty_clcyle_temp = (u64)NSEC_PER_SEC * (u64)cap->data[ch].val[0];
result->period = period_temp / cap->clk_rate;
result->duty_cycle = duty_clcyle_temp / cap->clk_rate;
dev_dbg(dev, "period:%d duty_cycle:%d\n", result->period, result->duty_cycle);
dev_dbg(dev, "cap->data[ch].val[1]:%d, cap->data[ch].val[0]:%d\n",
cap->data[ch].val[1], cap->data[ch].val[0]);
mutex_unlock(&cap->data[ch].lock);
return 0;
out:
/* Disable capture */
cap_reg_config(cap->regs, CAP_CONF2(ch), CAP_CNT_EN_MASK, CAP_CNT_EN_SHIFT, 0);
mutex_unlock(&cap->data[ch].lock);
return ret;
}
static const struct pwm_ops aic_cap_ops = {
.apply = aic_cap_apply,
.capture = aic_pwm_capture,
.owner = THIS_MODULE,
};
static irqreturn_t aic_cap_isr(int irq, void *dev_id)
{
struct aic_cap_chip *cap = dev_id;
u32 stat;
int i;
for (i = 0; i < AIC_CAP_CH_NUM; i++) {
stat = readl(cap->regs + CAP_FLG(i));
if (stat & CAP_EVENT3_FLG) {
cap->data[i].flag = 1;
/* Store the data */
cap->data[i].val[0] = readl((cap->regs + CAP_CNT_CMPV(i)));//reg1
cap->data[i].val[1] = readl((cap->regs + CAP_CNT_PRDV_SH(i)));//reg2
/* Disable capture */
cap_reg_config(cap->regs, CAP_CONF2(i), CAP_CNT_EN_MASK, CAP_CNT_EN_SHIFT, 0);
/* Clear the flag */
writel(CAP_EVENT3_FLG, cap->regs + CAP_FLG_CLR(i));
wake_up(&cap->data[i].wait);
}
}
return IRQ_HANDLED;
}
static const struct of_device_id aic_cap_of_match[] = {
{ .compatible = "artinchip,aic-cap-v1.0" },
{},
};
MODULE_DEVICE_TABLE(of, aic_cap_of_match);
static int aic_cap_parse_dt(struct device *dev)
{
int ret, i = 0;
struct device_node *child, *np = dev->of_node;
struct aic_cap_chip *cap = dev_get_drvdata(dev);
ret = of_property_read_u32(np, "clock-rate", (u32 *)&cap->clk_rate);
if (ret) {
dev_warn(dev, "Can't parse clock-rate\n");
return ret;
}
for_each_child_of_node(np, child) {
struct aic_cap_data *arg = &cap->data[i];
arg->available = of_device_is_available(child);
if (!arg->available) {
dev_dbg(dev, "ch%d is unavailable.\n", i);
i++;
continue;
}
dev_dbg(dev, "ch%d is available\n", i);
i++;
}
return 0;
}
static int aic_cap_probe(struct platform_device *pdev)
{
struct aic_cap_chip *cap;
struct clk *clk;
int ret, i;
int irq;
cap = devm_kzalloc(&pdev->dev,
sizeof(struct aic_cap_chip), GFP_KERNEL);
if (!cap)
return -ENOMEM;
cap->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(cap->regs))
return PTR_ERR(cap->regs);
clk = devm_clk_get(&pdev->dev, "sysclk");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "Failed to get sysclk clock\n");
return PTR_ERR(clk);
}
cap->pll_rate = clk_get_rate(clk);
if (!cap->pll_rate) {
dev_err(&pdev->dev, "Failed to get sysclk clock rate\n");
return -EINVAL;
}
cap->clk = devm_clk_get(&pdev->dev, "pwmcs");
if (IS_ERR(cap->clk)) {
dev_err(&pdev->dev, "Failed to get cap clk\n");
return PTR_ERR(cap->clk);
}
ret = clk_prepare_enable(cap->clk);
if (ret < 0) {
dev_err(&pdev->dev, "clk_prepare_enable() failed: %d\n", ret);
return ret;
}
cap->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
if (IS_ERR(cap->rst)) {
ret = PTR_ERR(cap->rst);
goto out_disable_clk;
}
reset_control_deassert(cap->rst);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "%s() Failed to get IRQ\n", __func__);
ret = irq;
goto out_disable_rst;
}
ret = devm_request_irq(&pdev->dev, irq, aic_cap_isr,
0, AIC_CAP_NAME, cap);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", irq);
goto out_disable_rst;
}
cap->irq = irq;
platform_set_drvdata(pdev, cap);
cap->chip.dev = &pdev->dev;
cap->chip.ops = &aic_cap_ops;
cap->chip.of_xlate = of_pwm_xlate_with_flags;
cap->chip.of_pwm_n_cells = 3;
cap->chip.base = -1;
cap->chip.npwm = AIC_CAP_CH_NUM;
ret = pwmchip_add(&cap->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
goto out_disable_rst;
}
ret = aic_cap_parse_dt(&pdev->dev);
if (ret)
goto out_disable_rst;
ret = clk_set_rate(cap->clk, cap->clk_rate);
if (ret) {
dev_err(&pdev->dev, "Failed to set clk_rate %ld\n",
cap->clk_rate);
goto out_disable_rst;
}
cap->glb_regs = of_iomap(pdev->dev.of_node, 1);
if (IS_ERR(cap->glb_regs))
goto out_disable_rst;
for (i = 0; i < AIC_CAP_CH_NUM; i++) {
if (cap->data[i].available) {
/* enable the glb clock */
cap_reg_enable(cap->glb_regs, GLB_CLK_CTL, GLB_CLK_CTL_CAP_EN(i), 1);
aic_cap_init(cap, i);
init_waitqueue_head(&cap->data[i].wait);
mutex_init(&cap->data[i].lock);
}
}
//unmap to be used by other PWMCS Submodules
iounmap(cap->glb_regs);
dev_info(&pdev->dev, "ArtInChip CAP Loaded.\n");
return 0;
out_disable_rst:
reset_control_assert(cap->rst);
out_disable_clk:
clk_disable_unprepare(cap->clk);
return ret;
}
static int aic_cap_remove(struct platform_device *pdev)
{
struct aic_cap_chip *cap = platform_get_drvdata(pdev);
pwmchip_remove(&cap->chip);
reset_control_assert(cap->rst);
clk_unprepare(cap->clk);
return 0;
}
#ifdef CONFIG_PM
static int aic_cap_pm_suspend(struct device *dev)
{
struct aic_cap_chip *cap = dev_get_drvdata(dev);
clk_disable_unprepare(cap->clk);
return 0;
}
static int aic_cap_pm_resume(struct device *dev)
{
struct aic_cap_chip *cap = dev_get_drvdata(dev);
clk_set_rate(cap->clk, cap->clk_rate);
clk_prepare_enable(cap->clk);
return 0;
}
static const struct dev_pm_ops aic_cap_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(aic_cap_pm_suspend, aic_cap_pm_resume)
};
#endif
static struct platform_driver aic_cap_driver = {
.driver = {
.name = AIC_CAP_NAME,
.of_match_table = aic_cap_of_match,
#ifdef CONFIG_PM
.pm = &aic_cap_pm_ops,
#endif
},
.probe = aic_cap_probe,
.remove = aic_cap_remove,
};
module_platform_driver(aic_cap_driver);
MODULE_AUTHOR("zrq <ruiqi.zheng@artinchip.com>");
MODULE_DESCRIPTION("cap driver of ArtInChip SoC");
MODULE_LICENSE("GPL");