mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	pwm: Add support for Meson PWM Controller
Add support for the PWM controller found in the Amlogic SoCs. This driver supports the Meson8b and GXBB SoCs. Signed-off-by: Neil Armstrong <narmstrong@baylibre.com> Tested-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com> Tested-by: Jerome Brunet <jbrunet@baylibre.com> Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
This commit is contained in:
		
							parent
							
								
									89394cc7c2
								
							
						
					
					
						commit
						211ed63075
					
				
					 3 changed files with 530 additions and 0 deletions
				
			
		| 
						 | 
					@ -262,6 +262,15 @@ config PWM_LPSS_PLATFORM
 | 
				
			||||||
	  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-lpss-platform.
 | 
						  will be called pwm-lpss-platform.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config PWM_MESON
 | 
				
			||||||
 | 
						tristate "Amlogic Meson PWM driver"
 | 
				
			||||||
 | 
						depends on ARCH_MESON
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  The platform driver for Amlogic Meson PWM controller.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						  To compile this driver as a module, choose M here: the module
 | 
				
			||||||
 | 
						  will be called pwm-meson.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config PWM_MTK_DISP
 | 
					config PWM_MTK_DISP
 | 
				
			||||||
	tristate "MediaTek display PWM driver"
 | 
						tristate "MediaTek display PWM driver"
 | 
				
			||||||
	depends on ARCH_MEDIATEK || COMPILE_TEST
 | 
						depends on ARCH_MEDIATEK || COMPILE_TEST
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,7 @@ obj-$(CONFIG_PWM_LPC32XX)	+= pwm-lpc32xx.o
 | 
				
			||||||
obj-$(CONFIG_PWM_LPSS)		+= pwm-lpss.o
 | 
					obj-$(CONFIG_PWM_LPSS)		+= pwm-lpss.o
 | 
				
			||||||
obj-$(CONFIG_PWM_LPSS_PCI)	+= pwm-lpss-pci.o
 | 
					obj-$(CONFIG_PWM_LPSS_PCI)	+= pwm-lpss-pci.o
 | 
				
			||||||
obj-$(CONFIG_PWM_LPSS_PLATFORM)	+= pwm-lpss-platform.o
 | 
					obj-$(CONFIG_PWM_LPSS_PLATFORM)	+= pwm-lpss-platform.o
 | 
				
			||||||
 | 
					obj-$(CONFIG_PWM_MESON)		+= pwm-meson.o
 | 
				
			||||||
obj-$(CONFIG_PWM_MTK_DISP)	+= pwm-mtk-disp.o
 | 
					obj-$(CONFIG_PWM_MTK_DISP)	+= pwm-mtk-disp.o
 | 
				
			||||||
obj-$(CONFIG_PWM_MXS)		+= pwm-mxs.o
 | 
					obj-$(CONFIG_PWM_MXS)		+= pwm-mxs.o
 | 
				
			||||||
