Hi Andrew,
The 01/22/2021 12:38, Andrew Jeffery wrote:
> Hi Troy,
>
> On Mon, 18 Jan 2021, at 17:20, Troy Lee wrote:
> > Add Aspeed AST2600 PWM/Fan tacho driver. AST2600 has 16 PWM channel and
> > 16 FAN tacho channel.
> >
> > Changes since v2:
> > - declare local function as static function
> >
> > Changes since v1:
> > - fixed review comments
> > - fixed double-looped calculation of div_h and div_l
> > - moving configuration to device tree
> > - register hwmon driver with devm_hwmon_device_register_with_info()
> >
> > Signed-off-by: Troy Lee <[email protected]>
> > Reported-by: kernel test robot <[email protected]>
> > ---
...
> > diff --git a/drivers/hwmon/aspeed2600-pwm-tacho.c
> > b/drivers/hwmon/aspeed2600-pwm-tacho.c
> > new file mode 100644
> > index 000000000000..00ff100db92f
> > --- /dev/null
> > +++ b/drivers/hwmon/aspeed2600-pwm-tacho.c
> > @@ -0,0 +1,756 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Copyright (C) ASPEED Technology Inc.
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 or
> > later as
> > + * published by the Free Software Foundation.
> > + */
>
> The license is captured by the SPDX marker above. This summary text should be
> dropped (though retain the copyright line).
>
> > +
> > +#include <linux/clk.h>
> > +#include <linux/errno.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/delay.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/hwmon-sysfs.h>
> > +#include <linux/io.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/of_platform.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/sysfs.h>
> > +#include <linux/reset.h>
> > +#include <linux/regmap.h>
> > +#include <linux/thermal.h>
> > +/**********************************************************
> > + * PWM HW register offset define
> > + *********************************************************/
>
> Banner comments like this generally aren't preferred. If you think the
> comment text itself is necessary, just do:
>
> /* PWM HW register offset define */
>
> However, the macro names should indicate what we're defining anyway, so I
> feel we can remove it entirely without too much loss
>
I'll remove the redundant description and comments.
> > +/* PWM Control Register */
> > +#define ASPEED_PWM_CTRL_CH(ch) (((ch) * 0x10) + 0x00)
> > +/* PWM Duty Cycle Register */
> > +#define ASPEED_PWM_DUTY_CYCLE_CH(ch) (((ch) * 0x10) + 0x04)
> > +/* TACH Control Register */
> > +#define ASPEED_TACHO_CTRL_CH(ch) (((ch) * 0x10) + 0x08)
> > +/* TACH Status Register */
> > +#define ASPEED_TACHO_STS_CH(x) (((x) * 0x10) + 0x0C)
> > +
> > +/**********************************************************
> > + * PWM register Bit field
> > + *********************************************************/
> > +/*PWM_CTRL */
> > +#define PWM_LOAD_SEL_AS_WDT_BIT (19) /* load selection as
> > WDT */
>
> The trailing comments don't really add any further information in the context
> of the macro name. Let's remove these as well.
>
> > +#define PWM_DUTY_LOAD_AS_WDT_EN BIT(18) /* enable PWM duty load
> > as
> > WDT */
> > +#define PWM_DUTY_SYNC_DIS BIT(17) /* disable PWM duty sync */
> > +#define PWM_CLK_ENABLE BIT(16) /* enable PWM clock */
> > +#define PWM_LEVEL_OUTPUT BIT(15) /* output PWM level */
> > +#define PWM_INVERSE BIT(14) /* inverse PWM pin */
> > +#define PWM_OPEN_DRAIN_EN BIT(13) /* enable open-drain */
> > +#define PWM_PIN_EN BIT(12) /* enable PWM pin */
> > +#define PWM_CLK_DIV_H_MASK (0xf << 8) /* PWM clock division H bit
> > [3:0] */
> > +#define PWM_CLK_DIV_L_MASK (0xff) /* PWM clock division H bit
> > [3:0] */
> > +/* [19] */
> > +#define LOAD_SEL_FALLING 0
> > +#define LOAD_SEL_RIGING 1
>
> It's much easier to track these macros if you place them alongside the
> associated register, and in a way that makes them self-documenting (i.e. we
> remove the need for the banner comments). Often the bit/mask macros are
> indented to help with visuals, for example:
>
> #define ASPEED_PWM_CTRL_CH(ch) (((ch) * 0x10) + 0x00)
> #define PWM_DUTY_LOAD_AS_WDT_EN BIT(18)
> #define PWM_DUTY_SYNC_DIS BIT(17)
> ...
>
> #define ASPEED_PWM_DUTY_CYCLE_CH(ch) (((ch) * 0x10) + 0x04)
> #define PWM_PERIOD_BIT (24)
> #define PWM_PERIOD_BIT_MASK (0xff
> << 24)
> ...
>
> etcetera. It also helps to make the name of the bitfield macros use a similar
> prefix to name of the associated register macro.
>
Yes, this will increase readibility, I'll re-order these in v4.
> > +
> > +/*PWM_DUTY_CYCLE */
> > +#define PWM_PERIOD_BIT (24) /* pwm period
> > bit [7:0] */
> > +#define PWM_PERIOD_BIT_MASK (0xff << 24) /* pwm period
> > bit [7:0] */
> > +#define PWM_RISING_FALLING_AS_WDT_BIT (16)
> > +#define PWM_RISING_FALLING_AS_WDT_MASK (0xff << 16) /*
> > rising/falling
> > point bit [7:0] as WDT */
> > +#define PWM_RISING_FALLING_MASK (0xffff)
> > +#define PWM_FALLING_POINT_BIT (8) /* pwm falling
> > point bit [7:0] */
> > +#define PWM_RISING_POINT_BIT (0) /* pwm rising
> > point bit [7:0] */
> > +/* [31:24] */
> > +#define DEFAULT_PWM_PERIOD 0xff
> > +
> > +/*PWM_TACHO_CTRL */
> > +#define TACHO_IER BIT(31) /* enable tacho
> > interrupt */
> > +#define TACHO_INVERS_LIMIT BIT(30) /* inverse tacho limit
> > comparison
> > */
> > +#define TACHO_LOOPBACK BIT(29) /* tacho
> > loopback */
> > +#define TACHO_ENABLE BIT(28) /* enable tacho
> > */
> > +#define TACHO_DEBOUNCE_MASK (0x3 << 26) /* tacho
> > de-bounce */
> > +#define TACHO_DEBOUNCE_BIT (26) /* tacho de-bounce */
> > +#define TECHIO_EDGE_MASK (0x3 << 24) /* tacho edge */
> > +#define TECHIO_EDGE_BIT (24) /* tacho edge */
> > +#define TACHO_CLK_DIV_T_MASK (0xf << 20)
> > +#define TACHO_CLK_DIV_BIT (20)
> > +#define TACHO_THRESHOLD_MASK (0xfffff) /*
> > tacho threshold bit */
> > +/* [27:26] */
> > +#define DEBOUNCE_3_CLK 0x00 /* 10b */
> > +#define DEBOUNCE_2_CLK 0x01 /* 10b */
> > +#define DEBOUNCE_1_CLK 0x02 /* 10b */
> > +#define DEBOUNCE_0_CLK 0x03 /* 10b */
> > +/* [25:24] */
> > +#define F2F_EDGES 0x00 /* 10b */
> > +#define R2R_EDGES 0x01 /* 10b */
> > +#define BOTH_EDGES 0x02 /* 10b */
> > +/* [23:20] */
> > +/* Cover rpm range 5~5859375 */
> > +#define DEFAULT_TACHO_DIV 5
> > +
> > +/*PWM_TACHO_STS */
> > +#define TACHO_ISR BIT(31) /* interrupt status and clear */
> > +#define PWM_OUT BIT(25) /* pwm_out */
> > +#define PWM_OEN BIT(24) /* pwm_oeN */
> > +#define TACHO_DEB_INPUT BIT(23) /* tacho deB input */
> > +#define TACHO_RAW_INPUT BIT(22) /* tacho raw input */
> > +#define TACHO_VALUE_UPDATE BIT(21) /* tacho value updated since
> > the
> > last read */
> > +#define TACHO_FULL_MEASUREMENT BIT(20) /* tacho full measurement */
> > +#define TACHO_VALUE_MASK 0xfffff /* tacho value bit [19:0] */
> > +/**********************************************************
> > + * Software setting
> > + *********************************************************/
> > +#define DEFAULT_TARGET_PWM_FREQ 25000
> > +#define DEFAULT_FAN_PULSE_PR 2
> > +#define DEFAULT_WDT_RISING_FALLING_PT 0x10
> > +#define DEFAULT_RISING_PT 0x00
> > +#define DEFAULT_FALLING_PT 0x0a
> > +#define MAX_CDEV_NAME_LEN 16
> > +
> > +struct aspeed_pwm_channel_params {
> > + int target_freq;
> > + int pwm_freq;
> > + int load_wdt_rising_falling_pt;
> > + int load_wdt_selection; /* 0: rising , 1: falling */
> > + int load_wdt_enable;
> > + int duty_sync_disable;
> > + int inverse_pin;
> > + u8 falling;
> > +};
> > +
> > +static struct aspeed_pwm_channel_params default_pwm_params[16];
> > +
> > +struct aspeed_tacho_channel_params {
> > + int limited_inverse;
> > + u16 threshold;
> > + u8 tacho_edge;
> > + u8 tacho_debounce;
> > + u8 pulse_pr;
> > + u32 divide;
> > +};
> > +
> > +static struct aspeed_tacho_channel_params default_tacho_params[16];
> > +
> > +struct aspeed_pwm_tachometer_data {
> > + struct regmap *regmap;
>
> What's the reason for using a regmap? For MMIO the benefit is that the API
> provides some locking for when regmap is used as a syscon, but that doesn't
> appear to be how it's used here? regmap generates a bunch of overhead
> compared to readl()/writel().
>
Nope, the readl()/writel() will work as well.
> > + unsigned long clk_freq;
> > + struct reset_control *reset;
> > + bool pwm_present[16];
> > + bool fan_tach_present[16];
> > + struct aspeed_pwm_channel_params *pwm_channel;
> > + struct aspeed_tacho_channel_params *tacho_channel;
> > + /* for thermal */
> > + struct aspeed_cooling_device *cdev[8];
> > +};
> > +
> > +struct aspeed_cooling_device {
> > + char name[16];
> > + struct aspeed_pwm_tachometer_data *priv;
> > + struct thermal_cooling_device *tcdev;
> > + int pwm_channel;
> > + u8 *cooling_levels;
> > + u8 max_state;
> > + u8 cur_state;
> > +};
> > +
> > +static int regmap_aspeed_pwm_tachometer_reg_write(void *context,
> > unsigned int reg,
> > + unsigned int val)
> > +{
> > + void __iomem *regs = (void __iomem *)context;
> > +
> > + writel(val, regs + reg);
> > + return 0;
> > +}
> > +
> > +static int regmap_aspeed_pwm_tachometer_reg_read(void *context,
> > unsigned int reg,
> > + unsigned int *val)
> > +{
> > + void __iomem *regs = (void __iomem *)context;
> > +
> > + *val = readl(regs + reg);
> > + return 0;
> > +}
> > +
> > +static const struct regmap_config aspeed_pwm_tachometer_regmap_config
> > = {
> > + .reg_bits = 32,
> > + .val_bits = 32,
> > + .reg_stride = 4,
> > + .max_register = 0x100,
> > + .reg_write = regmap_aspeed_pwm_tachometer_reg_write,
> > + .reg_read = regmap_aspeed_pwm_tachometer_reg_read,
>
> These just do an MMIO access, do we really need our own read/write
> definitions?
>
> > + .fast_io = true,
> > +};
> > +
> > +static void aspeed_set_pwm_channel_enable(struct regmap *regmap, u8
> > pwm_channel,
> > + bool enable)
> > +{
> > + regmap_update_bits(regmap, ASPEED_PWM_CTRL_CH(pwm_channel),
> > + PWM_CLK_ENABLE | PWM_PIN_EN,
> > + enable ? PWM_CLK_ENABLE | PWM_PIN_EN : 0);
>
> If the motivation for regmap is something like regmap_update_bits(), you can
> alternatively use the FIELD_*() macros from linux/bitfield.h
>
Got it.
> > +}
> > +
> > +static void aspeed_set_fan_tach_ch_enable(struct
> > aspeed_pwm_tachometer_data *priv, u8 fan_tach_ch,
> > + bool enable, u32 tacho_div)
> > +{
> > + u32 reg_value;
> > +
> > + if (enable) {
> > + /* divide = 2^(tacho_div*2) */
> > + priv->tacho_channel[fan_tach_ch].divide = 1 << (tacho_div << 1);
> > +
> > + reg_value = TACHO_ENABLE |
> > + (priv->tacho_channel[fan_tach_ch].tacho_edge <<
> > TECHIO_EDGE_BIT) |
> > + (tacho_div << TACHO_CLK_DIV_BIT) |
> > +
> > (priv->tacho_channel[fan_tach_ch].tacho_debounce <<
> > TACHO_DEBOUNCE_BIT);
> > +
> > + if (priv->tacho_channel[fan_tach_ch].limited_inverse)
> > + reg_value |= TACHO_INVERS_LIMIT;
> > +
> > + if (priv->tacho_channel[fan_tach_ch].threshold)
> > + reg_value |= (TACHO_IER |
> > priv->tacho_channel[fan_tach_ch].threshold);
>
> I feel like this could be cleaned up a bit with:
>
> const struct aspeed_tacho_channel_params *tacho =
> &priv->tacho_channel[fan_tach_ch];
>
> and replacing the expression with tacho throughout.
>
After replacing the expression, the code looks cleaner.
> > +
> > + regmap_write(priv->regmap, ASPEED_TACHO_CTRL_CH(fan_tach_ch),
> > reg_value);
> > + } else
> > + regmap_update_bits(priv->regmap,
> > ASPEED_TACHO_CTRL_CH(fan_tach_ch),
> > TACHO_ENABLE, 0);
> > +}
> > +
> > +/*
> > + * The PWM frequency = HCLK(200Mhz) / (clock division L bit *
> > + * clock division H bit * (period bit + 1))
> > + */
> > +static void aspeed_set_pwm_channel_fan_ctrl(struct device *dev,
> > + struct aspeed_pwm_tachometer_data
> > *priv,
> > + u8 index, u8 fan_ctrl)
> > +{
> > + u32 duty_value, ctrl_value;
> > + u32 target_div, div_h, div_l, cal_freq;
> > + u32 tmp_div_h, tmp_div_l, diff, min_diff = INT_MAX;
> > +
> > + if (fan_ctrl == 0) {
> > + aspeed_set_pwm_channel_enable(priv->regmap, index, false);
> > + return;
> > + }
> > +
> > + cal_freq = priv->clk_freq / (DEFAULT_PWM_PERIOD + 1);
>
> Is this always correct if we're relying on a default value? Or are we using
> DEFAULT_PWM_PERIOD because its value is the maximum period? Using a macro
> with DEFAULT in the name certainly makes me feel less sure that the code is
> right.
>
I'll check again about this.
> > + target_div = DIV_ROUND_UP(cal_freq,
> > priv->pwm_channel[index].target_freq);
> > +
> > + /* calculate for target frequence */
> > + for (tmp_div_h = 0; tmp_div_h < 0x10; tmp_div_h++) {
> > + tmp_div_l = target_div / BIT(tmp_div_h) - 1;
> > +
> > + if (tmp_div_l < 0 || tmp_div_l > 255)
> > + continue;
> > +
> > + diff = priv->pwm_channel[index].target_freq - cal_freq /
> > (BIT(tmp_div_h) * (tmp_div_l + 1));
> > + if (abs(diff) < abs(min_diff)) {
> > + min_diff = diff;
> > + div_l = tmp_div_l;
> > + div_h = tmp_div_h;
> > +
> > + if (diff == 0)
> > + break;
> > + }
> > + }
>
> I've spent some time thinking about this and haven't come up with anything
> that's concretely better. It wasn't fun to read though :(
>
Yep, me too. The intention of this segment is finding a valid pair of DIV_H and
DIV_L, and it will be used as clock prescaler.
> > +
> > + priv->pwm_channel[index].pwm_freq = cal_freq / (BIT(div_h) * (div_l +
> > 1));
> > + dev_info(dev, "div h %x, l : %x pwm out clk %d\n", div_h, div_l,
> > + priv->pwm_channel[index].pwm_freq);
> > + dev_info(dev, "hclk %ld, target pwm freq %d, real pwm freq %d\n",
> > priv->clk_freq,
> > + priv->pwm_channel[index].target_freq,
> > priv->pwm_channel[index].pwm_freq);
>
> These should be dev_dbg()
>
Updated.
> > +
> > + ctrl_value = (div_h << 8) | div_l;
> > +
> > + duty_value = (DEFAULT_PWM_PERIOD << PWM_PERIOD_BIT) |
> > + (0 << PWM_RISING_POINT_BIT) | (fan_ctrl <<
> > PWM_FALLING_POINT_BIT);
> > +
> > + if (priv->pwm_channel[index].load_wdt_enable) {
> > + ctrl_value |= PWM_DUTY_LOAD_AS_WDT_EN;
> > + ctrl_value |= priv->pwm_channel[index].load_wdt_selection <<
> > PWM_LOAD_SEL_AS_WDT_BIT;
> > + duty_value |=
> > (priv->pwm_channel[index].load_wdt_rising_falling_pt
> > << PWM_RISING_FALLING_AS_WDT_BIT);
> > + }
> > +
> > + if (priv->pwm_channel[index].inverse_pin)
> > + ctrl_value |= PWM_INVERSE;
> > + if (priv->pwm_channel[index].duty_sync_disable)
> > + ctrl_value |= PWM_DUTY_SYNC_DIS;
>
> Similar to my comment above about tacho, I think this can be cleaned up a bit
> with:
>
> const struct aspeed_pwm_channel_params *pwm = &priv->pwm_channel[index];
>
> and replacing the expression with pwm throughout.
>
Updated.
> > +
> > + regmap_write(priv->regmap, ASPEED_PWM_DUTY_CYCLE_CH(index),
> > duty_value);
> > + regmap_write(priv->regmap, ASPEED_PWM_CTRL_CH(index), ctrl_value);
> > +
> > + aspeed_set_pwm_channel_enable(priv->regmap, index, true);
> > +}
> > +
> > +static int aspeed_get_fan_tach_ch_rpm(struct device *dev, struct
> > aspeed_pwm_tachometer_data *priv,
> > + u8 fan_tach_ch)
> > +{
> > + u32 raw_data, tach_div, clk_source, val;
> > + int ret;
> > +
> > + ret = regmap_read_poll_timeout(priv->regmap,
> > + ASPEED_TACHO_STS_CH(fan_tach_ch),
> > + val,
> > + (val & TACHO_FULL_MEASUREMENT),
> > + 1000,
> > + 10000);
>
> Could just use readl_poll_timeout()?
>
Updated accordingly.
> > +
> > + /* return -ETIMEDOUT if we didn't get an answer. */
> > + if (ret)
> > + return ret;
> > +
> > + raw_data = val & TACHO_VALUE_MASK;
> > + if (raw_data == 0xfffff)
> > + return 0;
> > +
> > + raw_data += 1;
> > +
> > + /*
> > + * We need the mode to determine if the raw_data is double (from
> > + * counting both edges).
> > + */
> > + tach_div = raw_data * (priv->tacho_channel[fan_tach_ch].divide) *
> > (priv->tacho_channel[fan_tach_ch].pulse_pr);
>
> Again, lets extract the channel pointer to clean this up a little.
>
Updated.
> > +
> > + dev_dbg(dev, "clk %ld, raw_data %d , tach_div %d\n", priv->clk_freq,
> > raw_data, tach_div);
> > + clk_source = priv->clk_freq;
> > +
> > + return ((clk_source / tach_div) * 60);
> > +
> > +}
> > +
> > +static int set_pwm(struct device *dev, int index, long fan_ctrl)
> > +{
> > + struct aspeed_pwm_tachometer_data *priv = dev_get_drvdata(dev);
> > + u8 org_falling = priv->pwm_channel[index].falling;
> > +
> > + if (fan_ctrl > DEFAULT_PWM_PERIOD || fan_ctrl < 0)
> > + return -EINVAL;
> > +
> > + if (priv->pwm_channel[index].falling == fan_ctrl)
> > + return 0;
> > +
> > + priv->pwm_channel[index].falling = fan_ctrl;
>
> My understanding is the value of fan_ctrl is the value that userspace has
> written to the pwm[1-*] attribute in sysfs. The name 'falling' in struct
> aspeed_pwm_channel_params is not at all intuitive to me. I understand the
> hardware calls it something along these lines, but is there a better name?
> "period"? "duty"?
>
Good suggestion, I'll update accordingly.
> > +
> > + if (fan_ctrl == 0)
> > + aspeed_set_pwm_channel_enable(priv->regmap, index, false);
> > + else {
> > + if (fan_ctrl == DEFAULT_PWM_PERIOD)
> > + regmap_update_bits(priv->regmap,
> > + ASPEED_PWM_DUTY_CYCLE_CH(index),
> > + GENMASK(15, 0), 0);
> > + else
> > + regmap_update_bits(priv->regmap,
> > + ASPEED_PWM_DUTY_CYCLE_CH(index),
> > + GENMASK(15, 8),
>
> The asymmetry between these masks caught my eye, and I guess I understand
> what's going on a bit more with 'falling' at least. The hardware is
> definitely flexible :)
>
> > + (fan_ctrl << PWM_FALLING_POINT_BIT));
> > + }
> > +
> > + if (org_falling == 0)
>
> Do we have to assign .falling member before this point? Can we get rid of
> org_falling by assigning .falling after this test?
>
Yes, good suggestion.
> > + aspeed_set_pwm_channel_enable(priv->regmap, index, true);
> > +
> > + return 0;
> > +}
> > +
> > +static void aspeed_create_pwm_channel(struct device *dev, struct
> > aspeed_pwm_tachometer_data *priv,
> > + u8 pwm_channel)
> > +{
> > + priv->pwm_present[pwm_channel] = true;
>
> Is there a reason we can't have the present state as a member of the params
> struct?
>
Nope. I'll move the present into param struct.
> > +
> > + /* use default */
> > + aspeed_set_pwm_channel_fan_ctrl(dev,
> > + priv,
> > + pwm_channel,
> > + priv->pwm_channel[pwm_channel].falling);
> > +}
> > +
> > +static void aspeed_create_fan_tach_channel(struct
> > aspeed_pwm_tachometer_data *priv,
> > + u8 *fan_tach_ch, int count,
> > + u32 fan_pulse_pr, u32 tacho_div)
> > +{
> > + u8 val, index;
> > +
> > + for (val = 0; val < count; val++) {
> > + index = fan_tach_ch[val];
> > + priv->fan_tach_present[index] = true;
>
> Same query as pwm_present: Can we not have this as member of the params
> struct?
>
Same as pwm.
> > + priv->tacho_channel[index].pulse_pr = fan_pulse_pr;
> > + priv->tacho_channel[index].limited_inverse = 0;
> > + priv->tacho_channel[index].threshold = 0;
> > + priv->tacho_channel[index].tacho_edge = F2F_EDGES;
> > + priv->tacho_channel[index].tacho_debounce = DEBOUNCE_3_CLK;
> > + aspeed_set_fan_tach_ch_enable(priv, index, true, tacho_div);
> > + }
> > +}
> > +
> > +static int
> > +aspeed_pwm_cz_get_max_state(struct thermal_cooling_device *tcdev,
> > + unsigned long *state)
> > +{
> > + struct aspeed_cooling_device *cdev = tcdev->devdata;
> > +
> > + *state = cdev->max_state;
> > +
> > + return 0;
> > +}
> > +
> > +static int
> > +aspeed_pwm_cz_get_cur_state(struct thermal_cooling_device *tcdev,
> > + unsigned long *state)
> > +{
> > + struct aspeed_cooling_device *cdev = tcdev->devdata;
> > +
> > + *state = cdev->cur_state;
> > +
> > + return 0;
> > +}
> > +
> > +static int
> > +aspeed_pwm_cz_set_cur_state(struct thermal_cooling_device *tcdev,
> > + unsigned long state)
> > +{
> > + struct aspeed_cooling_device *cdev = tcdev->devdata;
> > +
> > + if (state > cdev->max_state)
> > + return -EINVAL;
> > +
> > + cdev->cur_state = state;
> > + cdev->priv->pwm_channel[cdev->pwm_channel].falling =
> > + cdev->cooling_levels[cdev->cur_state];
> > + aspeed_set_pwm_channel_fan_ctrl(&tcdev->device, cdev->priv,
> > cdev->pwm_channel,
> > + cdev->cooling_levels[cdev->cur_state]);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct thermal_cooling_device_ops aspeed_pwm_cool_ops = {
> > + .get_max_state = aspeed_pwm_cz_get_max_state,
> > + .get_cur_state = aspeed_pwm_cz_get_cur_state,
> > + .set_cur_state = aspeed_pwm_cz_set_cur_state,
> > +};
> > +
> > +static int aspeed_create_pwm_cooling(struct device *dev,
> > + struct device_node *child,
> > + struct aspeed_pwm_tachometer_data *priv,
> > + u32 pwm_channel, u8 num_levels)
> > +{
> > + int ret;
> > + struct aspeed_cooling_device *cdev;
> > +
> > + cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
> > + if (!cdev)
> > + return -ENOMEM;
> > +
> > + cdev->cooling_levels = devm_kzalloc(dev, num_levels, GFP_KERNEL);
> > + if (!cdev->cooling_levels)
> > + return -ENOMEM;
> > +
> > + cdev->max_state = num_levels - 1;
> > + ret = of_property_read_u8_array(child, "cooling-levels",
> > + cdev->cooling_levels,
> > + num_levels);
> > + if (ret) {
> > + dev_err(dev, "Property 'cooling-levels' cannot be read.\n");
> > + return ret;
> > + }
> > + snprintf(cdev->name, MAX_CDEV_NAME_LEN, "%s%d", child->name,
> > pwm_channel);
> > +
> > + cdev->tcdev = thermal_of_cooling_device_register(child,
> > + cdev->name,
> > + cdev,
> > + &aspeed_pwm_cool_ops);
> > + if (IS_ERR(cdev->tcdev))
> > + return PTR_ERR(cdev->tcdev);
> > +
> > + cdev->priv = priv;
> > + cdev->pwm_channel = pwm_channel;
> > +
> > + priv->cdev[pwm_channel] = cdev;
> > +
> > + return 0;
> > +}
>
> I'm not super familiar with the cooling zones stuff, so I'll leave that for
> someone else to comment on :)
>
> > +
> > +static int aspeed_pwm_create_fan(struct device *dev,
> > + struct device_node *child,
> > + struct aspeed_pwm_tachometer_data *priv)
> > +{
> > + u8 *fan_tach_ch;
> > + u32 fan_pulse_pr;
> > + u32 tacho_div;
> > + u32 pwm_channel;
> > + u32 target_pwm_freq = 0;
> > + u8 val;
> > + int ret, count;
> > +
> > + ret = of_property_read_u32(child, "reg", &pwm_channel);
> > + if (ret)
> > + return ret;
> > + else if (pwm_channel > 0x0f)
> > + return -EINVAL;
> > +
> > + ret = of_property_read_u32(child, "aspeed,pwm-freq-hz",
> > &target_pwm_freq);
> > + if (ret)
> > + target_pwm_freq = DEFAULT_TARGET_PWM_FREQ;
> > +
> > + priv->pwm_channel[pwm_channel].target_freq = target_pwm_freq;
>
> I made some comments on the binding patch, but I'll repeat some of that here.
>
> What are your thoughts on reusing a property like 'bus-frequency' here?
>
> > +
> > + priv->pwm_channel[pwm_channel].load_wdt_enable =
> > of_property_read_bool(child,
> > + "aspeed,enable-pwm-duty-load-wdt");
>
> This isn't documented in the binding.
>
> > + priv->pwm_channel[pwm_channel].load_wdt_selection =
> > of_property_read_bool(child,
> > + "aspeed,wdt-selection-rising");
>
> This isn't documented in the binding.
>
> > + priv->pwm_channel[pwm_channel].duty_sync_disable =
> > of_property_read_bool(child,
> > + "aspeed,disable-duty-instant-change");
>
> This isn't documented in the binding.
>
> However, does slowly increase/decrease the duty instead? Might it be better
> to name the property in terms of the positive case (the property name doesn't
> have to directly reflect the name of the hardware setting), e.g.
> 'aspeed,ramp-pwm-duty'?
>
I'll check about the functionaility.
> > + priv->pwm_channel[pwm_channel].inverse_pin =
> > of_property_read_bool(child,
> > + "aspeed,inverse-pin");
> > +
> > + ret = of_property_read_u8(child, "aspeed,wdt-rising-falling-point",
> > &val);
>
> This isn't documented in the binding.
>
> > + if (ret)
> > + val = DEFAULT_WDT_RISING_FALLING_PT;
> > + priv->pwm_channel[pwm_channel].load_wdt_rising_falling_pt = val;
> > +
> > + ret = of_property_read_u8(child, "aspeed,falling-point", &val);
> > + if (ret)
> > + val = DEFAULT_FALLING_PT;
> > + priv->pwm_channel[pwm_channel].falling = val;
> > +
> > + aspeed_create_pwm_channel(dev, priv, (u8)pwm_channel);
> > +
> > + ret = of_property_count_u8_elems(child, "cooling-levels");
> > + if (ret > 0) {
> > + if (IS_ENABLED(CONFIG_THERMAL)) {
> > + ret = aspeed_create_pwm_cooling(dev, child, priv,
> > pwm_channel,
> > + ret);
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > +
> > + count = of_property_count_u8_elems(child, "fan-tach-ch");
> > + if (count < 1)
> > + return -EINVAL;
> > +
> > + fan_tach_ch = devm_kzalloc(dev, sizeof(*fan_tach_ch) * count,
> > + GFP_KERNEL);
> > + if (!fan_tach_ch)
> > + return -ENOMEM;
> > + ret = of_property_read_u8_array(child, "fan-tach-ch",
> > + fan_tach_ch, count);
> > + if (ret)
> > + return ret;
> > +
> > + ret = of_property_read_u32(child, "pulses-per-revolution",
> > &fan_pulse_pr);
> > + if (ret)
> > + fan_pulse_pr = DEFAULT_FAN_PULSE_PR;
> > +
> > + ret = of_property_read_u32(child, "aspeed,tacho-div", &tacho_div);
>
> This isn't documented in the binding.
>
Missing properties will be added in v4 document.
> > + if (ret)
> > + tacho_div = DEFAULT_TACHO_DIV;
> > +
> > + aspeed_create_fan_tach_channel(priv, fan_tach_ch, count,
> > fan_pulse_pr, tacho_div);
> > +
> > + return 0;
> > +}
> > +
> > +static umode_t aspeed_pwm_tacho_is_visible(const void *data, enum
> > hwmon_sensor_types type,
> > + u32 attr, int channel)
> > +{
> > + const struct aspeed_pwm_tachometer_data *priv = data;
> > +
> > + switch (type) {
> > + case hwmon_pwm:
> > + switch (attr) {
> > + case hwmon_pwm_input:
> > + if (!priv->pwm_present[channel])
> > + return 0;
> > + return 0644;
> > + default:
> > + return 0;
> > + }
> > + break;
> > + case hwmon_fan:
> > + switch (attr) {
> > + case hwmon_fan_input:
> > + if (!priv->fan_tach_present[channel])
> > + return 0;
> > + return 0444;
> > + default:
> > + return 0;
> > + }
> > + default:
> > + return 0;
> > + }
> > +}
> > +
> > +static int aspeed_pwm_tacho_read(struct device *dev, enum
> > hwmon_sensor_types type,
> > + u32 attr, int channel, long *val)
> > +{
> > + struct aspeed_pwm_tachometer_data *priv = dev_get_drvdata(dev);
> > + long rpm;
> > +
> > + switch (type) {
> > + case hwmon_pwm:
> > + switch (attr) {
> > + case hwmon_pwm_input:
> > + *val = priv->pwm_channel[channel].falling;
> > + break;
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > + break;
> > + case hwmon_fan:
> > + switch (attr) {
> > + case hwmon_fan_input:
> > + rpm = aspeed_get_fan_tach_ch_rpm(dev, priv, channel);
> > + if (rpm < 0)
> > + return rpm;
> > + *val = rpm;
> > + break;
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > + break;
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > + return 0;
> > +}
> > +
> > +static int aspeed_pwm_tacho_write(struct device *dev, enum
> > hwmon_sensor_types type,
> > + u32 attr, int channel, long val)
> > +{
> > + switch (type) {
> > + case hwmon_pwm:
> > + switch (attr) {
> > + case hwmon_pwm_input:
> > + return set_pwm(dev, channel, val);
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > + break;
> > + default:
> > + return -EOPNOTSUPP;
> > + }
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +static const struct hwmon_ops aspeed_pwm_hwmon_ops = {
> > + .is_visible = aspeed_pwm_tacho_is_visible,
> > + .read = aspeed_pwm_tacho_read,
> > + .write = aspeed_pwm_tacho_write,
> > +};
> > +
> > +static const struct hwmon_channel_info *aspeed_pwm_hwmon_info[] = {
> > + HWMON_CHANNEL_INFO(pwm,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT,
> > + HWMON_PWM_INPUT),
> > + HWMON_CHANNEL_INFO(fan,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT,
> > + HWMON_F_INPUT),
> > + NULL,
> > +};
> > +
> > +static const struct hwmon_chip_info aspeed_pwm_chip_info = {
> > + .ops = &aspeed_pwm_hwmon_ops,
> > + .info = aspeed_pwm_hwmon_info,
> > +};
> > +
> > +static int aspeed_pwm_tachometer_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + struct device_node *np, *child;
> > + struct aspeed_pwm_tachometer_data *priv;
> > + void __iomem *regs;
> > + struct device *hwmon;
> > + struct clk *clk;
> > + int ret;
> > +
> > + np = dev->of_node;
> > +
> > + regs = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(regs))
> > + return PTR_ERR(regs);
> > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->pwm_channel = default_pwm_params;
> > + priv->tacho_channel = default_tacho_params;
> > + priv->regmap = devm_regmap_init(dev, NULL, (__force void *)regs,
> > + &aspeed_pwm_tachometer_regmap_config);
> > + if (IS_ERR(priv->regmap))
> > + return PTR_ERR(priv->regmap);
> > +
> > + clk = devm_clk_get(dev, NULL);
> > + if (IS_ERR(clk))
> > + return -ENODEV;
> > + priv->clk_freq = clk_get_rate(clk);
> > +
> > + priv->reset = devm_reset_control_get(&pdev->dev, NULL);
> > + if (IS_ERR(priv->reset)) {
> > + dev_err(&pdev->dev, "can't get aspeed_pwm_tacho reset\n");
> > + return PTR_ERR(priv->reset);
> > + }
> > +
> > + /* scu init */
> > + reset_control_assert(priv->reset);
> > + reset_control_deassert(priv->reset);
> > +
> > + for_each_child_of_node(np, child) {
> > + ret = aspeed_pwm_create_fan(dev, child, priv);
> > + if (ret) {
> > + of_node_put(child);
> > + return ret;
>
> Should you reassert the reset here? Or is that handled by the devm infra?
>
I don't think we need to reassert here. It should reset once at the
begining.
> > + }
> > + }
> > +
> > + dev_info(dev, "pwm tach probe done\n");
>
> This is a bit misleading because you're yet to register with hwmon :) I'd put
> it after the call below.
>
> Anyway, I think it should probably print something more informative if we're
> going to keep it. Print the number of PWMs/tachos configured as well?
Good catch. I think this message can be removed. How many pwm/tacho channels
configured can be found in sysfs.
>
> > + hwmon = devm_hwmon_device_register_with_info(dev,
> > "aspeed_pwm_tachometer",
> > + priv, &aspeed_pwm_chip_info, NULL);
> > +
> > + return PTR_ERR_OR_ZERO(hwmon);
> > +}
> > +
> > +static const struct of_device_id of_pwm_tachometer_match_table[] = {
> > + { .compatible = "aspeed,ast2600-pwm-tachometer", },
> > + {},
> > +};
> > +MODULE_DEVICE_TABLE(of, of_pwm_tachometer_match_table);
> > +
> > +static struct platform_driver aspeed_pwm_tachometer_driver = {
> > + .probe = aspeed_pwm_tachometer_probe,
> > + .driver = {
> > + .name = "aspeed_pwm_tachometer",
> > + .of_match_table = of_pwm_tachometer_match_table,
> > + },
> > +};
> > +
> > +module_platform_driver(aspeed_pwm_tachometer_driver);
> > +
> > +MODULE_AUTHOR("Ryan Chen <[email protected]>");
> > +MODULE_DESCRIPTION("ASPEED PWM and Fan Tachometer device driver");
>
> Given it's AST2600-specific:
>
> "AST2600 PWM and Fan Tachometer device driver"
>
> I've run out of steam, so that's enough for now :)
>
> Andrew
Thanks for reviewing, I'll update and submit a v4 patch.
Troy Lee