mirror of https://github.com/OpenIPC/firmware.git
932 lines
24 KiB
Diff
932 lines
24 KiB
Diff
diff -drupN a/drivers/mfd/ingenic-tcu.c b/drivers/mfd/ingenic-tcu.c
|
|
--- a/drivers/mfd/ingenic-tcu.c 1970-01-01 03:00:00.000000000 +0300
|
|
+++ b/drivers/mfd/ingenic-tcu.c 2022-06-09 05:02:30.000000000 +0300
|
|
@@ -0,0 +1,927 @@
|
|
+/*
|
|
+ * ingenic-tcu.c - Inegnic Soc TCU MFD driver.
|
|
+ *
|
|
+ * Copyright (C) 2015 Ingenic Semiconductor Co., Ltd.
|
|
+ * Written by bo.liu <bo.liu@ingenic.com>.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License version 2 as
|
|
+ * published by the Free Software Foundation.
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/of_irq.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/syscore_ops.h>
|
|
+//#include <linux/wakelock.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/mfd/core.h>
|
|
+#include <linux/mfd/ingenic-tcu.h>
|
|
+#include <linux/clk.h>
|
|
+
|
|
+#define WDT_TDR (0x00) /* rw, 32, 0x???????? */
|
|
+#define WDT_TCER (0x04) /* rw, 32, 0x???????? */
|
|
+#define WDT_TCNT (0x08) /* rw, 32, 0x???????? */
|
|
+#define WDT_TCSR (0x0c) /* rw, 32, 0x???????? */
|
|
+
|
|
+#define TCU_TSTR (0xF0) /* Timer Status Register,Only Used In Tcu2 Mode */
|
|
+#define TCU_TSTSR (0xF4) /* Timer Status Set Register */
|
|
+#define TCU_TSTCR (0xF8) /* Timer Status Clear Register */
|
|
+#define TCU_TSR (0x1C) /* Timer Stop Register */
|
|
+#define TCU_TSSR (0x2C) /* Timer Stop Set Register */
|
|
+#define TCU_TSCR (0x3C) /* Timer Stop Clear Register */
|
|
+#define TCU_TER (0x10) /* Timer Counter Enable Register */
|
|
+#define TCU_TESR (0x14) /* Timer Counter Enable Set Register */
|
|
+#define TCU_TECR (0x18) /* Timer Counter Enable Clear Register */
|
|
+#define TCU_TFR (0x20) /* Timer Flag Register */
|
|
+#define TCU_TFSR (0x24) /* Timer Flag Set Register */
|
|
+#define TCU_TFCR (0x28) /* Timer Flag Clear Register */
|
|
+#define TCU_TMR (0x30) /* Timer Mask Register */
|
|
+#define TCU_TMSR (0x34) /* Timer Mask Set Register */
|
|
+#define TCU_TMCR (0x38) /* Timer Mask Clear Register */
|
|
+
|
|
+#define CH_TDFR(n) (0x40 + (n)*0x10) /* Timer Data Full Reg */
|
|
+#define CH_TDHR(n) (0x44 + (n)*0x10) /* Timer Data Half Reg */
|
|
+#define CH_TCNT(n) (0x48 + (n)*0x10) /* Timer Counter Reg */
|
|
+#define CH_TCSR(n) (0x4C + (n)*0x10) /* Timer Control Reg */
|
|
+
|
|
+#define TCSR_PWM_BYPASS (1 << 11) /* clear counter to 0, only used in TCU2 mode */
|
|
+#define TCSR_CNT_CLRZ (1 << 10) /* clear counter to 0, only used in TCU2 mode */
|
|
+#define TCSR_PWM_SD (1 << 9) /* shut down the pwm output only used in TCU1 mode */
|
|
+#define TCSR_PWM_HIGH (1 << 8) /* selects an initial output level for pwm output */
|
|
+#define TCSR_PWM_EN (1 << 7) /* pwm pin output enable */
|
|
+#define TCSR_PWM_IN (1 << 6) /* pwm pin output enable */
|
|
+
|
|
+struct ingenic_tcu {
|
|
+ void __iomem *base;
|
|
+ struct device *dev;
|
|
+ struct clk *clk;
|
|
+ struct device_node *np;
|
|
+ struct irq_domain *irq_domain;
|
|
+ struct mfd_cell * tcu_cells;
|
|
+ struct ingenic_tcu_chn *tcu_chn;
|
|
+
|
|
+ char idmap[32];
|
|
+ int channel_mask;
|
|
+ int irq_tcu0;
|
|
+ int irq_tcu1;
|
|
+ int irq_tcu2;
|
|
+ int channel_num;
|
|
+ int channel_irq_num;
|
|
+ spinlock_t lock;
|
|
+};
|
|
+
|
|
+#define TCU_DEBUG 0
|
|
+
|
|
+static struct ingenic_tcu *tcu;
|
|
+
|
|
+static inline int tcu_readl(struct ingenic_tcu *tcu, unsigned int reg_addr)
|
|
+{
|
|
+ return readl(tcu->base + reg_addr);
|
|
+}
|
|
+static inline void tcu_writel(struct ingenic_tcu *tcu, unsigned int reg_addr,
|
|
+ unsigned int val)
|
|
+{
|
|
+ writel(val, tcu->base + reg_addr);
|
|
+}
|
|
+#if TCU_DEBUG
|
|
+static void tcu_dump(int id)
|
|
+{
|
|
+ printk("====================================channel %d===================================\n", id);
|
|
+
|
|
+ if(id == 16){
|
|
+ printk("tcu_readl(WDT_TDR %x) = %x\n", (unsigned int)(tcu->base + WDT_TDR), tcu_readl(tcu, WDT_TDR));
|
|
+ printk("tcu_readl(WDT_TCER %x) = %x\n", (unsigned int)(tcu->base + WDT_TCER), tcu_readl(tcu, WDT_TCER));
|
|
+ printk("tcu_readl(WDT_TCNT %x) = %x\n", (unsigned int)(tcu->base + WDT_TCNT), tcu_readl(tcu, WDT_TCNT));
|
|
+ printk("tcu_readl(WDT_TCSR %x) = %x\n", (unsigned int)(tcu->base + WDT_TCSR), tcu_readl(tcu, WDT_TCSR));
|
|
+ printk("tcu_readl(TCU_TSR %x) = %x\n", (unsigned int)(tcu->base + TCU_TSR), tcu_readl(tcu, TCU_TSR));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ printk("tcu_readl(CH_TDFR(id) %x) = %x\n", (unsigned int)(tcu->base + CH_TDFR(id)), tcu_readl(tcu, CH_TDFR(id)));
|
|
+ printk("tcu_readl(CH_TDHR(id) %x) = %x\n", (unsigned int)(tcu->base + CH_TDHR(id)), tcu_readl(tcu, CH_TDHR(id)));
|
|
+ printk("tcu_readl(CH_TCNT(id) %x) = %x\n", (unsigned int)(tcu->base + CH_TCNT(id)), tcu_readl(tcu, CH_TCNT(id)));
|
|
+ printk("tcu_readl(CH_TCSR(id) %x) = %x\n", (unsigned int)(tcu->base + CH_TCSR(id)), tcu_readl(tcu, CH_TCSR(id)));
|
|
+
|
|
+ printk("tcu_readl(TCU_TSTR %x) = %x\n", (unsigned int)(tcu->base + TCU_TSTR), tcu_readl(tcu, TCU_TSTR));
|
|
+ printk("tcu_readl(TCU_TSR %x) = %x\n", (unsigned int)(tcu->base + TCU_TSR), tcu_readl(tcu, TCU_TSR));
|
|
+ printk("tcu_readl(TCU_TER %x) = %x\n", (unsigned int)(tcu->base + TCU_TER), tcu_readl(tcu, TCU_TER));
|
|
+ printk("tcu_readl(TCU_TFR %x) = %x\n", (unsigned int)(tcu->base + TCU_TFR), tcu_readl(tcu, TCU_TFR));
|
|
+ printk("tcu_readl(TCU_TMR %x) = %x\n", (unsigned int)(tcu->base + TCU_TMR), tcu_readl(tcu, TCU_TMR));
|
|
+ printk("=======================================================================\n");
|
|
+}
|
|
+#endif
|
|
+
|
|
+static inline void tcu_mask_full_irq(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TMSR, 1 << id);
|
|
+}
|
|
+static inline void tcu_unmask_full_irq(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TMCR, 1 << id);
|
|
+}
|
|
+static inline void tcu_clear_full_irq(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TFCR, 1 << id);
|
|
+}
|
|
+static inline void tcu_mask_half_irq(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TMSR, 1 << (id + 16));
|
|
+}
|
|
+static inline void tcu_unmask_half_irq(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TMCR, 1 << (id + 16));
|
|
+}
|
|
+static inline void tcu_clear_half_irq(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TFCR, 1 << (id + 16));
|
|
+}
|
|
+
|
|
+void tcu_enable_counter(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TESR, BIT(id));
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(tcu_enable_counter);
|
|
+
|
|
+void tcu_disable_counter(int id)
|
|
+{
|
|
+ int index = tcu->idmap[id];
|
|
+ struct ingenic_tcu_chn *chn = &tcu->tcu_chn[index];
|
|
+ tcu_writel(tcu, TCU_TECR, BIT(id));
|
|
+ if (chn->cib.mode == TCU_MODE2) {
|
|
+ int timeout = 5000;
|
|
+ while ((tcu_readl(tcu, TCU_TSTR) & (1 << id)) || (timeout--));
|
|
+ if(!timeout){
|
|
+ dev_err(tcu->dev, "channel %d:the reset of counter is not finished now\n", id);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(tcu_disable_counter);
|
|
+
|
|
+void tcu_start_counter(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TSCR, BIT(id));
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(tcu_start_counter);
|
|
+
|
|
+void tcu_stop_counter(int id)
|
|
+{
|
|
+ tcu_writel(tcu, TCU_TSSR, BIT(id));
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(tcu_stop_counter);
|
|
+
|
|
+void tcu_set_counter(int id, unsigned int val)
|
|
+{
|
|
+ tcu_writel(tcu, CH_TCNT(id), val);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(tcu_set_counter);
|
|
+
|
|
+int tcu_get_counter(int id)
|
|
+{
|
|
+ return tcu_readl(tcu, CH_TCNT(id));
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(tcu_get_counter);
|
|
+
|
|
+static inline void tcu_disable_all_channel(void)
|
|
+{
|
|
+ /* Timer irqs are unmasked by default, mask them */
|
|
+ /*tcu_writel(tcu, TCU_TMSR, 0x00ff00ff);*/
|
|
+ tcu_writel(tcu, TCU_TMSR, 0x00ff00f7);/*tcu3 no set*/
|
|
+ /* Stop all timer counter */
|
|
+ /*tcu_writel(tcu, TCU_TECR, 0x000080ff);*/
|
|
+ tcu_writel(tcu, TCU_TECR, 0x000080f7);/*tcu3 no stop*/
|
|
+ /* Disable all timer clocks except for those used as system timers */
|
|
+ /*tcu_writel(tcu, TCU_TSSR, 0x000000ff);*/
|
|
+ tcu_writel(tcu, TCU_TSSR, 0x000000f7);/*tcu3 no stop*/
|
|
+}
|
|
+
|
|
+static int tcu_pwm_output_enable(int id)
|
|
+{
|
|
+ unsigned int val;
|
|
+
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ val |= TCSR_PWM_EN;
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void tcu_pwm_output_disable(int id)
|
|
+{
|
|
+ unsigned int val;
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ val &= ~TCSR_PWM_EN;
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+}
|
|
+
|
|
+static int tcu_pwm_bypass_enable(int id)
|
|
+{
|
|
+ unsigned int val;
|
|
+
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ if(val & TCU_CLKSRC_PCK) {
|
|
+ dev_err(tcu->dev, "TCU %d pwm bypass is not support clk source pck\n", id);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ val |= TCSR_PWM_BYPASS;
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int tcu_pwm_bypass_disable(int id)
|
|
+{
|
|
+ unsigned int val;
|
|
+
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ val &= ~TCSR_PWM_BYPASS;
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void tcu_clear_counter_to_zero(int id)
|
|
+{
|
|
+ int index = tcu->idmap[id];
|
|
+ struct ingenic_tcu_chn *chn = &tcu->tcu_chn[index];
|
|
+ if (chn->cib.mode == TCU_MODE2) {
|
|
+ unsigned int val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ tcu_writel(tcu, CH_TCSR(id), (val | TCSR_CNT_CLRZ));
|
|
+ }
|
|
+
|
|
+ tcu_writel(tcu, CH_TCNT(id), 0);
|
|
+}
|
|
+
|
|
+static void set_tcu_full_half_value(int id, unsigned int full_num,
|
|
+ unsigned int half_num)
|
|
+{
|
|
+ tcu_writel(tcu, CH_TDFR(id), full_num);
|
|
+ tcu_writel(tcu, CH_TDHR(id), half_num);
|
|
+}
|
|
+
|
|
+static void tcu_set_pwm_shutdown(int id, unsigned int shutdown)
|
|
+{
|
|
+ unsigned int val;
|
|
+
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ if (shutdown)
|
|
+ val |= TCSR_PWM_SD;
|
|
+ else
|
|
+ val &= ~TCSR_PWM_SD;
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+}
|
|
+
|
|
+static void tcu_set_start_state(int id)
|
|
+{
|
|
+ /*fix this*/
|
|
+ /*tcu_disable_counter(id);*/
|
|
+ tcu_start_counter(id);
|
|
+ tcu_writel(tcu, CH_TCSR(id), 0);
|
|
+}
|
|
+
|
|
+static void tcu_pwm_input_enable(int id)
|
|
+{
|
|
+ unsigned int val;
|
|
+
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ val |= TCSR_PWM_IN;
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+}
|
|
+
|
|
+static void tcu_pwm_input_disable(int id)
|
|
+{
|
|
+ unsigned int val;
|
|
+
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ val &= ~TCSR_PWM_IN;
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+}
|
|
+
|
|
+static int tcu_clock_enable(struct platform_device *pdev)
|
|
+{
|
|
+ if (!(tcu->channel_mask & BIT(pdev->id))) {
|
|
+ dev_err(&pdev->dev,
|
|
+ "current tcu channel %d busy\n",pdev->id);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if(pdev->id != 16)
|
|
+ tcu_enable_counter(pdev->id);
|
|
+
|
|
+ tcu_start_counter(pdev->id);
|
|
+ tcu->channel_mask &= ~(BIT(pdev->id));
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int tcu_clock_disable(struct platform_device *pdev)
|
|
+{
|
|
+ struct ingenic_tcu *tcu = dev_get_drvdata(pdev->dev.parent);
|
|
+ int id = pdev->id;
|
|
+ int index = tcu->idmap[id];
|
|
+ struct ingenic_tcu_chn *chn = &tcu->tcu_chn[index];
|
|
+
|
|
+ if(id != 16) {
|
|
+ switch (chn->irq_type) {
|
|
+ case FULL_IRQ_MODE :
|
|
+ tcu_unmask_full_irq(id);
|
|
+ tcu_clear_full_irq(id);
|
|
+ break;
|
|
+ case HALF_IRQ_MODE :
|
|
+ tcu_unmask_half_irq(id);
|
|
+ tcu_clear_half_irq(id);
|
|
+ break;
|
|
+ case FULL_HALF_IRQ_MODE :
|
|
+ tcu_unmask_full_irq(id);
|
|
+ tcu_unmask_half_irq(id);
|
|
+ tcu_clear_full_irq(id);
|
|
+ tcu_clear_half_irq(id);
|
|
+ break;
|
|
+ default :
|
|
+ break;
|
|
+ }
|
|
+ if(chn->is_count_clear) {
|
|
+ unsigned long flags;
|
|
+
|
|
+ tcu_disable_counter(id);
|
|
+ spin_lock_irqsave(&tcu->lock, flags);
|
|
+ tcu_clear_counter_to_zero(id);
|
|
+ spin_unlock_irqrestore(&tcu->lock, flags);
|
|
+ }
|
|
+ }
|
|
+ tcu_stop_counter(id);
|
|
+ tcu->channel_mask |= BIT(pdev->id);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int ingenic_tcu_counter_begin(struct ingenic_tcu_chn *chn)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ if (chn->is_pwm) {
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&tcu->lock, flags);
|
|
+ tcu_pwm_output_enable(chn->cib.id);
|
|
+ spin_unlock_irqrestore(&tcu->lock, flags);
|
|
+ }
|
|
+ tcu_enable_counter(chn->cib.id);
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_counter_begin);
|
|
+
|
|
+void ingenic_tcu_counter_stop(struct ingenic_tcu_chn *chn)
|
|
+{
|
|
+ if (chn->is_pwm) {
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&tcu->lock, flags);
|
|
+ tcu_pwm_output_disable(chn->cib.id);
|
|
+ spin_unlock_irqrestore(&tcu->lock, flags);
|
|
+ }
|
|
+
|
|
+ tcu_disable_counter(chn->cib.id);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_counter_stop);
|
|
+
|
|
+void ingenic_tcu_set_period(int id, uint16_t period)
|
|
+{
|
|
+ tcu_writel(tcu, CH_TDFR(id), period);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_set_period);
|
|
+
|
|
+void ingenic_tcu_set_duty(int id, uint16_t duty)
|
|
+{
|
|
+ tcu_writel(tcu, CH_TDHR(id), duty);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_set_duty);
|
|
+
|
|
+void ingenic_tcu_set_prescale(int id, enum tcu_prescale prescale)
|
|
+{
|
|
+ unsigned int val;
|
|
+ unsigned long flags;
|
|
+
|
|
+ //spin_lock_irqsave(&tcu->lock, flags);
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ val &= ~(0x7 << 3);
|
|
+ val |= (prescale << 3);
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+ //spin_unlock_irqrestore(&tcu->lock, flags);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_set_prescale);
|
|
+
|
|
+void ingenic_tcu_set_pwm_output_init_level(int id, int level)
|
|
+{
|
|
+ unsigned int val;
|
|
+ unsigned long flags;
|
|
+
|
|
+ //spin_lock_irqsave(&tcu->lock, flags);
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ if (level)
|
|
+ val |= (TCSR_PWM_HIGH);
|
|
+ else
|
|
+ val &= ~(TCSR_PWM_HIGH);
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+ //spin_unlock_irqrestore(&tcu->lock, flags);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_set_pwm_output_init_level);
|
|
+
|
|
+void ingenic_tcu_set_clksrc(int id, enum tcu_clksrc src)
|
|
+{
|
|
+ unsigned int val;
|
|
+ unsigned long flags;
|
|
+
|
|
+ //spin_lock_irqsave(&tcu->lock, flags);
|
|
+ val = tcu_readl(tcu, CH_TCSR(id));
|
|
+ val &= ~0x7;
|
|
+ val |= src;
|
|
+ tcu_writel(tcu, CH_TCSR(id), val);
|
|
+
|
|
+ //spin_unlock_irqrestore(&tcu->lock, flags);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_set_clksrc);
|
|
+
|
|
+int ingenic_tcu_get_count(int id)
|
|
+{
|
|
+ if(id == 1 || id == 2) {
|
|
+ int i = 0;
|
|
+ int tmp = 0;
|
|
+
|
|
+ while ((tmp == 0) && (i < 5)) {
|
|
+ tmp = tcu_readl(tcu, TCU_TSTR) & (1 << (id + 16));
|
|
+ i++;
|
|
+ }
|
|
+ if (tmp == 0)
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ return tcu_get_counter(id);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_get_count);
|
|
+
|
|
+void ingenic_tcu_channel_to_virq(struct ingenic_tcu_chn *chn)
|
|
+{
|
|
+ int index;
|
|
+
|
|
+ if(chn->cib.id == 5) {
|
|
+ chn->virq[0] = tcu->irq_tcu1;
|
|
+ return;
|
|
+ }
|
|
+ if(chn->cib.id == 15) {
|
|
+ chn->virq[0] = tcu->irq_tcu0;
|
|
+ return;
|
|
+ }
|
|
+ index = chn->cib.id * 2;
|
|
+
|
|
+ switch (chn->irq_type) {
|
|
+ case FULL_IRQ_MODE :
|
|
+ chn->virq[0] = irq_create_mapping(tcu->irq_domain, index);
|
|
+ break;
|
|
+ case HALF_IRQ_MODE :
|
|
+ chn->virq[1] = irq_create_mapping(tcu->irq_domain, index + 1);
|
|
+ break;
|
|
+ case FULL_HALF_IRQ_MODE :
|
|
+ chn->virq[0] = irq_create_mapping(tcu->irq_domain, index);
|
|
+ chn->virq[1] = irq_create_mapping(tcu->irq_domain, index + 1);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if(chn->virq[0] < 0 || chn->virq[1] < 0)
|
|
+ return;
|
|
+
|
|
+ if(chn->virq[0])
|
|
+ irq_set_chip_data(chn->virq[0], chn);
|
|
+
|
|
+ if(chn->virq[1])
|
|
+ irq_set_chip_data(chn->virq[1], chn);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_channel_to_virq);
|
|
+
|
|
+void ingenic_watchdog_set_count(unsigned int value)
|
|
+{
|
|
+ tcu_writel(tcu, WDT_TCNT, value);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_watchdog_set_count);
|
|
+
|
|
+void ingenic_watchdog_config(unsigned int tcsr_val, unsigned int timeout_value)
|
|
+{
|
|
+ tcu_writel(tcu, WDT_TCER, 0);
|
|
+ tcu_writel(tcu, WDT_TCSR, tcsr_val);
|
|
+ tcu_writel(tcu, WDT_TDR, timeout_value);
|
|
+ tcu_writel(tcu, WDT_TCNT, 0);
|
|
+ tcu_writel(tcu, WDT_TCER, 1);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_watchdog_config);
|
|
+
|
|
+struct mfd_cell *request_cell(int id)
|
|
+{
|
|
+ int i;
|
|
+ if (!(tcu->channel_mask & BIT(id))) {
|
|
+ dev_err(tcu->dev,
|
|
+ "current tcu channel %d busy\n",id);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ i = tcu->idmap[id];
|
|
+ tcu->channel_mask &= ~(BIT(id));
|
|
+
|
|
+ return &(tcu->tcu_cells[i]);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(request_cell);
|
|
+
|
|
+void free_cell(int id)
|
|
+{
|
|
+ tcu->channel_mask |= BIT(id);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(free_cell);
|
|
+
|
|
+static int check_pwm_is_availabl(struct ingenic_tcu_chn *chn)
|
|
+{
|
|
+ int id = chn->cib.id;
|
|
+ if(id != 15) {
|
|
+ if(chn->is_pwm && !(chn->cib.func & PWM_FUNC)) {
|
|
+ dev_err(tcu->dev, "channel %d not support pwm function\n", id);
|
|
+ return -1;
|
|
+ } else if(!chn->is_pwm && !(chn->cib.func & TRACKBALL_FUNC)) {
|
|
+ dev_err(tcu->dev, "channel %d not support trackball function\n", id);
|
|
+ return -1;
|
|
+ }
|
|
+ if(chn->pwm_in_en && !chn->cib.pwmin) {
|
|
+ dev_err(tcu->dev, "channel %d not support pwm in function\n", id);
|
|
+ return -1;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int ingenic_tcu_config(struct ingenic_tcu_chn *chn)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ int id = chn->cib.id;
|
|
+
|
|
+ if(check_pwm_is_availabl(chn) < 0)
|
|
+ return -1;
|
|
+
|
|
+ tcu_set_start_state(id);
|
|
+
|
|
+ tcu_clear_full_irq(id);
|
|
+ tcu_clear_half_irq(id);
|
|
+
|
|
+ switch (chn->irq_type) {
|
|
+ case NULL_IRQ_MODE :
|
|
+ tcu_mask_full_irq(id);
|
|
+ tcu_mask_half_irq(id);
|
|
+ tcu_clear_full_irq(id);
|
|
+ tcu_clear_half_irq(id);
|
|
+ break;
|
|
+ case FULL_IRQ_MODE :
|
|
+ tcu_unmask_full_irq(id);
|
|
+ tcu_mask_half_irq(id);
|
|
+ tcu_clear_full_irq(id);
|
|
+ break;
|
|
+ case HALF_IRQ_MODE :
|
|
+ tcu_mask_full_irq(id);
|
|
+ tcu_unmask_half_irq(id);
|
|
+ tcu_clear_half_irq(id);
|
|
+ break;
|
|
+ case FULL_HALF_IRQ_MODE :
|
|
+ tcu_unmask_full_irq(id);
|
|
+ tcu_unmask_half_irq(id);
|
|
+ tcu_clear_full_irq(id);
|
|
+ tcu_clear_half_irq(id);
|
|
+ break;
|
|
+ default :
|
|
+ break;
|
|
+ }
|
|
+ spin_lock_irqsave(&tcu->lock, flags);
|
|
+
|
|
+ ingenic_tcu_set_pwm_output_init_level(id, chn->init_level);
|
|
+
|
|
+ if(chn->cib.mode == TCU_MODE1) {
|
|
+ tcu_set_pwm_shutdown(id, chn->shutdown_mode);
|
|
+ }
|
|
+
|
|
+ if (chn->is_pwm) {
|
|
+ tcu_pwm_output_enable(id);
|
|
+ if(chn->pwm_bapass_mode) {
|
|
+ if(chn->clk_src == TCU_CLKSRC_PCK) {
|
|
+ dev_err(tcu->dev, "the current version can not bypass pclk\n");
|
|
+ }
|
|
+ tcu_pwm_bypass_enable(id);
|
|
+ } else {
|
|
+ tcu_pwm_bypass_disable(id);
|
|
+ }
|
|
+ } else {
|
|
+ tcu_pwm_output_disable(id);
|
|
+ }
|
|
+
|
|
+ ingenic_tcu_set_prescale(id, chn->clk_div);
|
|
+ set_tcu_full_half_value(id, chn->full_num, chn->half_num);
|
|
+ if (chn->pwm_in_en)
|
|
+ tcu_pwm_input_enable(id);
|
|
+ else
|
|
+ tcu_pwm_input_disable(id);
|
|
+ ingenic_tcu_set_clksrc(id, chn->clk_src);
|
|
+ tcu_clear_counter_to_zero(id);
|
|
+
|
|
+ spin_unlock_irqrestore(&tcu->lock, flags);
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ingenic_tcu_config);
|
|
+
|
|
+static void tcu_irq_mask(struct irq_data *data)
|
|
+{
|
|
+ struct ingenic_tcu_chn *chn = irq_data_get_irq_chip_data(data);
|
|
+ int id = chn->cib.id;
|
|
+
|
|
+ switch (chn->irq_type) {
|
|
+ case FULL_IRQ_MODE :
|
|
+ tcu_mask_full_irq(id);
|
|
+ break;
|
|
+ case HALF_IRQ_MODE :
|
|
+ tcu_mask_half_irq(id);
|
|
+ break;
|
|
+ case FULL_HALF_IRQ_MODE :
|
|
+ tcu_mask_full_irq(id);
|
|
+ tcu_mask_half_irq(id);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void tcu_irq_unmask(struct irq_data *data)
|
|
+{
|
|
+ struct ingenic_tcu_chn *chn = irq_data_get_irq_chip_data(data);
|
|
+ int id = chn->cib.id;
|
|
+
|
|
+ switch (chn->irq_type) {
|
|
+ case FULL_IRQ_MODE :
|
|
+ tcu_unmask_full_irq(id);
|
|
+ break;
|
|
+ case HALF_IRQ_MODE :
|
|
+ tcu_unmask_half_irq(id);
|
|
+ break;
|
|
+ case FULL_HALF_IRQ_MODE :
|
|
+ tcu_unmask_full_irq(id);
|
|
+ tcu_unmask_half_irq(id);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void tcu_mask_and_ack_irq(struct irq_data *data)
|
|
+{
|
|
+ struct ingenic_tcu_chn *chn = irq_data_get_irq_chip_data(data);
|
|
+ int id = chn->cib.id;
|
|
+
|
|
+ switch (chn->irq_type) {
|
|
+ case FULL_IRQ_MODE :
|
|
+ tcu_mask_full_irq(id);
|
|
+ tcu_clear_full_irq(id);
|
|
+ break;
|
|
+ case HALF_IRQ_MODE :
|
|
+ tcu_mask_half_irq(id);
|
|
+ tcu_clear_half_irq(id);
|
|
+ break;
|
|
+ case FULL_HALF_IRQ_MODE :
|
|
+ tcu_mask_full_irq(id);
|
|
+ tcu_mask_half_irq(id);
|
|
+ tcu_clear_full_irq(id);
|
|
+ tcu_clear_half_irq(id);
|
|
+ break;
|
|
+ default :
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static struct irq_chip tcu_irq_chip = {
|
|
+ .name = "ingenic-tcu",
|
|
+ .irq_disable = tcu_mask_and_ack_irq,
|
|
+ .irq_enable = tcu_irq_unmask,
|
|
+ .irq_unmask = tcu_irq_unmask,
|
|
+ .irq_mask = tcu_irq_mask,
|
|
+ .irq_mask_ack = tcu_mask_and_ack_irq,
|
|
+};
|
|
+
|
|
+static int tcu2_irq_domain_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw)
|
|
+{
|
|
+ struct ingenic_tcu *tcu = d->host_data;
|
|
+
|
|
+ irq_set_chip_data(virq, tcu);
|
|
+ irq_set_chip_and_handler(virq, &tcu_irq_chip, handle_level_irq);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct irq_domain_ops tcu2_irq_domain_ops = {
|
|
+ .map = tcu2_irq_domain_map,
|
|
+ .xlate = irq_domain_xlate_onetwocell,
|
|
+};
|
|
+
|
|
+static void tcu_irq_demux(struct irq_desc *desc)
|
|
+{
|
|
+ struct ingenic_tcu *tcu = irq_desc_get_handler_data(desc);
|
|
+ unsigned long pend, mask, tcu2_pend = 0;
|
|
+
|
|
+ pend = readl(tcu->base + TCU_TFR);
|
|
+ mask = readl(tcu->base + TCU_TMR) | (1 << 5 | 1 << (5 + 16));
|
|
+
|
|
+ pend = pend & ~(mask);
|
|
+ while(pend) {
|
|
+ struct ingenic_tcu_chn *chn;
|
|
+ int index;
|
|
+
|
|
+ tcu2_pend = ffs(pend) - 1;
|
|
+ index = tcu->idmap[tcu2_pend];
|
|
+ chn = &tcu->tcu_chn[index];
|
|
+ if ((pend & 0xffff)){
|
|
+ generic_handle_irq(chn->virq[FULL_BIT]);
|
|
+ } else {
|
|
+ generic_handle_irq(chn->virq[HALF_BIT]);
|
|
+ }
|
|
+ pend &= ~(1 << tcu2_pend);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int __init setup_tcu_irq(struct ingenic_tcu *tcu)
|
|
+{
|
|
+ irq_set_chip_and_handler(tcu->irq_tcu0, &tcu_irq_chip, handle_level_irq);
|
|
+ irq_set_chip_and_handler(tcu->irq_tcu1, &tcu_irq_chip, handle_level_irq);
|
|
+
|
|
+ tcu->irq_domain = irq_domain_add_linear(tcu->np, tcu->channel_irq_num, &tcu2_irq_domain_ops, (void *)tcu);
|
|
+ if(!tcu->irq_domain) {
|
|
+ pr_err("Failed to add tcu irq into irq domain\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ irq_set_chained_handler_and_data(tcu->irq_tcu2, tcu_irq_demux, tcu);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int init_mfd_cells(struct ingenic_tcu *tcu, struct mfd_cell *cells)
|
|
+{
|
|
+ struct device_node *child;
|
|
+ int i = 0, ret;
|
|
+
|
|
+ for_each_child_of_node(tcu->np, child) {
|
|
+ const char *tmp = NULL;
|
|
+ int id;
|
|
+ struct ingenic_tcu_chn *chn = &tcu->tcu_chn[i];
|
|
+
|
|
+ ret = of_property_read_u32(child, "ingenic,channel-info", &chn->chn_info);
|
|
+ if (ret < 0) {
|
|
+ dev_err(tcu->dev, "Cannot get ingenic,channel-info\n");
|
|
+ return -ENOENT;
|
|
+ }
|
|
+ ret = of_property_read_string(child, "compatible", &tmp);
|
|
+ if (ret < 0) {
|
|
+ dev_err(tcu->dev, "Cannot get compatible string\n");
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ id = chn->cib.id;
|
|
+ tcu->idmap[id] = i;
|
|
+ tcu->channel_mask |= BIT(id);
|
|
+ chn->enable = tcu_start_counter;
|
|
+ chn->disable = tcu_stop_counter;
|
|
+ cells[i].id = id;
|
|
+ cells[i].name = tmp;
|
|
+ cells[i].pdata_size = sizeof(struct ingenic_tcu_chn);
|
|
+ cells[i].of_compatible = tmp;
|
|
+ cells[i].platform_data = chn;
|
|
+ cells[i].enable = tcu_clock_enable;
|
|
+ cells[i].disable = tcu_clock_disable;
|
|
+ i ++;
|
|
+ }
|
|
+ /* for(i = 0; i < tcu->channel_num; i++) { */
|
|
+ /* printk("id = %d, name = %s, of_com = %s\n", cells[i].id, cells[i].name, cells[i].of_compatible); */
|
|
+ /* } */
|
|
+ return 0;
|
|
+}
|
|
+static int ingenic_tcu_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct resource *res;
|
|
+ int ret = 0;
|
|
+
|
|
+ tcu = devm_kzalloc(&pdev->dev, sizeof(struct ingenic_tcu), GFP_KERNEL);
|
|
+ if (!tcu) {
|
|
+ dev_err(&pdev->dev, "Failed to allocate driver structure\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ tcu->np = pdev->dev.of_node;
|
|
+ tcu->dev = &pdev->dev;
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (res == NULL) {
|
|
+ dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
|
|
+ return -ENOENT;
|
|
+ }
|
|
+ tcu->base = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (tcu->base == NULL) {
|
|
+ dev_err(&pdev->dev, "Cannot map IO\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ tcu->irq_tcu0 = of_irq_get_byname(tcu->np, "tcu_int0");
|
|
+ if (tcu->irq_tcu0 < 0) {
|
|
+ dev_err(tcu->dev, "int0:unable to get IRQ from DT, %d\n", tcu->irq_tcu0);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ tcu->irq_tcu1 = of_irq_get_byname(tcu->np, "tcu_int1");
|
|
+ if (tcu->irq_tcu1 < 0) {
|
|
+ dev_err(tcu->dev, "int1:unable to get IRQ from DT, %d\n", tcu->irq_tcu1);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ tcu->irq_tcu2 = of_irq_get_byname(tcu->np, "tcu_int2");
|
|
+ if (tcu->irq_tcu2 < 0) {
|
|
+ dev_err(tcu->dev, "int2:unable to get IRQ from DT, %d\n", tcu->irq_tcu2);
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ tcu->clk = devm_clk_get(tcu->dev,"gate_tcu");
|
|
+ clk_prepare_enable(tcu->clk);
|
|
+
|
|
+ tcu->channel_num = of_get_child_count(tcu->np);
|
|
+ tcu->tcu_chn = devm_kzalloc(tcu->dev, sizeof(struct ingenic_tcu_chn) * tcu->channel_num,
|
|
+ GFP_KERNEL);
|
|
+ if (!tcu->tcu_chn) {
|
|
+ dev_err(tcu->dev, "Failed to allocate driver structure tcu_channel\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ tcu->channel_irq_num = (tcu->channel_num - 1) * 2;
|
|
+
|
|
+ setup_tcu_irq(tcu);
|
|
+
|
|
+ spin_lock_init(&tcu->lock);
|
|
+ platform_set_drvdata(pdev, tcu);
|
|
+
|
|
+ tcu_disable_all_channel();
|
|
+
|
|
+ tcu->tcu_cells = devm_kzalloc(tcu->dev, sizeof(struct mfd_cell) * tcu->channel_num,
|
|
+ GFP_KERNEL);
|
|
+ if (!tcu->tcu_cells) {
|
|
+ dev_err(tcu->dev, "Failed to allocate driver structure cells\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ init_mfd_cells(tcu, tcu->tcu_cells);
|
|
+
|
|
+ ret = mfd_add_devices(&pdev->dev, 0, tcu->tcu_cells,
|
|
+ tcu->channel_num, res, tcu->irq_tcu0, NULL);
|
|
+
|
|
+ dev_info(&pdev->dev, "Ingenic TCU driver register completed ret = %d\n", ret);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ingenic_tcu_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct ingenic_tcu *tcu = platform_get_drvdata(pdev);
|
|
+
|
|
+ clk_disable_unprepare(tcu->clk);
|
|
+
|
|
+ mfd_remove_devices(&pdev->dev);
|
|
+
|
|
+ irq_set_handler_data(tcu->irq_tcu0, NULL);
|
|
+ irq_set_chained_handler(tcu->irq_tcu0, NULL);
|
|
+
|
|
+ irq_set_handler_data(tcu->irq_tcu1, NULL);
|
|
+ irq_set_chained_handler(tcu->irq_tcu1, NULL);
|
|
+
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+
|
|
+ devm_kfree(&pdev->dev, tcu);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id tcu_match[] = {
|
|
+ { .compatible = "ingenic,tcu", },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, tcu_match);
|
|
+
|
|
+static struct platform_driver ingenic_tcu_driver = {
|
|
+ .probe = ingenic_tcu_probe,
|
|
+ .remove = ingenic_tcu_remove,
|
|
+ .driver = {
|
|
+ .name = "ingenic-tcu",
|
|
+ .of_match_table = tcu_match,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(ingenic_tcu_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("ingenic SoC TCU driver");
|
|
+MODULE_AUTHOR("bo.liu <bo.liu@ingenic.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_ALIAS("platform:ingenic-tcu");
|