misc: rk628: add support for pwm

In addition, add pwm debugfs file to dump regs:

cat /sys/kernel/debug/rk628/2-0050/pwm_regs

Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
Change-Id: I80ae8e40562d7593f1a2183ea73e29de798495f4
This commit is contained in:
Damon Ding
2023-10-24 10:41:23 +08:00
committed by Zhibin Huang
parent 95a90239c1
commit 52f4ac49f4
6 changed files with 529 additions and 13 deletions

View File

@@ -3,7 +3,7 @@
rk628_misc-$(CONFIG_RK628_MISC) += rk628.o rk628_cru.o rk628_config.o rk628_post_process.o \
rk628_combrxphy.o rk628_hdmirx.o rk628_combtxphy.o rk628_dsi.o \
panel.o rk628_lvds.o rk628_rgb.o rk628_gvi.o rk628_pinctrl.o \
rk628_csi.o rk628_efuse.o
rk628_csi.o rk628_efuse.o rk628_pwm.o
rk628_misc-$(CONFIG_RK628_MISC_HDMITX) += rk628_hdmitx.o

View File

@@ -164,14 +164,15 @@ int rk628_panel_info_get(struct rk628 *rk628, struct device_node *np)
backlight = of_parse_phandle(dev->of_node, "panel-backlight", 0);
if (backlight) {
panel->backlight = of_find_backlight_by_node(backlight);
of_node_put(backlight);
if (!rk628->pwm_bl_en) {
panel->backlight = of_find_backlight_by_node(backlight);
of_node_put(backlight);
if (!panel->backlight) {
dev_err(dev, "failed to find backlight\n");
return -EPROBE_DEFER;
if (!panel->backlight) {
dev_err(dev, "%s: failed to find backlight\n", __func__);
return -EPROBE_DEFER;
}
}
}
device_property_read_u32(dev, "panel-prepare-delay-ms", &panel->delay.prepare);

View File

@@ -31,6 +31,7 @@
#include "rk628_hdmitx.h"
#include "rk628_efuse.h"
#include "rk628_config.h"
#include "rk628_pwm.h"
static const struct regmap_range rk628_cru_readable_ranges[] = {
regmap_reg_range(CRU_CPLL_CON0, CRU_CPLL_CON4),
@@ -586,6 +587,27 @@ static int rk628_fb_notifier_callback(struct notifier_block *nb,
}
#endif
static int rk628_get_pwm_bl(struct rk628 *rk628)
{
struct device_node *backlight;
backlight = of_parse_phandle(rk628->dev->of_node, "panel-backlight", 0);
if (backlight) {
rk628->panel->backlight = of_find_backlight_by_node(backlight);
of_node_put(backlight);
if (!rk628->panel->backlight) {
dev_err(rk628->dev, "%s: failed to find backlight\n", __func__);
return -EAGAIN;
}
} else {
dev_err(rk628->dev, "%s: failed to find panel-backlight\n", __func__);
return -EINVAL;
}
return 0;
}
static void rk628_display_work(struct work_struct *work)
{
u8 ret = 0;
@@ -609,7 +631,22 @@ static void rk628_display_work(struct work_struct *work)
if (ret & HDMIRX_PLUGIN) {
/* if resolution or input format change, disable first */
rk628_display_disable(rk628);
rk628_display_enable(rk628);
if (rk628->pwm_bl_en && !rk628->panel->backlight) {
int bl_ret = 0;
bl_ret = rk628_get_pwm_bl(rk628);
if (!bl_ret) {
rk628_display_enable(rk628);
} else if (bl_ret == -EAGAIN) {
queue_delayed_work(rk628->monitor_wq,
&rk628->delay_work, msecs_to_jiffies(50));
return;
} else {
return;
}
} else {
rk628_display_enable(rk628);
}
} else if (ret & HDMIRX_PLUGOUT) {
rk628_display_disable(rk628);
}
@@ -914,6 +951,19 @@ static int rk628_display_timings_get(struct rk628 *rk628)
}
static void rk628_pwm_work(struct work_struct *work)
{
struct rk628 *rk628 = container_of(work, struct rk628, pwm_delay_work.work);
int ret;
ret = rk628_get_pwm_bl(rk628);
if (!ret)
rk628_display_enable(rk628);
else if (ret == -EAGAIN)
queue_delayed_work(rk628->pwm_wq, &rk628->pwm_delay_work,
msecs_to_jiffies(50));
}
#define DEBUG_PRINT(args...) \
do { \
if (s) \
@@ -1377,6 +1427,7 @@ static void rk628_debugfs_create(struct rk628 *rk628)
rk628_mipi_dsi_create_debugfs_file(rk628);
rk628_gvi_create_debugfs_file(rk628);
rk628_hdmitx_create_debugfs_file(rk628);
rk628_pwm_create_debugfs_file(rk628);
}
static void rk628_loader_protect(struct rk628 *rk628)
@@ -1412,6 +1463,7 @@ rk628_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
struct rk628 *rk628;
int i, ret;
unsigned long irq_flags;
struct device_node *np;
rk628 = devm_kzalloc(dev, sizeof(*rk628), GFP_KERNEL);
if (!rk628)
@@ -1422,17 +1474,38 @@ rk628_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
i2c_set_clientdata(client, rk628);
rk628->hdmirx_irq = client->irq;
np = of_find_node_by_name(rk628->dev->of_node, "rk628-pwm");
if (of_device_is_available(np)) {
ret = rk628_pwm_probe(rk628, np);
if (ret) {
of_node_put(np);
dev_err(dev, "failed to probe pwm\n");
return ret;
}
}
of_node_put(np);
if (rk628->pwm_bl_en) {
rk628->pwm_wq = alloc_ordered_workqueue("%s",
WQ_MEM_RECLAIM | WQ_FREEZABLE, "rk628-pwm-wq");
if (!rk628->pwm_wq)
return -ENOMEM;
INIT_DELAYED_WORK(&rk628->pwm_delay_work, rk628_pwm_work);
}
ret = rk628_display_route_info_parse(rk628);
if (ret) {
dev_err(dev, "display route parse err\n");
return ret;
if (ret != -EPROBE_DEFER)
dev_err(dev, "display route err\n");
goto destroy_pwm_wq;
}
if (!rk628_output_is_csi(rk628)) {
ret = rk628_display_timings_get(rk628);
if (ret && !rk628_output_is_hdmi(rk628)) {
dev_info(dev, "display timings err\n");
return ret;
goto destroy_pwm_wq;
}
}
@@ -1443,7 +1516,7 @@ rk628_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
if (IS_ERR(rk628->soc_24M)) {
ret = PTR_ERR(rk628->soc_24M);
dev_err(dev, "Unable to get soc_24M: %d\n", ret);
return ret;
goto destroy_pwm_wq;
}
clk_prepare_enable(rk628->soc_24M);
@@ -1518,6 +1591,8 @@ rk628_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
INIT_DELAYED_WORK(&rk628->dsi_delay_work, rk628_dsi_work);
}
rk628_pwm_init(rk628);
rk628_cru_init(rk628);
if (rk628_output_is_csi(rk628))
@@ -1566,7 +1641,11 @@ rk628_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
msecs_to_jiffies(50));
}
} else {
rk628_display_enable(rk628);
if (rk628->pwm_bl_en)
queue_delayed_work(rk628->pwm_wq, &rk628->pwm_delay_work,
msecs_to_jiffies(50));
else
rk628_display_enable(rk628);
}
pm_runtime_enable(dev);
@@ -1576,6 +1655,11 @@ rk628_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
err_clk:
clk_disable_unprepare(rk628->soc_24M);
destroy_pwm_wq:
if (rk628->pwm_wq)
destroy_workqueue(rk628->pwm_wq);
return ret;
}
@@ -1601,6 +1685,9 @@ static int rk628_i2c_remove(struct i2c_client *client)
rk628_set_hdmirx_irq(rk628, GRF_INTR0_EN, false);
cancel_delayed_work_sync(&rk628->delay_work);
destroy_workqueue(rk628->monitor_wq);
cancel_delayed_work_sync(&rk628->pwm_delay_work);
destroy_workqueue(rk628->pwm_wq);
put_device(&rk628->panel->backlight->dev);
pm_runtime_disable(dev);
clk_disable_unprepare(rk628->soc_24M);
#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE

