aaec1a0f76
This is a reimplementation of the Generic Counter driver interface. There are no modifications to the Counter subsystem userspace interface, so existing userspace applications should continue to run seamlessly. The purpose of this patch is to internalize the sysfs interface code among the various counter drivers into a shared module. Counter drivers pass and take data natively (i.e. u8, u64, etc.) and the shared counter module handles the translation between the sysfs interface and the device drivers. This guarantees a standard userspace interface for all counter drivers, and helps generalize the Generic Counter driver ABI in order to support the Generic Counter chrdev interface (introduced in a subsequent patch) without significant changes to the existing counter drivers. Note, Counter device registration is the same as before: drivers populate a struct counter_device with components and callbacks, then pass the structure to the devm_counter_register function. However, what's different now is how the Counter subsystem code handles this registration internally. Whereas before callbacks would interact directly with sysfs data, this interaction is now abstracted and instead callbacks interact with native C data types. The counter_comp structure forms the basis for Counter extensions. The counter-sysfs.c file contains the code to parse through the counter_device structure and register the requested components and extensions. Attributes are created and populated based on type, with respective translation functions to handle the mapping between sysfs and the counter driver callbacks. The translation performed for each attribute is straightforward: the attribute type and data is parsed from the counter_attribute structure, the respective counter driver read/write callback is called, and sysfs I/O is handled before or after the driver read/write function is called. Cc: Jarkko Nikula <jarkko.nikula@linux.intel.com> Cc: Patrick Havelange <patrick.havelange@essensium.com> Cc: Kamel Bouhara <kamel.bouhara@bootlin.com> Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com> Cc: Alexandre Torgue <alexandre.torgue@st.com> Cc: Dan Carpenter <dan.carpenter@oracle.com> Acked-by: Syed Nayyar Waris <syednwaris@gmail.com> Reviewed-by: David Lechner <david@lechnology.com> Tested-by: David Lechner <david@lechnology.com> Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com> Reviewed-by: Fabrice Gasnier <fabrice.gasnier@foss.st.com> # for stm32 Link: https://lore.kernel.org/r/c68b4a1ffb195c1a2f65e8dd5ad7b7c14e79c6ef.1630031207.git.vilhelm.gray@gmail.com Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
414 lines
11 KiB
C
414 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* STM32 Timer Encoder and Counter driver
|
|
*
|
|
* Copyright (C) STMicroelectronics 2018
|
|
*
|
|
* Author: Benjamin Gaignard <benjamin.gaignard@st.com>
|
|
*
|
|
*/
|
|
#include <linux/counter.h>
|
|
#include <linux/mfd/stm32-timers.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/types.h>
|
|
|
|
#define TIM_CCMR_CCXS (BIT(8) | BIT(0))
|
|
#define TIM_CCMR_MASK (TIM_CCMR_CC1S | TIM_CCMR_CC2S | \
|
|
TIM_CCMR_IC1F | TIM_CCMR_IC2F)
|
|
#define TIM_CCER_MASK (TIM_CCER_CC1P | TIM_CCER_CC1NP | \
|
|
TIM_CCER_CC2P | TIM_CCER_CC2NP)
|
|
|
|
struct stm32_timer_regs {
|
|
u32 cr1;
|
|
u32 cnt;
|
|
u32 smcr;
|
|
u32 arr;
|
|
};
|
|
|
|
struct stm32_timer_cnt {
|
|
struct counter_device counter;
|
|
struct regmap *regmap;
|
|
struct clk *clk;
|
|
u32 max_arr;
|
|
bool enabled;
|
|
struct stm32_timer_regs bak;
|
|
};
|
|
|
|
static const enum counter_function stm32_count_functions[] = {
|
|
COUNTER_FUNCTION_INCREASE,
|
|
COUNTER_FUNCTION_QUADRATURE_X2_A,
|
|
COUNTER_FUNCTION_QUADRATURE_X2_B,
|
|
COUNTER_FUNCTION_QUADRATURE_X4,
|
|
};
|
|
|
|
static int stm32_count_read(struct counter_device *counter,
|
|
struct counter_count *count, u64 *val)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
u32 cnt;
|
|
|
|
regmap_read(priv->regmap, TIM_CNT, &cnt);
|
|
*val = cnt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_count_write(struct counter_device *counter,
|
|
struct counter_count *count, const u64 val)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
u32 ceiling;
|
|
|
|
regmap_read(priv->regmap, TIM_ARR, &ceiling);
|
|
if (val > ceiling)
|
|
return -EINVAL;
|
|
|
|
return regmap_write(priv->regmap, TIM_CNT, val);
|
|
}
|
|
|
|
static int stm32_count_function_read(struct counter_device *counter,
|
|
struct counter_count *count,
|
|
enum counter_function *function)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
u32 smcr;
|
|
|
|
regmap_read(priv->regmap, TIM_SMCR, &smcr);
|
|
|
|
switch (smcr & TIM_SMCR_SMS) {
|
|
case TIM_SMCR_SMS_SLAVE_MODE_DISABLED:
|
|
*function = COUNTER_FUNCTION_INCREASE;
|
|
return 0;
|
|
case TIM_SMCR_SMS_ENCODER_MODE_1:
|
|
*function = COUNTER_FUNCTION_QUADRATURE_X2_A;
|
|
return 0;
|
|
case TIM_SMCR_SMS_ENCODER_MODE_2:
|
|
*function = COUNTER_FUNCTION_QUADRATURE_X2_B;
|
|
return 0;
|
|
case TIM_SMCR_SMS_ENCODER_MODE_3:
|
|
*function = COUNTER_FUNCTION_QUADRATURE_X4;
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int stm32_count_function_write(struct counter_device *counter,
|
|
struct counter_count *count,
|
|
enum counter_function function)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
u32 cr1, sms;
|
|
|
|
switch (function) {
|
|
case COUNTER_FUNCTION_INCREASE:
|
|
sms = TIM_SMCR_SMS_SLAVE_MODE_DISABLED;
|
|
break;
|
|
case COUNTER_FUNCTION_QUADRATURE_X2_A:
|
|
sms = TIM_SMCR_SMS_ENCODER_MODE_1;
|
|
break;
|
|
case COUNTER_FUNCTION_QUADRATURE_X2_B:
|
|
sms = TIM_SMCR_SMS_ENCODER_MODE_2;
|
|
break;
|
|
case COUNTER_FUNCTION_QUADRATURE_X4:
|
|
sms = TIM_SMCR_SMS_ENCODER_MODE_3;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Store enable status */
|
|
regmap_read(priv->regmap, TIM_CR1, &cr1);
|
|
|
|
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0);
|
|
|
|
regmap_update_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS, sms);
|
|
|
|
/* Make sure that registers are updated */
|
|
regmap_update_bits(priv->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG);
|
|
|
|
/* Restore the enable status */
|
|
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, cr1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_count_direction_read(struct counter_device *counter,
|
|
struct counter_count *count,
|
|
enum counter_count_direction *direction)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
u32 cr1;
|
|
|
|
regmap_read(priv->regmap, TIM_CR1, &cr1);
|
|
*direction = (cr1 & TIM_CR1_DIR) ? COUNTER_COUNT_DIRECTION_BACKWARD :
|
|
COUNTER_COUNT_DIRECTION_FORWARD;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_count_ceiling_read(struct counter_device *counter,
|
|
struct counter_count *count, u64 *ceiling)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
u32 arr;
|
|
|
|
regmap_read(priv->regmap, TIM_ARR, &arr);
|
|
|
|
*ceiling = arr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_count_ceiling_write(struct counter_device *counter,
|
|
struct counter_count *count, u64 ceiling)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
|
|
if (ceiling > priv->max_arr)
|
|
return -ERANGE;
|
|
|
|
/* TIMx_ARR register shouldn't be buffered (ARPE=0) */
|
|
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, 0);
|
|
regmap_write(priv->regmap, TIM_ARR, ceiling);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_count_enable_read(struct counter_device *counter,
|
|
struct counter_count *count, u8 *enable)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
u32 cr1;
|
|
|
|
regmap_read(priv->regmap, TIM_CR1, &cr1);
|
|
|
|
*enable = cr1 & TIM_CR1_CEN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_count_enable_write(struct counter_device *counter,
|
|
struct counter_count *count, u8 enable)
|
|
{
|
|
struct stm32_timer_cnt *const priv = counter->priv;
|
|
u32 cr1;
|
|
|
|
if (enable) {
|
|
regmap_read(priv->regmap, TIM_CR1, &cr1);
|
|
if (!(cr1 & TIM_CR1_CEN))
|
|
clk_enable(priv->clk);
|
|
|
|
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN,
|
|
TIM_CR1_CEN);
|
|
} else {
|
|
regmap_read(priv->regmap, TIM_CR1, &cr1);
|
|
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0);
|
|
if (cr1 & TIM_CR1_CEN)
|
|
clk_disable(priv->clk);
|
|
}
|
|
|
|
/* Keep enabled state to properly handle low power states */
|
|
priv->enabled = enable;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct counter_comp stm32_count_ext[] = {
|
|
COUNTER_COMP_DIRECTION(stm32_count_direction_read),
|
|
COUNTER_COMP_ENABLE(stm32_count_enable_read, stm32_count_enable_write),
|
|
COUNTER_COMP_CEILING(stm32_count_ceiling_read,
|
|
stm32_count_ceiling_write),
|
|
};
|
|
|
|
static const enum counter_synapse_action stm32_synapse_actions[] = {
|
|
COUNTER_SYNAPSE_ACTION_NONE,
|
|
COUNTER_SYNAPSE_ACTION_BOTH_EDGES
|
|
};
|
|
|
|
static int stm32_action_read(struct counter_device *counter,
|
|
struct counter_count *count,
|
|
struct counter_synapse *synapse,
|
|
enum counter_synapse_action *action)
|
|
{
|
|
enum counter_function function;
|
|
int err;
|
|
|
|
err = stm32_count_function_read(counter, count, &function);
|
|
if (err)
|
|
return err;
|
|
|
|
switch (function) {
|
|
case COUNTER_FUNCTION_INCREASE:
|
|
/* counts on internal clock when CEN=1 */
|
|
*action = COUNTER_SYNAPSE_ACTION_NONE;
|
|
return 0;
|
|
case COUNTER_FUNCTION_QUADRATURE_X2_A:
|
|
/* counts up/down on TI1FP1 edge depending on TI2FP2 level */
|
|
if (synapse->signal->id == count->synapses[0].signal->id)
|
|
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
|
|
else
|
|
*action = COUNTER_SYNAPSE_ACTION_NONE;
|
|
return 0;
|
|
case COUNTER_FUNCTION_QUADRATURE_X2_B:
|
|
/* counts up/down on TI2FP2 edge depending on TI1FP1 level */
|
|
if (synapse->signal->id == count->synapses[1].signal->id)
|
|
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
|
|
else
|
|
*action = COUNTER_SYNAPSE_ACTION_NONE;
|
|
return 0;
|
|
case COUNTER_FUNCTION_QUADRATURE_X4:
|
|
/* counts up/down on both TI1FP1 and TI2FP2 edges */
|
|
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static const struct counter_ops stm32_timer_cnt_ops = {
|
|
.count_read = stm32_count_read,
|
|
.count_write = stm32_count_write,
|
|
.function_read = stm32_count_function_read,
|
|
.function_write = stm32_count_function_write,
|
|
.action_read = stm32_action_read,
|
|
};
|
|
|
|
static struct counter_signal stm32_signals[] = {
|
|
{
|
|
.id = 0,
|
|
.name = "Channel 1 Quadrature A"
|
|
},
|
|
{
|
|
.id = 1,
|
|
.name = "Channel 1 Quadrature B"
|
|
}
|
|
};
|
|
|
|
static struct counter_synapse stm32_count_synapses[] = {
|
|
{
|
|
.actions_list = stm32_synapse_actions,
|
|
.num_actions = ARRAY_SIZE(stm32_synapse_actions),
|
|
.signal = &stm32_signals[0]
|
|
},
|
|
{
|
|
.actions_list = stm32_synapse_actions,
|
|
.num_actions = ARRAY_SIZE(stm32_synapse_actions),
|
|
.signal = &stm32_signals[1]
|
|
}
|
|
};
|
|
|
|
static struct counter_count stm32_counts = {
|
|
.id = 0,
|
|
.name = "Channel 1 Count",
|
|
.functions_list = stm32_count_functions,
|
|
.num_functions = ARRAY_SIZE(stm32_count_functions),
|
|
.synapses = stm32_count_synapses,
|
|
.num_synapses = ARRAY_SIZE(stm32_count_synapses),
|
|
.ext = stm32_count_ext,
|
|
.num_ext = ARRAY_SIZE(stm32_count_ext)
|
|
};
|
|
|
|
static int stm32_timer_cnt_probe(struct platform_device *pdev)
|
|
{
|
|
struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent);
|
|
struct device *dev = &pdev->dev;
|
|
struct stm32_timer_cnt *priv;
|
|
|
|
if (IS_ERR_OR_NULL(ddata))
|
|
return -EINVAL;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->regmap = ddata->regmap;
|
|
priv->clk = ddata->clk;
|
|
priv->max_arr = ddata->max_arr;
|
|
|
|
priv->counter.name = dev_name(dev);
|
|
priv->counter.parent = dev;
|
|
priv->counter.ops = &stm32_timer_cnt_ops;
|
|
priv->counter.counts = &stm32_counts;
|
|
priv->counter.num_counts = 1;
|
|
priv->counter.signals = stm32_signals;
|
|
priv->counter.num_signals = ARRAY_SIZE(stm32_signals);
|
|
priv->counter.priv = priv;
|
|
|
|
platform_set_drvdata(pdev, priv);
|
|
|
|
/* Register Counter device */
|
|
return devm_counter_register(dev, &priv->counter);
|
|
}
|
|
|
|
static int __maybe_unused stm32_timer_cnt_suspend(struct device *dev)
|
|
{
|
|
struct stm32_timer_cnt *priv = dev_get_drvdata(dev);
|
|
|
|
/* Only take care of enabled counter: don't disturb other MFD child */
|
|
if (priv->enabled) {
|
|
/* Backup registers that may get lost in low power mode */
|
|
regmap_read(priv->regmap, TIM_SMCR, &priv->bak.smcr);
|
|
regmap_read(priv->regmap, TIM_ARR, &priv->bak.arr);
|
|
regmap_read(priv->regmap, TIM_CNT, &priv->bak.cnt);
|
|
regmap_read(priv->regmap, TIM_CR1, &priv->bak.cr1);
|
|
|
|
/* Disable the counter */
|
|
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0);
|
|
clk_disable(priv->clk);
|
|
}
|
|
|
|
return pinctrl_pm_select_sleep_state(dev);
|
|
}
|
|
|
|
static int __maybe_unused stm32_timer_cnt_resume(struct device *dev)
|
|
{
|
|
struct stm32_timer_cnt *priv = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = pinctrl_pm_select_default_state(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (priv->enabled) {
|
|
clk_enable(priv->clk);
|
|
|
|
/* Restore registers that may have been lost */
|
|
regmap_write(priv->regmap, TIM_SMCR, priv->bak.smcr);
|
|
regmap_write(priv->regmap, TIM_ARR, priv->bak.arr);
|
|
regmap_write(priv->regmap, TIM_CNT, priv->bak.cnt);
|
|
|
|
/* Also re-enables the counter */
|
|
regmap_write(priv->regmap, TIM_CR1, priv->bak.cr1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(stm32_timer_cnt_pm_ops, stm32_timer_cnt_suspend,
|
|
stm32_timer_cnt_resume);
|
|
|
|
static const struct of_device_id stm32_timer_cnt_of_match[] = {
|
|
{ .compatible = "st,stm32-timer-counter", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, stm32_timer_cnt_of_match);
|
|
|
|
static struct platform_driver stm32_timer_cnt_driver = {
|
|
.probe = stm32_timer_cnt_probe,
|
|
.driver = {
|
|
.name = "stm32-timer-counter",
|
|
.of_match_table = stm32_timer_cnt_of_match,
|
|
.pm = &stm32_timer_cnt_pm_ops,
|
|
},
|
|
};
|
|
module_platform_driver(stm32_timer_cnt_driver);
|
|
|
|
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
|
|
MODULE_ALIAS("platform:stm32-timer-counter");
|
|
MODULE_DESCRIPTION("STMicroelectronics STM32 TIMER counter driver");
|
|
MODULE_LICENSE("GPL v2");
|