blob: e83f934aaaadd477e3288a905116b4e32f44c9a8 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#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>
#include <linux/amlogic/pwm-meson.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
#include <linux/pinctrl/consumer.h>
static int meson_pwm_v2_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);
if (!channel)
return -ENODEV;
chip->ops->get_state(chip, pwm, &channel->state);
return 0;
}
static void meson_pwm_v2_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
return;
}
static int meson_pwm_v2_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;
u64 fin_ps;
if (~(meson->inverter_mask >> id) & 0x1)
duty = period - duty;
/* 1. Remove unnecessary logic.
* 2. When the polarity is 1, it may cause false early return.
* For example the following:
* duty_cycle = 0 period = 5555555 enabled = 1 polarity = 1
* duty_cycle = 5555555 period = 5555555 enabled = 1 polarity = 1
*
* if (period == channel->state.period &&
* duty == channel->state.duty_cycle)
* return 0;
*/
fin_freq = meson->data->default_extern_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_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;
}
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_ULL((u64)duty * 1000,
fin_ps * (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;
if (duty_cnt == 0) {
cnt = (cnt < 2 ? 2 : cnt);
channel->hi = 0;
channel->lo = cnt - 2;
} else if (cnt == duty_cnt) {
duty_cnt = (duty_cnt < 2 ? 2 : duty_cnt);
channel->hi = duty_cnt - 2;
channel->lo = 0;
} else {
channel->hi = duty_cnt - 1;
channel->lo = cnt - duty_cnt - 1;
}
}
/*
* duty_cycle equal 0% and 100%,constant should be enabled,
* high and low count will not incease one;
* otherwise, high and low count increase one.
*/
if (id < 2) {
if (duty == period || duty == 0)
pwm_constant_enable(meson, id);
else
pwm_constant_disable(meson, id);
}
return 0;
}
static void meson_pwm_v2_enable(struct meson_pwm *meson,
struct meson_pwm_channel *channel,
unsigned int id)
{
u32 value, clk_shift, clk_enable, enable;
unsigned int offset;
unsigned long set_clk, flags;
int err;
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;
case 2:
clk_shift = MISC_A_CLK_DIV_SHIFT;
clk_enable = MISC_A_CLK_EN;
enable = MISC_A2_EN;
offset = REG_PWM_A2;
break;
case 3:
clk_shift = MISC_B_CLK_DIV_SHIFT;
clk_enable = MISC_B_CLK_EN;
enable = MISC_B2_EN;
offset = REG_PWM_B2;
break;
default:
return;
}
set_clk = meson->data->default_extern_clk;
if (set_clk == 0)
dev_err(meson->chip.dev, "invalid source clock frequency\n");
set_clk /= (channel->pre_div + 1);
err = clk_set_rate(channel->clk_ext, set_clk);
if (err)
pr_err("%s: error in setting pwm rate!\n", __func__);
spin_lock_irqsave(&meson->lock, flags);
value = (channel->hi << PWM_HIGH_SHIFT) | channel->lo;
writel(value, meson->base + offset);
/* Unified reinitialization */
pwm_set_times(meson, id, channel->variant.times);
if (id < 2) {
pwm_set_blink_times(meson, id, channel->variant.blink_times);
if (channel->variant.constant)
pwm_constant_enable(meson, id);
else
pwm_constant_disable(meson, id);
if (channel->variant.blink_enable)
pwm_blink_enable(meson, id);
else
pwm_blink_disable(meson, id);
}
value = readl(meson->base + REG_MISC_AB);
value |= enable;
writel(value, meson->base + REG_MISC_AB);
spin_unlock_irqrestore(&meson->lock, flags);
}
static void meson_pwm_v2_disable(struct meson_pwm *meson, unsigned int id)
{
u32 value, enable;
unsigned long flags;
switch (id) {
case 0:
enable = MISC_A_EN;
break;
case 1:
enable = MISC_B_EN;
break;
case 2:
enable = MISC_A2_EN;
break;
case 3:
enable = MISC_B2_EN;
break;
default:
return;
}
spin_lock_irqsave(&meson->lock, flags);
value = readl(meson->base + REG_MISC_AB);
value &= ~enable;
writel(value, meson->base + REG_MISC_AB);
spin_unlock_irqrestore(&meson->lock, flags);
}
static int meson_pwm_v2_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);
int err = 0;
if (!state)
return -EINVAL;
if (state->enabled && !channel->state.enabled) {
/* Switch from disable to enable put runtime */
err = pm_runtime_get_sync(chip->dev);
if (err < 0)
return err;
}
if (!state->enabled && channel->state.enabled) {
meson_pwm_v2_disable(meson, pwm->hwpwm);
/* Switch from enable to disable put runtime */
pm_runtime_put_sync(chip->dev);
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 (channel->state.enabled)
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_v2_calc(meson, channel, pwm->hwpwm,
state->duty_cycle, state->period);
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_v2_enable(meson, channel, pwm->hwpwm);
channel->state.enabled = true;
}
return err;
}
static void meson_pwm_v2_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;
int err;
if (!state)
return;
switch (pwm->hwpwm) {
case 0:
mask = MISC_A_EN;
break;
case 1:
mask = MISC_B_EN;
break;
case 2:
mask = MISC_A2_EN;
break;
case 3:
mask = MISC_B2_EN;
break;
default:
return;
}
/* Independent operation register needs add runtime */
err = pm_runtime_get_sync(chip->dev);
if (err < 0) {
dev_err(meson->chip.dev, "get runtime fail\n");
return;
}
value = readl(meson->base + REG_MISC_AB);
pm_runtime_put_sync(chip->dev);
state->enabled = (value & mask) != 0;
}
static const struct pwm_ops meson_pwm_v2_ops = {
.request = meson_pwm_v2_request,
.free = meson_pwm_v2_free,
.apply = meson_pwm_v2_apply,
.get_state = meson_pwm_v2_get_state,
.owner = THIS_MODULE,
};
static const struct meson_pwm_data pwm_v2_data = {
.double_channel = true,
.default_extern_clk = 24000000,
.runtime_enabled = true,
};
static const struct of_device_id meson_pwm_v2_matches[] __initconst = {
{
.compatible = "amlogic,meson-v2-pwm",
.data = &pwm_v2_data
},
{},
};
MODULE_DEVICE_TABLE(of, meson_pwm_v2_matches);
static int meson_pwm_v2_init_channels(struct meson_pwm *meson,
struct meson_pwm_channel *channels)
{
struct device *dev = meson->chip.dev;
unsigned int i, err;
char name[255];
for (i = 0; i < (meson->chip.npwm / 2); i++) {
snprintf(name, sizeof(name), "clkin%u", i);
(channels + i)->clk_ext = devm_clk_get(dev, name);
if (IS_ERR((channels + i)->clk_ext)) {
dev_err(meson->chip.dev, "can't get device clock\n");
return PTR_ERR((channels + i)->clk_ext);
}
(channels + i + 2)->clk_ext = (channels + i)->clk_ext;
/* init clk rate here */
err = clk_set_rate((channels + i)->clk_ext, DEFAULT_EXTERN_CLK);
if (err) {
dev_err(meson->chip.dev, "error in setting pwm rate!\n");
return err;
}
}
return 0;
}
static void meson_pwm_v2_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 struct regmap_config meson_pwm_v2_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static int meson_pwm_v2_probe(struct platform_device *pdev)
{
struct meson_pwm *meson;
struct resource *regs;
int ret, i;
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_pwm_v2_regmap_config.max_register = resource_size(regs) - 4;
meson_pwm_v2_regmap_config.name = devm_kasprintf(&pdev->dev,
GFP_KERNEL,
"%s", "pwm");
meson->regmap_base = devm_regmap_init_mmio(&pdev->dev,
meson->base,
&meson_pwm_v2_regmap_config);
spin_lock_init(&meson->lock);
spin_lock_init(&meson->pwm_lock);
meson->chip.dev = &pdev->dev;
meson->chip.ops = &meson_pwm_v2_ops;
meson->chip.base = -1;
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);
if (meson->data->double_channel)
meson->chip.npwm = 4;
else
meson->chip.npwm = 2;
meson->inverter_mask = BIT(meson->chip.npwm) - 1;
meson->channels = devm_kcalloc(&pdev->dev, meson->chip.npwm,
sizeof(*meson->channels), GFP_KERNEL);
if (!meson->channels)
return -ENOMEM;
/* if the PWM clock is transferred to the clk tree */
ret = meson_pwm_v2_init_channels(meson, meson->channels);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, meson);
/* enable runtime, set auto suspend delay 5000ms */
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev,
MESON_PWM_PM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
ret = pm_runtime_get_sync(&pdev->dev);
if (ret < 0)
goto err;
ret = pwmchip_add(&meson->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register PWM chip: %d\n", ret);
goto err;
}
meson_pwm_v2_add_channels(meson, meson->channels);
/*for constant,blinks functions*/
if (meson->data->double_channel)
meson_pwm_sysfs_init(&pdev->dev);
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
/* Calculate the pwm channel that has been enabled before the kernel */
for (i = 0; i < meson->chip.npwm; i++) {
meson_pwm_v2_get_state(&meson->chip, meson->chip.pwms + i,
&meson->channels[i].state);
if (meson->channels[i].state.enabled)
pm_runtime_get_sync(&pdev->dev);
}
return 0;
err:
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return ret;
}
static int meson_pwm_v2_runtime_suspend(struct device *dev)
{
struct meson_pwm *meson = (struct meson_pwm *)dev_get_drvdata(dev);
struct meson_pwm_channel *channel;
int i;
for (i = 0; i < meson->chip.npwm / 2; i++) {
channel = meson->channels + i;
clk_disable_unprepare(channel->clk_ext);
}
pinctrl_pm_select_sleep_state(dev);
return 0;
}
static int meson_pwm_v2_runtime_resume(struct device *dev)
{
struct meson_pwm *meson = (struct meson_pwm *)dev_get_drvdata(dev);
struct meson_pwm_channel *channel;
int err, i;
for (i = 0; i < meson->chip.npwm / 2; i++) {
channel = meson->channels + i;
err = clk_prepare_enable(channel->clk_ext);
if (err)
pr_err("Failed to enable pwm clock\n");
}
pinctrl_pm_select_default_state(dev);
return 0;
}
static int meson_pwm_v2_remove(struct platform_device *pdev)
{
struct meson_pwm *meson = platform_get_drvdata(pdev);
if (meson->data->double_channel)
meson_pwm_sysfs_exit(&pdev->dev);
return pwmchip_remove(&meson->chip);
}
static const struct dev_pm_ops meson_pwm_v2_pm_ops = {
SET_RUNTIME_PM_OPS(meson_pwm_v2_runtime_suspend,
meson_pwm_v2_runtime_resume, NULL)
};
static struct platform_driver meson_pwm_v2_driver = {
.driver = {
.name = "meson-pwm-v2",
.pm = &meson_pwm_v2_pm_ops,
},
.probe = meson_pwm_v2_probe,
.remove = meson_pwm_v2_remove,
};
static int __init meson_pwm_v2_init(void)
{
const struct of_device_id *match_id;
int ret;
match_id = meson_pwm_v2_matches;
meson_pwm_v2_driver.driver.of_match_table = match_id;
ret = platform_driver_register(&meson_pwm_v2_driver);
return ret;
}
static void __exit meson_pwm_v2_exit(void)
{
platform_driver_unregister(&meson_pwm_v2_driver);
}
fs_initcall_sync(meson_pwm_v2_init);
module_exit(meson_pwm_v2_exit);
MODULE_DESCRIPTION("Amlogic Meson PWM Generator driver");
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
MODULE_LICENSE("Dual BSD/GPL");