From 52f4ac49f491bc363c9ded8cb513d3e46e3584e4 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Tue, 24 Oct 2023 10:41:23 +0800 Subject: [PATCH] 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 Change-Id: I80ae8e40562d7593f1a2183ea73e29de798495f4 --- drivers/misc/rk628/Makefile | 2 +- drivers/misc/rk628/panel.c | 13 +- drivers/misc/rk628/rk628.c | 99 ++++++++- drivers/misc/rk628/rk628.h | 19 ++ drivers/misc/rk628/rk628_pwm.c | 393 +++++++++++++++++++++++++++++++++ drivers/misc/rk628/rk628_pwm.h | 16 ++ 6 files changed, 529 insertions(+), 13 deletions(-) create mode 100644 drivers/misc/rk628/rk628_pwm.c create mode 100644 drivers/misc/rk628/rk628_pwm.h diff --git a/drivers/misc/rk628/Makefile b/drivers/misc/rk628/Makefile index fe5b33b4e52f..d463d90b6782 100644 --- a/drivers/misc/rk628/Makefile +++ b/drivers/misc/rk628/Makefile @@ -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 diff --git a/drivers/misc/rk628/panel.c b/drivers/misc/rk628/panel.c index ed7b89b335ea..a3feb4dbfde1 100644 --- a/drivers/misc/rk628/panel.c +++ b/drivers/misc/rk628/panel.c @@ -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); diff --git a/drivers/misc/rk628/rk628.c b/drivers/misc/rk628/rk628.c index af7069748d43..102e8b12ff83 100644 --- a/drivers/misc/rk628/rk628.c +++ b/drivers/misc/rk628/rk628.c @@ -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 diff --git a/drivers/misc/rk628/rk628.h b/drivers/misc/rk628/rk628.h index e257a29a3e67..3ceacf1822ad 100644 --- a/drivers/misc/rk628/rk628.h +++ b/drivers/misc/rk628/rk628.h @@ -18,6 +18,7 @@ #include #include #include +#include #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) diff --git a/drivers/misc/rk628/rk628_pwm.c b/drivers/misc/rk628/rk628_pwm.c new file mode 100644 index 000000000000..4ad962989116 --- /dev/null +++ b/drivers/misc/rk628/rk628_pwm.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Rockchip Electronics Co., Ltd. + * + * Author: Lingsong Ding + */ + +#include +#include +#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); +} diff --git a/drivers/misc/rk628/rk628_pwm.h b/drivers/misc/rk628/rk628_pwm.h new file mode 100644 index 000000000000..a2c8c9295ba1 --- /dev/null +++ b/drivers/misc/rk628/rk628_pwm.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (c) 2023 Rockchip Electronics Co., Ltd. + * + * Author: Lingsong Ding + */ + +#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