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 <troy_...@aspeedtech.com>
> > Reported-by: kernel test robot <l...@intel.com>
> > ---
...

> > 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 <ryan_c...@aspeedtech.com>");
> > +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

Reply via email to