obj-$(CONFIG_PWM_OMAP_DMTIMER)	+= pwm-omap-dmtimer.o
 | 
					obj-$(CONFIG_PWM_OMAP_DMTIMER)	+= pwm-omap-dmtimer.o
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										520
									
								
								drivers/pwm/pwm-meson.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										520
									
								
								drivers/pwm/pwm-meson.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,520 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * This file is provided under a dual BSD/GPLv2 license.  When using or
 | 
				
			||||||
 | 
					 * redistributing this file, you may do so under either license.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * GPL LICENSE SUMMARY
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (c) 2016 BayLibre, SAS.
 | 
				
			||||||
 | 
					 * Author: Neil Armstrong <narmstrong@baylibre.com>
 | 
				
			||||||
 | 
					 * Copyright (C) 2014 Amlogic, Inc.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is free software; you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of version 2 of the GNU General Public License as
 | 
				
			||||||
 | 
					 * published by the Free Software Foundation.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is distributed in the hope that it will be useful, but
 | 
				
			||||||
 | 
					 * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
				
			||||||
 | 
					 * General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 * The full GNU General Public License is included in this distribution
 | 
				
			||||||
 | 
					 * in the file called COPYING.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * BSD LICENSE
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (c) 2016 BayLibre, SAS.
 | 
				
			||||||
 | 
					 * Author: Neil Armstrong <narmstrong@baylibre.com>
 | 
				
			||||||
 | 
					 * Copyright (C) 2014 Amlogic, Inc.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Redistribution and use in source and binary forms, with or without
 | 
				
			||||||
 | 
					 * modification, are permitted provided that the following conditions
 | 
				
			||||||
 | 
					 * are met:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *   * Redistributions of source code must retain the above copyright
 | 
				
			||||||
 | 
					 *     notice, this list of conditions and the following disclaimer.
 | 
				
			||||||
 | 
					 *   * Redistributions in binary form must reproduce the above copyright
 | 
				
			||||||
 | 
					 *     notice, this list of conditions and the following disclaimer in
 | 
				
			||||||
 | 
					 *     the documentation and/or other materials provided with the
 | 
				
			||||||
 | 
					 *     distribution.
 | 
				
			||||||
 | 
					 *   * Neither the name of Intel Corporation nor the names of its
 | 
				
			||||||
 | 
					 *     contributors may be used to endorse or promote products derived
 | 
				
			||||||
 | 
					 *     from this software without specific prior written permission.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
				
			||||||
 | 
					 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
				
			||||||
 | 
					 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
				
			||||||
 | 
					 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | 
				
			||||||
 | 
					 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
				
			||||||
 | 
					 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
				
			||||||
 | 
					 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
				
			||||||
 | 
					 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
				
			||||||
 | 
					 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
				
			||||||
 | 
					 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
				
			||||||
 | 
					 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/clk.h>
 | 
				
			||||||
 | 
					#include <linux/clk-provider.h>
 | 
				
			||||||
 | 
					#include <linux/err.h>
 | 
				
			||||||
 | 
					#include <linux/io.h>
 | 
				
			||||||
 | 
					#include <linux/kernel.h>
 | 
				
			||||||
 | 
					#include <linux/module.h>
 | 
				
			||||||
 | 
					#include <linux/of.h>
 | 
				
			||||||
 | 
					#include <linux/of_device.h>
 | 
				
			||||||
 | 
					#include <linux/platform_device.h>
 | 
				
			||||||
 | 
					#include <linux/pwm.h>
 | 
				
			||||||
 | 
					#include <linux/slab.h>
 | 
				
			||||||
 | 
					#include <linux/spinlock.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define REG_PWM_A		0x0
 | 
				
			||||||
 | 
					#define REG_PWM_B		0x4
 | 
				
			||||||
 | 
					#define PWM_HIGH_SHIFT		16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define REG_MISC_AB		0x8
 | 
				
			||||||
 | 
					#define MISC_B_CLK_EN		BIT(23)
 | 
				
			||||||
 | 
					#define MISC_A_CLK_EN		BIT(15)
 | 
				
			||||||
 | 
					#define MISC_CLK_DIV_MASK	0x7f
 | 
				
			||||||
 | 
					#define MISC_B_CLK_DIV_SHIFT	16
 | 
				
			||||||
 | 
					#define MISC_A_CLK_DIV_SHIFT	8
 | 
				
			||||||
 | 
					#define MISC_B_CLK_SEL_SHIFT	6
 | 
				
			||||||
 | 
					#define MISC_A_CLK_SEL_SHIFT	4
 | 
				
			||||||
 | 
					#define MISC_CLK_SEL_WIDTH	2
 | 
				
			||||||
 | 
					#define MISC_B_EN		BIT(1)
 | 
				
			||||||
 | 
					#define MISC_A_EN		BIT(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const unsigned int mux_reg_shifts[] = {
 | 
				
			||||||
 | 
						MISC_A_CLK_SEL_SHIFT,
 | 
				
			||||||
 | 
						MISC_B_CLK_SEL_SHIFT
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct meson_pwm_channel {
 | 
				
			||||||
 | 
						unsigned int hi;
 | 
				
			||||||
 | 
						unsigned int lo;
 | 
				
			||||||
 | 
						u8 pre_div;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct pwm_state state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct clk *clk_parent;
 | 
				
			||||||
 | 
						struct clk_mux mux;
 | 
				
			||||||
 | 
						struct clk *clk;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct meson_pwm_data {
 | 
				
			||||||
 | 
						const char * const *parent_names;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct meson_pwm {
 | 
				
			||||||
 | 
						struct pwm_chip chip;
 | 
				
			||||||
 | 
						const struct meson_pwm_data *data;
 | 
				
			||||||
 | 
						void __iomem *base;
 | 
				
			||||||
 | 
						u8 inverter_mask;
 | 
				
			||||||
 | 
						spinlock_t lock;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct meson_pwm *to_meson_pwm(struct pwm_chip *chip)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return container_of(chip, struct meson_pwm, chip);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);
 | 
				
			||||||
 | 
						struct device *dev = chip->dev;
 | 
				
			||||||
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!channel)
 | 
				
			||||||
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (channel->clk_parent) {
 | 
				
			||||||
 | 
							err = clk_set_parent(channel->clk, channel->clk_parent);
 | 
				
			||||||
 | 
							if (err < 0) {
 | 
				
			||||||
 | 
								dev_err(dev, "failed to set parent %s for %s: %d\n",
 | 
				
			||||||
 | 
									__clk_get_name(channel->clk_parent),
 | 
				
			||||||
 | 
									__clk_get_name(channel->clk), err);
 | 
				
			||||||
 | 
									return err;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = clk_prepare_enable(channel->clk);
 | 
				
			||||||
 | 
						if (err < 0) {
 | 
				
			||||||
 | 
							dev_err(dev, "failed to enable clock %s: %d\n",
 | 
				
			||||||
 | 
								__clk_get_name(channel->clk), err);
 | 
				
			||||||
 | 
							return err;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						chip->ops->get_state(chip, pwm, &channel->state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (channel)
 | 
				
			||||||
 | 
							clk_disable_unprepare(channel->clk);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int meson_pwm_calc(struct meson_pwm *meson,
 | 
				
			||||||
 | 
								  struct meson_pwm_channel *channel, unsigned int id,
 | 
				
			||||||
 | 
								  unsigned int duty, unsigned int period)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned int pre_div, cnt, duty_cnt;
 | 
				
			||||||
 | 
						unsigned long fin_freq = -1, fin_ns;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (~(meson->inverter_mask >> id) & 0x1)
 | 
				
			||||||
 | 
							duty = period - duty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (period == channel->state.period &&
 | 
				
			||||||
 | 
						    duty == channel->state.duty_cycle)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fin_freq = clk_get_rate(channel->clk);
 | 
				
			||||||
 | 
						if (fin_freq == 0) {
 | 
				
			||||||
 | 
							dev_err(meson->chip.dev, "invalid source clock frequency\n");
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq);
 | 
				
			||||||
 | 
						fin_ns = NSEC_PER_SEC / fin_freq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Calc pre_div with the period */
 | 
				
			||||||
 | 
						for (pre_div = 0; pre_div < MISC_CLK_DIV_MASK; pre_div++) {
 | 
				
			||||||
 | 
							cnt = DIV_ROUND_CLOSEST(period, fin_ns * (pre_div + 1));
 | 
				
			||||||
 | 
							dev_dbg(meson->chip.dev, "fin_ns=%lu pre_div=%u cnt=%u\n",
 | 
				
			||||||
 | 
								fin_ns, pre_div, cnt);
 | 
				
			||||||
 | 
							if (cnt <= 0xffff)
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (pre_div == MISC_CLK_DIV_MASK) {
 | 
				
			||||||
 | 
							dev_err(meson->chip.dev, "unable to get period pre_div\n");
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period,
 | 
				
			||||||
 | 
							pre_div, cnt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (duty == period) {
 | 
				
			||||||
 | 
							channel->pre_div = pre_div;
 | 
				
			||||||
 | 
							channel->hi = cnt;
 | 
				
			||||||
 | 
							channel->lo = 0;
 | 
				
			||||||
 | 
						} else if (duty == 0) {
 | 
				
			||||||
 | 
							channel->pre_div = pre_div;
 | 
				
			||||||
 | 
							channel->hi = 0;
 | 
				
			||||||
 | 
							channel->lo = cnt;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							/* Then check is we can have the duty with the same pre_div */
 | 
				
			||||||
 | 
							duty_cnt = DIV_ROUND_CLOSEST(duty, fin_ns * (pre_div + 1));
 | 
				
			||||||
 | 
							if (duty_cnt > 0xffff) {
 | 
				
			||||||
 | 
								dev_err(meson->chip.dev, "unable to get duty cycle\n");
 | 
				
			||||||
 | 
								return -EINVAL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							dev_dbg(meson->chip.dev, "duty=%u pre_div=%u duty_cnt=%u\n",
 | 
				
			||||||
 | 
								duty, pre_div, duty_cnt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							channel->pre_div = pre_div;
 | 
				
			||||||
 | 
							channel->hi = duty_cnt;
 | 
				
			||||||
 | 
							channel->lo = cnt - duty_cnt;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void meson_pwm_enable(struct meson_pwm *meson,
 | 
				
			||||||
 | 
								     struct meson_pwm_channel *channel,
 | 
				
			||||||
 | 
								     unsigned int id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u32 value, clk_shift, clk_enable, enable;
 | 
				
			||||||
 | 
						unsigned int offset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (id) {
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
							clk_shift = MISC_A_CLK_DIV_SHIFT;
 | 
				
			||||||
 | 
							clk_enable = MISC_A_CLK_EN;
 | 
				
			||||||
 | 
							enable = MISC_A_EN;
 | 
				
			||||||
 | 
							offset = REG_PWM_A;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case 1:
 | 
				
			||||||
 | 
							clk_shift = MISC_B_CLK_DIV_SHIFT;
 | 
				
			||||||
 | 
							clk_enable = MISC_B_CLK_EN;
 | 
				
			||||||
 | 
							enable = MISC_B_EN;
 | 
				
			||||||
 | 
							offset = REG_PWM_B;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						value = readl(meson->base + REG_MISC_AB);
 | 
				
			||||||
 | 
						value &= ~(MISC_CLK_DIV_MASK << clk_shift);
 | 
				
			||||||
 | 
						value |= channel->pre_div << clk_shift;
 | 
				
			||||||
 | 
						value |= clk_enable;
 | 
				
			||||||
 | 
						writel(value, meson->base + REG_MISC_AB);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						value = (channel->hi << PWM_HIGH_SHIFT) | channel->lo;
 | 
				
			||||||
 | 
						writel(value, meson->base + offset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						value = readl(meson->base + REG_MISC_AB);
 | 
				
			||||||
 | 
						value |= enable;
 | 
				
			||||||
 | 
						writel(value, meson->base + REG_MISC_AB);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void meson_pwm_disable(struct meson_pwm *meson, unsigned int id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u32 value, enable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (id) {
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
							enable = MISC_A_EN;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case 1:
 | 
				
			||||||
 | 
							enable = MISC_B_EN;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						value = readl(meson->base + REG_MISC_AB);
 | 
				
			||||||
 | 
						value &= ~enable;
 | 
				
			||||||
 | 
						writel(value, meson->base + REG_MISC_AB);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 | 
				
			||||||
 | 
								   struct pwm_state *state)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);
 | 
				
			||||||
 | 
						struct meson_pwm *meson = to_meson_pwm(chip);
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						int err = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!state)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irqsave(&meson->lock, flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!state->enabled) {
 | 
				
			||||||
 | 
							meson_pwm_disable(meson, pwm->hwpwm);
 | 
				
			||||||
 | 
							channel->state.enabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							goto unlock;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (state->period != channel->state.period ||
 | 
				
			||||||
 | 
						    state->duty_cycle != channel->state.duty_cycle ||
 | 
				
			||||||
 | 
						    state->polarity != channel->state.polarity) {
 | 
				
			||||||
 | 
							if (channel->state.enabled) {
 | 
				
			||||||
 | 
								meson_pwm_disable(meson, pwm->hwpwm);
 | 
				
			||||||
 | 
								channel->state.enabled = false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (state->polarity != channel->state.polarity) {
 | 
				
			||||||
 | 
								if (state->polarity == PWM_POLARITY_NORMAL)
 | 
				
			||||||
 | 
									meson->inverter_mask |= BIT(pwm->hwpwm);
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									meson->inverter_mask &= ~BIT(pwm->hwpwm);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = meson_pwm_calc(meson, channel, pwm->hwpwm,
 | 
				
			||||||
 | 
									     state->duty_cycle, state->period);
 | 
				
			||||||
 | 
							if (err < 0)
 | 
				
			||||||
 | 
								goto unlock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							channel->state.polarity = state->polarity;
 | 
				
			||||||
 | 
							channel->state.period = state->period;
 | 
				
			||||||
 | 
							channel->state.duty_cycle = state->duty_cycle;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (state->enabled && !channel->state.enabled) {
 | 
				
			||||||
 | 
							meson_pwm_enable(meson, channel, pwm->hwpwm);
 | 
				
			||||||
 | 
							channel->state.enabled = true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unlock:
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&meson->lock, flags);
 | 
				
			||||||
 | 
						return err;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
 | 
				
			||||||
 | 
									struct pwm_state *state)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct meson_pwm *meson = to_meson_pwm(chip);
 | 
				
			||||||
 | 
						u32 value, mask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!state)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (pwm->hwpwm) {
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
							mask = MISC_A_EN;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case 1:
 | 
				
			||||||
 | 
							mask = MISC_B_EN;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						value = readl(meson->base + REG_MISC_AB);
 | 
				
			||||||
 | 
						state->enabled = (value & mask) != 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct pwm_ops meson_pwm_ops = {
 | 
				
			||||||
 | 
						.request = meson_pwm_request,
 | 
				
			||||||
 | 
						.free = meson_pwm_free,
 | 
				
			||||||
 | 
						.apply = meson_pwm_apply,
 | 
				
			||||||
 | 
						.get_state = meson_pwm_get_state,
 | 
				
			||||||
 | 
						.owner = THIS_MODULE,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char * const pwm_meson8b_parent_names[] = {
 | 
				
			||||||
 | 
						"xtal", "vid_pll", "fclk_div4", "fclk_div3"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct meson_pwm_data pwm_meson8b_data = {
 | 
				
			||||||
 | 
						.parent_names = pwm_meson8b_parent_names,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char * const pwm_gxbb_parent_names[] = {
 | 
				
			||||||
 | 
						"xtal", "hdmi_pll", "fclk_div4", "fclk_div3"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct meson_pwm_data pwm_gxbb_data = {
 | 
				
			||||||
 | 
						.parent_names = pwm_gxbb_parent_names,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct of_device_id meson_pwm_matches[] = {
 | 
				
			||||||
 | 
						{ .compatible = "amlogic,meson8b-pwm", .data = &pwm_meson8b_data },
 | 
				
			||||||
 | 
						{ .compatible = "amlogic,meson-gxbb-pwm", .data = &pwm_gxbb_data },
 | 
				
			||||||
 | 
						{},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					MODULE_DEVICE_TABLE(of, meson_pwm_matches);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int meson_pwm_init_channels(struct meson_pwm *meson,
 | 
				
			||||||
 | 
									   struct meson_pwm_channel *channels)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device *dev = meson->chip.dev;
 | 
				
			||||||
 | 
						struct device_node *np = dev->of_node;
 | 
				
			||||||
 | 
						struct clk_init_data init;
 | 
				
			||||||
 | 
						unsigned int i;
 | 
				
			||||||
 | 
						char name[255];
 | 
				
			||||||
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < meson->chip.npwm; i++) {
 | 
				
			||||||
 | 
							struct meson_pwm_channel *channel = &channels[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							snprintf(name, sizeof(name), "%s#mux%u", np->full_name, i);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							init.name = name;
 | 
				
			||||||
 | 
							init.ops = &clk_mux_ops;
 | 
				
			||||||
 | 
							init.flags = CLK_IS_BASIC;
 | 
				
			||||||
 | 
							init.parent_names = meson->data->parent_names;
 | 
				
			||||||
 | 
							init.num_parents = 1 << MISC_CLK_SEL_WIDTH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							channel->mux.reg = meson->base + REG_MISC_AB;
 | 
				
			||||||
 | 
							channel->mux.shift = mux_reg_shifts[i];
 | 
				
			||||||
 | 
							channel->mux.mask = BIT(MISC_CLK_SEL_WIDTH) - 1;
 | 
				
			||||||
 | 
							channel->mux.flags = 0;
 | 
				
			||||||
 | 
							channel->mux.lock = &meson->lock;
 | 
				
			||||||
 | 
							channel->mux.table = NULL;
 | 
				
			||||||
 | 
							channel->mux.hw.init = &init;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							channel->clk = devm_clk_register(dev, &channel->mux.hw);
 | 
				
			||||||
 | 
							if (IS_ERR(channel->clk)) {
 | 
				
			||||||
 | 
								err = PTR_ERR(channel->clk);
 | 
				
			||||||
 | 
								dev_err(dev, "failed to register %s: %d\n", name, err);
 | 
				
			||||||
 | 
								return err;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							snprintf(name, sizeof(name), "clkin%u", i);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							channel->clk_parent = devm_clk_get(dev, name);
 | 
				
			||||||
 | 
							if (IS_ERR(channel->clk_parent)) {
 | 
				
			||||||
 | 
								err = PTR_ERR(channel->clk_parent);
 | 
				
			||||||
 | 
								if (err == -EPROBE_DEFER)
 | 
				
			||||||
 | 
									return err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								channel->clk_parent = NULL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void meson_pwm_add_channels(struct meson_pwm *meson,
 | 
				
			||||||
 | 
									   struct meson_pwm_channel *channels)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned int i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < meson->chip.npwm; i++)
 | 
				
			||||||
 | 
							pwm_set_chip_data(&meson->chip.pwms[i], &channels[i]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int meson_pwm_probe(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct meson_pwm_channel *channels;
 | 
				
			||||||
 | 
						struct meson_pwm *meson;
 | 
				
			||||||
 | 
						struct resource *regs;
 | 
				
			||||||
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						meson = devm_kzalloc(&pdev->dev, sizeof(*meson), GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!meson)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | 
				
			||||||
 | 
						meson->base = devm_ioremap_resource(&pdev->dev, regs);
 | 
				
			||||||
 | 
						if (IS_ERR(meson->base))
 | 
				
			||||||
 | 
							return PTR_ERR(meson->base);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						meson->chip.dev = &pdev->dev;
 | 
				
			||||||
 | 
						meson->chip.ops = &meson_pwm_ops;
 | 
				
			||||||
 | 
						meson->chip.base = -1;
 | 
				
			||||||
 | 
						meson->chip.npwm = 2;
 | 
				
			||||||
 | 
						meson->chip.of_xlate = of_pwm_xlate_with_flags;
 | 
				
			||||||
 | 
						meson->chip.of_pwm_n_cells = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						meson->data = of_device_get_match_data(&pdev->dev);
 | 
				
			||||||
 | 
						meson->inverter_mask = BIT(meson->chip.npwm) - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						channels = devm_kcalloc(&pdev->dev, meson->chip.npwm, sizeof(*meson),
 | 
				
			||||||
 | 
									GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!channels)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = meson_pwm_init_channels(meson, channels);
 | 
				
			||||||
 | 
						if (err < 0)
 | 
				
			||||||
 | 
							return err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = pwmchip_add(&meson->chip);
 | 
				
			||||||
 | 
						if (err < 0) {
 | 
				
			||||||
 | 
							dev_err(&pdev->dev, "failed to register PWM chip: %d\n", err);
 | 
				
			||||||
 | 
							return err;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						meson_pwm_add_channels(meson, channels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						platform_set_drvdata(pdev, meson);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int meson_pwm_remove(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct meson_pwm *meson = platform_get_drvdata(pdev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return pwmchip_remove(&meson->chip);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct platform_driver meson_pwm_driver = {
 | 
				
			||||||
 | 
						.driver = {
 | 
				
			||||||
 | 
							.name = "meson-pwm",
 | 
				
			||||||
 | 
							.of_match_table = meson_pwm_matches,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						.probe = meson_pwm_probe,
 | 
				
			||||||
 | 
						.remove = meson_pwm_remove,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					module_platform_driver(meson_pwm_driver);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_ALIAS("platform:meson-pwm");
 | 
				
			||||||
 | 
					MODULE_DESCRIPTION("Amlogic Meson PWM Generator driver");
 | 
				
			||||||
 | 
					MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
 | 
				
			||||||
 | 
					MODULE_LICENSE("Dual BSD/GPL");
 | 
				
			||||||
		Loading…
	
		Reference in a new issue