diff options
| author | Neil Armstrong <narmstrong@baylibre.com> | 2016-08-22 11:36:30 -0400 |
|---|---|---|
| committer | Thierry Reding <thierry.reding@gmail.com> | 2016-09-08 04:55:00 -0400 |
| commit | 211ed630753d2f0553ff642346e9995503bc240d (patch) | |
| tree | 8f89f7dd22b0aca4372e14198db74c4c8fbe4aa1 /drivers/pwm | |
| parent | 89394cc7c20c48d5e213d05cc37a87ceec23d963 (diff) | |
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>
Diffstat (limited to 'drivers/pwm')
| -rw-r--r-- | drivers/pwm/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
| -rw-r--r-- | drivers/pwm/pwm-meson.c | 520 |
3 files changed, 530 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 80a566a00d04..bf0128899c09 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
| @@ -262,6 +262,15 @@ config PWM_LPSS_PLATFORM | |||
| 262 | To compile this driver as a module, choose M here: the module | 262 | To compile this driver as a module, choose M here: the module |
| 263 | will be called pwm-lpss-platform. | 263 | will be called pwm-lpss-platform. |
| 264 | 264 | ||
| 265 | config PWM_MESON | ||
| 266 | tristate "Amlogic Meson PWM driver" | ||
| 267 | depends on ARCH_MESON | ||
| 268 | help | ||
| 269 | The platform driver for Amlogic Meson PWM controller. | ||
| 270 | |||
| 271 | To compile this driver as a module, choose M here: the module | ||
| 272 | will be called pwm-meson. | ||
| 273 | |||
| 265 | config PWM_MTK_DISP | 274 | config PWM_MTK_DISP |
| 266 | tristate "MediaTek display PWM driver" | 275 | tristate "MediaTek display PWM driver" |
| 267 | depends on ARCH_MEDIATEK || COMPILE_TEST | 276 | depends on ARCH_MEDIATEK || COMPILE_TEST |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index feef1dd29f73..1194c54efcc2 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
| @@ -24,6 +24,7 @@ obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o | |||
| 24 | obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o | 24 | obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o |
| 25 | obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o | 25 | obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o |
| 26 | obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o | 26 | obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o |
| 27 | obj-$(CONFIG_PWM_MESON) += pwm-meson.o | ||
| 27 | obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o | 28 | obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o |
| 28 | obj-$(CONFIG_PWM_MXS) += pwm-mxs.o | 29 | obj-$(CONFIG_PWM_MXS) += pwm-mxs.o |
| 29 | obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o | 30 | obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o |
diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c new file mode 100644 index 000000000000..bfbbe7f3cdc1 --- /dev/null +++ b/drivers/pwm/pwm-meson.c | |||
| @@ -0,0 +1,520 @@ | |||
| 1 | /* | ||
| 2 | * This file is provided under a dual BSD/GPLv2 license. When using or | ||
| 3 | * redistributing this file, you may do so under either license. | ||
| 4 | * | ||
| 5 | * GPL LICENSE SUMMARY | ||
| 6 | * | ||
| 7 | * Copyright (c) 2016 BayLibre, SAS. | ||
| 8 | * Author: Neil Armstrong <narmstrong@baylibre.com> | ||
| 9 | * Copyright (C) 2014 Amlogic, Inc. | ||
| 10 | * | ||
| 11 | * This program is free software; you can redistribute it and/or modify | ||
| 12 | * it under the terms of version 2 of the GNU General Public License as | ||
| 13 | * published by the Free Software Foundation. | ||
| 14 | * | ||
| 15 | * This program is distributed in the hope that it will be useful, but | ||
| 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 18 | * General Public License for more details. | ||
| 19 | * | ||
| 20 | * You should have received a copy of the GNU General Public License | ||
| 21 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
| 22 | * The full GNU General Public License is included in this distribution | ||
| 23 | * in the file called COPYING. | ||
| 24 | * | ||
| 25 | * BSD LICENSE | ||
| 26 | * | ||
| 27 | * Copyright (c) 2016 BayLibre, SAS. | ||
| 28 | * Author: Neil Armstrong <narmstrong@baylibre.com> | ||
| 29 | * Copyright (C) 2014 Amlogic, Inc. | ||
| 30 | * | ||
| 31 | * Redistribution and use in source and binary forms, with or without | ||
| 32 | * modification, are permitted provided that the following conditions | ||
| 33 | * are met: | ||
| 34 | * | ||
| 35 | * * Redistributions of source code must retain the above copyright | ||
| 36 | * notice, this list of conditions and the following disclaimer. | ||
| 37 | * * Redistributions in binary form must reproduce the above copyright | ||
| 38 | * notice, this list of conditions and the following disclaimer in | ||
| 39 | * the documentation and/or other materials provided with the | ||
| 40 | * distribution. | ||
| 41 | * * Neither the name of Intel Corporation nor the names of its | ||
| 42 | * contributors may be used to endorse or promote products derived | ||
| 43 | * from this software without specific prior written permission. | ||
| 44 | * | ||
| 45 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| 46 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| 47 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| 48 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| 49 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| 50 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| 51 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| 52 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| 53 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| 54 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| 55 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| 56 | */ | ||
| 57 | |||
| 58 | #include <linux/clk.h> | ||
| 59 | #include <linux/clk-provider.h> | ||
| 60 | #include <linux/err.h> | ||
| 61 | #include <linux/io.h> | ||
| 62 | #include <linux/kernel.h> | ||
| 63 | #include <linux/module.h> | ||
| 64 | #include <linux/of.h> | ||
| 65 | #include <linux/of_device.h> | ||
| 66 | #include <linux/platform_device.h> | ||
| 67 | #include <linux/pwm.h> | ||
| 68 | #include <linux/slab.h> | ||
| 69 | #include <linux/spinlock.h> | ||
| 70 | |||
| 71 | #define REG_PWM_A 0x0 | ||
| 72 | #define REG_PWM_B 0x4 | ||
| 73 | #define PWM_HIGH_SHIFT 16 | ||
| 74 | |||
| 75 | #define REG_MISC_AB 0x8 | ||
| 76 | #define MISC_B_CLK_EN BIT(23) | ||
| 77 | #define MISC_A_CLK_EN BIT(15) | ||
| 78 | #define MISC_CLK_DIV_MASK 0x7f | ||
| 79 | #define MISC_B_CLK_DIV_SHIFT 16 | ||
| 80 | #define MISC_A_CLK_DIV_SHIFT 8 | ||
| 81 | #define MISC_B_CLK_SEL_SHIFT 6 | ||
| 82 | #define MISC_A_CLK_SEL_SHIFT 4 | ||
| 83 | #define MISC_CLK_SEL_WIDTH 2 | ||
| 84 | #define MISC_B_EN BIT(1) | ||
| 85 | #define MISC_A_EN BIT(0) | ||
| 86 | |||
| 87 | static const unsigned int mux_reg_shifts[] = { | ||
| 88 | MISC_A_CLK_SEL_SHIFT, | ||
| 89 | MISC_B_CLK_SEL_SHIFT | ||
| 90 | }; | ||
| 91 | |||
| 92 | struct meson_pwm_channel { | ||
| 93 | unsigned int hi; | ||
| 94 | unsigned int lo; | ||
| 95 | u8 pre_div; | ||
| 96 | |||
| 97 | struct pwm_state state; | ||
| 98 | |||
| 99 | struct clk *clk_parent; | ||
| 100 | struct clk_mux mux; | ||
| 101 | struct clk *clk; | ||
| 102 | }; | ||
| 103 | |||
| 104 | struct meson_pwm_data { | ||
| 105 | const char * const *parent_names; | ||
| 106 | }; | ||
| 107 | |||
| 108 | struct meson_pwm { | ||
| 109 | struct pwm_chip chip; | ||
| 110 | const struct meson_pwm_data *data; | ||
| 111 | void __iomem *base; | ||
| 112 | u8 inverter_mask; | ||
| 113 | spinlock_t lock; | ||
| 114 | }; | ||
| 115 | |||
| 116 | static inline struct meson_pwm *to_meson_pwm(struct pwm_chip *chip) | ||
| 117 | { | ||
| 118 | return container_of(chip, struct meson_pwm, chip); | ||
| 119 | } | ||
| 120 | |||
| 121 | static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) | ||
| 122 | { | ||
| 123 | struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); | ||
| 124 | struct device *dev = chip->dev; | ||
| 125 | int err; | ||
| 126 | |||
| 127 | if (!channel) | ||
| 128 | return -ENODEV; | ||
| 129 | |||
| 130 | if (channel->clk_parent) { | ||
| 131 | err = clk_set_parent(channel->clk, channel->clk_parent); | ||
| 132 | if (err < 0) { | ||
| 133 | dev_err(dev, "failed to set parent %s for %s: %d\n", | ||
| 134 | __clk_get_name(channel->clk_parent), | ||
| 135 | __clk_get_name(channel->clk), err); | ||
| 136 | return err; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | err = clk_prepare_enable(channel->clk); | ||
| 141 | if (err < 0) { | ||
| 142 | dev_err(dev, "failed to enable clock %s: %d\n", | ||
| 143 | __clk_get_name(channel->clk), err); | ||
| 144 | return err; | ||
| 145 | } | ||
| 146 | |||
| 147 | chip->ops->get_state(chip, pwm, &channel->state); | ||
| 148 | |||
| 149 | return 0; | ||
| 150 | } | ||
| 151 | |||
| 152 | static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) | ||
| 153 | { | ||
| 154 | struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); | ||
| 155 | |||
| 156 | if (channel) | ||
| 157 | clk_disable_unprepare(channel->clk); | ||
| 158 | } | ||
| 159 | |||
| 160 | static int meson_pwm_calc(struct meson_pwm *meson, | ||
| 161 | struct meson_pwm_channel *channel, unsigned int id, | ||
| 162 | unsigned int duty, unsigned int period) | ||
| 163 | { | ||
| 164 | unsigned int pre_div, cnt, duty_cnt; | ||
| 165 | unsigned long fin_freq = -1, fin_ns; | ||
| 166 | |||
| 167 | if (~(meson->inverter_mask >> id) & 0x1) | ||
| 168 | duty = period - duty; | ||
| 169 | |||
| 170 | if (period == channel->state.period && | ||
| 171 | duty == channel->state.duty_cycle) | ||
| 172 | return 0; | ||
| 173 | |||
| 174 | fin_freq = clk_get_rate(channel->clk); | ||
| 175 | if (fin_freq == 0) { | ||
| 176 | dev_err(meson->chip.dev, "invalid source clock frequency\n"); | ||
| 177 | return -EINVAL; | ||
| 178 | } | ||
| 179 | |||
| 180 | dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); | ||
| 181 | fin_ns = NSEC_PER_SEC / fin_freq; | ||
| 182 | |||
| 183 | /* Calc pre_div with the period */ | ||
| 184 | for (pre_div = 0; pre_div < MISC_CLK_DIV_MASK; pre_div++) { | ||
| 185 | cnt = DIV_ROUND_CLOSEST(period, fin_ns * (pre_div + 1)); | ||
| 186 | dev_dbg(meson->chip.dev, "fin_ns=%lu pre_div=%u cnt=%u\n", | ||
| 187 | fin_ns, pre_div, cnt); | ||
| 188 | if (cnt <= 0xffff) | ||
| 189 | break; | ||
| 190 | } | ||
| 191 | |||
| 192 | if (pre_div == MISC_CLK_DIV_MASK) { | ||
| 193 | dev_err(meson->chip.dev, "unable to get period pre_div\n"); | ||
| 194 | return -EINVAL; | ||
| 195 | } | ||
| 196 | |||
| 197 | dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period, | ||
| 198 | pre_div, cnt); | ||
| 199 | |||
| 200 | if (duty == period) { | ||
| 201 | channel->pre_div = pre_div; | ||
| 202 | channel->hi = cnt; | ||
| 203 | channel->lo = 0; | ||
| 204 | } else if (duty == 0) { | ||
| 205 | channel->pre_div = pre_div; | ||
| 206 | channel->hi = 0; | ||
| 207 | channel->lo = cnt; | ||
| 208 | } else { | ||
| 209 | /* Then check is we can have the duty with the same pre_div */ | ||
| 210 | duty_cnt = DIV_ROUND_CLOSEST(duty, fin_ns * (pre_div + 1)); | ||
| 211 | if (duty_cnt > 0xffff) { | ||
| 212 | dev_err(meson->chip.dev, "unable to get duty cycle\n"); | ||
| 213 | return -EINVAL; | ||
| 214 | } | ||
| 215 | |||
| 216 | dev_dbg(meson->chip.dev, "duty=%u pre_div=%u duty_cnt=%u\n", | ||
| 217 | duty, pre_div, duty_cnt); | ||
| 218 | |||
| 219 | channel->pre_div = pre_div; | ||
| 220 | channel->hi = duty_cnt; | ||
| 221 | channel->lo = cnt - duty_cnt; | ||
| 222 | } | ||
| 223 | |||
| 224 | return 0; | ||
| 225 | } | ||
| 226 | |||
| 227 | static void meson_pwm_enable(struct meson_pwm *meson, | ||
| 228 | struct meson_pwm_channel *channel, | ||
| 229 | unsigned int id) | ||
| 230 | { | ||
| 231 | u32 value, clk_shift, clk_enable, enable; | ||
| 232 | unsigned int offset; | ||
| 233 | |||
| 234 | switch (id) { | ||
| 235 | case 0: | ||
| 236 | clk_shift = MISC_A_CLK_DIV_SHIFT; | ||
| 237 | clk_enable = MISC_A_CLK_EN; | ||
| 238 | enable = MISC_A_EN; | ||
| 239 | offset = REG_PWM_A; | ||
| 240 | break; | ||
| 241 | |||
| 242 | case 1: | ||
| 243 | clk_shift = MISC_B_CLK_DIV_SHIFT; | ||
| 244 | clk_enable = MISC_B_CLK_EN; | ||
| 245 | enable = MISC_B_EN; | ||
| 246 | offset = REG_PWM_B; | ||
| 247 | break; | ||
| 248 | } | ||
| 249 | |||
| 250 | value = readl(meson->base + REG_MISC_AB); | ||
| 251 | value &= ~(MISC_CLK_DIV_MASK << clk_shift); | ||
| 252 | value |= channel->pre_div << clk_shift; | ||
| 253 | value |= clk_enable; | ||
| 254 | writel(value, meson->base + REG_MISC_AB); | ||
| 255 | |||
| 256 | value = (channel->hi << PWM_HIGH_SHIFT) | channel->lo; | ||
| 257 | writel(value, meson->base + offset); | ||
| 258 | |||
| 259 | value = readl(meson->base + REG_MISC_AB); | ||
| 260 | value |= enable; | ||
| 261 | writel(value, meson->base + REG_MISC_AB); | ||
| 262 | } | ||
| 263 | |||
| 264 | static void meson_pwm_disable(struct meson_pwm *meson, unsigned int id) | ||
| 265 | { | ||
| 266 | u32 value, enable; | ||
| 267 | |||
| 268 | switch (id) { | ||
| 269 | case 0: | ||
| 270 | enable = MISC_A_EN; | ||
| 271 | break; | ||
| 272 | |||
| 273 | case 1: | ||
| 274 | enable = MISC_B_EN; | ||
| 275 | break; | ||
| 276 | } | ||
| 277 | |||
| 278 | value = readl(meson->base + REG_MISC_AB); | ||
| 279 | value &= ~enable; | ||
| 280 | writel(value, meson->base + REG_MISC_AB); | ||
| 281 | } | ||
| 282 | |||
| 283 | static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, | ||
| 284 | struct pwm_state *state) | ||
| 285 | { | ||
| 286 | struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); | ||
| 287 | struct meson_pwm *meson = to_meson_pwm(chip); | ||
| 288 | unsigned long flags; | ||
| 289 | int err = 0; | ||
| 290 | |||
| 291 | if (!state) | ||
| 292 | return -EINVAL; | ||
| 293 | |||
| 294 | spin_lock_irqsave(&meson->lock, flags); | ||
| 295 | |||
| 296 | if (!state->enabled) { | ||
| 297 | meson_pwm_disable(meson, pwm->hwpwm); | ||
| 298 | channel->state.enabled = false; | ||
| 299 | |||
| 300 | goto unlock; | ||
| 301 | } | ||
| 302 | |||
| 303 | if (state->period != channel->state.period || | ||
| 304 | state->duty_cycle != channel->state.duty_cycle || | ||
| 305 | state->polarity != channel->state.polarity) { | ||
| 306 | if (channel->state.enabled) { | ||
| 307 | meson_pwm_disable(meson, pwm->hwpwm); | ||
| 308 | channel->state.enabled = false; | ||
| 309 | } | ||
| 310 | |||
| 311 | if (state->polarity != channel->state.polarity) { | ||
| 312 | if (state->polarity == PWM_POLARITY_NORMAL) | ||
| 313 | meson->inverter_mask |= BIT(pwm->hwpwm); | ||
| 314 | else | ||
| 315 | meson->inverter_mask &= ~BIT(pwm->hwpwm); | ||
| 316 | } | ||
| 317 | |||
| 318 | err = meson_pwm_calc(meson, channel, pwm->hwpwm, | ||
| 319 | state->duty_cycle, state->period); | ||
| 320 | if (err < 0) | ||
| 321 | goto unlock; | ||
| 322 | |||
| 323 | channel->state.polarity = state->polarity; | ||
| 324 | channel->state.period = state->period; | ||
| 325 | channel->state.duty_cycle = state->duty_cycle; | ||
| 326 | } | ||
| 327 | |||
| 328 | if (state->enabled && !channel->state.enabled) { | ||
| 329 | meson_pwm_enable(meson, channel, pwm->hwpwm); | ||
| 330 | channel->state.enabled = true; | ||
| 331 | } | ||
| 332 | |||
| 333 | unlock: | ||
| 334 | spin_unlock_irqrestore(&meson->lock, flags); | ||
| 335 | return err; | ||
| 336 | } | ||
| 337 | |||
| 338 | static void meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, | ||
| 339 | struct pwm_state *state) | ||
| 340 | { | ||
| 341 | struct meson_pwm *meson = to_meson_pwm(chip); | ||
| 342 | u32 value, mask; | ||
| 343 | |||
| 344 | if (!state) | ||
| 345 | return; | ||
| 346 | |||
| 347 | switch (pwm->hwpwm) { | ||
| 348 | case 0: | ||
| 349 | mask = MISC_A_EN; | ||
| 350 | break; | ||
| 351 | |||
| 352 | case 1: | ||
| 353 | mask = MISC_B_EN; | ||
| 354 | break; | ||
| 355 | } | ||
| 356 | |||
| 357 | value = readl(meson->base + REG_MISC_AB); | ||
| 358 | state->enabled = (value & mask) != 0; | ||
| 359 | } | ||
| 360 | |||
| 361 | static const struct pwm_ops meson_pwm_ops = { | ||
| 362 | .request = meson_pwm_request, | ||
| 363 | .free = meson_pwm_free, | ||
| 364 | .apply = meson_pwm_apply, | ||
| 365 | .get_state = meson_pwm_get_state, | ||
| 366 | .owner = THIS_MODULE, | ||
| 367 | }; | ||
| 368 | |||
| 369 | static const char * const pwm_meson8b_parent_names[] = { | ||
| 370 | "xtal", "vid_pll", "fclk_div4", "fclk_div3" | ||
| 371 | }; | ||
| 372 | |||
| 373 | static const struct meson_pwm_data pwm_meson8b_data = { | ||
| 374 | .parent_names = pwm_meson8b_parent_names, | ||
| 375 | }; | ||
| 376 | |||
| 377 | static const char * const pwm_gxbb_parent_names[] = { | ||
| 378 | "xtal", "hdmi_pll", "fclk_div4", "fclk_div3" | ||
| 379 | }; | ||
| 380 | |||
| 381 | static const struct meson_pwm_data pwm_gxbb_data = { | ||
| 382 | .parent_names = pwm_gxbb_parent_names, | ||
| 383 | }; | ||
| 384 | |||
| 385 | static const struct of_device_id meson_pwm_matches[] = { | ||
| 386 | { .compatible = "amlogic,meson8b-pwm", .data = &pwm_meson8b_data }, | ||
| 387 | { .compatible = "amlogic,meson-gxbb-pwm", .data = &pwm_gxbb_data }, | ||
| 388 | {}, | ||
| 389 | }; | ||
| 390 | MODULE_DEVICE_TABLE(of, meson_pwm_matches); | ||
| 391 | |||
| 392 | static int meson_pwm_init_channels(struct meson_pwm *meson, | ||
| 393 | struct meson_pwm_channel *channels) | ||
| 394 | { | ||
| 395 | struct device *dev = meson->chip.dev; | ||
| 396 | struct device_node *np = dev->of_node; | ||
| 397 | struct clk_init_data init; | ||
| 398 | unsigned int i; | ||
| 399 | char name[255]; | ||
| 400 | int err; | ||
| 401 | |||
| 402 | for (i = 0; i < meson->chip.npwm; i++) { | ||
| 403 | struct meson_pwm_channel *channel = &channels[i]; | ||
| 404 | |||
| 405 | snprintf(name, sizeof(name), "%s#mux%u", np->full_name, i); | ||
| 406 | |||
| 407 | init.name = name; | ||
| 408 | init.ops = &clk_mux_ops; | ||
| 409 | init.flags = CLK_IS_BASIC; | ||
| 410 | init.parent_names = meson->data->parent_names; | ||
| 411 | init.num_parents = 1 << MISC_CLK_SEL_WIDTH; | ||
| 412 | |||
| 413 | channel->mux.reg = meson->base + REG_MISC_AB; | ||
| 414 | channel->mux.shift = mux_reg_shifts[i]; | ||
| 415 | channel->mux.mask = BIT(MISC_CLK_SEL_WIDTH) - 1; | ||
| 416 | channel->mux.flags = 0; | ||
| 417 | channel->mux.lock = &meson->lock; | ||
| 418 | channel->mux.table = NULL; | ||
| 419 | channel->mux.hw.init = &init; | ||
| 420 | |||
| 421 | channel->clk = devm_clk_register(dev, &channel->mux.hw); | ||
| 422 | if (IS_ERR(channel->clk)) { | ||
| 423 | err = PTR_ERR(channel->clk); | ||
| 424 | dev_err(dev, "failed to register %s: %d\n", name, err); | ||
| 425 | return err; | ||
| 426 | } | ||
| 427 | |||
| 428 | snprintf(name, sizeof(name), "clkin%u", i); | ||
| 429 | |||
| 430 | channel->clk_parent = devm_clk_get(dev, name); | ||
| 431 | if (IS_ERR(channel->clk_parent)) { | ||
| 432 | err = PTR_ERR(channel->clk_parent); | ||
| 433 | if (err == -EPROBE_DEFER) | ||
| 434 | return err; | ||
| 435 | |||
| 436 | channel->clk_parent = NULL; | ||
| 437 | } | ||
| 438 | } | ||
| 439 | |||
| 440 | return 0; | ||
| 441 | } | ||
| 442 | |||
| 443 | static void meson_pwm_add_channels(struct meson_pwm *meson, | ||
| 444 | struct meson_pwm_channel *channels) | ||
| 445 | { | ||
| 446 | unsigned int i; | ||
| 447 | |||
| 448 | for (i = 0; i < meson->chip.npwm; i++) | ||
| 449 | pwm_set_chip_data(&meson->chip.pwms[i], &channels[i]); | ||
| 450 | } | ||
| 451 | |||
| 452 | static int meson_pwm_probe(struct platform_device *pdev) | ||
| 453 | { | ||
| 454 | struct meson_pwm_channel *channels; | ||
| 455 | struct meson_pwm *meson; | ||
| 456 | struct resource *regs; | ||
| 457 | int err; | ||
| 458 | |||
| 459 | meson = devm_kzalloc(&pdev->dev, sizeof(*meson), GFP_KERNEL); | ||
| 460 | if (!meson) | ||
| 461 | return -ENOMEM; | ||
| 462 | |||
| 463 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
| 464 | meson->base = devm_ioremap_resource(&pdev->dev, regs); | ||
| 465 | if (IS_ERR(meson->base)) | ||
| 466 | return PTR_ERR(meson->base); | ||
| 467 | |||
| 468 | meson->chip.dev = &pdev->dev; | ||
| 469 | meson->chip.ops = &meson_pwm_ops; | ||
| 470 | meson->chip.base = -1; | ||
| 471 | meson->chip.npwm = 2; | ||
| 472 | meson->chip.of_xlate = of_pwm_xlate_with_flags; | ||
| 473 | meson->chip.of_pwm_n_cells = 3; | ||
| 474 | |||
| 475 | meson->data = of_device_get_match_data(&pdev->dev); | ||
| 476 | meson->inverter_mask = BIT(meson->chip.npwm) - 1; | ||
| 477 | |||
| 478 | channels = devm_kcalloc(&pdev->dev, meson->chip.npwm, sizeof(*meson), | ||
| 479 | GFP_KERNEL); | ||
| 480 | if (!channels) | ||
| 481 | return -ENOMEM; | ||
| 482 | |||
| 483 | err = meson_pwm_init_channels(meson, channels); | ||
| 484 | if (err < 0) | ||
| 485 | return err; | ||
| 486 | |||
| 487 | err = pwmchip_add(&meson->chip); | ||
| 488 | if (err < 0) { | ||
| 489 | dev_err(&pdev->dev, "failed to register PWM chip: %d\n", err); | ||
| 490 | return err; | ||
| 491 | } | ||
| 492 | |||
| 493 | meson_pwm_add_channels(meson, channels); | ||
| 494 | |||
| 495 | platform_set_drvdata(pdev, meson); | ||
| 496 | |||
| 497 | return 0; | ||
| 498 | } | ||
| 499 | |||
| 500 | static int meson_pwm_remove(struct platform_device *pdev) | ||
| 501 | { | ||
| 502 | struct meson_pwm *meson = platform_get_drvdata(pdev); | ||
| 503 | |||
| 504 | return pwmchip_remove(&meson->chip); | ||
| 505 | } | ||
| 506 | |||
| 507 | static struct platform_driver meson_pwm_driver = { | ||
| 508 | .driver = { | ||
| 509 | .name = "meson-pwm", | ||
| 510 | .of_match_table = meson_pwm_matches, | ||
| 511 | }, | ||
| 512 | .probe = meson_pwm_probe, | ||
| 513 | .remove = meson_pwm_remove, | ||
| 514 | }; | ||
| 515 | module_platform_driver(meson_pwm_driver); | ||
| 516 | |||
| 517 | MODULE_ALIAS("platform:meson-pwm"); | ||
| 518 | MODULE_DESCRIPTION("Amlogic Meson PWM Generator driver"); | ||
| 519 | MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); | ||
| 520 | MODULE_LICENSE("Dual BSD/GPL"); | ||
