mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-30 23:54:04 +08:00
Merge branch 'ib-5.8-tcb' into HEAD
Series needed as base for a clocksource tree hence immutable branch
This commit is contained in:
commit
1c2f21be65
@ -1,56 +0,0 @@
|
||||
* Device tree bindings for Atmel Timer Counter Blocks
|
||||
- compatible: Should be "atmel,<chip>-tcb", "simple-mfd", "syscon".
|
||||
<chip> can be "at91rm9200" or "at91sam9x5"
|
||||
- reg: Should contain registers location and length
|
||||
- #address-cells: has to be 1
|
||||
- #size-cells: has to be 0
|
||||
- interrupts: Should contain all interrupts for the TC block
|
||||
Note that you can specify several interrupt cells if the TC
|
||||
block has one interrupt per channel.
|
||||
- clock-names: tuple listing input clock names.
|
||||
Required elements: "t0_clk", "slow_clk"
|
||||
Optional elements: "t1_clk", "t2_clk"
|
||||
- clocks: phandles to input clocks.
|
||||
|
||||
The TCB can expose multiple subdevices:
|
||||
* a timer
|
||||
- compatible: Should be "atmel,tcb-timer"
|
||||
- reg: Should contain the TCB channels to be used. If the
|
||||
counter width is 16 bits (at91rm9200-tcb), two consecutive
|
||||
channels are needed. Else, only one channel will be used.
|
||||
|
||||
Examples:
|
||||
|
||||
One interrupt per TC block:
|
||||
tcb0: timer@fff7c000 {
|
||||
compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0xfff7c000 0x100>;
|
||||
interrupts = <18 4>;
|
||||
clocks = <&tcb0_clk>, <&clk32k>;
|
||||
clock-names = "t0_clk", "slow_clk";
|
||||
|
||||
timer@0 {
|
||||
compatible = "atmel,tcb-timer";
|
||||
reg = <0>, <1>;
|
||||
};
|
||||
|
||||
timer@2 {
|
||||
compatible = "atmel,tcb-timer";
|
||||
reg = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
One interrupt per TC channel in a TC block:
|
||||
tcb1: timer@fffdc000 {
|
||||
compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0xfffdc000 0x100>;
|
||||
interrupts = <26 4>, <27 4>, <28 4>;
|
||||
clocks = <&tcb1_clk>, <&clk32k>;
|
||||
clock-names = "t0_clk", "slow_clk";
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,181 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: "http://devicetree.org/schemas/soc/microchip/atmel,at91rm9200-tcb.yaml#"
|
||||
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
|
||||
|
||||
title: Atmel Timer Counter Block
|
||||
|
||||
maintainers:
|
||||
- Alexandre Belloni <alexandre.belloni@bootlin.com>
|
||||
|
||||
description: |
|
||||
The Atmel (now Microchip) SoCs have timers named Timer Counter Block. Each
|
||||
timer has three channels with two counters each.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- atmel,at91rm9200-tcb
|
||||
- atmel,at91sam9x5-tcb
|
||||
- atmel,sama5d2-tcb
|
||||
- const: simple-mfd
|
||||
- const: syscon
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
description:
|
||||
List of interrupts. One interrupt per TCB channel if available or one
|
||||
interrupt for the TC block
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
|
||||
clock-names:
|
||||
description:
|
||||
List of clock names. Always includes t0_clk and slow clk. Also includes
|
||||
t1_clk and t2_clk if a clock per channel is available.
|
||||
minItems: 2
|
||||
maxItems: 4
|
||||
|
||||
clocks:
|
||||
minItems: 2
|
||||
maxItems: 4
|
||||
|
||||
'#address-cells':
|
||||
const: 1
|
||||
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
patternProperties:
|
||||
"^timer@[0-2]$":
|
||||
description: The timer block channels that are used as timers or counters.
|
||||
type: object
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- atmel,tcb-timer
|
||||
- microchip,tcb-capture
|
||||
reg:
|
||||
description:
|
||||
List of channels to use for this particular timer. In Microchip TCB capture
|
||||
mode channels are registered as a counter devices, for the qdec mode TCB0's
|
||||
channel <0> and <1> are required.
|
||||
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: atmel,sama5d2-tcb
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 3
|
||||
maxItems: 3
|
||||
clock-names:
|
||||
items:
|
||||
- const: t0_clk
|
||||
- const: gclk
|
||||
- const: slow_clk
|
||||
else:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 2
|
||||
maxItems: 4
|
||||
clock-names:
|
||||
oneOf:
|
||||
- items:
|
||||
- const: t0_clk
|
||||
- const: slow_clk
|
||||
- items:
|
||||
- const: t0_clk
|
||||
- const: t1_clk
|
||||
- const: t2_clk
|
||||
- const: slow_clk
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
- '#address-cells'
|
||||
- '#size-cells'
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
/* One interrupt per TC block: */
|
||||
tcb0: timer@fff7c000 {
|
||||
compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0xfff7c000 0x100>;
|
||||
interrupts = <18 4>;
|
||||
clocks = <&tcb0_clk>, <&clk32k>;
|
||||
clock-names = "t0_clk", "slow_clk";
|
||||
|
||||
timer@0 {
|
||||
compatible = "atmel,tcb-timer";
|
||||
reg = <0>, <1>;
|
||||
};
|
||||
|
||||
timer@2 {
|
||||
compatible = "atmel,tcb-timer";
|
||||
reg = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
/* One interrupt per TC channel in a TC block: */
|
||||
tcb1: timer@fffdc000 {
|
||||
compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0xfffdc000 0x100>;
|
||||
interrupts = <26 4>, <27 4>, <28 4>;
|
||||
clocks = <&tcb1_clk>, <&clk32k>;
|
||||
clock-names = "t0_clk", "slow_clk";
|
||||
|
||||
timer@0 {
|
||||
compatible = "atmel,tcb-timer";
|
||||
reg = <0>;
|
||||
};
|
||||
|
||||
timer@1 {
|
||||
compatible = "atmel,tcb-timer";
|
||||
reg = <1>;
|
||||
};
|
||||
};
|
||||
/* TCB0 Capture with QDEC: */
|
||||
timer@f800c000 {
|
||||
compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0xfff7c000 0x100>;
|
||||
interrupts = <18 4>;
|
||||
clocks = <&tcb0_clk>, <&clk32k>;
|
||||
clock-names = "t0_clk", "slow_clk";
|
||||
|
||||
timer@0 {
|
||||
compatible = "microchip,tcb-capture";
|
||||
reg = <0>, <1>;
|
||||
};
|
||||
|
||||
timer@2 {
|
||||
compatible = "atmel,tcb-timer";
|
||||
reg = <2>;
|
||||
};
|
||||
};
|
@ -70,4 +70,15 @@ config FTM_QUADDEC
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ftm-quaddec.
|
||||
|
||||
config MICROCHIP_TCB_CAPTURE
|
||||
tristate "Microchip Timer Counter Capture driver"
|
||||
depends on HAS_IOMEM && OF
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
Select this option to enable the Microchip Timer Counter Block
|
||||
capture driver.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called microchip-tcb-capture.
|
||||
|
||||
endif # COUNTER
|
||||
|
@ -10,3 +10,4 @@ obj-$(CONFIG_STM32_TIMER_CNT) += stm32-timer-cnt.o
|
||||
obj-$(CONFIG_STM32_LPTIMER_CNT) += stm32-lptimer-cnt.o
|
||||
obj-$(CONFIG_TI_EQEP) += ti-eqep.o
|
||||
obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o
|
||||
obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o
|
||||
|
397
drivers/counter/microchip-tcb-capture.c
Normal file
397
drivers/counter/microchip-tcb-capture.c
Normal file
@ -0,0 +1,397 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/**
|
||||
* Copyright (C) 2020 Microchip
|
||||
*
|
||||
* Author: Kamel Bouhara <kamel.bouhara@bootlin.com>
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/counter.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.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_QDEN BIT(8)
|
||||
#define ATMEL_TC_POSEN BIT(9)
|
||||
|
||||
struct mchp_tc_data {
|
||||
const struct atmel_tcb_config *tc_cfg;
|
||||
struct counter_device counter;
|
||||
struct regmap *regmap;
|
||||
int qdec_mode;
|
||||
int num_channels;
|
||||
int channel[2];
|
||||
bool trig_inverted;
|
||||
};
|
||||
|
||||
enum mchp_tc_count_function {
|
||||
MCHP_TC_FUNCTION_INCREASE,
|
||||
MCHP_TC_FUNCTION_QUADRATURE,
|
||||
};
|
||||
|
||||
static enum counter_count_function mchp_tc_count_functions[] = {
|
||||
[MCHP_TC_FUNCTION_INCREASE] = COUNTER_COUNT_FUNCTION_INCREASE,
|
||||
[MCHP_TC_FUNCTION_QUADRATURE] = COUNTER_COUNT_FUNCTION_QUADRATURE_X4,
|
||||
};
|
||||
|
||||
enum mchp_tc_synapse_action {
|
||||
MCHP_TC_SYNAPSE_ACTION_NONE = 0,
|
||||
MCHP_TC_SYNAPSE_ACTION_RISING_EDGE,
|
||||
MCHP_TC_SYNAPSE_ACTION_FALLING_EDGE,
|
||||
MCHP_TC_SYNAPSE_ACTION_BOTH_EDGE
|
||||
};
|
||||
|
||||
static enum counter_synapse_action mchp_tc_synapse_actions[] = {
|
||||
[MCHP_TC_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE,
|
||||
[MCHP_TC_SYNAPSE_ACTION_RISING_EDGE] = COUNTER_SYNAPSE_ACTION_RISING_EDGE,
|
||||
[MCHP_TC_SYNAPSE_ACTION_FALLING_EDGE] = COUNTER_SYNAPSE_ACTION_FALLING_EDGE,
|
||||
[MCHP_TC_SYNAPSE_ACTION_BOTH_EDGE] = COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
|
||||
};
|
||||
|
||||
static struct counter_signal mchp_tc_count_signals[] = {
|
||||
{
|
||||
.id = 0,
|
||||
.name = "Channel A",
|
||||
},
|
||||
{
|
||||
.id = 1,
|
||||
.name = "Channel B",
|
||||
}
|
||||
};
|
||||
|
||||
static struct counter_synapse mchp_tc_count_synapses[] = {
|
||||
{
|
||||
.actions_list = mchp_tc_synapse_actions,
|
||||
.num_actions = ARRAY_SIZE(mchp_tc_synapse_actions),
|
||||
.signal = &mchp_tc_count_signals[0]
|
||||
},
|
||||
{
|
||||
.actions_list = mchp_tc_synapse_actions,
|
||||
.num_actions = ARRAY_SIZE(mchp_tc_synapse_actions),
|
||||
.signal = &mchp_tc_count_signals[1]
|
||||
}
|
||||
};
|
||||
|
||||
static int mchp_tc_count_function_get(struct counter_device *counter,
|
||||
struct counter_count *count,
|
||||
size_t *function)
|
||||
{
|
||||
struct mchp_tc_data *const priv = counter->priv;
|
||||
|
||||
if (priv->qdec_mode)
|
||||
*function = MCHP_TC_FUNCTION_QUADRATURE;
|
||||
else
|
||||
*function = MCHP_TC_FUNCTION_INCREASE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mchp_tc_count_function_set(struct counter_device *counter,
|
||||
struct counter_count *count,
|
||||
size_t function)
|
||||
{
|
||||
struct mchp_tc_data *const priv = counter->priv;
|
||||
u32 bmr, cmr;
|
||||
|
||||
regmap_read(priv->regmap, ATMEL_TC_BMR, &bmr);
|
||||
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), &cmr);
|
||||
|
||||
/* Set capture mode */
|
||||
cmr &= ~ATMEL_TC_WAVE;
|
||||
|
||||
switch (function) {
|
||||
case MCHP_TC_FUNCTION_INCREASE:
|
||||
priv->qdec_mode = 0;
|
||||
/* Set highest rate based on whether soc has gclk or not */
|
||||
bmr &= ~(ATMEL_TC_QDEN | ATMEL_TC_POSEN);
|
||||
if (priv->tc_cfg->has_gclk)
|
||||
cmr |= ATMEL_TC_TIMER_CLOCK2;
|
||||
else
|
||||
cmr |= ATMEL_TC_TIMER_CLOCK1;
|
||||
/* Setup the period capture mode */
|
||||
cmr |= ATMEL_TC_CMR_MASK;
|
||||
cmr &= ~(ATMEL_TC_ABETRG | ATMEL_TC_XC0);
|
||||
break;
|
||||
case MCHP_TC_FUNCTION_QUADRATURE:
|
||||
if (!priv->tc_cfg->has_qdec)
|
||||
return -EINVAL;
|
||||
/* In QDEC mode settings both channels 0 and 1 are required */
|
||||
if (priv->num_channels < 2 || priv->channel[0] != 0 ||
|
||||
priv->channel[1] != 1) {
|
||||
pr_err("Invalid channels number or id for quadrature mode\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
priv->qdec_mode = 1;
|
||||
bmr |= ATMEL_TC_QDEN | ATMEL_TC_POSEN;
|
||||
cmr |= ATMEL_TC_ETRGEDG_RISING | ATMEL_TC_ABETRG | ATMEL_TC_XC0;
|
||||
break;
|
||||
}
|
||||
|
||||
regmap_write(priv->regmap, ATMEL_TC_BMR, bmr);
|
||||
regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), cmr);
|
||||
|
||||
/* Enable clock and trigger counter */
|
||||
regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], CCR),
|
||||
ATMEL_TC_CLKEN | ATMEL_TC_SWTRG);
|
||||
|
||||
if (priv->qdec_mode) {
|
||||
regmap_write(priv->regmap,
|
||||
ATMEL_TC_REG(priv->channel[1], CMR), cmr);
|
||||
regmap_write(priv->regmap,
|
||||
ATMEL_TC_REG(priv->channel[1], CCR),
|
||||
ATMEL_TC_CLKEN | ATMEL_TC_SWTRG);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mchp_tc_count_signal_read(struct counter_device *counter,
|
||||
struct counter_signal *signal,
|
||||
enum counter_signal_value *val)
|
||||
{
|
||||
struct mchp_tc_data *const priv = counter->priv;
|
||||
bool sigstatus;
|
||||
u32 sr;
|
||||
|
||||
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], SR), &sr);
|
||||
|
||||
if (priv->trig_inverted)
|
||||
sigstatus = (sr & ATMEL_TC_MTIOB);
|
||||
else
|
||||
sigstatus = (sr & ATMEL_TC_MTIOA);
|
||||
|
||||
*val = sigstatus ? COUNTER_SIGNAL_HIGH : COUNTER_SIGNAL_LOW;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mchp_tc_count_action_get(struct counter_device *counter,
|
||||
struct counter_count *count,
|
||||
struct counter_synapse *synapse,
|
||||
size_t *action)
|
||||
{
|
||||
struct mchp_tc_data *const priv = counter->priv;
|
||||
u32 cmr;
|
||||
|
||||
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), &cmr);
|
||||
|
||||
*action = MCHP_TC_SYNAPSE_ACTION_NONE;
|
||||
|
||||
if (cmr & ATMEL_TC_ETRGEDG_NONE)
|
||||
*action = MCHP_TC_SYNAPSE_ACTION_NONE;
|
||||
else if (cmr & ATMEL_TC_ETRGEDG_RISING)
|
||||
*action = MCHP_TC_SYNAPSE_ACTION_RISING_EDGE;
|
||||
else if (cmr & ATMEL_TC_ETRGEDG_FALLING)
|
||||
*action = MCHP_TC_SYNAPSE_ACTION_FALLING_EDGE;
|
||||
else if (cmr & ATMEL_TC_ETRGEDG_BOTH)
|
||||
*action = MCHP_TC_SYNAPSE_ACTION_BOTH_EDGE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mchp_tc_count_action_set(struct counter_device *counter,
|
||||
struct counter_count *count,
|
||||
struct counter_synapse *synapse,
|
||||
size_t action)
|
||||
{
|
||||
struct mchp_tc_data *const priv = counter->priv;
|
||||
u32 edge = ATMEL_TC_ETRGEDG_NONE;
|
||||
|
||||
/* QDEC mode is rising edge only */
|
||||
if (priv->qdec_mode)
|
||||
return -EINVAL;
|
||||
|
||||
switch (action) {
|
||||
case MCHP_TC_SYNAPSE_ACTION_NONE:
|
||||
edge = ATMEL_TC_ETRGEDG_NONE;
|
||||
break;
|
||||
case MCHP_TC_SYNAPSE_ACTION_RISING_EDGE:
|
||||
edge = ATMEL_TC_ETRGEDG_RISING;
|
||||
break;
|
||||
case MCHP_TC_SYNAPSE_ACTION_FALLING_EDGE:
|
||||
edge = ATMEL_TC_ETRGEDG_FALLING;
|
||||
break;
|
||||
case MCHP_TC_SYNAPSE_ACTION_BOTH_EDGE:
|
||||
edge = ATMEL_TC_ETRGEDG_BOTH;
|
||||
break;
|
||||
}
|
||||
|
||||
return regmap_write_bits(priv->regmap,
|
||||
ATMEL_TC_REG(priv->channel[0], CMR),
|
||||
ATMEL_TC_ETRGEDG, edge);
|
||||
}
|
||||
|
||||
static int mchp_tc_count_read(struct counter_device *counter,
|
||||
struct counter_count *count,
|
||||
unsigned long *val)
|
||||
{
|
||||
struct mchp_tc_data *const priv = counter->priv;
|
||||
u32 cnt;
|
||||
|
||||
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CV), &cnt);
|
||||
*val = cnt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct counter_count mchp_tc_counts[] = {
|
||||
{
|
||||
.id = 0,
|
||||
.name = "Timer Counter",
|
||||
.functions_list = mchp_tc_count_functions,
|
||||
.num_functions = ARRAY_SIZE(mchp_tc_count_functions),
|
||||
.synapses = mchp_tc_count_synapses,
|
||||
.num_synapses = ARRAY_SIZE(mchp_tc_count_synapses),
|
||||
},
|
||||
};
|
||||
|
||||
static struct counter_ops mchp_tc_ops = {
|
||||
.signal_read = mchp_tc_count_signal_read,
|
||||
.count_read = mchp_tc_count_read,
|
||||
.function_get = mchp_tc_count_function_get,
|
||||
.function_set = mchp_tc_count_function_set,
|
||||
.action_get = mchp_tc_count_action_get,
|
||||
.action_set = mchp_tc_count_action_set
|
||||
};
|
||||
|
||||
static const struct atmel_tcb_config tcb_rm9200_config = {
|
||||
.counter_width = 16,
|
||||
};
|
||||
|
||||
static const struct atmel_tcb_config tcb_sam9x5_config = {
|
||||
.counter_width = 32,
|
||||
};
|
||||
|
||||
static const struct atmel_tcb_config tcb_sama5d2_config = {
|
||||
.counter_width = 32,
|
||||
.has_gclk = true,
|
||||
.has_qdec = true,
|
||||
};
|
||||
|
||||
static const struct atmel_tcb_config tcb_sama5d3_config = {
|
||||
.counter_width = 32,
|
||||
.has_qdec = true,
|
||||
};
|
||||
|
||||
static const struct of_device_id atmel_tc_of_match[] = {
|
||||
{ .compatible = "atmel,at91rm9200-tcb", .data = &tcb_rm9200_config, },
|
||||
{ .compatible = "atmel,at91sam9x5-tcb", .data = &tcb_sam9x5_config, },
|
||||
{ .compatible = "atmel,sama5d2-tcb", .data = &tcb_sama5d2_config, },
|
||||
{ .compatible = "atmel,sama5d3-tcb", .data = &tcb_sama5d3_config, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static void mchp_tc_clk_remove(void *ptr)
|
||||
{
|
||||
clk_disable_unprepare((struct clk *)ptr);
|
||||
}
|
||||
|
||||
static int mchp_tc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
const struct atmel_tcb_config *tcb_config;
|
||||
const struct of_device_id *match;
|
||||
struct mchp_tc_data *priv;
|
||||
char clk_name[7];
|
||||
struct regmap *regmap;
|
||||
struct clk *clk[3];
|
||||
int channel;
|
||||
int ret, i;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
match = of_match_node(atmel_tc_of_match, np->parent);
|
||||
tcb_config = match->data;
|
||||
if (!tcb_config) {
|
||||
dev_err(&pdev->dev, "No matching parent node found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
regmap = syscon_node_to_regmap(np->parent);
|
||||
if (IS_ERR(priv->regmap))
|
||||
return PTR_ERR(priv->regmap);
|
||||
|
||||
/* max. channels number is 2 when in QDEC mode */
|
||||
priv->num_channels = of_property_count_u32_elems(np, "reg");
|
||||
if (priv->num_channels < 0) {
|
||||
dev_err(&pdev->dev, "Invalid or missing channel\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Register channels and initialize clocks */
|
||||
for (i = 0; i < priv->num_channels; i++) {
|
||||
ret = of_property_read_u32_index(np, "reg", i, &channel);
|
||||
if (ret < 0 || channel > 2)
|
||||
return -ENODEV;
|
||||
|
||||
priv->channel[i] = channel;
|
||||
|
||||
snprintf(clk_name, sizeof(clk_name), "t%d_clk", channel);
|
||||
|
||||
clk[i] = of_clk_get_by_name(np->parent, clk_name);
|
||||
if (IS_ERR(clk[i])) {
|
||||
/* Fallback to t0_clk */
|
||||
clk[i] = of_clk_get_by_name(np->parent, "t0_clk");
|
||||
if (IS_ERR(clk[i]))
|
||||
return PTR_ERR(clk[i]);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(clk[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(&pdev->dev,
|
||||
mchp_tc_clk_remove,
|
||||
clk[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_dbg(&pdev->dev,
|
||||
"Initialized capture mode on channel %d\n",
|
||||
channel);
|
||||
}
|
||||
|
||||
priv->tc_cfg = tcb_config;
|
||||
priv->regmap = regmap;
|
||||
priv->counter.name = dev_name(&pdev->dev);
|
||||
priv->counter.parent = &pdev->dev;
|
||||
priv->counter.ops = &mchp_tc_ops;
|
||||
priv->counter.num_counts = ARRAY_SIZE(mchp_tc_counts);
|
||||
priv->counter.counts = mchp_tc_counts;
|
||||
priv->counter.num_signals = ARRAY_SIZE(mchp_tc_count_signals);
|
||||
priv->counter.signals = mchp_tc_count_signals;
|
||||
priv->counter.priv = priv;
|
||||
|
||||
return devm_counter_register(&pdev->dev, &priv->counter);
|
||||
}
|
||||
|
||||
static const struct of_device_id mchp_tc_dt_ids[] = {
|
||||
{ .compatible = "microchip,tcb-capture", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mchp_tc_dt_ids);
|
||||
|
||||
static struct platform_driver mchp_tc_driver = {
|
||||
.probe = mchp_tc_probe,
|
||||
.driver = {
|
||||
.name = "microchip-tcb-capture",
|
||||
.of_match_table = mchp_tc_dt_ids,
|
||||
},
|
||||
};
|
||||
module_platform_driver(mchp_tc_driver);
|
||||
|
||||
MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>");
|
||||
MODULE_DESCRIPTION("Microchip TCB Capture driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -36,9 +36,14 @@ struct clk;
|
||||
/**
|
||||
* struct atmel_tcb_config - SoC data for a Timer/Counter Block
|
||||
* @counter_width: size in bits of a timer counter register
|
||||
* @has_gclk: boolean indicating if a timer counter has a generic clock
|
||||
* @has_qdec: boolean indicating if a timer counter has a quadrature
|
||||
* decoder.
|
||||
*/
|
||||
struct atmel_tcb_config {
|
||||
size_t counter_width;
|
||||
bool has_gclk;
|
||||
bool has_qdec;
|
||||
};
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user