diff options
Diffstat (limited to 'drivers/counter/microchip-tcb-capture.c')
-rw-r--r-- | drivers/counter/microchip-tcb-capture.c | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/drivers/counter/microchip-tcb-capture.c b/drivers/counter/microchip-tcb-capture.c index 2f096a5b973d..1de3c50b9804 100644 --- a/drivers/counter/microchip-tcb-capture.c +++ b/drivers/counter/microchip-tcb-capture.c @@ -6,18 +6,24 @@ */ #include <linux/clk.h> #include <linux/counter.h> +#include <linux/interrupt.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> +#include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/regmap.h> +#include <uapi/linux/counter/microchip-tcb-capture.h> #include <soc/at91/atmel_tcb.h> #define ATMEL_TC_CMR_MASK (ATMEL_TC_LDRA_RISING | ATMEL_TC_LDRB_FALLING | \ ATMEL_TC_ETRGEDG_RISING | ATMEL_TC_LDBDIS | \ ATMEL_TC_LDBSTOP) +#define ATMEL_TC_DEF_IRQS (ATMEL_TC_ETRGS | ATMEL_TC_COVFS | \ + ATMEL_TC_LDRAS | ATMEL_TC_LDRBS | ATMEL_TC_CPCS) + #define ATMEL_TC_QDEN BIT(8) #define ATMEL_TC_POSEN BIT(9) @@ -247,6 +253,90 @@ static int mchp_tc_count_read(struct counter_device *counter, return 0; } +static int mchp_tc_count_cap_read(struct counter_device *counter, + struct counter_count *count, size_t idx, u64 *val) +{ + struct mchp_tc_data *const priv = counter_priv(counter); + u32 cnt; + int ret; + + switch (idx) { + case COUNTER_MCHP_EXCAP_RA: + ret = regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], RA), &cnt); + break; + case COUNTER_MCHP_EXCAP_RB: + ret = regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], RB), &cnt); + break; + default: + return -EINVAL; + } + + if (ret < 0) + return ret; + + *val = cnt; + + return 0; +} + +static int mchp_tc_count_cap_write(struct counter_device *counter, + struct counter_count *count, size_t idx, u64 val) +{ + struct mchp_tc_data *const priv = counter_priv(counter); + int ret; + + if (val > U32_MAX) + return -ERANGE; + + switch (idx) { + case COUNTER_MCHP_EXCAP_RA: + ret = regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], RA), val); + break; + case COUNTER_MCHP_EXCAP_RB: + ret = regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], RB), val); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int mchp_tc_count_compare_read(struct counter_device *counter, struct counter_count *count, + u64 *val) +{ + struct mchp_tc_data *const priv = counter_priv(counter); + u32 cnt; + int ret; + + ret = regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], RC), &cnt); + if (ret < 0) + return ret; + + *val = cnt; + + return 0; +} + +static int mchp_tc_count_compare_write(struct counter_device *counter, struct counter_count *count, + u64 val) +{ + struct mchp_tc_data *const priv = counter_priv(counter); + + if (val > U32_MAX) + return -ERANGE; + + return regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], RC), val); +} + +static DEFINE_COUNTER_ARRAY_CAPTURE(mchp_tc_cnt_cap_array, 2); + +static struct counter_comp mchp_tc_count_ext[] = { + COUNTER_COMP_ARRAY_CAPTURE(mchp_tc_count_cap_read, mchp_tc_count_cap_write, + mchp_tc_cnt_cap_array), + COUNTER_COMP_COMPARE(mchp_tc_count_compare_read, mchp_tc_count_compare_write), +}; + static struct counter_count mchp_tc_counts[] = { { .id = 0, @@ -255,6 +345,8 @@ static struct counter_count mchp_tc_counts[] = { .num_functions = ARRAY_SIZE(mchp_tc_count_functions), .synapses = mchp_tc_count_synapses, .num_synapses = ARRAY_SIZE(mchp_tc_count_synapses), + .ext = mchp_tc_count_ext, + .num_ext = ARRAY_SIZE(mchp_tc_count_ext), }, }; @@ -294,6 +386,65 @@ static const struct of_device_id atmel_tc_of_match[] = { { /* sentinel */ } }; +static irqreturn_t mchp_tc_isr(int irq, void *dev_id) +{ + struct counter_device *const counter = dev_id; + struct mchp_tc_data *const priv = counter_priv(counter); + u32 sr, mask; + + regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], SR), &sr); + regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], IMR), &mask); + + sr &= mask; + if (!(sr & ATMEL_TC_ALL_IRQ)) + return IRQ_NONE; + + if (sr & ATMEL_TC_ETRGS) + counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE, + COUNTER_MCHP_EVCHN_CV); + if (sr & ATMEL_TC_LDRAS) + counter_push_event(counter, COUNTER_EVENT_CAPTURE, + COUNTER_MCHP_EVCHN_RA); + if (sr & ATMEL_TC_LDRBS) + counter_push_event(counter, COUNTER_EVENT_CAPTURE, + COUNTER_MCHP_EVCHN_RB); + if (sr & ATMEL_TC_CPCS) + counter_push_event(counter, COUNTER_EVENT_THRESHOLD, + COUNTER_MCHP_EVCHN_RC); + if (sr & ATMEL_TC_COVFS) + counter_push_event(counter, COUNTER_EVENT_OVERFLOW, + COUNTER_MCHP_EVCHN_CV); + + return IRQ_HANDLED; +} + +static void mchp_tc_irq_remove(void *ptr) +{ + struct mchp_tc_data *priv = ptr; + + regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], IDR), ATMEL_TC_DEF_IRQS); +} + +static int mchp_tc_irq_enable(struct counter_device *const counter, int irq) +{ + struct mchp_tc_data *const priv = counter_priv(counter); + int ret = devm_request_irq(counter->parent, irq, mchp_tc_isr, 0, + dev_name(counter->parent), counter); + + if (ret < 0) + return ret; + + ret = regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], IER), ATMEL_TC_DEF_IRQS); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(counter->parent, mchp_tc_irq_remove, priv); + if (ret < 0) + return ret; + + return 0; +} + static void mchp_tc_clk_remove(void *ptr) { clk_disable_unprepare((struct clk *)ptr); @@ -368,6 +519,25 @@ static int mchp_tc_probe(struct platform_device *pdev) channel); } + /* Disable Quadrature Decoder and position measure */ + ret = regmap_update_bits(regmap, ATMEL_TC_BMR, ATMEL_TC_QDEN | ATMEL_TC_POSEN, 0); + if (ret) + return ret; + + /* Setup the period capture mode */ + ret = regmap_update_bits(regmap, ATMEL_TC_REG(priv->channel[0], CMR), + ATMEL_TC_WAVE | ATMEL_TC_ABETRG | ATMEL_TC_CMR_MASK | + ATMEL_TC_TCCLKS, + ATMEL_TC_CMR_MASK); + if (ret) + return ret; + + /* Enable clock and trigger counter */ + ret = regmap_write(regmap, ATMEL_TC_REG(priv->channel[0], CCR), + ATMEL_TC_CLKEN | ATMEL_TC_SWTRG); + if (ret) + return ret; + priv->tc_cfg = tcb_config; priv->regmap = regmap; counter->name = dev_name(&pdev->dev); @@ -378,6 +548,15 @@ static int mchp_tc_probe(struct platform_device *pdev) counter->num_signals = ARRAY_SIZE(mchp_tc_count_signals); counter->signals = mchp_tc_count_signals; + i = of_irq_get(np->parent, 0); + if (i == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (i > 0) { + ret = mchp_tc_irq_enable(counter, i); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to set up IRQ"); + } + ret = devm_counter_add(&pdev->dev, counter); if (ret < 0) return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n"); |