mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-11 22:03:15 +00:00
41575d8e4c
This construct is quite long-winded. In earlier days it made some sense since auto-allocation was a strange concept. But with driver model now used pretty universally, we can shorten this to 'auto'. This reduces verbosity and makes it easier to read. Coincidentally it also ensures that every declaration is on one line, thus making dtoc's job easier. Signed-off-by: Simon Glass <sjg@chromium.org>
188 lines
4.6 KiB
C
188 lines
4.6 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
|
|
*
|
|
* Author: Sam Shih <sam.shih@mediatek.com>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <clk.h>
|
|
#include <dm.h>
|
|
#include <pwm.h>
|
|
#include <div64.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/io.h>
|
|
|
|
/* PWM registers and bits definitions */
|
|
#define PWMCON 0x00
|
|
#define PWMHDUR 0x04
|
|
#define PWMLDUR 0x08
|
|
#define PWMGDUR 0x0c
|
|
#define PWMWAVENUM 0x28
|
|
#define PWMDWIDTH 0x2c
|
|
#define PWM45DWIDTH_FIXUP 0x30
|
|
#define PWMTHRES 0x30
|
|
#define PWM45THRES_FIXUP 0x34
|
|
|
|
#define PWM_CLK_DIV_MAX 7
|
|
#define MAX_PWM_NUM 8
|
|
|
|
#define NSEC_PER_SEC 1000000000L
|
|
|
|
static const unsigned int mtk_pwm_reg_offset[] = {
|
|
0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220
|
|
};
|
|
|
|
struct mtk_pwm_soc {
|
|
unsigned int num_pwms;
|
|
bool pwm45_fixup;
|
|
};
|
|
|
|
struct mtk_pwm_priv {
|
|
void __iomem *base;
|
|
struct clk top_clk;
|
|
struct clk main_clk;
|
|
struct clk pwm_clks[MAX_PWM_NUM];
|
|
const struct mtk_pwm_soc *soc;
|
|
};
|
|
|
|
static void mtk_pwm_w32(struct udevice *dev, uint channel, uint reg, uint val)
|
|
{
|
|
struct mtk_pwm_priv *priv = dev_get_priv(dev);
|
|
u32 offset = mtk_pwm_reg_offset[channel];
|
|
|
|
writel(val, priv->base + offset + reg);
|
|
}
|
|
|
|
static int mtk_pwm_set_config(struct udevice *dev, uint channel,
|
|
uint period_ns, uint duty_ns)
|
|
{
|
|
struct mtk_pwm_priv *priv = dev_get_priv(dev);
|
|
u32 clkdiv = 0, clksel = 0, cnt_period, cnt_duty,
|
|
reg_width = PWMDWIDTH, reg_thres = PWMTHRES;
|
|
u64 resolution;
|
|
int ret = 0;
|
|
|
|
clk_enable(&priv->top_clk);
|
|
clk_enable(&priv->main_clk);
|
|
/* Using resolution in picosecond gets accuracy higher */
|
|
resolution = (u64)NSEC_PER_SEC * 1000;
|
|
do_div(resolution, clk_get_rate(&priv->pwm_clks[channel]));
|
|
cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
|
|
while (cnt_period > 8191) {
|
|
resolution *= 2;
|
|
clkdiv++;
|
|
cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000,
|
|
resolution);
|
|
if (clkdiv > PWM_CLK_DIV_MAX && clksel == 0) {
|
|
clksel = 1;
|
|
clkdiv = 0;
|
|
resolution = (u64)NSEC_PER_SEC * 1000 * 1625;
|
|
do_div(resolution,
|
|
clk_get_rate(&priv->pwm_clks[channel]));
|
|
cnt_period = DIV_ROUND_CLOSEST_ULL(
|
|
(u64)period_ns * 1000, resolution);
|
|
clk_enable(&priv->pwm_clks[channel]);
|
|
}
|
|
}
|
|
if (clkdiv > PWM_CLK_DIV_MAX && clksel == 1) {
|
|
printf("pwm period %u not supported\n", period_ns);
|
|
return -EINVAL;
|
|
}
|
|
if (priv->soc->pwm45_fixup && channel > 2) {
|
|
/*
|
|
* PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES
|
|
* from the other PWMs on MT7623.
|
|
*/
|
|
reg_width = PWM45DWIDTH_FIXUP;
|
|
reg_thres = PWM45THRES_FIXUP;
|
|
}
|
|
cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution);
|
|
if (clksel == 1)
|
|
mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | BIT(3) | clkdiv);
|
|
else
|
|
mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | clkdiv);
|
|
mtk_pwm_w32(dev, channel, reg_width, cnt_period);
|
|
mtk_pwm_w32(dev, channel, reg_thres, cnt_duty);
|
|
|
|
return ret;
|
|
};
|
|
|
|
static int mtk_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
|
|
{
|
|
struct mtk_pwm_priv *priv = dev_get_priv(dev);
|
|
u32 val = 0;
|
|
|
|
val = readl(priv->base);
|
|
if (enable)
|
|
val |= BIT(channel);
|
|
else
|
|
val &= ~BIT(channel);
|
|
writel(val, priv->base);
|
|
|
|
return 0;
|
|
};
|
|
|
|
static int mtk_pwm_probe(struct udevice *dev)
|
|
{
|
|
struct mtk_pwm_priv *priv = dev_get_priv(dev);
|
|
int ret = 0;
|
|
int i;
|
|
|
|
priv->soc = (struct mtk_pwm_soc *)dev_get_driver_data(dev);
|
|
priv->base = dev_read_addr_ptr(dev);
|
|
if (!priv->base)
|
|
return -EINVAL;
|
|
ret = clk_get_by_name(dev, "top", &priv->top_clk);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = clk_get_by_name(dev, "main", &priv->main_clk);
|
|
if (ret < 0)
|
|
return ret;
|
|
for (i = 0; i < priv->soc->num_pwms; i++) {
|
|
char name[8];
|
|
|
|
snprintf(name, sizeof(name), "pwm%d", i + 1);
|
|
ret = clk_get_by_name(dev, name, &priv->pwm_clks[i]);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct pwm_ops mtk_pwm_ops = {
|
|
.set_config = mtk_pwm_set_config,
|
|
.set_enable = mtk_pwm_set_enable,
|
|
};
|
|
|
|
static const struct mtk_pwm_soc mt7622_data = {
|
|
.num_pwms = 6,
|
|
.pwm45_fixup = false,
|
|
};
|
|
|
|
static const struct mtk_pwm_soc mt7623_data = {
|
|
.num_pwms = 5,
|
|
.pwm45_fixup = true,
|
|
};
|
|
|
|
static const struct mtk_pwm_soc mt7629_data = {
|
|
.num_pwms = 1,
|
|
.pwm45_fixup = false,
|
|
};
|
|
|
|
static const struct udevice_id mtk_pwm_ids[] = {
|
|
{ .compatible = "mediatek,mt7622-pwm", .data = (ulong)&mt7622_data },
|
|
{ .compatible = "mediatek,mt7623-pwm", .data = (ulong)&mt7623_data },
|
|
{ .compatible = "mediatek,mt7629-pwm", .data = (ulong)&mt7629_data },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(mtk_pwm) = {
|
|
.name = "mtk_pwm",
|
|
.id = UCLASS_PWM,
|
|
.of_match = mtk_pwm_ids,
|
|
.ops = &mtk_pwm_ops,
|
|
.probe = mtk_pwm_probe,
|
|
.priv_auto = sizeof(struct mtk_pwm_priv),
|
|
};
|