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-dwc.c (7955B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * DesignWare PWM Controller driver
      4 *
      5 * Copyright (C) 2018-2020 Intel Corporation
      6 *
      7 * Author: Felipe Balbi (Intel)
      8 * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
      9 * Author: Raymond Tan <raymond.tan@intel.com>
     10 *
     11 * Limitations:
     12 * - The hardware cannot generate a 0 % or 100 % duty cycle. Both high and low
     13 *   periods are one or more input clock periods long.
     14 */
     15
     16#include <linux/bitops.h>
     17#include <linux/export.h>
     18#include <linux/kernel.h>
     19#include <linux/module.h>
     20#include <linux/pci.h>
     21#include <linux/pm_runtime.h>
     22#include <linux/pwm.h>
     23
     24#define DWC_TIM_LD_CNT(n)	((n) * 0x14)
     25#define DWC_TIM_LD_CNT2(n)	(((n) * 4) + 0xb0)
     26#define DWC_TIM_CUR_VAL(n)	(((n) * 0x14) + 0x04)
     27#define DWC_TIM_CTRL(n)		(((n) * 0x14) + 0x08)
     28#define DWC_TIM_EOI(n)		(((n) * 0x14) + 0x0c)
     29#define DWC_TIM_INT_STS(n)	(((n) * 0x14) + 0x10)
     30
     31#define DWC_TIMERS_INT_STS	0xa0
     32#define DWC_TIMERS_EOI		0xa4
     33#define DWC_TIMERS_RAW_INT_STS	0xa8
     34#define DWC_TIMERS_COMP_VERSION	0xac
     35
     36#define DWC_TIMERS_TOTAL	8
     37#define DWC_CLK_PERIOD_NS	10
     38
     39/* Timer Control Register */
     40#define DWC_TIM_CTRL_EN		BIT(0)
     41#define DWC_TIM_CTRL_MODE	BIT(1)
     42#define DWC_TIM_CTRL_MODE_FREE	(0 << 1)
     43#define DWC_TIM_CTRL_MODE_USER	(1 << 1)
     44#define DWC_TIM_CTRL_INT_MASK	BIT(2)
     45#define DWC_TIM_CTRL_PWM	BIT(3)
     46
     47struct dwc_pwm_ctx {
     48	u32 cnt;
     49	u32 cnt2;
     50	u32 ctrl;
     51};
     52
     53struct dwc_pwm {
     54	struct pwm_chip chip;
     55	void __iomem *base;
     56	struct dwc_pwm_ctx ctx[DWC_TIMERS_TOTAL];
     57};
     58#define to_dwc_pwm(p)	(container_of((p), struct dwc_pwm, chip))
     59
     60static inline u32 dwc_pwm_readl(struct dwc_pwm *dwc, u32 offset)
     61{
     62	return readl(dwc->base + offset);
     63}
     64
     65static inline void dwc_pwm_writel(struct dwc_pwm *dwc, u32 value, u32 offset)
     66{
     67	writel(value, dwc->base + offset);
     68}
     69
     70static void __dwc_pwm_set_enable(struct dwc_pwm *dwc, int pwm, int enabled)
     71{
     72	u32 reg;
     73
     74	reg = dwc_pwm_readl(dwc, DWC_TIM_CTRL(pwm));
     75
     76	if (enabled)
     77		reg |= DWC_TIM_CTRL_EN;
     78	else
     79		reg &= ~DWC_TIM_CTRL_EN;
     80
     81	dwc_pwm_writel(dwc, reg, DWC_TIM_CTRL(pwm));
     82}
     83
     84static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc,
     85				     struct pwm_device *pwm,
     86				     const struct pwm_state *state)
     87{
     88	u64 tmp;
     89	u32 ctrl;
     90	u32 high;
     91	u32 low;
     92
     93	/*
     94	 * Calculate width of low and high period in terms of input clock
     95	 * periods and check are the result within HW limits between 1 and
     96	 * 2^32 periods.
     97	 */
     98	tmp = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, DWC_CLK_PERIOD_NS);
     99	if (tmp < 1 || tmp > (1ULL << 32))
    100		return -ERANGE;
    101	low = tmp - 1;
    102
    103	tmp = DIV_ROUND_CLOSEST_ULL(state->period - state->duty_cycle,
    104				    DWC_CLK_PERIOD_NS);
    105	if (tmp < 1 || tmp > (1ULL << 32))
    106		return -ERANGE;
    107	high = tmp - 1;
    108
    109	/*
    110	 * Specification says timer usage flow is to disable timer, then
    111	 * program it followed by enable. It also says Load Count is loaded
    112	 * into timer after it is enabled - either after a disable or
    113	 * a reset. Based on measurements it happens also without disable
    114	 * whenever Load Count is updated. But follow the specification.
    115	 */
    116	__dwc_pwm_set_enable(dwc, pwm->hwpwm, false);
    117
    118	/*
    119	 * Write Load Count and Load Count 2 registers. Former defines the
    120	 * width of low period and latter the width of high period in terms
    121	 * multiple of input clock periods:
    122	 * Width = ((Count + 1) * input clock period).
    123	 */
    124	dwc_pwm_writel(dwc, low, DWC_TIM_LD_CNT(pwm->hwpwm));
    125	dwc_pwm_writel(dwc, high, DWC_TIM_LD_CNT2(pwm->hwpwm));
    126
    127	/*
    128	 * Set user-defined mode, timer reloads from Load Count registers
    129	 * when it counts down to 0.
    130	 * Set PWM mode, it makes output to toggle and width of low and high
    131	 * periods are set by Load Count registers.
    132	 */
    133	ctrl = DWC_TIM_CTRL_MODE_USER | DWC_TIM_CTRL_PWM;
    134	dwc_pwm_writel(dwc, ctrl, DWC_TIM_CTRL(pwm->hwpwm));
    135
    136	/*
    137	 * Enable timer. Output starts from low period.
    138	 */
    139	__dwc_pwm_set_enable(dwc, pwm->hwpwm, state->enabled);
    140
    141	return 0;
    142}
    143
    144static int dwc_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
    145			 const struct pwm_state *state)
    146{
    147	struct dwc_pwm *dwc = to_dwc_pwm(chip);
    148
    149	if (state->polarity != PWM_POLARITY_INVERSED)
    150		return -EINVAL;
    151
    152	if (state->enabled) {
    153		if (!pwm->state.enabled)
    154			pm_runtime_get_sync(chip->dev);
    155		return __dwc_pwm_configure_timer(dwc, pwm, state);
    156	} else {
    157		if (pwm->state.enabled) {
    158			__dwc_pwm_set_enable(dwc, pwm->hwpwm, false);
    159			pm_runtime_put_sync(chip->dev);
    160		}
    161	}
    162
    163	return 0;
    164}
    165
    166static void dwc_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
    167			      struct pwm_state *state)
    168{
    169	struct dwc_pwm *dwc = to_dwc_pwm(chip);
    170	u64 duty, period;
    171
    172	pm_runtime_get_sync(chip->dev);
    173
    174	state->enabled = !!(dwc_pwm_readl(dwc,
    175				DWC_TIM_CTRL(pwm->hwpwm)) & DWC_TIM_CTRL_EN);
    176
    177	duty = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(pwm->hwpwm));
    178	duty += 1;
    179	duty *= DWC_CLK_PERIOD_NS;
    180	state->duty_cycle = duty;
    181
    182	period = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(pwm->hwpwm));
    183	period += 1;
    184	period *= DWC_CLK_PERIOD_NS;
    185	period += duty;
    186	state->period = period;
    187
    188	state->polarity = PWM_POLARITY_INVERSED;
    189
    190	pm_runtime_put_sync(chip->dev);
    191}
    192
    193static const struct pwm_ops dwc_pwm_ops = {
    194	.apply = dwc_pwm_apply,
    195	.get_state = dwc_pwm_get_state,
    196	.owner = THIS_MODULE,
    197};
    198
    199static int dwc_pwm_probe(struct pci_dev *pci, const struct pci_device_id *id)
    200{
    201	struct device *dev = &pci->dev;
    202	struct dwc_pwm *dwc;
    203	int ret;
    204
    205	dwc = devm_kzalloc(&pci->dev, sizeof(*dwc), GFP_KERNEL);
    206	if (!dwc)
    207		return -ENOMEM;
    208
    209	ret = pcim_enable_device(pci);
    210	if (ret) {
    211		dev_err(&pci->dev,
    212			"Failed to enable device (%pe)\n", ERR_PTR(ret));
    213		return ret;
    214	}
    215
    216	pci_set_master(pci);
    217
    218	ret = pcim_iomap_regions(pci, BIT(0), pci_name(pci));
    219	if (ret) {
    220		dev_err(&pci->dev,
    221			"Failed to iomap PCI BAR (%pe)\n", ERR_PTR(ret));
    222		return ret;
    223	}
    224
    225	dwc->base = pcim_iomap_table(pci)[0];
    226	if (!dwc->base) {
    227		dev_err(&pci->dev, "Base address missing\n");
    228		return -ENOMEM;
    229	}
    230
    231	pci_set_drvdata(pci, dwc);
    232
    233	dwc->chip.dev = dev;
    234	dwc->chip.ops = &dwc_pwm_ops;
    235	dwc->chip.npwm = DWC_TIMERS_TOTAL;
    236
    237	ret = pwmchip_add(&dwc->chip);
    238	if (ret)
    239		return ret;
    240
    241	pm_runtime_put(dev);
    242	pm_runtime_allow(dev);
    243
    244	return 0;
    245}
    246
    247static void dwc_pwm_remove(struct pci_dev *pci)
    248{
    249	struct dwc_pwm *dwc = pci_get_drvdata(pci);
    250
    251	pm_runtime_forbid(&pci->dev);
    252	pm_runtime_get_noresume(&pci->dev);
    253
    254	pwmchip_remove(&dwc->chip);
    255}
    256
    257#ifdef CONFIG_PM_SLEEP
    258static int dwc_pwm_suspend(struct device *dev)
    259{
    260	struct pci_dev *pdev = container_of(dev, struct pci_dev, dev);
    261	struct dwc_pwm *dwc = pci_get_drvdata(pdev);
    262	int i;
    263
    264	for (i = 0; i < DWC_TIMERS_TOTAL; i++) {
    265		if (dwc->chip.pwms[i].state.enabled) {
    266			dev_err(dev, "PWM %u in use by consumer (%s)\n",
    267				i, dwc->chip.pwms[i].label);
    268			return -EBUSY;
    269		}
    270		dwc->ctx[i].cnt = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(i));
    271		dwc->ctx[i].cnt2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(i));
    272		dwc->ctx[i].ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(i));
    273	}
    274
    275	return 0;
    276}
    277
    278static int dwc_pwm_resume(struct device *dev)
    279{
    280	struct pci_dev *pdev = container_of(dev, struct pci_dev, dev);
    281	struct dwc_pwm *dwc = pci_get_drvdata(pdev);
    282	int i;
    283
    284	for (i = 0; i < DWC_TIMERS_TOTAL; i++) {
    285		dwc_pwm_writel(dwc, dwc->ctx[i].cnt, DWC_TIM_LD_CNT(i));
    286		dwc_pwm_writel(dwc, dwc->ctx[i].cnt2, DWC_TIM_LD_CNT2(i));
    287		dwc_pwm_writel(dwc, dwc->ctx[i].ctrl, DWC_TIM_CTRL(i));
    288	}
    289
    290	return 0;
    291}
    292#endif
    293
    294static SIMPLE_DEV_PM_OPS(dwc_pwm_pm_ops, dwc_pwm_suspend, dwc_pwm_resume);
    295
    296static const struct pci_device_id dwc_pwm_id_table[] = {
    297	{ PCI_VDEVICE(INTEL, 0x4bb7) }, /* Elkhart Lake */
    298	{  }	/* Terminating Entry */
    299};
    300MODULE_DEVICE_TABLE(pci, dwc_pwm_id_table);
    301
    302static struct pci_driver dwc_pwm_driver = {
    303	.name = "pwm-dwc",
    304	.probe = dwc_pwm_probe,
    305	.remove = dwc_pwm_remove,
    306	.id_table = dwc_pwm_id_table,
    307	.driver = {
    308		.pm = &dwc_pwm_pm_ops,
    309	},
    310};
    311
    312module_pci_driver(dwc_pwm_driver);
    313
    314MODULE_AUTHOR("Felipe Balbi (Intel)");
    315MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
    316MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>");
    317MODULE_DESCRIPTION("DesignWare PWM Controller");
    318MODULE_LICENSE("GPL");