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-atmel-hlcdc.c (7521B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * Copyright (C) 2014 Free Electrons
      4 * Copyright (C) 2014 Atmel
      5 *
      6 * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
      7 */
      8
      9#include <linux/clk.h>
     10#include <linux/delay.h>
     11#include <linux/mfd/atmel-hlcdc.h>
     12#include <linux/module.h>
     13#include <linux/platform_device.h>
     14#include <linux/pwm.h>
     15#include <linux/regmap.h>
     16
     17#define ATMEL_HLCDC_PWMCVAL_MASK	GENMASK(15, 8)
     18#define ATMEL_HLCDC_PWMCVAL(x)		(((x) << 8) & ATMEL_HLCDC_PWMCVAL_MASK)
     19#define ATMEL_HLCDC_PWMPOL		BIT(4)
     20#define ATMEL_HLCDC_PWMPS_MASK		GENMASK(2, 0)
     21#define ATMEL_HLCDC_PWMPS_MAX		0x6
     22#define ATMEL_HLCDC_PWMPS(x)		((x) & ATMEL_HLCDC_PWMPS_MASK)
     23
     24struct atmel_hlcdc_pwm_errata {
     25	bool slow_clk_erratum;
     26	bool div1_clk_erratum;
     27};
     28
     29struct atmel_hlcdc_pwm {
     30	struct pwm_chip chip;
     31	struct atmel_hlcdc *hlcdc;
     32	struct clk *cur_clk;
     33	const struct atmel_hlcdc_pwm_errata *errata;
     34};
     35
     36static inline struct atmel_hlcdc_pwm *to_atmel_hlcdc_pwm(struct pwm_chip *chip)
     37{
     38	return container_of(chip, struct atmel_hlcdc_pwm, chip);
     39}
     40
     41static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
     42				 const struct pwm_state *state)
     43{
     44	struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
     45	struct atmel_hlcdc *hlcdc = chip->hlcdc;
     46	unsigned int status;
     47	int ret;
     48
     49	if (state->enabled) {
     50		struct clk *new_clk = hlcdc->slow_clk;
     51		u64 pwmcval = state->duty_cycle * 256;
     52		unsigned long clk_freq;
     53		u64 clk_period_ns;
     54		u32 pwmcfg;
     55		int pres;
     56
     57		if (!chip->errata || !chip->errata->slow_clk_erratum) {
     58			clk_freq = clk_get_rate(new_clk);
     59			if (!clk_freq)
     60				return -EINVAL;
     61
     62			clk_period_ns = (u64)NSEC_PER_SEC * 256;
     63			do_div(clk_period_ns, clk_freq);
     64		}
     65
     66		/* Errata: cannot use slow clk on some IP revisions */
     67		if ((chip->errata && chip->errata->slow_clk_erratum) ||
     68		    clk_period_ns > state->period) {
     69			new_clk = hlcdc->sys_clk;
     70			clk_freq = clk_get_rate(new_clk);
     71			if (!clk_freq)
     72				return -EINVAL;
     73
     74			clk_period_ns = (u64)NSEC_PER_SEC * 256;
     75			do_div(clk_period_ns, clk_freq);
     76		}
     77
     78		for (pres = 0; pres <= ATMEL_HLCDC_PWMPS_MAX; pres++) {
     79		/* Errata: cannot divide by 1 on some IP revisions */
     80			if (!pres && chip->errata &&
     81			    chip->errata->div1_clk_erratum)
     82				continue;
     83
     84			if ((clk_period_ns << pres) >= state->period)
     85				break;
     86		}
     87
     88		if (pres > ATMEL_HLCDC_PWMPS_MAX)
     89			return -EINVAL;
     90
     91		pwmcfg = ATMEL_HLCDC_PWMPS(pres);
     92
     93		if (new_clk != chip->cur_clk) {
     94			u32 gencfg = 0;
     95			int ret;
     96
     97			ret = clk_prepare_enable(new_clk);
     98			if (ret)
     99				return ret;
    100
    101			clk_disable_unprepare(chip->cur_clk);
    102			chip->cur_clk = new_clk;
    103
    104			if (new_clk == hlcdc->sys_clk)
    105				gencfg = ATMEL_HLCDC_CLKPWMSEL;
    106
    107			ret = regmap_update_bits(hlcdc->regmap,
    108						 ATMEL_HLCDC_CFG(0),
    109						 ATMEL_HLCDC_CLKPWMSEL,
    110						 gencfg);
    111			if (ret)
    112				return ret;
    113		}
    114
    115		do_div(pwmcval, state->period);
    116
    117		/*
    118		 * The PWM duty cycle is configurable from 0/256 to 255/256 of
    119		 * the period cycle. Hence we can't set a duty cycle occupying
    120		 * the whole period cycle if we're asked to.
    121		 * Set it to 255 if pwmcval is greater than 256.
    122		 */
    123		if (pwmcval > 255)
    124			pwmcval = 255;
    125
    126		pwmcfg |= ATMEL_HLCDC_PWMCVAL(pwmcval);
    127
    128		if (state->polarity == PWM_POLARITY_NORMAL)
    129			pwmcfg |= ATMEL_HLCDC_PWMPOL;
    130
    131		ret = regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6),
    132					 ATMEL_HLCDC_PWMCVAL_MASK |
    133					 ATMEL_HLCDC_PWMPS_MASK |
    134					 ATMEL_HLCDC_PWMPOL,
    135					 pwmcfg);
    136		if (ret)
    137			return ret;
    138
    139		ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_EN,
    140				   ATMEL_HLCDC_PWM);
    141		if (ret)
    142			return ret;
    143
    144		ret = regmap_read_poll_timeout(hlcdc->regmap, ATMEL_HLCDC_SR,
    145					       status,
    146					       status & ATMEL_HLCDC_PWM,
    147					       10, 0);
    148		if (ret)
    149			return ret;
    150	} else {
    151		ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_DIS,
    152				   ATMEL_HLCDC_PWM);
    153		if (ret)
    154			return ret;
    155
    156		ret = regmap_read_poll_timeout(hlcdc->regmap, ATMEL_HLCDC_SR,
    157					       status,
    158					       !(status & ATMEL_HLCDC_PWM),
    159					       10, 0);
    160		if (ret)
    161			return ret;
    162
    163		clk_disable_unprepare(chip->cur_clk);
    164		chip->cur_clk = NULL;
    165	}
    166
    167	return 0;
    168}
    169
    170static const struct pwm_ops atmel_hlcdc_pwm_ops = {
    171	.apply = atmel_hlcdc_pwm_apply,
    172	.owner = THIS_MODULE,
    173};
    174
    175static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_at91sam9x5_errata = {
    176	.slow_clk_erratum = true,
    177};
    178
    179static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_sama5d3_errata = {
    180	.div1_clk_erratum = true,
    181};
    182
    183#ifdef CONFIG_PM_SLEEP
    184static int atmel_hlcdc_pwm_suspend(struct device *dev)
    185{
    186	struct atmel_hlcdc_pwm *chip = dev_get_drvdata(dev);
    187
    188	/* Keep the periph clock enabled if the PWM is still running. */
    189	if (pwm_is_enabled(&chip->chip.pwms[0]))
    190		clk_disable_unprepare(chip->hlcdc->periph_clk);
    191
    192	return 0;
    193}
    194
    195static int atmel_hlcdc_pwm_resume(struct device *dev)
    196{
    197	struct atmel_hlcdc_pwm *chip = dev_get_drvdata(dev);
    198	struct pwm_state state;
    199	int ret;
    200
    201	pwm_get_state(&chip->chip.pwms[0], &state);
    202
    203	/* Re-enable the periph clock it was stopped during suspend. */
    204	if (!state.enabled) {
    205		ret = clk_prepare_enable(chip->hlcdc->periph_clk);
    206		if (ret)
    207			return ret;
    208	}
    209
    210	return atmel_hlcdc_pwm_apply(&chip->chip, &chip->chip.pwms[0], &state);
    211}
    212#endif
    213
    214static SIMPLE_DEV_PM_OPS(atmel_hlcdc_pwm_pm_ops,
    215			 atmel_hlcdc_pwm_suspend, atmel_hlcdc_pwm_resume);
    216
    217static const struct of_device_id atmel_hlcdc_dt_ids[] = {
    218	{
    219		.compatible = "atmel,at91sam9n12-hlcdc",
    220		/* 9n12 has same errata as 9x5 HLCDC PWM */
    221		.data = &atmel_hlcdc_pwm_at91sam9x5_errata,
    222	},
    223	{
    224		.compatible = "atmel,at91sam9x5-hlcdc",
    225		.data = &atmel_hlcdc_pwm_at91sam9x5_errata,
    226	},
    227	{
    228		.compatible = "atmel,sama5d2-hlcdc",
    229	},
    230	{
    231		.compatible = "atmel,sama5d3-hlcdc",
    232		.data = &atmel_hlcdc_pwm_sama5d3_errata,
    233	},
    234	{
    235		.compatible = "atmel,sama5d4-hlcdc",
    236		.data = &atmel_hlcdc_pwm_sama5d3_errata,
    237	},
    238	{	.compatible = "microchip,sam9x60-hlcdc", },
    239	{ /* sentinel */ },
    240};
    241MODULE_DEVICE_TABLE(of, atmel_hlcdc_dt_ids);
    242
    243static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
    244{
    245	const struct of_device_id *match;
    246	struct device *dev = &pdev->dev;
    247	struct atmel_hlcdc_pwm *chip;
    248	struct atmel_hlcdc *hlcdc;
    249	int ret;
    250
    251	hlcdc = dev_get_drvdata(dev->parent);
    252
    253	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
    254	if (!chip)
    255		return -ENOMEM;
    256
    257	ret = clk_prepare_enable(hlcdc->periph_clk);
    258	if (ret)
    259		return ret;
    260
    261	match = of_match_node(atmel_hlcdc_dt_ids, dev->parent->of_node);
    262	if (match)
    263		chip->errata = match->data;
    264
    265	chip->hlcdc = hlcdc;
    266	chip->chip.ops = &atmel_hlcdc_pwm_ops;
    267	chip->chip.dev = dev;
    268	chip->chip.npwm = 1;
    269
    270	ret = pwmchip_add(&chip->chip);
    271	if (ret) {
    272		clk_disable_unprepare(hlcdc->periph_clk);
    273		return ret;
    274	}
    275
    276	platform_set_drvdata(pdev, chip);
    277
    278	return 0;
    279}
    280
    281static int atmel_hlcdc_pwm_remove(struct platform_device *pdev)
    282{
    283	struct atmel_hlcdc_pwm *chip = platform_get_drvdata(pdev);
    284
    285	pwmchip_remove(&chip->chip);
    286
    287	clk_disable_unprepare(chip->hlcdc->periph_clk);
    288
    289	return 0;
    290}
    291
    292static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = {
    293	{ .compatible = "atmel,hlcdc-pwm" },
    294	{ /* sentinel */ },
    295};
    296
    297static struct platform_driver atmel_hlcdc_pwm_driver = {
    298	.driver = {
    299		.name = "atmel-hlcdc-pwm",
    300		.of_match_table = atmel_hlcdc_pwm_dt_ids,
    301		.pm = &atmel_hlcdc_pwm_pm_ops,
    302	},
    303	.probe = atmel_hlcdc_pwm_probe,
    304	.remove = atmel_hlcdc_pwm_remove,
    305};
    306module_platform_driver(atmel_hlcdc_pwm_driver);
    307
    308MODULE_ALIAS("platform:atmel-hlcdc-pwm");
    309MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
    310MODULE_DESCRIPTION("Atmel HLCDC PWM driver");
    311MODULE_LICENSE("GPL v2");