View File

@@ -18,6 +18,7 @@
#include <linux/wakelock.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#include <linux/pwm.h>
#define DRIVER_VERSION "0.1.0"
#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l)))
@@ -223,6 +224,11 @@
#define GRF_OS_REG1 0x0144
#define GRF_OS_REG2 0x0148
#define GRF_OS_REG3 0x014c
#define GRF_PWM_PERIOD 0x0150
#define GRF_PWM_DUTY 0x0154
#define GRF_PWM_CTRL 0x0158
#define GRF_PWM_CH_CNT 0x015c
#define GRF_PWM_STATUS 0x0160
#define GRF_RGB_RX_DBG_MEAS0 0x0170
#define RGB_RX_EVAL_TIME_MASK GENMASK(27, 16)
#define RGB_RX_MODET_EN BIT(1)
@@ -547,6 +553,15 @@ struct rk628_rgb {
bool bt1120_uv_swap;
};
struct rk628_pwm {
struct pwm_chip chip;
unsigned long clk_rate;
bool center_aligned;
bool oneshot_en;
bool is_output_m1;
int irq;
};
struct rk628 {
struct device *dev;
struct i2c_client *client;
@@ -578,12 +593,16 @@ struct rk628 {
struct rk628_lvds lvds;
struct rk628_gvi gvi;
struct rk628_combtxphy combtxphy;
struct rk628_pwm pwm;
int sync_pol;
void *csi;
struct notifier_block fb_nb;
u32 version;
struct rk628_rgb rgb;
int old_blank;
struct workqueue_struct *pwm_wq;
struct delayed_work pwm_delay_work;
bool pwm_bl_en;
};
static inline bool rk628_input_is_hdmi(struct rk628 *rk628)

View File

@@ -0,0 +1,393 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Rockchip Electronics Co., Ltd.
*
* Author: Lingsong Ding <damon.ding@rock-chips.com>
*/
#include <linux/debugfs.h>
#include <linux/platform_device.h>
#include "rk628.h"
#include "rk628_cru.h"
#include "rk628_gpio.h"
#include "rk628_pwm.h"
#define PWM_ENABLE (1 << 0)
#define PWM_MODE_SHIFT 1
#define PWM_MODE_MASK (0x3 << PWM_MODE_SHIFT)
#define PWM_ONESHOT (0 << PWM_MODE_SHIFT)
#define PWM_CONTINUOUS (1 << PWM_MODE_SHIFT)
#define PWM_DUTY_POSITIVE (1 << 3)
#define PWM_DUTY_NEGATIVE (0 << 3)
#define PWM_INACTIVE_NEGATIVE (0 << 4)
#define PWM_INACTIVE_POSITIVE (1 << 4)
#define PWM_POLARITY_MASK (PWM_DUTY_POSITIVE | PWM_INACTIVE_POSITIVE)
#define PWM_OUTPUT_LEFT (0 << 5)
#define PWM_OUTPUT_CENTER (1 << 5)
#define PWM_LP_DISABLE (0 << 8)
#define PWM_CLK_SEL_SHIFT 9
#define PWM_CLK_SEL_MASK (1 << PWM_CLK_SEL_SHIFT)
#define PWM_SEL_NO_SCALED_CLOCK (0 << PWM_CLK_SEL_SHIFT)
#define PWM_SEL_SCALED_CLOCK (1 << PWM_CLK_SEL_SHIFT)
#define PWM_PRESCELE_SHIFT 12
#define PWM_PRESCALE_MASK (0x3 << PWM_PRESCELE_SHIFT)
#define PWM_SCALE_SHIFT 16
#define PWM_SCALE_MASK (0xff << PWM_SCALE_SHIFT)
#define PWM_ONESHOT_COUNT_SHIFT 24
#define PWM_ONESHOT_COUNT_MASK (0xff << PWM_ONESHOT_COUNT_SHIFT)
#define PWM_ONESHOT_COUNT_MAX 256
#define PWM_EN_CLR_SHIFT 2
#define PWM_EN_CLR (1 << PWM_EN_CLR_SHIFT)
#define PWM_INT_SET_SHIFT 3
#define PWM_ENABLE_CONF (PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | PWM_CONTINUOUS)
#define PWM_ENABLE_CONF_MASK (GENMASK(2, 0) | BIT(5) | BIT(8))
#define PWM_DCLK_RATE 24000000
#define DEBUG_PRINT(args...) \
do { \
if (s) \
seq_printf(s, args); \
else \
pr_info(args); \
} while (0)
static inline struct rk628 *to_rk628(struct rk628_pwm *p)
{
return container_of(p, struct rk628, pwm);
}
static inline struct rk628_pwm *to_rk628_pwm(struct pwm_chip *pc)
{
return container_of(pc, struct rk628_pwm, chip);
}
static void rk628_pwm_dclk_enable(struct rk628 *rk628)
{
}
static void rk628_pwm_dclk_disable(struct rk628 *rk628)
{
}
#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE
static void rk628_pwm_get_state(struct pwm_chip *chip,
struct pwm_device *pwm,
struct pwm_state *state)
#else
static int rk628_pwm_get_state(struct pwm_chip *chip,
struct pwm_device *pwm,
struct pwm_state *state)
#endif
{
struct rk628_pwm *p = to_rk628_pwm(chip);
struct rk628 *rk628 = to_rk628(p);
u32 enable_conf = PWM_ENABLE_CONF;
u32 val;
u32 dclk_div;
u64 tmp;
dclk_div = p->oneshot_en ? 2 : 1;
rk628_i2c_read(rk628, GRF_PWM_PERIOD, &val);
tmp = val * dclk_div * NSEC_PER_SEC;
state->period = DIV_ROUND_CLOSEST_ULL(tmp, p->clk_rate);
rk628_i2c_read(rk628, GRF_PWM_DUTY, &val);
tmp = val * dclk_div * NSEC_PER_SEC;
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, p->clk_rate);
rk628_i2c_read(rk628, GRF_PWM_CTRL, &val);
if (p->oneshot_en)
enable_conf &= ~PWM_CONTINUOUS;
state->enabled = (val & enable_conf) == enable_conf;
if (!(val & PWM_DUTY_POSITIVE))
state->polarity = PWM_POLARITY_INVERSED;
else
state->polarity = PWM_POLARITY_NORMAL;
#if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE
return 0;
#endif
}
static void rk628_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct rk628_pwm *p = to_rk628_pwm(chip);
struct rk628 *rk628 = to_rk628(p);
unsigned long period, duty;
u64 div;
u32 ctrl;
u8 dclk_div = 1;
#ifdef CONFIG_PWM_ROCKCHIP_ONESHOT
if (state->oneshot_count > 0 && state->oneshot_count <= PWM_ONESHOT_COUNT_MAX)
dclk_div = 2;
#endif
/*
* Since period and duty cycle registers have a width of 32
* bits, every possible input period can be obtained using the
* default prescaler value for all practical clock rate values.
*/
div = (u64)p->clk_rate * state->period;
period = DIV_ROUND_CLOSEST_ULL(div, dclk_div * NSEC_PER_SEC);
div = (u64)p->clk_rate * state->duty_cycle;
duty = DIV_ROUND_CLOSEST_ULL(div, dclk_div * NSEC_PER_SEC);
/*
* Lock the period and duty of previous configuration, then
* change the duty and period, that would not be effective.
*/
rk628_i2c_read(rk628, GRF_PWM_CTRL, &ctrl);
#ifdef CONFIG_PWM_ROCKCHIP_ONESHOT
if (state->oneshot_count > 0 && state->oneshot_count <= PWM_ONESHOT_COUNT_MAX) {
u32 int_ctrl;
/*
* This is a workaround, an uncertain waveform will be
* generated after oneshot ends. It is needed to enable
* the dclk scale function to resolve it. It doesn't
* matter what the scale factor is, just make sure the
* scale function is turned on, for which we set scale
* factor to 2.
*/
ctrl &= ~PWM_SCALE_MASK;
ctrl |= (dclk_div / 2) << PWM_SCALE_SHIFT;
ctrl &= ~PWM_CLK_SEL_MASK;
ctrl |= PWM_SEL_SCALED_CLOCK;
p->oneshot_en = true;
ctrl &= ~PWM_MODE_MASK;
ctrl |= PWM_ONESHOT;
ctrl &= ~PWM_ONESHOT_COUNT_MASK;
ctrl |= (state->oneshot_count - 1) << PWM_ONESHOT_COUNT_SHIFT;
rk628_i2c_read(rk628, GRF_PWM_STATUS, &int_ctrl);
int_ctrl |= PWM_EN_CLR;
rk628_i2c_write(rk628, GRF_PWM_STATUS, int_ctrl);
} else {
u32 int_ctrl;
ctrl &= ~PWM_SCALE_MASK;
ctrl &= ~PWM_CLK_SEL_MASK;
ctrl |= PWM_SEL_NO_SCALED_CLOCK;
if (state->oneshot_count)
dev_err(chip->dev, "Oneshot_count must be between 1 and 256.\n");
p->oneshot_en = false;
ctrl &= ~PWM_MODE_MASK;
ctrl |= PWM_CONTINUOUS;
ctrl &= ~PWM_ONESHOT_COUNT_MASK;
rk628_i2c_read(rk628, GRF_PWM_STATUS, &int_ctrl);
int_ctrl &= ~PWM_EN_CLR;
rk628_i2c_write(rk628, GRF_PWM_STATUS, int_ctrl);
}
#endif
rk628_i2c_write(rk628, GRF_PWM_PERIOD, period);
rk628_i2c_write(rk628, GRF_PWM_DUTY, duty);
ctrl &= ~PWM_POLARITY_MASK;
if (state->polarity == PWM_POLARITY_INVERSED)
ctrl |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
else
ctrl |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
rk628_i2c_write(rk628, GRF_PWM_CTRL, ctrl);
}
static int rk628_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, bool enable)
{
struct rk628_pwm *p = to_rk628_pwm(chip);
struct rk628 *rk628 = to_rk628(p);
u32 val;
if (enable)
rk628_pwm_dclk_enable(rk628);
rk628_i2c_read(rk628, GRF_PWM_CTRL, &val);
val &= ~PWM_ENABLE_CONF_MASK;
if (p->center_aligned)
val |= PWM_OUTPUT_CENTER;
if (enable) {
val |= PWM_ENABLE_CONF;
if (p->oneshot_en)
val &= ~PWM_CONTINUOUS;
} else {
val &= ~PWM_ENABLE_CONF;
}
rk628_i2c_write(rk628, GRF_PWM_CTRL, val);
if (!enable)
rk628_pwm_dclk_disable(rk628);
return 0;
}
static void rk628_pwm_set_iomux(struct pwm_chip *chip)
{
struct rk628_pwm *p = to_rk628_pwm(chip);
struct rk628 *rk628 = to_rk628(p);
/*
* The pwm output iomux:
* m0: gpio3b4
* m1: gpio0a6
*/
if (p->is_output_m1) {
rk628_i2c_write(rk628, GRF_GPIO0AB_SEL_CON, 0x03000300);
rk628_i2c_write(rk628, RK628_GPIO0_BASE + GPIO_SWPORT_DDR_L, 0x00400040);
} else {
rk628_i2c_write(rk628, GRF_GPIO3AB_SEL_CON, 0x30003000);
rk628_i2c_write(rk628, RK628_GPIO3_BASE + GPIO_SWPORT_DDR_L, 0x10001000);
}
}
static int rk628_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct pwm_state curstate;
bool enabled;
int ret = 0;
pwm_get_state(pwm, &curstate);
enabled = curstate.enabled;
if (state->polarity != curstate.polarity && enabled) {
ret = rk628_pwm_enable(chip, pwm, false);
if (ret)
goto out;
enabled = false;
}
rk628_pwm_config(chip, pwm, state);
if (state->enabled != enabled) {
ret = rk628_pwm_enable(chip, pwm, state->enabled);
if (ret)
goto out;
}
if (state->enabled)
rk628_pwm_set_iomux(chip);
out:
return ret;
}
static const struct pwm_ops rk628_pwm_ops = {
.get_state = rk628_pwm_get_state,
.apply = rk628_pwm_apply,
.owner = THIS_MODULE,
};
int rk628_pwm_probe(struct rk628 *rk628, struct device_node *pwm_np)
{
struct pwm_chip *chip = &rk628->pwm.chip;
struct platform_device *pd;
int ret;
pd = platform_device_alloc("rk628-pwm", -1);
if (!pd) {
dev_err(rk628->dev, "failed to alloc pwm platform device\n");
return -EINVAL;
}
ret = platform_device_add(pd);
if (ret) {
dev_err(rk628->dev, "failed to add pwm platform device\n");
return -EINVAL;
}
chip->dev = &pd->dev;
chip->dev->of_node = pwm_np;
chip->ops = &rk628_pwm_ops;
chip->base = -1;
chip->npwm = 1;
chip->of_xlate = of_pwm_xlate_with_flags;
chip->of_pwm_n_cells = 3;
rk628->pwm.clk_rate = PWM_DCLK_RATE;
rk628->pwm.center_aligned = of_property_read_bool(pwm_np, "pwm,center-aligned");
rk628->pwm.is_output_m1 = of_property_read_bool(pwm_np, "pwm,output-m1");
rk628->pwm_bl_en = of_property_read_bool(pwm_np, "pwm,backlight");
ret = pwmchip_add(chip);
if (ret < 0) {
dev_err(rk628->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
}
/*
* To avoid warning in devm_pwm_get() process.
*/
pd->dev.links.status = DL_DEV_PROBING;
return 0;
}
void rk628_pwm_init(struct rk628 *rk628)
{
u32 ctrl;
bool enabled;
rk628_i2c_read(rk628, GRF_PWM_CTRL, &ctrl);
enabled = (ctrl & PWM_ENABLE_CONF) == PWM_ENABLE_CONF;
if (!enabled)
rk628_pwm_dclk_disable(rk628);
}
static int rk628_debugfs_pwm_regs_dump(struct seq_file *s, void *data)
{
struct rk628 *rk628 = s->private;
u32 val;
DEBUG_PRINT("pwm regs:\n");
rk628_i2c_read(rk628, GRF_PWM_PERIOD, &val);
DEBUG_PRINT("period: 0x%08x\n", val);
rk628_i2c_read(rk628, GRF_PWM_DUTY, &val);
DEBUG_PRINT("duty: 0x%08x\n", val);
rk628_i2c_read(rk628, GRF_PWM_CTRL, &val);
DEBUG_PRINT("ctrl: 0x%08x\n", val);
rk628_i2c_read(rk628, GRF_PWM_CH_CNT, &val);
DEBUG_PRINT("ch_cnt: 0x%08x\n", val);
rk628_i2c_read(rk628, GRF_PWM_STATUS, &val);
DEBUG_PRINT("status: 0x%08x\n", val);
return 0;
}
static int rk628_debugfs_pwm_regs_open(struct inode *inode, struct file *file)
{
struct rk628 *rk628 = inode->i_private;
return single_open(file, rk628_debugfs_pwm_regs_dump, rk628);
}
static const struct file_operations rk628_debugfs_pwm_regs_fops = {
.owner = THIS_MODULE,
.open = rk628_debugfs_pwm_regs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void rk628_pwm_create_debugfs_file(struct rk628 *rk628)
{
debugfs_create_file("pwm_regs", 0400, rk628->debug_dir, rk628,
&rk628_debugfs_pwm_regs_fops);
}

View File

@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (c) 2023 Rockchip Electronics Co., Ltd.
*
* Author: Lingsong Ding <damon.ding@rock-chips.com>
*/
#ifndef RK628_PWM_H
#define RK628_PWM_H
#include "rk628.h"
int rk628_pwm_probe(struct rk628 *rk628, struct device_node *pwm_np);
void rk628_pwm_init(struct rk628 *rk628);
void rk628_pwm_create_debugfs_file(struct rk628 *rk628);
#endif