cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

pwm-sl28cpld.c (8482B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * sl28cpld PWM driver
      4 *
      5 * Copyright (c) 2020 Michael Walle <michael@walle.cc>
      6 *
      7 * There is no public datasheet available for this PWM core. But it is easy
      8 * enough to be briefly explained. It consists of one 8-bit counter. The PWM
      9 * supports four distinct frequencies by selecting when to reset the counter.
     10 * With the prescaler setting you can select which bit of the counter is used
     11 * to reset it. This implies that the higher the frequency the less remaining
     12 * bits are available for the actual counter.
     13 *
     14 * Let cnt[7:0] be the counter, clocked at 32kHz:
     15 * +-----------+--------+--------------+-----------+---------------+
     16 * | prescaler |  reset | counter bits | frequency | period length |
     17 * +-----------+--------+--------------+-----------+---------------+
     18 * |         0 | cnt[7] |     cnt[6:0] |    250 Hz |    4000000 ns |
     19 * |         1 | cnt[6] |     cnt[5:0] |    500 Hz |    2000000 ns |
     20 * |         2 | cnt[5] |     cnt[4:0] |     1 kHz |    1000000 ns |
     21 * |         3 | cnt[4] |     cnt[3:0] |     2 kHz |     500000 ns |
     22 * +-----------+--------+--------------+-----------+---------------+
     23 *
     24 * Limitations:
     25 * - The hardware cannot generate a 100% duty cycle if the prescaler is 0.
     26 * - The hardware cannot atomically set the prescaler and the counter value,
     27 *   which might lead to glitches and inconsistent states if a write fails.
     28 * - The counter is not reset if you switch the prescaler which leads
     29 *   to glitches, too.
     30 * - The duty cycle will switch immediately and not after a complete cycle.
     31 * - Depending on the actual implementation, disabling the PWM might have
     32 *   side effects. For example, if the output pin is shared with a GPIO pin
     33 *   it will automatically switch back to GPIO mode.
     34 */
     35
     36#include <linux/bitfield.h>
     37#include <linux/kernel.h>
     38#include <linux/mod_devicetable.h>
     39#include <linux/module.h>
     40#include <linux/platform_device.h>
     41#include <linux/pwm.h>
     42#include <linux/regmap.h>
     43
     44/*
     45 * PWM timer block registers.
     46 */
     47#define SL28CPLD_PWM_CTRL			0x00
     48#define   SL28CPLD_PWM_CTRL_ENABLE		BIT(7)
     49#define   SL28CPLD_PWM_CTRL_PRESCALER_MASK	GENMASK(1, 0)
     50#define SL28CPLD_PWM_CYCLE			0x01
     51#define   SL28CPLD_PWM_CYCLE_MAX		GENMASK(6, 0)
     52
     53#define SL28CPLD_PWM_CLK			32000 /* 32 kHz */
     54#define SL28CPLD_PWM_MAX_DUTY_CYCLE(prescaler)	(1 << (7 - (prescaler)))
     55#define SL28CPLD_PWM_PERIOD(prescaler) \
     56	(NSEC_PER_SEC / SL28CPLD_PWM_CLK * SL28CPLD_PWM_MAX_DUTY_CYCLE(prescaler))
     57
     58/*
     59 * We calculate the duty cycle like this:
     60 *   duty_cycle_ns = pwm_cycle_reg * max_period_ns / max_duty_cycle
     61 *
     62 * With
     63 *   max_period_ns = 1 << (7 - prescaler) / SL28CPLD_PWM_CLK * NSEC_PER_SEC
     64 *   max_duty_cycle = 1 << (7 - prescaler)
     65 * this then simplifies to:
     66 *   duty_cycle_ns = pwm_cycle_reg / SL28CPLD_PWM_CLK * NSEC_PER_SEC
     67 *                 = NSEC_PER_SEC / SL28CPLD_PWM_CLK * pwm_cycle_reg
     68 *
     69 * NSEC_PER_SEC is a multiple of SL28CPLD_PWM_CLK, therefore we're not losing
     70 * precision by doing the divison first.
     71 */
     72#define SL28CPLD_PWM_TO_DUTY_CYCLE(reg) \
     73	(NSEC_PER_SEC / SL28CPLD_PWM_CLK * (reg))
     74#define SL28CPLD_PWM_FROM_DUTY_CYCLE(duty_cycle) \
     75	(DIV_ROUND_DOWN_ULL((duty_cycle), NSEC_PER_SEC / SL28CPLD_PWM_CLK))
     76
     77#define sl28cpld_pwm_read(priv, reg, val) \
     78	regmap_read((priv)->regmap, (priv)->offset + (reg), (val))
     79#define sl28cpld_pwm_write(priv, reg, val) \
     80	regmap_write((priv)->regmap, (priv)->offset + (reg), (val))
     81
     82struct sl28cpld_pwm {
     83	struct pwm_chip pwm_chip;
     84	struct regmap *regmap;
     85	u32 offset;
     86};
     87#define sl28cpld_pwm_from_chip(_chip) \
     88	container_of(_chip, struct sl28cpld_pwm, pwm_chip)
     89
     90static void sl28cpld_pwm_get_state(struct pwm_chip *chip,
     91				   struct pwm_device *pwm,
     92				   struct pwm_state *state)
     93{
     94	struct sl28cpld_pwm *priv = sl28cpld_pwm_from_chip(chip);
     95	unsigned int reg;
     96	int prescaler;
     97
     98	sl28cpld_pwm_read(priv, SL28CPLD_PWM_CTRL, &reg);
     99
    100	state->enabled = reg & SL28CPLD_PWM_CTRL_ENABLE;
    101
    102	prescaler = FIELD_GET(SL28CPLD_PWM_CTRL_PRESCALER_MASK, reg);
    103	state->period = SL28CPLD_PWM_PERIOD(prescaler);
    104
    105	sl28cpld_pwm_read(priv, SL28CPLD_PWM_CYCLE, &reg);
    106	state->duty_cycle = SL28CPLD_PWM_TO_DUTY_CYCLE(reg);
    107	state->polarity = PWM_POLARITY_NORMAL;
    108
    109	/*
    110	 * Sanitize values for the PWM core. Depending on the prescaler it
    111	 * might happen that we calculate a duty_cycle greater than the actual
    112	 * period. This might happen if someone (e.g. the bootloader) sets an
    113	 * invalid combination of values. The behavior of the hardware is
    114	 * undefined in this case. But we need to report sane values back to
    115	 * the PWM core.
    116	 */
    117	state->duty_cycle = min(state->duty_cycle, state->period);
    118}
    119
    120static int sl28cpld_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
    121			      const struct pwm_state *state)
    122{
    123	struct sl28cpld_pwm *priv = sl28cpld_pwm_from_chip(chip);
    124	unsigned int cycle, prescaler;
    125	bool write_duty_cycle_first;
    126	int ret;
    127	u8 ctrl;
    128
    129	/* Polarity inversion is not supported */
    130	if (state->polarity != PWM_POLARITY_NORMAL)
    131		return -EINVAL;
    132
    133	/*
    134	 * Calculate the prescaler. Pick the biggest period that isn't
    135	 * bigger than the requested period.
    136	 */
    137	prescaler = DIV_ROUND_UP_ULL(SL28CPLD_PWM_PERIOD(0), state->period);
    138	prescaler = order_base_2(prescaler);
    139
    140	if (prescaler > field_max(SL28CPLD_PWM_CTRL_PRESCALER_MASK))
    141		return -ERANGE;
    142
    143	ctrl = FIELD_PREP(SL28CPLD_PWM_CTRL_PRESCALER_MASK, prescaler);
    144	if (state->enabled)
    145		ctrl |= SL28CPLD_PWM_CTRL_ENABLE;
    146
    147	cycle = SL28CPLD_PWM_FROM_DUTY_CYCLE(state->duty_cycle);
    148	cycle = min_t(unsigned int, cycle, SL28CPLD_PWM_MAX_DUTY_CYCLE(prescaler));
    149
    150	/*
    151	 * Work around the hardware limitation. See also above. Trap 100% duty
    152	 * cycle if the prescaler is 0. Set prescaler to 1 instead. We don't
    153	 * care about the frequency because its "all-one" in either case.
    154	 *
    155	 * We don't need to check the actual prescaler setting, because only
    156	 * if the prescaler is 0 we can have this particular value.
    157	 */
    158	if (cycle == SL28CPLD_PWM_MAX_DUTY_CYCLE(0)) {
    159		ctrl &= ~SL28CPLD_PWM_CTRL_PRESCALER_MASK;
    160		ctrl |= FIELD_PREP(SL28CPLD_PWM_CTRL_PRESCALER_MASK, 1);
    161		cycle = SL28CPLD_PWM_MAX_DUTY_CYCLE(1);
    162	}
    163
    164	/*
    165	 * To avoid glitches when we switch the prescaler, we have to make sure
    166	 * we have a valid duty cycle for the new mode.
    167	 *
    168	 * Take the current prescaler (or the current period length) into
    169	 * account to decide whether we have to write the duty cycle or the new
    170	 * prescaler first. If the period length is decreasing we have to
    171	 * write the duty cycle first.
    172	 */
    173	write_duty_cycle_first = pwm->state.period > state->period;
    174
    175	if (write_duty_cycle_first) {
    176		ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CYCLE, cycle);
    177		if (ret)
    178			return ret;
    179	}
    180
    181	ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CTRL, ctrl);
    182	if (ret)
    183		return ret;
    184
    185	if (!write_duty_cycle_first) {
    186		ret = sl28cpld_pwm_write(priv, SL28CPLD_PWM_CYCLE, cycle);
    187		if (ret)
    188			return ret;
    189	}
    190
    191	return 0;
    192}
    193
    194static const struct pwm_ops sl28cpld_pwm_ops = {
    195	.apply = sl28cpld_pwm_apply,
    196	.get_state = sl28cpld_pwm_get_state,
    197	.owner = THIS_MODULE,
    198};
    199
    200static int sl28cpld_pwm_probe(struct platform_device *pdev)
    201{
    202	struct sl28cpld_pwm *priv;
    203	struct pwm_chip *chip;
    204	int ret;
    205
    206	if (!pdev->dev.parent) {
    207		dev_err(&pdev->dev, "no parent device\n");
    208		return -ENODEV;
    209	}
    210
    211	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    212	if (!priv)
    213		return -ENOMEM;
    214
    215	priv->regmap = dev_get_regmap(pdev->dev.parent, NULL);
    216	if (!priv->regmap) {
    217		dev_err(&pdev->dev, "could not get parent regmap\n");
    218		return -ENODEV;
    219	}
    220
    221	ret = device_property_read_u32(&pdev->dev, "reg", &priv->offset);
    222	if (ret) {
    223		dev_err(&pdev->dev, "no 'reg' property found (%pe)\n",
    224			ERR_PTR(ret));
    225		return -EINVAL;
    226	}
    227
    228	/* Initialize the pwm_chip structure */
    229	chip = &priv->pwm_chip;
    230	chip->dev = &pdev->dev;
    231	chip->ops = &sl28cpld_pwm_ops;
    232	chip->npwm = 1;
    233
    234	ret = devm_pwmchip_add(&pdev->dev, &priv->pwm_chip);
    235	if (ret) {
    236		dev_err(&pdev->dev, "failed to add PWM chip (%pe)",
    237			ERR_PTR(ret));
    238		return ret;
    239	}
    240
    241	return 0;
    242}
    243
    244static const struct of_device_id sl28cpld_pwm_of_match[] = {
    245	{ .compatible = "kontron,sl28cpld-pwm" },
    246	{}
    247};
    248MODULE_DEVICE_TABLE(of, sl28cpld_pwm_of_match);
    249
    250static struct platform_driver sl28cpld_pwm_driver = {
    251	.probe = sl28cpld_pwm_probe,
    252	.driver = {
    253		.name = "sl28cpld-pwm",
    254		.of_match_table = sl28cpld_pwm_of_match,
    255	},
    256};
    257module_platform_driver(sl28cpld_pwm_driver);
    258
    259MODULE_DESCRIPTION("sl28cpld PWM Driver");
    260MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
    261MODULE_LICENSE("GPL");