summaryrefslogtreecommitdiff
path: root/drivers/hwmon/aspeed-g6-pwm-tacho.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/aspeed-g6-pwm-tacho.c')
-rw-r--r--drivers/hwmon/aspeed-g6-pwm-tacho.c1163
1 files changed, 1163 insertions, 0 deletions
diff --git a/drivers/hwmon/aspeed-g6-pwm-tacho.c b/drivers/hwmon/aspeed-g6-pwm-tacho.c
new file mode 100644
index 000000000000..f9bfc83b32fe
--- /dev/null
+++ b/drivers/hwmon/aspeed-g6-pwm-tacho.c
@@ -0,0 +1,1163 @@
+/*
+ * 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.
+ */
+
+#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>
+#include <linux/pwm.h>
+
+#define ASPEED_PWM_CTRL 0x00 //PWM0 General Register
+#define ASPEED_PWM_CTRL_CH(x) ((x * 0x10) + 0x00)
+#define PWM_LOAD_AS_WDT BIT(19) //load selection as WDT
+#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]
+
+/*
+\xregmid {11:8 }{RW}{PWM clock division H bit [3:0]}{
+ 0: divide 1 \n
+ 1: divide 2 \n
+ 2: divide 4 \n
+ 3: divide 8 \n
+ ... \n
+ F: divide 32768}
+\xregmid {7 :0 }{RW}{PWM clock division L bit [7:0]}{
+ 00: divide 1 \n
+ 01: divide 2 \n
+ 02: divide 3 \n
+ 03: divide 4 \n
+ ... \n
+ FF: divide 256}
+*/
+
+#define ASPEED_PWM_DUTY_CYCLE 0x04 //PWM0 Duty Cycle Register
+#define ASPEED_PWM_DUTY_CYCLE_CH(x) ((x * 0x10) + 0x04)
+#define PWM_LOOP_BIT_MASK (0xf << 24) //loop bit [7:0]
+#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) //pwm rising/falling point bit [7:0] as WDT
+#define PWM_RISING_FALLING_MASK (0xffff)
+#define PWM_RISING_FALLING_BIT (8) //pwm falling point bit [7:0]
+#define PWM_RISING_RISING_BIT (0) //pwm rising point bit [7:0]
+
+#define PWM_PERIOD_MAX 255
+#define PWM_FALLING_DEFAULT 255 /* 100% */
+
+#define ASPEED_TACHO_CTRL 0x08 //TACH0 General Register
+#define ASPEED_TACHO_CTRL_CH(x) ((x * 0x10) + 0x08)
+#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_BIT (26) //{tacho de-bounce}
+#define TACHO_DEBOUNCE_MASK (0x3 << 26) //{tacho de-bounce}
+#define TACHIO_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
+/*
+\xregmid {23:20}{RW}{tacho clock division T bit [3:0]}{
+ 0: divide 1 \n
+ 1: divide 4 \n
+ 2: divide 16 \n
+ 3: divide 64 \n
+ ... \n
+ B: divide 4194304 \n
+ others: reserved}
+\xregmidb{19 :0 }{RW}{tacho threshold bit [19:0]}
+*/
+
+#define ASPEED_TACHO_STS 0x0C //TACH0 Status Register
+#define ASPEED_TACHO_STS_CH(x) ((x * 0x10) + 0x0C)
+#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]}
+
+#define MAX_CDEV_NAME_LEN 16
+
+#define DEFAULT_TARGET_PWM_FREQ 25000
+#define DEFAULT_MIN_RPM 2900
+
+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_enable;
+ int invert_pin;
+ u8 rising;
+ u8 falling;
+};
+
+static struct aspeed_pwm_channel_params default_pwm_params[] = {
+ [0] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 1,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [1] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [2] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [3] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [4] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [5] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [6] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [7] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [8] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [9] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [10] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [11] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [12] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [13] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [14] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+ [15] = {
+ .target_freq = 25000,
+ .load_wdt_rising_falling_pt = 0x10,
+ .load_wdt_selection = 0,
+ .load_wdt_enable = 0,
+ .duty_sync_enable = 0,
+ .invert_pin = 0,
+ .rising = 0x00,
+ .falling = PWM_FALLING_DEFAULT,
+ },
+};
+
+/*
+ * 5:4 fan tach edge mode selection bit:
+ * 00: falling
+ * 01: rising
+ * 10: both
+ * 11: reserved.
+ */
+
+#define F2F_EDGES 0x00
+#define R2R_EDGES 0x01
+#define BOTH_EDGES 0x02
+
+struct aspeed_tacho_channel_params {
+ u32 min_rpm;
+ int limited_inverse;
+ u16 threshold;
+ u8 tacho_edge;
+ u8 tacho_debounce;
+ u32 divide;
+};
+
+
+static struct aspeed_tacho_channel_params default_tacho_params[] = {
+ [0] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [1] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [2] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [3] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [4] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [5] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [6] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [7] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [8] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [9] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [10] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [11] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [12] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [13] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [14] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+ [15] = {
+ .min_rpm = 2900,
+ .limited_inverse = 0,
+ .threshold = 0,
+ .tacho_edge = F2F_EDGES,
+ .tacho_debounce = 0,
+ .divide = 8,
+ },
+};
+
+struct aspeed_pwm_tachometer_data {
+ struct regmap *regmap;
+ 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;
+ struct aspeed_cooling_device *cdev[8];
+ const struct attribute_group *groups[3];
+ struct pwm_chip chip;
+ u32 clk_tick_ns;
+};
+
+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;
+};
+
+struct aspeed_pwm_output_chan {
+ u32 period_ns;
+ u32 duty_ns;
+};
+
+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,
+ .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);
+}
+
+static void aspeed_set_fan_tach_ch_enable(struct aspeed_pwm_tachometer_data *priv, u8 fan_tach_ch,
+ bool enable)
+{
+ u32 i;
+ u32 divide_val = 0;
+ u32 target_div;
+ u32 reg_value = 0;
+
+ if(enable) {
+ /*RPM calculation as per ast2600 datasheet*/
+ target_div = (priv->clk_freq * 60 / priv->tacho_channel[fan_tach_ch].min_rpm * 2) / (0xfffff + 1);
+ if (target_div) {
+ for (i = 0; i < 12; i++) {
+ divide_val = BIT(i) * BIT(i);
+ if (divide_val > target_div)
+ break;
+ }
+ } else {
+ i = 0;
+ divide_val = 1;
+ }
+ priv->tacho_channel[fan_tach_ch].divide = divide_val;
+
+ reg_value = TACHO_ENABLE |
+ (priv->tacho_channel[fan_tach_ch].tacho_edge << TACHIO_EDGE_BIT) |
+ (i << 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);
+
+ 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);
+}
+
+static void aspeed_set_pwm_channel_fan_ctrl(struct aspeed_pwm_tachometer_data *priv,
+ u8 index, u8 fan_ctrl)
+{
+ u32 duty_value, ctrl_value;
+ u32 div_h, div_l, cal_freq;
+
+ if (fan_ctrl == 0) {
+ aspeed_set_pwm_channel_enable(priv->regmap, index, false);
+ } else {
+ cal_freq = priv->clk_freq / (PWM_PERIOD_MAX + 1);
+ /*calculate for target frequence*/
+ for (div_l = 0; div_l < 0x100; div_l++) {
+ for (div_h = 0; div_h < 0x10; div_h++) {
+ if ((cal_freq / (BIT(div_h) * (div_l + 1))) < priv->pwm_channel[index].target_freq)
+ break;
+ }
+ if ((cal_freq / (BIT(div_h) * (div_l + 1))) < priv->pwm_channel[index].target_freq)
+ break;
+ }
+
+ priv->pwm_channel[index].pwm_freq = cal_freq / (BIT(div_h) * (div_l + 1));
+
+ ctrl_value = (div_h << 8) | div_l;
+
+ duty_value = (PWM_PERIOD_MAX << PWM_PERIOD_BIT) |
+ (0 << PWM_RISING_RISING_BIT) | (fan_ctrl << PWM_RISING_FALLING_BIT);
+
+ if (priv->pwm_channel[index].load_wdt_enable) {
+ ctrl_value |= PWM_DUTY_LOAD_AS_WDT_EN;
+ if(priv->pwm_channel[index].load_wdt_selection) {
+ ctrl_value |= PWM_LOAD_AS_WDT;
+ duty_value |= (priv->pwm_channel[index].load_wdt_rising_falling_pt << PWM_RISING_FALLING_AS_WDT_BIT);
+ } else {
+ duty_value |= (priv->pwm_channel[index].load_wdt_rising_falling_pt << PWM_RISING_FALLING_AS_WDT_BIT);
+ }
+ }
+
+ regmap_write(priv->regmap, ASPEED_PWM_DUTY_CYCLE_CH(index), duty_value);
+
+ regmap_write(priv->regmap, ASPEED_PWM_CTRL_CH(index), ctrl_value);
+// printk("pwm clk is %d \n", priv->clk_freq / (priv->pwm_channel[index].period + 1));
+ aspeed_set_pwm_channel_enable(priv->regmap, index, true);
+ }
+}
+
+static int aspeed_get_fan_tach_ch_rpm(struct aspeed_pwm_tachometer_data *priv,
+ u8 fan_tach_ch)
+{
+ u32 raw_data, tach_div, clk_source, val;
+ u8 multiplier = 2;
+ int i, retries = 3;
+
+ for(i = 0; i < retries; i++) {
+ regmap_read(priv->regmap, ASPEED_TACHO_STS_CH(fan_tach_ch), &val);
+ if (TACHO_FULL_MEASUREMENT & val)
+ break;
+ }
+
+ raw_data = val & TACHO_VALUE_MASK;
+ if(raw_data == 0xfffff)
+ return 0;
+
+ raw_data += 1;
+ tach_div = raw_data * (priv->tacho_channel[fan_tach_ch].divide) * (multiplier);
+
+ clk_source = priv->clk_freq;
+
+ if (raw_data == 0)
+ return 0;
+
+ return (clk_source / tach_div * 60);
+
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+ int index = sensor_attr->index;
+ int ret;
+ struct aspeed_pwm_tachometer_data *priv = dev_get_drvdata(dev);
+ long fan_ctrl;
+ u8 org_falling = priv->pwm_channel[index].falling;
+
+ ret = kstrtol(buf, 10, &fan_ctrl);
+ if (ret != 0)
+ return ret;
+
+ if (fan_ctrl < 0 || fan_ctrl > PWM_PERIOD_MAX)
+ return -EINVAL;
+
+ if (priv->pwm_channel[index].falling == fan_ctrl)
+ return count;
+
+ priv->pwm_channel[index].falling = fan_ctrl;
+
+ if (fan_ctrl == 0)
+ aspeed_set_pwm_channel_enable(priv->regmap, index, false);
+ else
+ regmap_update_bits(priv->regmap, ASPEED_PWM_DUTY_CYCLE_CH(index), GENMASK(15, 8), (fan_ctrl << PWM_RISING_FALLING_BIT));
+
+ if (org_falling == 0)
+ aspeed_set_pwm_channel_enable(priv->regmap, index, true);
+
+ return count;
+}
+
+static ssize_t show_pwm(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+ int index = sensor_attr->index;
+ struct aspeed_pwm_tachometer_data *priv = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", priv->pwm_channel[index].falling);
+}
+
+static ssize_t show_rpm(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+ int index = sensor_attr->index;
+ int rpm;
+ struct aspeed_pwm_tachometer_data *priv = dev_get_drvdata(dev);
+
+ rpm = aspeed_get_fan_tach_ch_rpm(priv, index);
+ if (rpm < 0)
+ return rpm;
+
+ return sprintf(buf, "%d\n", rpm);
+}
+
+static umode_t pwm_is_visible(struct kobject *kobj,
+ struct attribute *a, int index)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct aspeed_pwm_tachometer_data *priv = dev_get_drvdata(dev);
+
+ if (!priv->pwm_present[index])
+ return 0;
+ return a->mode;
+}
+
+static umode_t fan_dev_is_visible(struct kobject *kobj,
+ struct attribute *a, int index)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct aspeed_pwm_tachometer_data *priv = dev_get_drvdata(dev);
+
+ if (!priv->fan_tach_present[index])
+ return 0;
+ return a->mode;
+}
+
+static SENSOR_DEVICE_ATTR(pwm1, 0644,
+ show_pwm, set_pwm, 0);
+static SENSOR_DEVICE_ATTR(pwm2, 0644,
+ show_pwm, set_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm3, 0644,
+ show_pwm, set_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm4, 0644,
+ show_pwm, set_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm5, 0644,
+ show_pwm, set_pwm, 4);
+static SENSOR_DEVICE_ATTR(pwm6, 0644,
+ show_pwm, set_pwm, 5);
+static SENSOR_DEVICE_ATTR(pwm7, 0644,
+ show_pwm, set_pwm, 6);
+static SENSOR_DEVICE_ATTR(pwm8, 0644,
+ show_pwm, set_pwm, 7);
+static SENSOR_DEVICE_ATTR(pwm9, 0644,
+ show_pwm, set_pwm, 8);
+static SENSOR_DEVICE_ATTR(pwm10, 0644,
+ show_pwm, set_pwm, 9);
+static SENSOR_DEVICE_ATTR(pwm11, 0644,
+ show_pwm, set_pwm, 10);
+static SENSOR_DEVICE_ATTR(pwm12, 0644,
+ show_pwm, set_pwm, 11);
+static SENSOR_DEVICE_ATTR(pwm13, 0644,
+ show_pwm, set_pwm, 12);
+static SENSOR_DEVICE_ATTR(pwm14, 0644,
+ show_pwm, set_pwm, 13);
+static SENSOR_DEVICE_ATTR(pwm15, 0644,
+ show_pwm, set_pwm, 14);
+static SENSOR_DEVICE_ATTR(pwm16, 0644,
+ show_pwm, set_pwm, 15);
+static struct attribute *pwm_dev_attrs[] = {
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_pwm2.dev_attr.attr,
+ &sensor_dev_attr_pwm3.dev_attr.attr,
+ &sensor_dev_attr_pwm4.dev_attr.attr,
+ &sensor_dev_attr_pwm5.dev_attr.attr,
+ &sensor_dev_attr_pwm6.dev_attr.attr,
+ &sensor_dev_attr_pwm7.dev_attr.attr,
+ &sensor_dev_attr_pwm8.dev_attr.attr,
+ &sensor_dev_attr_pwm9.dev_attr.attr,
+ &sensor_dev_attr_pwm10.dev_attr.attr,
+ &sensor_dev_attr_pwm11.dev_attr.attr,
+ &sensor_dev_attr_pwm12.dev_attr.attr,
+ &sensor_dev_attr_pwm13.dev_attr.attr,
+ &sensor_dev_attr_pwm14.dev_attr.attr,
+ &sensor_dev_attr_pwm15.dev_attr.attr,
+ &sensor_dev_attr_pwm16.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group pwm_dev_group = {
+ .attrs = pwm_dev_attrs,
+ .is_visible = pwm_is_visible,
+};
+
+static SENSOR_DEVICE_ATTR(fan1_input, 0444,
+ show_rpm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_input, 0444,
+ show_rpm, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan3_input, 0444,
+ show_rpm, NULL, 2);
+static SENSOR_DEVICE_ATTR(fan4_input, 0444,
+ show_rpm, NULL, 3);
+static SENSOR_DEVICE_ATTR(fan5_input, 0444,
+ show_rpm, NULL, 4);
+static SENSOR_DEVICE_ATTR(fan6_input, 0444,
+ show_rpm, NULL, 5);
+static SENSOR_DEVICE_ATTR(fan7_input, 0444,
+ show_rpm, NULL, 6);
+static SENSOR_DEVICE_ATTR(fan8_input, 0444,
+ show_rpm, NULL, 7);
+static SENSOR_DEVICE_ATTR(fan9_input, 0444,
+ show_rpm, NULL, 8);
+static SENSOR_DEVICE_ATTR(fan10_input, 0444,
+ show_rpm, NULL, 9);
+static SENSOR_DEVICE_ATTR(fan11_input, 0444,
+ show_rpm, NULL, 10);
+static SENSOR_DEVICE_ATTR(fan12_input, 0444,
+ show_rpm, NULL, 11);
+static SENSOR_DEVICE_ATTR(fan13_input, 0444,
+ show_rpm, NULL, 12);
+static SENSOR_DEVICE_ATTR(fan14_input, 0444,
+ show_rpm, NULL, 13);
+static SENSOR_DEVICE_ATTR(fan15_input, 0444,
+ show_rpm, NULL, 14);
+static SENSOR_DEVICE_ATTR(fan16_input, 0444,
+ show_rpm, NULL, 15);
+static struct attribute *fan_dev_attrs[] = {
+ &sensor_dev_attr_fan1_input.dev_attr.attr,
+ &sensor_dev_attr_fan2_input.dev_attr.attr,
+ &sensor_dev_attr_fan3_input.dev_attr.attr,
+ &sensor_dev_attr_fan4_input.dev_attr.attr,
+ &sensor_dev_attr_fan5_input.dev_attr.attr,
+ &sensor_dev_attr_fan6_input.dev_attr.attr,
+ &sensor_dev_attr_fan7_input.dev_attr.attr,
+ &sensor_dev_attr_fan8_input.dev_attr.attr,
+ &sensor_dev_attr_fan9_input.dev_attr.attr,
+ &sensor_dev_attr_fan10_input.dev_attr.attr,
+ &sensor_dev_attr_fan11_input.dev_attr.attr,
+ &sensor_dev_attr_fan12_input.dev_attr.attr,
+ &sensor_dev_attr_fan13_input.dev_attr.attr,
+ &sensor_dev_attr_fan14_input.dev_attr.attr,
+ &sensor_dev_attr_fan15_input.dev_attr.attr,
+ &sensor_dev_attr_fan16_input.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group fan_dev_group = {
+ .attrs = fan_dev_attrs,
+ .is_visible = fan_dev_is_visible,
+};
+
+static void aspeed_create_pwm_channel(struct aspeed_pwm_tachometer_data *priv,
+ u8 pwm_channel)
+{
+ priv->pwm_present[pwm_channel] = true;
+
+ //use default
+ aspeed_set_pwm_channel_fan_ctrl(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 min_rpm)
+{
+ u8 val, index;
+
+ for (val = 0; val < count; val++) {
+ index = fan_tach_ch[val];
+ priv->fan_tach_present[index] = true;
+ priv->tacho_channel[index].min_rpm = min_rpm;
+ aspeed_set_fan_tach_ch_enable(priv, index, true);
+ }
+}
+
+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(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;
+}
+
+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_min_rpm;
+ u32 pwm_channel;
+ u32 target_pwm_freq;
+ int ret, count;
+
+ ret = of_property_read_u32(child, "reg", &pwm_channel);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_u32(child, "aspeed,target_pwm", &target_pwm_freq);
+ if (ret)
+ target_pwm_freq = DEFAULT_TARGET_PWM_FREQ;
+
+ aspeed_create_pwm_channel(priv, (u8)pwm_channel);
+
+ ret = of_property_count_u8_elems(child, "cooling-levels");
+ if (ret > 0) {
+ ret = aspeed_create_pwm_cooling(dev, child, priv, pwm_channel,
+ ret);
+ if (ret)
+ return ret;
+ }
+
+ count = of_property_count_u8_elems(child, "aspeed,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, "aspeed,fan-tach-ch",
+ fan_tach_ch, count);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(child, "aspeed,min_rpm", &fan_min_rpm);
+ if (ret)
+ fan_min_rpm = DEFAULT_MIN_RPM;
+
+ aspeed_create_fan_tach_channel(priv, fan_tach_ch, count, fan_min_rpm);
+ return 0;
+}
+
+static inline
+struct aspeed_pwm_tachometer_data *to_aspeed_pwm(struct pwm_chip *chip)
+{
+ return container_of(chip, struct aspeed_pwm_tachometer_data, chip);
+}
+
+static int aspeed_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct aspeed_pwm_output_chan *chan;
+
+ chan = devm_kzalloc(chip->dev, sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ pwm_set_chip_data(pwm, chan);
+
+ return 0;
+}
+
+static void aspeed_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ devm_kfree(chip->dev, pwm_get_chip_data(pwm));
+ pwm_set_chip_data(pwm, NULL);
+}
+
+static int aspeed_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct aspeed_pwm_tachometer_data *priv = to_aspeed_pwm(chip);
+
+ aspeed_set_pwm_channel_enable(priv->regmap, pwm->hwpwm, true);
+
+ return 0;
+}
+
+static void aspeed_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct aspeed_pwm_tachometer_data *priv = to_aspeed_pwm(chip);
+
+ aspeed_set_pwm_channel_enable(priv->regmap, pwm->hwpwm, false);
+}
+
+static int aspeed_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ int duty_ns, int period_ns)
+{
+ struct aspeed_pwm_tachometer_data *priv = to_aspeed_pwm(chip);
+ struct aspeed_pwm_output_chan *chan = pwm_get_chip_data(pwm);
+ u8 div_h, div_l, period_value, falling_point, rising_point;
+ u32 ctrl_value, duty_value, tick_ns;
+
+ /*
+ * We currently avoid using 64bit arithmetic by using the
+ * fact that anything faster than 1Hz is easily representable
+ * by 32bits.
+ */
+ if (period_ns > NSEC_PER_SEC)
+ return -ERANGE;
+
+ if (chan->period_ns == period_ns && chan->duty_ns == duty_ns)
+ return 0;
+
+ for (div_l = 0; div_l <= 0xff; div_l++) {
+ for (div_h = 0; div_h <= 0xf; div_h++) {
+ tick_ns = priv->clk_tick_ns * BIT(div_h) * (div_l + 1);
+ if (tick_ns * PWM_PERIOD_MAX >= period_ns)
+ break;
+ }
+ if (tick_ns * PWM_PERIOD_MAX >= period_ns)
+ break;
+ }
+
+ if (period_ns / tick_ns > PWM_PERIOD_MAX)
+ return -ERANGE;
+
+ ctrl_value = div_h << 8 | div_l;
+ period_value = period_ns / tick_ns;
+ falling_point = 0;
+ rising_point = duty_ns / tick_ns;
+ duty_value = period_value << PWM_PERIOD_BIT |
+ falling_point << PWM_RISING_RISING_BIT |
+ rising_point << PWM_RISING_FALLING_BIT;
+
+ regmap_update_bits(priv->regmap, ASPEED_PWM_DUTY_CYCLE_CH(pwm->hwpwm),
+ PWM_PERIOD_BIT_MASK | PWM_RISING_FALLING_MASK,
+ duty_value);
+ regmap_update_bits(priv->regmap, ASPEED_PWM_CTRL_CH(pwm->hwpwm),
+ PWM_CLK_DIV_H_MASK | PWM_CLK_DIV_L_MASK, ctrl_value);
+
+ chan->period_ns = period_ns;
+ chan->duty_ns = duty_ns;
+
+ return 0;
+}
+
+static const struct pwm_ops aspeed_pwm_ops = {
+ .request = aspeed_pwm_request,
+ .free = aspeed_pwm_free,
+ .enable = aspeed_pwm_enable,
+ .disable = aspeed_pwm_disable,
+ .config = aspeed_pwm_config,
+ .owner = THIS_MODULE,
+};
+
+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 resource *res;
+ struct device *hwmon;
+ struct clk *clk;
+ int ret;
+
+ np = dev->of_node;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+ regs = devm_ioremap_resource(dev, res);
+ 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->clk_tick_ns = NSEC_PER_SEC / priv->clk_freq;
+
+ 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;
+ }
+ }
+
+ priv->chip.dev = &pdev->dev;
+ priv->chip.ops = &aspeed_pwm_ops;
+ priv->chip.base = -1;
+ priv->chip.npwm = 16;
+ priv->chip.of_xlate = of_pwm_xlate_with_flags;
+ priv->chip.of_pwm_n_cells = 3;
+
+ ret = pwmchip_add(&priv->chip);
+ if (ret < 0) {
+ dev_err(dev, "failed to register PWM chip\n");
+ return ret;
+ }
+
+ priv->groups[0] = &pwm_dev_group;
+ priv->groups[1] = &fan_dev_group;
+ priv->groups[2] = NULL;
+ hwmon = devm_hwmon_device_register_with_groups(dev,
+ "aspeed_g6_pwm_tacho",
+ priv, priv->groups);
+
+ return PTR_ERR_OR_ZERO(hwmon);
+}
+
+static const struct of_device_id of_pwm_tachometer_match_table[] = {
+ { .compatible = "aspeed,ast2600-pwm-tacho", },
+ {},
+};
+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_g6_pwm_tacho",
+ .of_match_table = of_pwm_tachometer_match_table,
+ },
+};
+
+module_platform_driver(aspeed_pwm_tachometer_driver);
+
+MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
+MODULE_DESCRIPTION("ASPEED PWM and Fan Tachometer device driver");
+MODULE_LICENSE("GPL");