firmware/br-ext-chip-ingenic/board/t40/kernel/patches/00000-drivers_mfd_ingenic-t...

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");