diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0076-arm-ast2600-add-pwm_tacho-driver-from-aspeed.patch')
-rw-r--r-- | meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0076-arm-ast2600-add-pwm_tacho-driver-from-aspeed.patch | 1107 |
1 files changed, 1107 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0076-arm-ast2600-add-pwm_tacho-driver-from-aspeed.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0076-arm-ast2600-add-pwm_tacho-driver-from-aspeed.patch new file mode 100644 index 000000000..9515a1a89 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0076-arm-ast2600-add-pwm_tacho-driver-from-aspeed.patch @@ -0,0 +1,1107 @@ +From a17a084c4889dcc7cb43ef5f93032997cfcee4f0 Mon Sep 17 00:00:00 2001 +From: Vernon Mauery <vernon.mauery@intel.com> +Date: Fri, 27 Sep 2019 13:09:48 -0700 +Subject: [PATCH] arm: ast2600: add pwm_tacho driver from aspeed + +Add the pwm_tacho driver from Aspeed to get pwm working until an +upstream PWM/Tacho driver is available. This was copied from the v5.02 +BSP from Aspeed. + +Signed-off-by: Vernon Mauery <vernon.mauery@intel.com> +--- + arch/arm/boot/dts/aspeed-g6.dtsi | 10 + + drivers/hwmon/Kconfig | 11 + + drivers/hwmon/Makefile | 1 + + drivers/hwmon/aspeed-g6-pwm-tacho.c | 1025 +++++++++++++++++++++++++++++++++++ + 4 files changed, 1047 insertions(+) + create mode 100644 drivers/hwmon/aspeed-g6-pwm-tacho.c + +diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi +index eeace4b7b725..33fcd89db6b8 100644 +--- a/arch/arm/boot/dts/aspeed-g6.dtsi ++++ b/arch/arm/boot/dts/aspeed-g6.dtsi +@@ -278,6 +278,16 @@ + #size-cells = <1>; + ranges; + ++ pwm_tacho: pwm-tacho-controller@1e610000 { ++ compatible = "aspeed,ast2600-pwm-tacho"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x1e610000 0x100>; ++ clocks = <&syscon ASPEED_CLK_AHB>; ++ resets = <&syscon ASPEED_RESET_PWM>; ++ status = "disabled"; ++ }; ++ + syscon: syscon@1e6e2000 { + compatible = "aspeed,ast2600-scu", "syscon", "simple-mfd"; + reg = <0x1e6e2000 0x1000>; +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index e244a7901392..8312b3798b82 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -371,6 +371,17 @@ config SENSORS_ASPEED + This driver can also be built as a module. If so, the module + will be called aspeed_pwm_tacho. + ++config SENSORS_ASPEED_G6 ++ tristate "ASPEED AST2600 PWM and Fan tach driver" ++ depends on THERMAL || THERMAL=n ++ select REGMAP ++ help ++ This driver provides support for ASPEED AST2600 PWM ++ and Fan Tacho controllers. ++ ++ This driver can also be built as a module. If so, the module ++ will be called aspeed_g6_pwm_tacho. ++ + config SENSORS_ATXP1 + tristate "Attansic ATXP1 VID controller" + depends on I2C +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index 1c7ab361adc7..e74ea925fb56 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -50,6 +50,7 @@ obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o + obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o + obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o + obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o ++obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tacho.o + obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o + obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o + obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o +diff --git a/drivers/hwmon/aspeed-g6-pwm-tacho.c b/drivers/hwmon/aspeed-g6-pwm-tacho.c +new file mode 100644 +index 000000000000..1894f6ad5edb +--- /dev/null ++++ b/drivers/hwmon/aspeed-g6-pwm-tacho.c +@@ -0,0 +1,1025 @@ ++/* ++ * 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> ++ ++#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 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 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 ++/* ++\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 ++ ++struct aspeed_pwm_channel_params { ++ 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 divide_h; ++ u8 divide_l; ++ u8 period; ++ u8 rising; ++ u8 falling; ++}; ++ ++static struct aspeed_pwm_channel_params default_pwm_params[] = { ++ [0] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 1, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [1] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [2] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [3] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [4] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [5] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [6] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [7] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [8] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [9] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [10] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [11] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [12] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [13] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [14] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++ [15] = { ++ .load_wdt_rising_falling_pt = 0x10, ++ .load_wdt_selection = 0, ++ .load_wdt_enable = 0, ++ .duty_sync_enable = 0, ++ .invert_pin = 0, ++ .divide_h = 0x5, ++ .divide_l = 0x6, ++ .period = 0x13, //5% ~~ ++ .rising = 0x00, ++ .falling = 0x0a, ++ }, ++}; ++ ++/* ++ * 5:4 fan tach edge mode selection bit: ++ * 00: falling ++ * 01: rising ++ * 10: both ++ * 11: reserved. ++ */ ++ ++struct aspeed_tacho_channel_params { ++ int limited_inverse; ++ u16 threshold; ++ u8 tacho_edge; ++ u8 tacho_debounce; ++ u8 divide; ++}; ++ ++ ++static struct aspeed_tacho_channel_params default_tacho_params[] = { ++ [0] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [1] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [2] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [3] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [4] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [5] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [6] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [7] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [8] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [9] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [10] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [11] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [12] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [13] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [14] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .tacho_debounce = 0, ++ .divide = 8, ++ }, ++ [15] = { ++ .limited_inverse = 0, ++ .threshold = 0, ++ .tacho_edge = 0, ++ .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 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, ++ .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 = 0, j; ++ u32 divide_val = 0; ++ u32 reg_value = 0; ++ ++ if(enable) { ++ //4 ^ n ++ //check pwm clk and to change tacho devide 25KZ ++ for(i = 0; i < 12; i++) { ++ divide_val = 1; ++ for (j = 1; j <= i; j++) ++ divide_val *= 4; ++// printk("i : %d , priv->clk_freq/divide_val %d ",i, priv->clk_freq/divide_val); ++ if((priv->clk_freq/divide_val) < 250000) ++ break; ++ } ++ i--; ++ divide_val = ((1 << i) * (1 << i)); ++// printk("tacho divide_val %d , i %x max tacho clk %d \n", divide_val, i, priv->clk_freq / divide_val); ++ priv->tacho_channel[fan_tach_ch].divide = i; ++ ++ reg_value = TACHO_ENABLE | ++ (priv->tacho_channel[fan_tach_ch].tacho_edge << TECHIO_EDGE_BIT) | ++ (priv->tacho_channel[fan_tach_ch].divide << 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; ++ ++ if (fan_ctrl == 0) { ++ aspeed_set_pwm_channel_enable(priv->regmap, index, false); ++ } else { ++ duty_value = (priv->pwm_channel[index].period << PWM_PERIOD_BIT) | ++ (0 << PWM_RISING_RISING_BIT) | (fan_ctrl << PWM_RISING_FALLING_BIT); ++ ++ ctrl_value = (priv->pwm_channel[index].divide_h << 8) | priv->pwm_channel[index].divide_l; ++ ++ 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); ++ } ++} ++ ++#define BOTH_EDGES 0x02 /* 10b */ ++ ++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 mode, both; ++ 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; ++ ++ tach_div = priv->tacho_channel[fan_tach_ch].divide; ++ /* ++ * We need the mode to determine if the raw_data is double (from ++ * counting both edges). ++ */ ++ mode = priv->tacho_channel[fan_tach_ch].tacho_edge; ++ both = (mode & BOTH_EDGES) ? 1 : 0; ++// printk("clk %ld, raw_data %x , tach_div %x both %x \n", priv->clk_freq, raw_data, tach_div, both); ++ ++ tach_div = (tach_div * 2) * (0x1 << both); ++ clk_source = priv->clk_freq; ++ ++ if (raw_data == 0) ++ return 0; ++ ++ return (clk_source * 60) / (2 * raw_data * tach_div); ++ ++} ++ ++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; ++ ++ ret = kstrtol(buf, 10, &fan_ctrl); ++ if (ret != 0) ++ return ret; ++ ++ if (fan_ctrl < 0 || fan_ctrl > priv->pwm_channel[index].period) ++ return -EINVAL; ++ ++ if (priv->pwm_channel[index].falling == fan_ctrl) ++ return count; ++ ++ priv->pwm_channel[index].falling = fan_ctrl; ++ aspeed_set_pwm_channel_fan_ctrl(priv, index, fan_ctrl); ++ ++ 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(pwm0, 0644, ++ show_pwm, set_pwm, 0); ++static SENSOR_DEVICE_ATTR(pwm1, 0644, ++ show_pwm, set_pwm, 1); ++static SENSOR_DEVICE_ATTR(pwm2, 0644, ++ show_pwm, set_pwm, 2); ++static SENSOR_DEVICE_ATTR(pwm3, 0644, ++ show_pwm, set_pwm, 3); ++static SENSOR_DEVICE_ATTR(pwm4, 0644, ++ show_pwm, set_pwm, 4); ++static SENSOR_DEVICE_ATTR(pwm5, 0644, ++ show_pwm, set_pwm, 5); ++static SENSOR_DEVICE_ATTR(pwm6, 0644, ++ show_pwm, set_pwm, 6); ++static SENSOR_DEVICE_ATTR(pwm7, 0644, ++ show_pwm, set_pwm, 7); ++static SENSOR_DEVICE_ATTR(pwm8, 0644, ++ show_pwm, set_pwm, 8); ++static SENSOR_DEVICE_ATTR(pwm9, 0644, ++ show_pwm, set_pwm, 9); ++static SENSOR_DEVICE_ATTR(pwm10, 0644, ++ show_pwm, set_pwm, 10); ++static SENSOR_DEVICE_ATTR(pwm11, 0644, ++ show_pwm, set_pwm, 11); ++static SENSOR_DEVICE_ATTR(pwm12, 0644, ++ show_pwm, set_pwm, 12); ++static SENSOR_DEVICE_ATTR(pwm13, 0644, ++ show_pwm, set_pwm, 13); ++static SENSOR_DEVICE_ATTR(pwm14, 0644, ++ show_pwm, set_pwm, 14); ++static SENSOR_DEVICE_ATTR(pwm15, 0644, ++ show_pwm, set_pwm, 15); ++static struct attribute *pwm_dev_attrs[] = { ++ &sensor_dev_attr_pwm0.dev_attr.attr, ++ &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, ++ NULL, ++}; ++ ++static const struct attribute_group pwm_dev_group = { ++ .attrs = pwm_dev_attrs, ++ .is_visible = pwm_is_visible, ++}; ++ ++static SENSOR_DEVICE_ATTR(fan0_input, 0444, ++ show_rpm, NULL, 0); ++static SENSOR_DEVICE_ATTR(fan1_input, 0444, ++ show_rpm, NULL, 1); ++static SENSOR_DEVICE_ATTR(fan2_input, 0444, ++ show_rpm, NULL, 2); ++static SENSOR_DEVICE_ATTR(fan3_input, 0444, ++ show_rpm, NULL, 3); ++static SENSOR_DEVICE_ATTR(fan4_input, 0444, ++ show_rpm, NULL, 4); ++static SENSOR_DEVICE_ATTR(fan5_input, 0444, ++ show_rpm, NULL, 5); ++static SENSOR_DEVICE_ATTR(fan6_input, 0444, ++ show_rpm, NULL, 6); ++static SENSOR_DEVICE_ATTR(fan7_input, 0444, ++ show_rpm, NULL, 7); ++static SENSOR_DEVICE_ATTR(fan8_input, 0444, ++ show_rpm, NULL, 8); ++static SENSOR_DEVICE_ATTR(fan9_input, 0444, ++ show_rpm, NULL, 9); ++static SENSOR_DEVICE_ATTR(fan10_input, 0444, ++ show_rpm, NULL, 10); ++static SENSOR_DEVICE_ATTR(fan11_input, 0444, ++ show_rpm, NULL, 11); ++static SENSOR_DEVICE_ATTR(fan12_input, 0444, ++ show_rpm, NULL, 12); ++static SENSOR_DEVICE_ATTR(fan13_input, 0444, ++ show_rpm, NULL, 13); ++static SENSOR_DEVICE_ATTR(fan14_input, 0444, ++ show_rpm, NULL, 14); ++static SENSOR_DEVICE_ATTR(fan15_input, 0444, ++ show_rpm, NULL, 15); ++static struct attribute *fan_dev_attrs[] = { ++ &sensor_dev_attr_fan0_input.dev_attr.attr, ++ &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, ++ 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) ++{ ++ u8 val, index; ++ ++ for (val = 0; val < count; val++) { ++ index = fan_tach_ch[val]; ++ priv->fan_tach_present[index] = true; ++ 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 pwm_channel; ++ int ret, count; ++ ++ ret = of_property_read_u32(child, "reg", &pwm_channel); ++ if (ret) ++ return ret; ++ ++ 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; ++ ++ aspeed_create_fan_tach_channel(priv, fan_tach_ch, count); ++ ++ return 0; ++} ++ ++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->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->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"); +-- +2.7.4 + |