forked from mirrors/linux
		
	pwm: Imagination Technologies PWM DAC driver
The Pistachio SOC from Imagination Technologies includes a Pulse Width Modulation DAC which produces 1 to 4 digital bit-outputs which represent digital waveforms. These PWM outputs are primarily in charge of controlling backlight LED devices. Reviewed-by: Andrew Bresticker <abrestic@chromium.org> Signed-off-by: Naidu Tellapati <Naidu.Tellapati@imgtec.com> Signed-off-by: Sai Masarapu <Sai.Masarapu@imgtec.com> Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com> Reviewed-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com> [thierry.reding: fixup license header as discussed on list] Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
This commit is contained in:
		
							parent
							
								
									cd264b6a65
								
							
						
					
					
						commit
						277bb6a29e
					
				
					 3 changed files with 263 additions and 0 deletions
				
			
		|  | @ -131,6 +131,19 @@ config PWM_FSL_FTM | ||||||
| 	  To compile this driver as a module, choose M here: the module | 	  To compile this driver as a module, choose M here: the module | ||||||
| 	  will be called pwm-fsl-ftm. | 	  will be called pwm-fsl-ftm. | ||||||
| 
 | 
 | ||||||
|  | config PWM_IMG | ||||||
|  | 	tristate "Imagination Technologies PWM driver" | ||||||
|  | 	depends on HAS_IOMEM | ||||||
|  | 	depends on MFD_SYSCON | ||||||
|  | 	depends on COMMON_CLK | ||||||
|  | 	depends on MIPS || COMPILE_TEST | ||||||
|  | 	help | ||||||
|  | 	  Generic PWM framework driver for Imagination Technologies | ||||||
|  | 	  PWM block which supports 4 channels. | ||||||
|  | 
 | ||||||
|  | 	  To compile this driver as a module, choose M here: the module | ||||||
|  | 	  will be called pwm-img | ||||||
|  | 
 | ||||||
| config PWM_IMX | config PWM_IMX | ||||||
| 	tristate "i.MX PWM support" | 	tristate "i.MX PWM support" | ||||||
| 	depends on ARCH_MXC | 	depends on ARCH_MXC | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ obj-$(CONFIG_PWM_BFIN)		+= pwm-bfin.o | ||||||
| obj-$(CONFIG_PWM_CLPS711X)	+= pwm-clps711x.o | obj-$(CONFIG_PWM_CLPS711X)	+= pwm-clps711x.o | ||||||
| obj-$(CONFIG_PWM_EP93XX)	+= pwm-ep93xx.o | obj-$(CONFIG_PWM_EP93XX)	+= pwm-ep93xx.o | ||||||
| obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o | obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o | ||||||
|  | obj-$(CONFIG_PWM_IMG)		+= pwm-img.o | ||||||
| obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o | obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o | ||||||
| obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o | obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o | ||||||
| obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o | obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o | ||||||
|  |  | ||||||
							
								
								
									
										249
									
								
								drivers/pwm/pwm-img.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								drivers/pwm/pwm-img.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,249 @@ | ||||||
|  | /*
 | ||||||
|  |  * Imagination Technologies Pulse Width Modulator driver | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2015, Imagination Technologies | ||||||
|  |  * | ||||||
|  |  * Based on drivers/pwm/pwm-tegra.c, Copyright (c) 2010, NVIDIA Corporation | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 2 of the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <linux/clk.h> | ||||||
|  | #include <linux/err.h> | ||||||
|  | #include <linux/io.h> | ||||||
|  | #include <linux/mfd/syscon.h> | ||||||
|  | #include <linux/module.h> | ||||||
|  | #include <linux/of.h> | ||||||
|  | #include <linux/platform_device.h> | ||||||
|  | #include <linux/pwm.h> | ||||||
|  | #include <linux/regmap.h> | ||||||
|  | #include <linux/slab.h> | ||||||
|  | 
 | ||||||
|  | /* PWM registers */ | ||||||
|  | #define PWM_CTRL_CFG				0x0000 | ||||||
|  | #define PWM_CTRL_CFG_NO_SUB_DIV			0 | ||||||
|  | #define PWM_CTRL_CFG_SUB_DIV0			1 | ||||||
|  | #define PWM_CTRL_CFG_SUB_DIV1			2 | ||||||
|  | #define PWM_CTRL_CFG_SUB_DIV0_DIV1		3 | ||||||
|  | #define PWM_CTRL_CFG_DIV_SHIFT(ch)		((ch) * 2 + 4) | ||||||
|  | #define PWM_CTRL_CFG_DIV_MASK			0x3 | ||||||
|  | 
 | ||||||
|  | #define PWM_CH_CFG(ch)				(0x4 + (ch) * 4) | ||||||
|  | #define PWM_CH_CFG_TMBASE_SHIFT			0 | ||||||
|  | #define PWM_CH_CFG_DUTY_SHIFT			16 | ||||||
|  | 
 | ||||||
