brintos

brintos / linux-shallow public Read only

0
0
Text · 4.6 KiB · c842549 Raw
184 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * DesignWare PWM Controller driver core4 *5 * Copyright (C) 2018-2020 Intel Corporation6 *7 * Author: Felipe Balbi (Intel)8 * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>9 * Author: Raymond Tan <raymond.tan@intel.com>10 */11 12#define DEFAULT_SYMBOL_NAMESPACE dwc_pwm13 14#include <linux/bitops.h>15#include <linux/export.h>16#include <linux/kernel.h>17#include <linux/module.h>18#include <linux/pci.h>19#include <linux/pm_runtime.h>20#include <linux/pwm.h>21 22#include "pwm-dwc.h"23 24static void __dwc_pwm_set_enable(struct dwc_pwm *dwc, int pwm, int enabled)25{26	u32 reg;27 28	reg = dwc_pwm_readl(dwc, DWC_TIM_CTRL(pwm));29 30	if (enabled)31		reg |= DWC_TIM_CTRL_EN;32	else33		reg &= ~DWC_TIM_CTRL_EN;34 35	dwc_pwm_writel(dwc, reg, DWC_TIM_CTRL(pwm));36}37 38static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc,39				     struct pwm_device *pwm,40				     const struct pwm_state *state)41{42	u64 tmp;43	u32 ctrl;44	u32 high;45	u32 low;46 47	/*48	 * Calculate width of low and high period in terms of input clock49	 * periods and check are the result within HW limits between 1 and50	 * 2^32 periods.51	 */52	tmp = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, dwc->clk_ns);53	if (tmp < 1 || tmp > (1ULL << 32))54		return -ERANGE;55	low = tmp - 1;56 57	tmp = DIV_ROUND_CLOSEST_ULL(state->period - state->duty_cycle,58				    dwc->clk_ns);59	if (tmp < 1 || tmp > (1ULL << 32))60		return -ERANGE;61	high = tmp - 1;62 63	/*64	 * Specification says timer usage flow is to disable timer, then65	 * program it followed by enable. It also says Load Count is loaded66	 * into timer after it is enabled - either after a disable or67	 * a reset. Based on measurements it happens also without disable68	 * whenever Load Count is updated. But follow the specification.69	 */70	__dwc_pwm_set_enable(dwc, pwm->hwpwm, false);71 72	/*73	 * Write Load Count and Load Count 2 registers. Former defines the74	 * width of low period and latter the width of high period in terms75	 * multiple of input clock periods:76	 * Width = ((Count + 1) * input clock period).77	 */78	dwc_pwm_writel(dwc, low, DWC_TIM_LD_CNT(pwm->hwpwm));79	dwc_pwm_writel(dwc, high, DWC_TIM_LD_CNT2(pwm->hwpwm));80 81	/*82	 * Set user-defined mode, timer reloads from Load Count registers83	 * when it counts down to 0.84	 * Set PWM mode, it makes output to toggle and width of low and high85	 * periods are set by Load Count registers.86	 */87	ctrl = DWC_TIM_CTRL_MODE_USER | DWC_TIM_CTRL_PWM;88	dwc_pwm_writel(dwc, ctrl, DWC_TIM_CTRL(pwm->hwpwm));89 90	/*91	 * Enable timer. Output starts from low period.92	 */93	__dwc_pwm_set_enable(dwc, pwm->hwpwm, state->enabled);94 95	return 0;96}97 98static int dwc_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,99			 const struct pwm_state *state)100{101	struct dwc_pwm *dwc = to_dwc_pwm(chip);102 103	if (state->polarity != PWM_POLARITY_INVERSED)104		return -EINVAL;105 106	if (state->enabled) {107		if (!pwm->state.enabled)108			pm_runtime_get_sync(pwmchip_parent(chip));109		return __dwc_pwm_configure_timer(dwc, pwm, state);110	} else {111		if (pwm->state.enabled) {112			__dwc_pwm_set_enable(dwc, pwm->hwpwm, false);113			pm_runtime_put_sync(pwmchip_parent(chip));114		}115	}116 117	return 0;118}119 120static int dwc_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,121			     struct pwm_state *state)122{123	struct dwc_pwm *dwc = to_dwc_pwm(chip);124	u64 duty, period;125	u32 ctrl, ld, ld2;126 127	pm_runtime_get_sync(pwmchip_parent(chip));128 129	ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(pwm->hwpwm));130	ld = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(pwm->hwpwm));131	ld2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(pwm->hwpwm));132 133	state->enabled = !!(ctrl & DWC_TIM_CTRL_EN);134 135	/*136	 * If we're not in PWM, technically the output is a 50-50137	 * based on the timer load-count only.138	 */139	if (ctrl & DWC_TIM_CTRL_PWM) {140		duty = (ld + 1) * dwc->clk_ns;141		period = (ld2 + 1)  * dwc->clk_ns;142		period += duty;143	} else {144		duty = (ld + 1) * dwc->clk_ns;145		period = duty * 2;146	}147 148	state->polarity = PWM_POLARITY_INVERSED;149	state->period = period;150	state->duty_cycle = duty;151 152	pm_runtime_put_sync(pwmchip_parent(chip));153 154	return 0;155}156 157static const struct pwm_ops dwc_pwm_ops = {158	.apply = dwc_pwm_apply,159	.get_state = dwc_pwm_get_state,160};161 162struct pwm_chip *dwc_pwm_alloc(struct device *dev)163{164	struct pwm_chip *chip;165	struct dwc_pwm *dwc;166 167	chip = devm_pwmchip_alloc(dev, DWC_TIMERS_TOTAL, sizeof(*dwc));168	if (IS_ERR(chip))169		return chip;170	dwc = to_dwc_pwm(chip);171 172	dwc->clk_ns = 10;173	chip->ops = &dwc_pwm_ops;174 175	return chip;176}177EXPORT_SYMBOL_GPL(dwc_pwm_alloc);178 179MODULE_AUTHOR("Felipe Balbi (Intel)");180MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");181MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>");182MODULE_DESCRIPTION("DesignWare PWM Controller");183MODULE_LICENSE("GPL");184