diff options
Diffstat (limited to 'drivers/pwm/pwm-meson.c')
-rw-r--r-- | drivers/pwm/pwm-meson.c | 386 |
1 files changed, 182 insertions, 204 deletions
diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index fb5a369b1a8d..3cbff5cbb789 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -1,65 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. + * PWM controller driver for Amlogic Meson SoCs. * - * GPL LICENSE SUMMARY + * This PWM is only a set of Gates, Dividers and Counters: + * PWM output is achieved by calculating a clock that permits calculating + * two periods (low and high). The counter then has to be set to switch after + * N cycles for the first half period. + * The hardware has no "polarity" setting. This driver reverses the period + * cycles (the low length is inverted with the high length) for + * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity + * from the hardware. + * Setting the duty cycle will disable and re-enable the PWM output. + * Disabling the PWM stops the output immediately (without waiting for the + * current period to complete first). * - * 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 + * The public S912 (GXM) datasheet contains some documentation for this PWM + * controller starting on page 543: + * https://dl.khadas.com/Hardware/VIM2/Datasheet/S912_Datasheet_V0.220170314publicversion-Wesion.pdf + * An updated version of this IP block is found in S922X (G12B) SoCs. The + * datasheet contains the description for this IP block revision starting at + * page 1084: + * https://dn.odroid.com/S922X/ODROID-N2/Datasheet/S922X_Public_Datasheet_V0.2.pdf * * 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/bitfield.h> +#include <linux/bits.h> #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/err.h> #include <linux/io.h> #include <linux/kernel.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> @@ -70,7 +45,8 @@ #define REG_PWM_A 0x0 #define REG_PWM_B 0x4 -#define PWM_HIGH_SHIFT 16 +#define PWM_LOW_MASK GENMASK(15, 0) +#define PWM_HIGH_MASK GENMASK(31, 16) #define REG_MISC_AB 0x8 #define MISC_B_CLK_EN BIT(23) @@ -80,13 +56,33 @@ #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_CLK_SEL_MASK 0x3 #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 +#define MESON_NUM_PWMS 2 + +static struct meson_pwm_channel_data { + u8 reg_offset; + u8 clk_sel_shift; + u8 clk_div_shift; + u32 clk_en_mask; + u32 pwm_en_mask; +} meson_pwm_per_channel_data[MESON_NUM_PWMS] = { + { + .reg_offset = REG_PWM_A, + .clk_sel_shift = MISC_A_CLK_SEL_SHIFT, + .clk_div_shift = MISC_A_CLK_DIV_SHIFT, + .clk_en_mask = MISC_A_CLK_EN, + .pwm_en_mask = MISC_A_EN, + }, + { + .reg_offset = REG_PWM_B, + .clk_sel_shift = MISC_B_CLK_SEL_SHIFT, + .clk_div_shift = MISC_B_CLK_DIV_SHIFT, + .clk_en_mask = MISC_B_CLK_EN, + .pwm_en_mask = MISC_B_EN, + } }; struct meson_pwm_channel { @@ -94,8 +90,6 @@ struct meson_pwm_channel { unsigned int lo; u8 pre_div; - struct pwm_state state; - struct clk *clk_parent; struct clk_mux mux; struct clk *clk; @@ -109,8 +103,8 @@ struct meson_pwm_data { struct meson_pwm { struct pwm_chip chip; const struct meson_pwm_data *data; + struct meson_pwm_channel channels[MESON_NUM_PWMS]; void __iomem *base; - u8 inverter_mask; /* * Protects register (write) access to the REG_MISC_AB register * that is shared between the two PWMs. @@ -125,12 +119,16 @@ static inline struct meson_pwm *to_meson_pwm(struct pwm_chip *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 meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel; struct device *dev = chip->dev; int err; - if (!channel) - return -ENODEV; + channel = pwm_get_chip_data(pwm); + if (channel) + return 0; + + channel = &meson->channels[pwm->hwpwm]; if (channel->clk_parent) { err = clk_set_parent(channel->clk, channel->clk_parent); @@ -149,9 +147,7 @@ static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) return err; } - chip->ops->get_state(chip, pwm, &channel->state); - - return 0; + return pwm_set_chip_data(pwm, channel); } static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) @@ -162,20 +158,18 @@ static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) 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) +static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, + struct pwm_state *state) { - unsigned int pre_div, cnt, duty_cnt; + struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); + unsigned int duty, period, pre_div, cnt, duty_cnt; unsigned long fin_freq = -1; - u64 fin_ps; - if (~(meson->inverter_mask >> id) & 0x1) - duty = period - duty; + duty = state->duty_cycle; + period = state->period; - if (period == channel->state.period && - duty == channel->state.duty_cycle) - return 0; + if (state->polarity == PWM_POLARITY_INVERSED) + duty = period - duty; fin_freq = clk_get_rate(channel->clk); if (fin_freq == 0) { @@ -184,24 +178,19 @@ static int meson_pwm_calc(struct meson_pwm *meson, } dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); - fin_ps = (u64)NSEC_PER_SEC * 1000; - do_div(fin_ps, fin_freq); - - /* Calc pre_div with the period */ - for (pre_div = 0; pre_div <= MISC_CLK_DIV_MASK; pre_div++) { - cnt = DIV_ROUND_CLOSEST_ULL((u64)period * 1000, - fin_ps * (pre_div + 1)); - dev_dbg(meson->chip.dev, "fin_ps=%llu pre_div=%u cnt=%u\n", - fin_ps, pre_div, cnt); - if (cnt <= 0xffff) - break; - } + pre_div = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * 0xffffLL); if (pre_div > MISC_CLK_DIV_MASK) { dev_err(meson->chip.dev, "unable to get period pre_div\n"); return -EINVAL; } + cnt = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * (pre_div + 1)); + if (cnt > 0xffff) { + dev_err(meson->chip.dev, "unable to get period cnt\n"); + return -EINVAL; + } + dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period, pre_div, cnt); @@ -215,8 +204,8 @@ static int meson_pwm_calc(struct meson_pwm *meson, channel->lo = cnt; } else { /* Then check is we can have the duty with the same pre_div */ - duty_cnt = DIV_ROUND_CLOSEST_ULL((u64)duty * 1000, - fin_ps * (pre_div + 1)); + duty_cnt = div64_u64(fin_freq * (u64)duty, + NSEC_PER_SEC * (pre_div + 1)); if (duty_cnt > 0xffff) { dev_err(meson->chip.dev, "unable to get duty cycle\n"); return -EINVAL; @@ -233,73 +222,43 @@ static int meson_pwm_calc(struct meson_pwm *meson, return 0; } -static void meson_pwm_enable(struct meson_pwm *meson, - struct meson_pwm_channel *channel, - unsigned int id) +static void meson_pwm_enable(struct meson_pwm *meson, struct pwm_device *pwm) { - u32 value, clk_shift, clk_enable, enable; - unsigned int offset; + struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); + struct meson_pwm_channel_data *channel_data; unsigned long flags; + u32 value; - 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; - - default: - return; - } + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; spin_lock_irqsave(&meson->lock, flags); value = readl(meson->base + REG_MISC_AB); - value &= ~(MISC_CLK_DIV_MASK << clk_shift); - value |= channel->pre_div << clk_shift; - value |= clk_enable; + value &= ~(MISC_CLK_DIV_MASK << channel_data->clk_div_shift); + value |= channel->pre_div << channel_data->clk_div_shift; + value |= channel_data->clk_en_mask; writel(value, meson->base + REG_MISC_AB); - value = (channel->hi << PWM_HIGH_SHIFT) | channel->lo; - writel(value, meson->base + offset); + value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) | + FIELD_PREP(PWM_LOW_MASK, channel->lo); + writel(value, meson->base + channel_data->reg_offset); value = readl(meson->base + REG_MISC_AB); - value |= enable; + value |= channel_data->pwm_en_mask; writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); } -static void meson_pwm_disable(struct meson_pwm *meson, unsigned int id) +static void meson_pwm_disable(struct meson_pwm *meson, struct pwm_device *pwm) { - u32 value, enable; unsigned long flags; - - switch (id) { - case 0: - enable = MISC_A_EN; - break; - - case 1: - enable = MISC_B_EN; - break; - - default: - return; - } + u32 value; spin_lock_irqsave(&meson->lock, flags); value = readl(meson->base + REG_MISC_AB); - value &= ~enable; + value &= ~meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask; writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); @@ -316,64 +275,97 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return -EINVAL; if (!state->enabled) { - meson_pwm_disable(meson, pwm->hwpwm); - channel->state.enabled = false; - - return 0; - } - - if (state->period != channel->state.period || - state->duty_cycle != channel->state.duty_cycle || - state->polarity != channel->state.polarity) { - 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); + if (state->polarity == PWM_POLARITY_INVERSED) { + /* + * This IP block revision doesn't have an "always high" + * setting which we can use for "inverted disabled". + * Instead we achieve this using the same settings + * that we use a pre_div of 0 (to get the shortest + * possible duration for one "count") and + * "period == duty_cycle". This results in a signal + * which is LOW for one "count", while being HIGH for + * the rest of the (so the signal is HIGH for slightly + * less than 100% of the period, but this is the best + * we can achieve). + */ + channel->pre_div = 0; + channel->hi = ~0; + channel->lo = 0; + + meson_pwm_enable(meson, pwm); + } else { + meson_pwm_disable(meson, pwm); } - - err = meson_pwm_calc(meson, channel, pwm->hwpwm, - state->duty_cycle, state->period); + } else { + err = meson_pwm_calc(meson, pwm, state); if (err < 0) return err; - 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; + meson_pwm_enable(meson, pwm); } return 0; } +static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip, + struct pwm_device *pwm, u32 cnt) +{ + struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel; + unsigned long fin_freq; + u32 fin_ns; + + /* to_meson_pwm() can only be used after .get_state() is called */ + channel = &meson->channels[pwm->hwpwm]; + + fin_freq = clk_get_rate(channel->clk); + if (fin_freq == 0) + return 0; + + fin_ns = div_u64(NSEC_PER_SEC, fin_freq); + + return cnt * fin_ns * (channel->pre_div + 1); +} + 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; + struct meson_pwm_channel_data *channel_data; + struct meson_pwm_channel *channel; + u32 value, tmp; if (!state) return; - switch (pwm->hwpwm) { - case 0: - mask = MISC_A_EN; - break; + channel = &meson->channels[pwm->hwpwm]; + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; - case 1: - mask = MISC_B_EN; - break; + value = readl(meson->base + REG_MISC_AB); - default: - return; - } + tmp = channel_data->pwm_en_mask | channel_data->clk_en_mask; + state->enabled = (value & tmp) == tmp; - value = readl(meson->base + REG_MISC_AB); - state->enabled = (value & mask) != 0; + tmp = value >> channel_data->clk_div_shift; + channel->pre_div = FIELD_GET(MISC_CLK_DIV_MASK, tmp); + + value = readl(meson->base + channel_data->reg_offset); + + channel->lo = FIELD_GET(PWM_LOW_MASK, value); + channel->hi = FIELD_GET(PWM_HIGH_MASK, value); + + if (channel->lo == 0) { + state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); + state->duty_cycle = state->period; + } else if (channel->lo >= channel->hi) { + state->period = meson_pwm_cnt_to_ns(chip, pwm, + channel->lo + channel->hi); + state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, + channel->hi); + } else { + state->period = 0; + state->duty_cycle = 0; + } } static const struct pwm_ops meson_pwm_ops = { @@ -433,8 +425,17 @@ static const struct meson_pwm_data pwm_axg_ao_data = { .num_parents = ARRAY_SIZE(pwm_axg_ao_parent_names), }; +static const char * const pwm_g12a_ao_ab_parent_names[] = { + "xtal", "aoclk81", "fclk_div4", "fclk_div5" +}; + +static const struct meson_pwm_data pwm_g12a_ao_ab_data = { + .parent_names = pwm_g12a_ao_ab_parent_names, + .num_parents = ARRAY_SIZE(pwm_g12a_ao_ab_parent_names), +}; + static const char * const pwm_g12a_ao_cd_parent_names[] = { - "aoclk81", "xtal", + "xtal", "aoclk81", }; static const struct meson_pwm_data pwm_g12a_ao_cd_data = { @@ -478,7 +479,7 @@ static const struct of_device_id meson_pwm_matches[] = { }, { .compatible = "amlogic,meson-g12a-ao-pwm-ab", - .data = &pwm_axg_ao_data + .data = &pwm_g12a_ao_ab_data }, { .compatible = "amlogic,meson-g12a-ao-pwm-cd", @@ -488,8 +489,7 @@ static const struct of_device_id meson_pwm_matches[] = { }; MODULE_DEVICE_TABLE(of, meson_pwm_matches); -static int meson_pwm_init_channels(struct meson_pwm *meson, - struct meson_pwm_channel *channels) +static int meson_pwm_init_channels(struct meson_pwm *meson) { struct device *dev = meson->chip.dev; struct clk_init_data init; @@ -498,7 +498,7 @@ static int meson_pwm_init_channels(struct meson_pwm *meson, int err; for (i = 0; i < meson->chip.npwm; i++) { - struct meson_pwm_channel *channel = &channels[i]; + struct meson_pwm_channel *channel = &meson->channels[i]; snprintf(name, sizeof(name), "%s#mux%u", dev_name(dev), i); @@ -509,8 +509,9 @@ static int meson_pwm_init_channels(struct meson_pwm *meson, init.num_parents = meson->data->num_parents; 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.shift = + meson_pwm_per_channel_data[i].clk_sel_shift; + channel->mux.mask = MISC_CLK_SEL_MASK; channel->mux.flags = 0; channel->mux.lock = &meson->lock; channel->mux.table = NULL; @@ -525,31 +526,16 @@ static int meson_pwm_init_channels(struct meson_pwm *meson, 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; - } + channel->clk_parent = devm_clk_get_optional(dev, name); + if (IS_ERR(channel->clk_parent)) + return PTR_ERR(channel->clk_parent); } 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; @@ -567,19 +553,13 @@ static int meson_pwm_probe(struct platform_device *pdev) meson->chip.dev = &pdev->dev; meson->chip.ops = &meson_pwm_ops; meson->chip.base = -1; - meson->chip.npwm = 2; + meson->chip.npwm = MESON_NUM_PWMS; 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(*channels), GFP_KERNEL); - if (!channels) - return -ENOMEM; - - err = meson_pwm_init_channels(meson, channels); + err = meson_pwm_init_channels(meson); if (err < 0) return err; @@ -589,8 +569,6 @@ static int meson_pwm_probe(struct platform_device *pdev) return err; } - meson_pwm_add_channels(meson, channels); - platform_set_drvdata(pdev, meson); return 0; |