|  | #define PERIP_PWM_PDM_CONTROL			0x0140 | ||||||
|  | #define PERIP_PWM_PDM_CONTROL_CH_MASK		0x1 | ||||||
|  | #define PERIP_PWM_PDM_CONTROL_CH_SHIFT(ch)	((ch) * 4) | ||||||
|  | 
 | ||||||
|  | #define MAX_TMBASE_STEPS			65536 | ||||||
|  | 
 | ||||||
|  | struct img_pwm_chip { | ||||||
|  | 	struct device	*dev; | ||||||
|  | 	struct pwm_chip	chip; | ||||||
|  | 	struct clk	*pwm_clk; | ||||||
|  | 	struct clk	*sys_clk; | ||||||
|  | 	void __iomem	*base; | ||||||
|  | 	struct regmap	*periph_regs; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static inline struct img_pwm_chip *to_img_pwm_chip(struct pwm_chip *chip) | ||||||
|  | { | ||||||
|  | 	return container_of(chip, struct img_pwm_chip, chip); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void img_pwm_writel(struct img_pwm_chip *chip, | ||||||
|  | 				  u32 reg, u32 val) | ||||||
|  | { | ||||||
|  | 	writel(val, chip->base + reg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline u32 img_pwm_readl(struct img_pwm_chip *chip, | ||||||
|  | 					 u32 reg) | ||||||
|  | { | ||||||
|  | 	return readl(chip->base + reg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||||||
|  | 			  int duty_ns, int period_ns) | ||||||
|  | { | ||||||
|  | 	u32 val, div, duty, timebase; | ||||||
|  | 	unsigned long mul, output_clk_hz, input_clk_hz; | ||||||
|  | 	struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); | ||||||
|  | 
 | ||||||
|  | 	input_clk_hz = clk_get_rate(pwm_chip->pwm_clk); | ||||||
|  | 	output_clk_hz = DIV_ROUND_UP(NSEC_PER_SEC, period_ns); | ||||||
|  | 
 | ||||||
|  | 	mul = DIV_ROUND_UP(input_clk_hz, output_clk_hz); | ||||||
|  | 	if (mul <= MAX_TMBASE_STEPS) { | ||||||
|  | 		div = PWM_CTRL_CFG_NO_SUB_DIV; | ||||||
|  | 		timebase = DIV_ROUND_UP(mul, 1); | ||||||
|  | 	} else if (mul <= MAX_TMBASE_STEPS * 8) { | ||||||
|  | 		div = PWM_CTRL_CFG_SUB_DIV0; | ||||||
|  | 		timebase = DIV_ROUND_UP(mul, 8); | ||||||
|  | 	} else if (mul <= MAX_TMBASE_STEPS * 64) { | ||||||
|  | 		div = PWM_CTRL_CFG_SUB_DIV1; | ||||||
|  | 		timebase = DIV_ROUND_UP(mul, 64); | ||||||
|  | 	} else if (mul <= MAX_TMBASE_STEPS * 512) { | ||||||
|  | 		div = PWM_CTRL_CFG_SUB_DIV0_DIV1; | ||||||
|  | 		timebase = DIV_ROUND_UP(mul, 512); | ||||||
|  | 	} else if (mul > MAX_TMBASE_STEPS * 512) { | ||||||
|  | 		dev_err(chip->dev, | ||||||
|  | 			"failed to configure timebase steps/divider value\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	duty = DIV_ROUND_UP(timebase * duty_ns, period_ns); | ||||||
|  | 
 | ||||||
|  | 	val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); | ||||||
|  | 	val &= ~(PWM_CTRL_CFG_DIV_MASK << PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm)); | ||||||
|  | 	val |= (div & PWM_CTRL_CFG_DIV_MASK) << | ||||||
|  | 		PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm); | ||||||
|  | 	img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); | ||||||
|  | 
 | ||||||
|  | 	val = (duty << PWM_CH_CFG_DUTY_SHIFT) | | ||||||
|  | 	      (timebase << PWM_CH_CFG_TMBASE_SHIFT); | ||||||
|  | 	img_pwm_writel(pwm_chip, PWM_CH_CFG(pwm->hwpwm), val); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int img_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||||||
|  | { | ||||||
|  | 	u32 val; | ||||||
|  | 	struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); | ||||||
|  | 
 | ||||||
|  | 	val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); | ||||||
|  | 	val |= BIT(pwm->hwpwm); | ||||||
|  | 	img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); | ||||||
|  | 
 | ||||||
|  | 	regmap_update_bits(pwm_chip->periph_regs, PERIP_PWM_PDM_CONTROL, | ||||||
|  | 			   PERIP_PWM_PDM_CONTROL_CH_MASK << | ||||||
|  | 			   PERIP_PWM_PDM_CONTROL_CH_SHIFT(pwm->hwpwm), 0); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void img_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||||||
|  | { | ||||||
|  | 	u32 val; | ||||||
|  | 	struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); | ||||||
|  | 
 | ||||||
|  | 	val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); | ||||||
|  | 	val &= ~BIT(pwm->hwpwm); | ||||||
|  | 	img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const struct pwm_ops img_pwm_ops = { | ||||||
|  | 	.config = img_pwm_config, | ||||||
|  | 	.enable = img_pwm_enable, | ||||||
|  | 	.disable = img_pwm_disable, | ||||||
|  | 	.owner = THIS_MODULE, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static int img_pwm_probe(struct platform_device *pdev) | ||||||
|  | { | ||||||
|  | 	int ret; | ||||||
|  | 	struct resource *res; | ||||||
|  | 	struct img_pwm_chip *pwm; | ||||||
|  | 
 | ||||||
|  | 	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); | ||||||
|  | 	if (!pwm) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	pwm->dev = &pdev->dev; | ||||||
|  | 
 | ||||||
|  | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||||||
|  | 	pwm->base = devm_ioremap_resource(&pdev->dev, res); | ||||||
|  | 	if (IS_ERR(pwm->base)) | ||||||
|  | 		return PTR_ERR(pwm->base); | ||||||
|  | 
 | ||||||
|  | 	pwm->periph_regs = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | ||||||
|  | 							   "img,cr-periph"); | ||||||
|  | 	if (IS_ERR(pwm->periph_regs)) | ||||||
|  | 		return PTR_ERR(pwm->periph_regs); | ||||||
|  | 
 | ||||||
