forked from mirrors/linux
		
	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
	
	 Neil Armstrong
						Neil Armstrong