|  | 	pwm->sys_clk = devm_clk_get(&pdev->dev, "sys"); | ||||||
|  | 	if (IS_ERR(pwm->sys_clk)) { | ||||||
|  | 		dev_err(&pdev->dev, "failed to get system clock\n"); | ||||||
|  | 		return PTR_ERR(pwm->sys_clk); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pwm->pwm_clk = devm_clk_get(&pdev->dev, "pwm"); | ||||||
|  | 	if (IS_ERR(pwm->pwm_clk)) { | ||||||
|  | 		dev_err(&pdev->dev, "failed to get pwm clock\n"); | ||||||
|  | 		return PTR_ERR(pwm->pwm_clk); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ret = clk_prepare_enable(pwm->sys_clk); | ||||||
|  | 	if (ret < 0) { | ||||||
|  | 		dev_err(&pdev->dev, "could not prepare or enable sys clock\n"); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ret = clk_prepare_enable(pwm->pwm_clk); | ||||||
|  | 	if (ret < 0) { | ||||||
|  | 		dev_err(&pdev->dev, "could not prepare or enable pwm clock\n"); | ||||||
|  | 		goto disable_sysclk; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pwm->chip.dev = &pdev->dev; | ||||||
|  | 	pwm->chip.ops = &img_pwm_ops; | ||||||
|  | 	pwm->chip.base = -1; | ||||||
|  | 	pwm->chip.npwm = 4; | ||||||
|  | 
 | ||||||
|  | 	ret = pwmchip_add(&pwm->chip); | ||||||
|  | 	if (ret < 0) { | ||||||
|  | 		dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret); | ||||||
|  | 		goto disable_pwmclk; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	platform_set_drvdata(pdev, pwm); | ||||||
|  | 	return 0; | ||||||
|  | 
 | ||||||
|  | disable_pwmclk: | ||||||
|  | 	clk_disable_unprepare(pwm->pwm_clk); | ||||||
|  | disable_sysclk: | ||||||
|  | 	clk_disable_unprepare(pwm->sys_clk); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int img_pwm_remove(struct platform_device *pdev) | ||||||
|  | { | ||||||
|  | 	struct img_pwm_chip *pwm_chip = platform_get_drvdata(pdev); | ||||||
|  | 	u32 val; | ||||||
|  | 	unsigned int i; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < pwm_chip->chip.npwm; i++) { | ||||||
|  | 		val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); | ||||||
|  | 		val &= ~BIT(i); | ||||||
|  | 		img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	clk_disable_unprepare(pwm_chip->pwm_clk); | ||||||
|  | 	clk_disable_unprepare(pwm_chip->sys_clk); | ||||||
|  | 
 | ||||||
|  | 	return pwmchip_remove(&pwm_chip->chip); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const struct of_device_id img_pwm_of_match[] = { | ||||||
|  | 	{ .compatible = "img,pistachio-pwm", }, | ||||||
|  | 	{ } | ||||||
|  | }; | ||||||
|  | MODULE_DEVICE_TABLE(of, img_pwm_of_match); | ||||||
|  | 
 | ||||||
|  | static struct platform_driver img_pwm_driver = { | ||||||
|  | 	.driver = { | ||||||
|  | 		.name = "img-pwm", | ||||||
|  | 		.of_match_table = img_pwm_of_match, | ||||||
|  | 	}, | ||||||
|  | 	.probe = img_pwm_probe, | ||||||
|  | 	.remove = img_pwm_remove, | ||||||
|  | }; | ||||||
|  | module_platform_driver(img_pwm_driver); | ||||||
|  | 
 | ||||||
|  | MODULE_AUTHOR("Sai Masarapu <Sai.Masarapu@imgtec.com>"); | ||||||
|  | MODULE_DESCRIPTION("Imagination Technologies PWM DAC driver"); | ||||||
|  | MODULE_LICENSE("GPL v2"); | ||||||
		Loading…
	
		Reference in a new issue
	
	 Naidu Tellapati
						Naidu Tellapati