[dev-4.7 patch 2/5] hwmon (aspeed) introduce Aspeed SoC pwm-fan driver

vadimp at mellanox.com vadimp at mellanox.com
Wed Aug 10 17:02:54 AEST 2016


From: Vadim Pasternak <vadimp at mellanox.com>

The Kconfig controlling compilation of this code are:
hwmon/aspeed/Kconfig:config SENSORS_ASPEED_PWM_FAN

Porting pwm-fan driver from 3.18 to 4.7.

Signed-off-by: Vadim Pasternak <vadimp at mellanox.com>
---
 drivers/hwmon/Kconfig                 |    2 +
 drivers/hwmon/Makefile                |    1 +
 drivers/hwmon/aspeed/Kconfig          |   11 +
 drivers/hwmon/aspeed/Makefile         |    5 +
 drivers/hwmon/aspeed/aspeed-pwm-fan.c | 2735 +++++++++++++++++++++++++++++++++
 5 files changed, 2754 insertions(+)
 create mode 100644 drivers/hwmon/aspeed/Kconfig
 create mode 100644 drivers/hwmon/aspeed/Makefile
 create mode 100644 drivers/hwmon/aspeed/aspeed-pwm-fan.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 3b34ba9..b08ea92 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1828,4 +1828,6 @@ config SENSORS_ATK0110
 
 endif # ACPI
 
+source drivers/hwmon/aspeed/Kconfig
+
 endif # HWMON
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index c0f3201..7220d0c 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -165,6 +165,7 @@ obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
 
 obj-$(CONFIG_PMBUS)		+= pmbus/
+obj-$(CONFIG_MACH_ASPEED_G5)	+= aspeed/
 
 ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
 
diff --git a/drivers/hwmon/aspeed/Kconfig b/drivers/hwmon/aspeed/Kconfig
new file mode 100644
index 0000000..6739973
--- /dev/null
+++ b/drivers/hwmon/aspeed/Kconfig
@@ -0,0 +1,11 @@
+menu "ASPEED hwmon support"
+
+config SENSORS_ASPEED_PWM_FAN
+        tristate "ASPEED PWM & FAN Tacho Controller Driver"
+        depends on ARCH_ASPEED
+        default n
+        help
+          This driver provides support for the ASPEED PWM & FAN Tachometer
+          Controller, which provides an Sensor, fan control.
+
+endmenu
diff --git a/drivers/hwmon/aspeed/Makefile b/drivers/hwmon/aspeed/Makefile
new file mode 100644
index 0000000..e372511
--- /dev/null
+++ b/drivers/hwmon/aspeed/Makefile
@@ -0,0 +1,5 @@
+
+ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := \
+        -I$(srctree)/arch/arm/mach-aspeed/include
+
+obj-$(CONFIG_SENSORS_ASPEED_PWM_FAN)	+= aspeed-pwm-fan.o
diff --git a/drivers/hwmon/aspeed/aspeed-pwm-fan.c b/drivers/hwmon/aspeed/aspeed-pwm-fan.c
new file mode 100644
index 0000000..414ae6d
--- /dev/null
+++ b/drivers/hwmon/aspeed/aspeed-pwm-fan.c
@@ -0,0 +1,2735 @@
+/*
+ *  drivers/hwmon/aspeed/aspeed-pwm-fan.c
+ *
+ *  ASPEED PWM & Fan Tacho controller driver
+ *
+ *  Copyright (C) 2012-2020  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 as
+ *  published by the Free Software Foundation.
+ *
+ *  History:
+ *    2012.08.06: Initial version [Ryan Chen]
+ *    2016.08.06: Porting to kernel 4.7 [Vadim Pasternak vadimp at mellanox.com]
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <asm/irq.h>
+#include <asm/mach-types.h>
+#include <asm/mach/arch.h>
+#include <mach/reset.h>
+
+/* CLK sysfs
+ * 0 : enable
+ * 1 : clk_source
+ *
+ * PWM sysfs A~H (0~7)
+ * 0 - show/store enable
+ * 1 - show/store type
+ * 2 - show/store falling
+ * 3 - show/store rising
+ *
+ * PWM M/N/O Type sysfs
+ * 0 - show/store unit
+ * 1 - show/store division_l
+ * 2 - show/store division_h
+ *
+ * FAN sysfs (0~15)
+ *  show/store enable
+ *  show/store source
+ *  show/store rpm
+ *  show/store alarm
+ *  show/store alarm_en
+ *
+ * Fan M/N/O Type sysfs
+ *  0 - show/store enable
+ *  1 - show/store mode
+ *  2 - show/store unit
+ *  3 - show/store division
+ *  4 - show/store limit
+ */
+
+#define AST_SCU_FUN_PIN_CTRL1	0x80	/* Multi-function Pin Control#1 */
+#define AST_SCU_FUN_PIN_CTRL2	0x84	/* Multi-function Pin Control#2 */
+#define AST_SCU_FUN_PIN_CTRL3	0x88	/* Multi-function Pin Control#3 */
+#define AST_SCU_FUN_PIN_CTRL4	0x8C	/* Multi-function Pin Control#4 */
+#define AST_SCU_FUN_PIN_CTRL5	0x90	/* Multi-function Pin Control#5 */
+#define AST_PWM_GROUP_NUM	(1 + PWM_CH_NUM + 2 * PWM_TYPE_NUM + TACHO_NUM)
+
+/* Aspeed PWM & FAN Register Definition */
+#define AST_PTCR_CTRL		0x00
+#define AST_PTCR_CLK_CTRL	0x04
+#define AST_PTCR_DUTY0_CTRL	0x08
+#define AST_PTCR_DUTY1_CTRL	0x0c
+#define AST_PTCR_TYPEM_CTRL0	0x10
+#define AST_PTCR_TYPEM_CTRL1	0x14
+#define AST_PTCR_TYPEN_CTRL0	0x18
+#define AST_PTCR_TYPEN_CTRL1	0x1c
+#define AST_PTCR_TACH_SOURCE	0x20
+#define AST_PTCR_TRIGGER	0x28
+#define AST_PTCR_RESULT		0x2c
+#define AST_PTCR_INTR_CTRL	0x30
+#define AST_PTCR_INTR_STS	0x34
+#define AST_PTCR_TYPEM_LIMIT	0x38
+#define AST_PTCR_TYPEN_LIMIT	0x3C
+#define AST_PTCR_CTRL_EXT	0x40
+#define AST_PTCR_CLK_EXT_CTRL	0x44
+#define AST_PTCR_DUTY2_CTRL	0x48
+#define AST_PTCR_DUTY3_CTRL	0x4c
+#define AST_PTCR_TYPEO_CTRL0	0x50
+#define AST_PTCR_TYPEO_CTRL1	0x54
+#define AST_PTCR_TACH_SOURCE_EXT 0x60
+#define AST_PTCR_TYPEO_LIMIT	0x78
+
+/* Common definitions */
+#define FALL_EDGE	(0)
+#define RISE_EDGE	(0x1)
+#define BOTH_EDGE	(0x2)
+#define PWM_TYPE_NUM	3
+#define PWM_TYPE_M	0x0
+#define PWM_TYPE_N	0x1
+#define PWM_TYPE_O	0x2
+#define PWM_TYPE_MASK	0x3
+
+#define TACHO_NUM	16
+#define PWM_CH_NUM	8
+#define PWMA		0x0
+#define PWMB		0x1
+#define PWMC		0x2
+#define PWMD		0x3
+#define PWME		0x4
+#define PWMF		0x5
+#define PWMG		0x6
+#define PWMH		0x7
+
+
+/* AST_PTCR_CTRL:0x00 - PWM-FAN General Control Register */
+#define AST_PTCR_CTRL_SET_PWMD_TYPE(x) \
+	((x & 0x1) << 15 | (x & 0x2) << 6)
+#define AST_PTCR_CTRL_GET_PWMD_TYPE(x) \
+	(((x & (0x1 << 7)) >> 6) | ((x & (0x1 << 15)) >> 15))
+#define AST_PTCR_CTRL_SET_PWMD_TYPE_MASK \
+	((0x1 << 7) | (0x1 << 15))
+
+#define AST_PTCR_CTRL_SET_PWMC_TYPE(x) \
+	((x & 0x1) << 14 | (x & 0x2) << 5)
+#define AST_PTCR_CTRL_GET_PWMC_TYPE(x) \
+	(((x & (0x1 << 6)) >> 5) | ((x & (0x1 << 14)) >> 14))
+#define AST_PTCR_CTRL_SET_PWMC_TYPE_MASK \
+	((0x1 << 6) | (0x1 << 14))
+
+#define AST_PTCR_CTRL_SET_PWMB_TYPE(x) \
+	((x & 0x1)<<13 | (x & 0x2) << 4)
+#define AST_PTCR_CTRL_GET_PWMB_TYPE(x) \
+	(((x & (0x1 << 5)) >> 4) | ((x & (0x1 << 13)) >> 13))
+#define AST_PTCR_CTRL_SET_PWMB_TYPE_MASK \
+	((0x1 << 5) | (0x1 << 13))
+
+#define AST_PTCR_CTRL_SET_PWMA_TYPE(x) \
+	((x & 0x1) << 12 | (x & 0x2) << 3)
+#define AST_PTCR_CTRL_GET_PWMA_TYPE(x) \
+	(((x & (0x1 << 4)) >> 3) | ((x & (0x1 << 12)) >> 12))
+#define AST_PTCR_CTRL_SET_PWMA_TYPE_MASK \
+	((0x1 << 4) | (0x1 << 12))
+
+#define	AST_PTCR_CTRL_FAN_NUM_EN(x)		(0x1 << (16 + x))
+
+#define	AST_PTCR_CTRL_PMWD		(11)
+#define	AST_PTCR_CTRL_PMWD_EN		(0x1 << 11)
+#define	AST_PTCR_CTRL_PMWC		(10)
+#define	AST_PTCR_CTRL_PMWC_EN		(0x1 << 10)
+#define	AST_PTCR_CTRL_PMWB		(9)
+#define	AST_PTCR_CTRL_PMWB_EN		(0x1 << 9)
+#define	AST_PTCR_CTRL_PMWA		(8)
+#define	AST_PTCR_CTRL_PMWA_EN		(0x1 << 8)
+#define	AST_PTCR_CTRL_CLK_MCLK		0x2 /* 0:24Mhz, 1:MCLK */
+#define	AST_PTCR_CTRL_CLK_EN		0x1
+
+/* AST_PTCR_CLK_CTRL:0x04 - PWM-FAN Clock Control Register */
+/* TYPE N */
+#define AST_PTCR_CLK_CTRL_TYPEN_UNIT		(24)
+#define AST_PTCR_CLK_CTRL_TYPEN_UNIT_MASK	(0xff << 24)
+#define AST_PTCR_CLK_CTRL_TYPEN_H		(20)
+#define AST_PTCR_CLK_CTRL_TYPEN_H_MASK		(0xf << 20)
+#define AST_PTCR_CLK_CTRL_TYPEN_L		(16)
+#define AST_PTCR_CLK_CTRL_TYPEN_L_MASK		(0xf << 16)
+/* TYPE M */
+#define AST_PTCR_CLK_CTRL_TYPEM_UNIT		(8)
+#define AST_PTCR_CLK_CTRL_TYPEM_UNIT_MASK	(0xff << 8)
+#define AST_PTCR_CLK_CTRL_TYPEM_H		(4)
+#define AST_PTCR_CLK_CTRL_TYPEM_H_MASK		(0xf << 4)
+#define AST_PTCR_CLK_CTRL_TYPEM_L		(0)
+#define AST_PTCR_CLK_CTRL_TYPEM_L_MASK		(0xf)
+
+
+/* AST_PTCR_DUTY_CTRL0:0x08 - PWM-FAN duty control 0 register */
+#define DUTY_CTRL0_PWMB_FALL_POINT		(24)
+#define DUTY_CTRL0_PWMB_FALL_POINT_MASK		(0xff << 24)
+#define DUTY_CTRL0_PWMB_RISE_POINT		(16)
+#define DUTY_CTRL0_PWMB_RISE_POINT_MASK		(0xff << 16)
+#define DUTY_CTRL0_PWMA_FALL_POINT		(8)
+#define DUTY_CTRL0_PWMA_FALL_POINT_MASK		(0xff << 8)
+#define DUTY_CTRL0_PWMA_RISE_POINT		(0)
+#define DUTY_CTRL0_PWMA_RISE_POINT_MASK		(0xff)
+
+
+/* AST_PTCR_DUTY_CTRL1 : 0x0c - PWM-FAN duty control 1 register */
+#define DUTY_CTRL1_PWMD_FALL_POINT		(24)
+#define DUTY_CTRL1_PWMD_FALL_POINT_MASK		(0xff << 24)
+#define DUTY_CTRL1_PWMD_RISE_POINT		(16)
+#define DUTY_CTRL1_PWMD_RISE_POINT_MASK		(0xff << 16)
+#define DUTY_CTRL1_PWMC_FALL_POINT		(8)
+#define DUTY_CTRL1_PWMC_FALL_POINT_MASK		(0xff << 8)
+#define DUTY_CTRL1_PWMC_RISE_POINT		(0)
+#define DUTY_CTRL1_PWMC_RISE_POINT_MASK		(0xff)
+
+
+/* AST_PTCR_TYPEM_CTRL0 : 0x10/0x18/0x50 - Type M/N/O Ctrl 0 Register */
+#define TYPE_CTRL0_FAN_PERIOD			(16)
+#define TYPE_CTRL0_FAN_PERIOD_MASK		(0xffff << 16)
+#define TYPE_CTRL0_FLAT_EN			(0x1 << 7)
+
+
+/* 0 : FALL_EDGE,	0x1 : RISE_EDGE , 0x2 :BOTH_EDGE */
+#define TYPE_CTRL0_FAN_MODE			(4)
+#define TYPE_CTRL0_FAN_MODE_MASK		(0x3<<4)
+#define TYPE_CTRL0_CLK_DIVISION			(1)
+#define TYPE_CTRL0_CLK_DIVISION_MASK		(0x7<<1)
+#define TYPE_CTRL0_FAN_TYPE_EN			(1)
+
+
+/* AST_PTCR_TYPEM_CTRL1 : 0x14/0x1c/0x54 - Type M/N/O Ctrl 1 Register */
+#define TYPE_CTRL1_FALL_POINT			(16)
+#define TYPE_CTRL1_FALL_POINT_MASK		(0xff << 16)
+#define TYPE_CTRL1_RISE_POINT			(0)
+#define TYPE_CTRL1_RISE_POINT_MASK		(0xff)
+
+
+/* AST_PTCR_TACH_SOURCE : 0x20/0x60 - Tach Source Register
+ * bit [0,1] at 0x20, bit [2] at 0x60
+ */
+#define TACH_PWM_SOURCE_BIT01(x)		(x * 2)
+#define TACH_PWM_SOURCE_BIT2(x)			(x * 2)
+#define TACH_PWM_SOURCE_MASK_BIT01(x)		(0x3 << (x * 2))
+#define TACH_PWM_SOURCE_MASK_BIT2(x)		(0x1 << (x * 2))
+
+/* AST_PTCR_TRIGGER : 0x28 - Trigger Register */
+#define TRIGGER_READ_FAN_NUM(x)			(0x1 << x)
+
+/* AST_PTCR_RESULT : 0x2c - Result Register */
+#define RESULT_STATUS				(31)
+#define RESULT_VALUE_MASK			(0xfffff)
+
+/* AST_PTCR_INTR_CTRL : 0x30 - Interrupt Ctrl Register */
+#define INTR_CTRL_EN_NUM(x)			(0x1 << x)
+
+/* AST_PTCR_INTR_STS : 0x34 - Interrupt Status Register	*/
+#define INTR_CTRL_NUM(x)			(0x1 << x)
+
+/* AST_PTCR_TYPEM_LIMIT, AST_PTCR_TYPEN_LIMIT,AST_PTCR_TYPEO_LIMIT:
+ * 0x38/0x3C/0x78 - Type M / N / O Limit Register
+ */
+#define FAN_LIMIT_MASK				(0xfffff)
+
+/* AST_PTCR_CTRL_EXT : 0x40 - General Ctrl Extension #1 */
+#define AST_PTCR_CTRL_SET_PWMH_TYPE(x) \
+	((x & 0x1) << 15 | (x & 0x2) << 6)
+#define AST_PTCR_CTRL_GET_PWMH_TYPE(x) \
+	(((x & (0x1 << 7))>>6) | ((x & (0x1 << 15)) >> 15))
+#define AST_PTCR_CTRL_SET_PWMH_TYPE_MASK	((0x1 << 7) | (0x1 << 15))
+
+#define AST_PTCR_CTRL_SET_PWMG_TYPE(x) \
+	((x & 0x1)<<14 | (x & 0x2) << 5)
+#define AST_PTCR_CTRL_GET_PWMG_TYPE(x) \
+	(((x & (0x1 << 6))>>5) | ((x & (0x1 << 14)) >> 14))
+#define AST_PTCR_CTRL_SET_PWMG_TYPE_MASK	((0x1 << 6) | (0x1 << 14))
+
+#define AST_PTCR_CTRL_SET_PWMF_TYPE(x) \
+		((x & 0x1) << 13 | (x & 0x2) << 4)
+#define AST_PTCR_CTRL_GET_PWMF_TYPE(x) \
+	(((x & (0x1 << 5)) >> 4) | ((x & (0x1 << 13)) >> 13))
+#define AST_PTCR_CTRL_SET_PWMF_TYPE_MASK	((0x1 << 5) | (0x1 << 13))
+
+#define AST_PTCR_CTRL_SET_PWME_TYPE(x) \
+	((x & 0x1) << 12 | (x & 0x2) << 3)
+#define AST_PTCR_CTRL_GET_PWME_TYPE(x) \
+	(((x & (0x1 << 4)) >> 3) | ((x & (0x1 << 12)) >> 12))
+#define AST_PTCR_CTRL_SET_PWME_TYPE_MASK	((0x1 << 4) | (0x1 << 12))
+
+#define	AST_PTCR_CTRL_PMWH			(11)
+#define	AST_PTCR_CTRL_PMWH_EN			(0x1 << 11)
+#define	AST_PTCR_CTRL_PMWG			(10)
+#define	AST_PTCR_CTRL_PMWG_EN			(0x1 << 10)
+#define	AST_PTCR_CTRL_PMWF			(9)
+#define	AST_PTCR_CTRL_PMWF_EN			(0x1 << 9)
+#define	AST_PTCR_CTRL_PMWE			(8)
+#define	AST_PTCR_CTRL_PMWE_EN			(0x1 << 8)
+
+/* AST_PTCR_CLK_EXT_CTRL : 0x44 - Clock Control Extension #1 */
+/* TYPE O */
+#define AST_PTCR_CLK_CTRL_TYPEO_UNIT		(8)
+#define AST_PTCR_CLK_CTRL_TYPEO_UNIT_MASK	(0xff << 8)
+#define AST_PTCR_CLK_CTRL_TYPEO_H		(4)
+#define AST_PTCR_CLK_CTRL_TYPEO_H_MASK		(0xf << 4)
+#define AST_PTCR_CLK_CTRL_TYPEO_L		(0)
+#define AST_PTCR_CLK_CTRL_TYPEO_L_MASK		(0xf)
+
+/* AST_PTCR_DUTY2_CTRL : 0x48 - Duty Control 2 Register */
+#define DUTY_CTRL2_PWMF_FALL_POINT		(24)
+#define DUTY_CTRL2_PWMF_FALL_POINT_MASK		(0xff << 24)
+#define DUTY_CTRL2_PWMF_RISE_POINT		(16)
+#define DUTY_CTRL2_PWMF_RISE_POINT_MASK		(0xff << 16)
+#define DUTY_CTRL2_PWME_FALL_POINT		(8)
+#define DUTY_CTRL2_PWME_FALL_POINT_MASK		(0xff << 8)
+#define DUTY_CTRL2_PWME_RISE_POINT		(0)
+#define DUTY_CTRL2_PWME_RISE_POINT_MASK		(0xff)
+
+/* AST_PTCR_DUTY3_CTRL : 0x4c - Duty Control 3 Register */
+#define DUTY_CTRL3_PWMH_FALL_POINT		(24)
+#define DUTY_CTRL3_PWMH_FALL_POINT_MASK		(0xff << 24)
+#define DUTY_CTRL3_PWMH_RISE_POINT		(16)
+#define DUTY_CTRL3_PWMH_RISE_POINT_MASK		(0xff << 16)
+#define DUTY_CTRL3_PWMG_FALL_POINT		(8)
+#define DUTY_CTRL3_PWMG_FALL_POINT_MASK		(0xff << 8)
+#define DUTY_CTRL3_PWMG_RISE_POINT		(0)
+#define DUTY_CTRL3_PWMG_RISE_POINT_MASK		(0xff)
+
+struct aspeed_pwm_driver_data {
+	u32 (*get_pwm_clock)(void);
+};
+
+struct aspeed_pwm_tacho_data {
+	struct device			*hwmon_dev;
+	void __iomem			*reg_base;
+	int				irq;
+	struct aspeed_pwm_driver_data	*aspeed_pwm_data;
+	const struct attribute_group	*groups[AST_PWM_GROUP_NUM + 1];
+};
+
+struct aspeed_pwm_tacho_data *aspeed_pwm_tacho;
+
+static u8
+aspeed_get_pwm_type(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho, u8 pwm_ch);
+static u8
+aspeed_get_pwm_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho, u8 pwm_ch);
+static u8
+aspeed_get_tacho_type_division(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			       u8 pwm_type);
+static u16
+aspeed_get_tacho_type_unit(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type);
+static u8
+aspeed_get_pwm_clock_div_h(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type);
+static u8
+aspeed_get_pwm_clock_div_l(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type);
+static u8
+aspeed_get_pwm_clock_unit(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			  u8 pwm_type);
+
+static inline void
+aspeed_pwm_tacho_write(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho, u32 val,
+		       u32 reg)
+{
+	writel(val, aspeed_pwm_tacho->reg_base + reg);
+}
+
+static inline u32
+aspeed_pwm_tacho_read(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho, u32 reg)
+{
+	return readl(aspeed_pwm_tacho->reg_base + reg);
+}
+
+/*
+ * (1) The PWM base clock = 24Mhz / (Clock_Division_H D[7:4] in PTCR04 *
+ *	Clock_Division_L D[3:0] in PTCR04)
+ * (2) The frequency of PWM = The PWM base clock / (PWM period D[15:8] in
+ *	PTCR04 + 1)
+ * (3) If a plan to output 25Khz PWM frequency and 10% step of duty cycle,
+ *	suggested to set 0x943 in PTCR04 register.
+ * The PWM frequency = 24Mhz / (16 * 6 * (9 + 1)) = 25Khz
+ * duty cycle settings in the PTCR08 register:
+ * 0x1e786008 D[15:0] = 0x0900, duty = 90%
+ * 0x1e786008 D[15:0] = 0x0902, duty = 70%
+ * ...
+ * 0x1e786008 D[15:0] = 0x0908, duty = 10%
+ * 0x1e786008 D[15:0] = 0x0909, duty = 100%
+ * 0x1e786008 D[15:0] = 0x0000, duty = 100%
+ * (falling) - (rising+1) /unit
+ */
+static void aspeed_pwm_taco_init(void)
+{
+	/* Enable PWM TACH CLK
+	 * Set M/N/O out is 25Khz
+	 * The PWM frequency = 24Mhz / (16 * 6 * (9 + 1)) = 25Khz
+	 */
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x09430943,
+			       AST_PTCR_CLK_CTRL);
+#ifdef PWM_TYPE_O
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x0943,
+			       AST_PTCR_CLK_EXT_CTRL);
+#endif
+	/* FULL SPEED at initialize 100% pwm A~H */
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x0, AST_PTCR_DUTY0_CTRL);
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x0, AST_PTCR_DUTY1_CTRL);
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x0, AST_PTCR_DUTY2_CTRL);
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x0, AST_PTCR_DUTY3_CTRL);
+
+	/* Set TACO M/N/O initial unit 0x1000, falling , divide 4 , Enable */
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x10000001,
+			       AST_PTCR_TYPEM_CTRL0);
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x10000001,
+			       AST_PTCR_TYPEN_CTRL0);
+#ifdef PWM_TYPE_O
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x10000001,
+			       AST_PTCR_TYPEO_CTRL0);
+#endif
+
+	/* TACO measure period = 24000000 / 2 / 2 / 256 / 4096 / 1 (only enable
+	 * 1 TACHO) = 5.72Hz, it means that software needs to wait at least 0.2
+	 * sec to get refreshed TACO value. If you will enable more TACO or
+	 * require faster response, you have to control the clock divisor and
+	 * the period to be smaller.
+	 *
+	 * Full Range to do measure unit 0x1000
+	 * PTCRM/N/O[3:1] = 0, Type M/N/O fan tach clock is div 4. -->
+	 * calculate RPM.
+	 */
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x10000000,
+			       AST_PTCR_TYPEM_CTRL1);
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x10000000,
+			       AST_PTCR_TYPEN_CTRL1);
+#ifdef PWM_TYPE_O
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x10000000,
+			       AST_PTCR_TYPEO_CTRL1);
+#endif
+
+	/* TACO Source Selection, PWMA for fan0~15 */
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x0, AST_PTCR_TACH_SOURCE);
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x0,
+			       AST_PTCR_TACH_SOURCE_EXT);
+
+	/* PWM A~D -> Disable , type M, Tacho 0~15 Disable, CLK source 24Mhz */
+#ifdef MCLK
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, AST_PTCR_CTRL_CLK_MCLK |
+			       AST_PTCR_CTRL_CLK_EN, AST_PTCR_CTRL);
+#else
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, AST_PTCR_CTRL_CLK_EN,
+			       AST_PTCR_CTRL);
+#endif
+}
+
+/* index 0 : clk_en , 1: clk_source */
+static ssize_t aspeed_store_clk(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	unsigned long input_val;
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+	int ret;
+
+	ret = kstrtoul(buf, 10, &input_val);
+	if (ret)
+		return ret;
+
+	if ((input_val > 1) || (input_val < 0))
+		return -EINVAL;
+
+	switch (sensor_attr->nr) {
+	case 0:	/* clk_en */
+		if (input_val)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					AST_PTCR_CTRL) | AST_PTCR_CTRL_CLK_EN,
+					AST_PTCR_CTRL);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+					aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					AST_PTCR_CTRL) & ~AST_PTCR_CTRL_CLK_EN,
+					AST_PTCR_CTRL);
+		break;
+
+	case 1: /* clk_source */
+		if (input_val)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					AST_PTCR_CTRL) |
+					AST_PTCR_CTRL_CLK_MCLK,
+					AST_PTCR_CTRL);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+					aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					AST_PTCR_CTRL) &
+					~AST_PTCR_CTRL_CLK_MCLK,
+					AST_PTCR_CTRL);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return count;
+
+}
+
+
+static ssize_t aspeed_show_clk(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+
+	switch (sensor_attr->nr)	 {
+	case 0:	/* clk_en */
+		if (AST_PTCR_CTRL_CLK_EN &
+		    aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL))
+			return sprintf(buf, "1: Enable\n");
+		else
+			return sprintf(buf, "0: Disable\n");
+		break;
+
+	case 1: /* clk_source */
+		if (AST_PTCR_CTRL_CLK_MCLK &
+		    aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL))
+			return sprintf(buf, "1: MCLK\n");
+		else
+			return sprintf(buf, "0: 24Mhz\n");
+
+		break;
+
+	default:
+		return sprintf(buf, "error CLK Index\n");
+	}
+}
+
+static u32
+aspeed_get_tacho_measure_period(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+				u8 pwm_type)
+{
+	u32 clk, clk_unit, div_h, div_l, tacho_unit, tacho_div;
+
+	if (AST_PTCR_CTRL_CLK_MCLK & aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+							   AST_PTCR_CTRL))
+		clk = aspeed_pwm_tacho->aspeed_pwm_data->get_pwm_clock();
+	else
+		clk = 24 * 1000 * 1000;
+
+	clk_unit = aspeed_get_pwm_clock_unit(aspeed_pwm_tacho, pwm_type);
+	div_h = aspeed_get_pwm_clock_div_h(aspeed_pwm_tacho, pwm_type);
+	div_h = 0x1 << div_h;
+	div_l = aspeed_get_pwm_clock_div_l(aspeed_pwm_tacho, pwm_type);
+	if (div_l == 0)
+		div_l = 1;
+	else
+		div_l = div_l * 2;
+
+	tacho_unit = aspeed_get_tacho_type_unit(aspeed_pwm_tacho, pwm_type);
+	tacho_div = aspeed_get_tacho_type_division(aspeed_pwm_tacho, pwm_type);
+
+	tacho_div = 0x4 << (tacho_div * 2);
+
+	return clk/(clk_unit * div_h * div_l * tacho_div * tacho_unit);
+}
+
+static u8
+aspeed_get_tacho_type_division(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			       u8 pwm_type)
+{
+	u32 tmp = 0;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEM_CTRL0);
+		break;
+
+	case PWM_TYPE_N:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEN_CTRL0);
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEO_CTRL0);
+		break;
+
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+
+	}
+
+	return ((tmp & TYPE_CTRL0_CLK_DIVISION_MASK) >>
+		TYPE_CTRL0_CLK_DIVISION);
+}
+
+static void
+aspeed_set_tacho_type_division(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			       u8 pwm_type, u32 division)
+{
+	u32 tmp = 0;
+
+	if (division > 0x7)
+		return;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEM_CTRL0);
+		break;
+
+	case PWM_TYPE_N:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEN_CTRL0);
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEO_CTRL0);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+
+	tmp &= ~TYPE_CTRL0_CLK_DIVISION_MASK;
+	tmp |= (division << TYPE_CTRL0_CLK_DIVISION);
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEM_CTRL0);
+		break;
+	case PWM_TYPE_N:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEN_CTRL0);
+		break;
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEO_CTRL0);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+}
+
+static u16
+aspeed_get_tacho_type_unit(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type)
+{
+	u32 tmp = 0;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEM_CTRL0);
+		break;
+	case PWM_TYPE_N:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEN_CTRL0);
+		break;
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEO_CTRL0);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+
+	return ((tmp & TYPE_CTRL0_FAN_PERIOD_MASK) >> TYPE_CTRL0_FAN_PERIOD);
+}
+
+static void
+aspeed_set_tacho_type_unit(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type, u32 unit)
+{
+	u32 tmp = 0;
+
+	if (unit > 0xffff)
+		return;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEM_CTRL0);
+		break;
+	case PWM_TYPE_N:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEN_CTRL0);
+		break;
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEO_CTRL0);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+
+	tmp &= ~TYPE_CTRL0_FAN_PERIOD_MASK;
+	tmp |= (unit << TYPE_CTRL0_FAN_PERIOD);
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEM_CTRL0);
+		break;
+
+	case PWM_TYPE_N:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEN_CTRL0);
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEO_CTRL0);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+}
+
+static u32
+aspeed_get_tacho_type_mode(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type)
+{
+	u32 tmp = 0;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEM_CTRL0);
+		break;
+	case PWM_TYPE_N:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEN_CTRL0);
+		break;
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEO_CTRL0);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+
+	return ((tmp & TYPE_CTRL0_FAN_MODE_MASK) >> TYPE_CTRL0_FAN_MODE);
+}
+
+static void
+aspeed_set_tacho_type_mode(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type, u32 mode)
+{
+	u32 tmp = 0;
+
+	if (mode > 0x2)
+		return;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEM_CTRL0);
+		break;
+
+	case PWM_TYPE_N:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEN_CTRL0);
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEO_CTRL0);
+		break;
+
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+
+	tmp &= ~TYPE_CTRL0_FAN_MODE_MASK;
+	tmp |= (mode << TYPE_CTRL0_FAN_MODE);
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEM_CTRL0);
+		break;
+
+	case PWM_TYPE_N:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEN_CTRL0);
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_TYPEO_CTRL0);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+
+}
+
+static u8
+aspeed_get_tacho_type_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			 u8 pwm_type)
+{
+	u8 tmp = 0;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = (TYPE_CTRL0_FAN_TYPE_EN &
+		       aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_TYPEM_CTRL0));
+		break;
+
+	case PWM_TYPE_N:
+		tmp = (TYPE_CTRL0_FAN_TYPE_EN &
+		      aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_TYPEN_CTRL0));
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = (TYPE_CTRL0_FAN_TYPE_EN &
+		      aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+		      AST_PTCR_TYPEO_CTRL0));
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+
+	return tmp;
+}
+
+static void
+aspeed_set_tacho_type_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			 u8 pwm_type, u32 enable)
+{
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				       aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					      AST_PTCR_TYPEM_CTRL0) | enable,
+			AST_PTCR_TYPEM_CTRL0);
+
+		break;
+	case PWM_TYPE_N:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_TYPEN_CTRL0)
+		| enable, AST_PTCR_TYPEN_CTRL0);
+
+		break;
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_TYPEO_CTRL0) |
+				      enable, AST_PTCR_TYPEO_CTRL0);
+
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+}
+
+static u32
+aspeed_get_tacho_type_limit(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			    u8 pwm_type)
+{
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		return (FAN_LIMIT_MASK &
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					      AST_PTCR_TYPEM_LIMIT));
+
+	case PWM_TYPE_N:
+		return (FAN_LIMIT_MASK &
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					      AST_PTCR_TYPEN_LIMIT));
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		return (FAN_LIMIT_MASK &
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					      AST_PTCR_TYPEO_LIMIT));
+
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+	return 0;
+}
+
+static void
+aspeed_set_tacho_type_limit(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			    u8 pwm_type, u32 limit)
+{
+	if (limit > FAN_LIMIT_MASK)
+		return;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, limit,
+				       AST_PTCR_TYPEM_LIMIT);
+		break;
+	case PWM_TYPE_N:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, limit,
+				       AST_PTCR_TYPEN_LIMIT);
+		break;
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, limit,
+				       AST_PTCR_TYPEO_LIMIT);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error type\n");
+		break;
+	}
+}
+
+static u8
+aspeed_get_tacho_alarm_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			  u8 tacho_ch)
+{
+	/* tacho source */
+	if (aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_INTR_CTRL) &
+				  INTR_CTRL_EN_NUM(tacho_ch))
+		return 1;
+
+	return 0;
+}
+
+static void
+aspeed_set_tacho_alarm_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			  u8 tacho_ch, u8 enable)
+{
+	/* tacho source  */
+	if (enable == 1)
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					      AST_PTCR_INTR_CTRL) |
+			INTR_CTRL_EN_NUM(tacho_ch), AST_PTCR_INTR_CTRL);
+	else
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					      AST_PTCR_INTR_CTRL) &
+			~(INTR_CTRL_EN_NUM(tacho_ch)), AST_PTCR_INTR_CTRL);
+}
+
+static u8
+aspeed_get_tacho_alarm(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+		       u8 tacho_ch)
+{
+	/* tacho source */
+	if (aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_INTR_STS) &
+				  INTR_CTRL_NUM(tacho_ch))
+		return 1;
+
+	return 0;
+}
+
+static u8
+aspeed_get_tacho_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho, u8 tacho_ch)
+{
+	if (aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL) &
+				  AST_PTCR_CTRL_FAN_NUM_EN(tacho_ch))
+		return 1;
+
+	return 0;
+}
+
+static void
+aspeed_set_tacho_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+		    u8 tacho_ch, u8 enable)
+{
+	/* tacho number enable */
+	if (enable)
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL) |
+			AST_PTCR_CTRL_FAN_NUM_EN(tacho_ch), AST_PTCR_CTRL);
+	else
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL) &
+			~(AST_PTCR_CTRL_FAN_NUM_EN(tacho_ch)), AST_PTCR_CTRL);
+}
+
+static u8
+aspeed_get_tacho_source(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			u8 tacho_ch)
+{
+	u32 tmp1, tmp2;
+
+	/* tacho source */
+	tmp1 = aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_TACH_SOURCE);
+	tmp1 &= TACH_PWM_SOURCE_MASK_BIT01(tacho_ch);
+	tmp1 = tmp1 >> (TACH_PWM_SOURCE_BIT01(tacho_ch));
+	tmp2 = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+				     AST_PTCR_TACH_SOURCE_EXT);
+	tmp2 &= TACH_PWM_SOURCE_MASK_BIT2(tacho_ch);
+	tmp2 = tmp2 >> (TACH_PWM_SOURCE_BIT2(tacho_ch));
+	tmp2 = tmp2 << 2;
+
+	return (tmp2 | tmp1);
+}
+
+static void
+aspeed_set_tacho_source(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			u8 tacho_ch, u8 tacho_source)
+{
+	u32 tmp1, tmp2;
+
+	if (tacho_source > 7)
+		return;
+
+	/* tacho source */
+	tmp1 = aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_TACH_SOURCE);
+	tmp1 &= ~(TACH_PWM_SOURCE_MASK_BIT01(tacho_ch));
+	tmp1 |= ((tacho_source & 0x3) << (TACH_PWM_SOURCE_BIT01(tacho_ch)));
+
+	tmp2 = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+				     AST_PTCR_TACH_SOURCE_EXT);
+	tmp2 &= ~(TACH_PWM_SOURCE_MASK_BIT2(tacho_ch));
+	tmp2 |= (((tacho_source & 0x4) >> 2) <<
+		(TACH_PWM_SOURCE_BIT2(tacho_ch)));
+
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp1, AST_PTCR_TACH_SOURCE);
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp2,
+			       AST_PTCR_TACH_SOURCE_EXT);
+
+}
+
+static u32
+aspeed_get_tacho_rpm(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+		     u8 tacho_ch)
+{
+	u32 raw_data, rpm, tacho_clk_div, clk_source, timeout = 0;
+	u8 tacho_source, pwm_type, tacho_type_en;
+
+	if (!(aspeed_get_tacho_en(aspeed_pwm_tacho, tacho_ch)))
+		return 0;
+
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0, AST_PTCR_TRIGGER);
+	aspeed_pwm_tacho_write(aspeed_pwm_tacho, 0x1 << tacho_ch,
+			       AST_PTCR_TRIGGER);
+
+	tacho_source = aspeed_get_tacho_source(aspeed_pwm_tacho, tacho_ch);
+	pwm_type = aspeed_get_pwm_type(aspeed_pwm_tacho, tacho_source);
+	tacho_type_en = aspeed_get_tacho_type_en(aspeed_pwm_tacho, pwm_type);
+
+	/* check pwm_type and get clock division */
+	if (!tacho_type_en)
+		return 0;
+
+	/* Wait ready */
+	while (!(aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_RESULT) &
+		(0x1 << RESULT_STATUS))) {
+		timeout++;
+		if (timeout > 25)
+			return 0;
+	};
+
+	raw_data = aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_RESULT) &
+		   RESULT_VALUE_MASK;
+	tacho_clk_div = aspeed_get_tacho_type_division(aspeed_pwm_tacho,
+						       pwm_type);
+
+	tacho_clk_div = 0x4 << (tacho_clk_div*2);
+
+	if (AST_PTCR_CTRL_CLK_MCLK & aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+							   AST_PTCR_CTRL))
+		clk_source = 166 * 1000 * 1000;
+	else
+		clk_source = 24 * 1000 * 1000;
+
+	rpm = (clk_source * 60) / (2 * raw_data * tacho_clk_div);
+
+	return rpm;
+}
+
+static u8
+aspeed_get_pwm_clock_div_h(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type)
+{
+	u8 tmp = 0;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_CTRL) &
+		      AST_PTCR_CLK_CTRL_TYPEM_H_MASK) >>
+		      AST_PTCR_CLK_CTRL_TYPEM_H;
+		break;
+
+	case PWM_TYPE_N:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_CTRL) &
+		       AST_PTCR_CLK_CTRL_TYPEN_H_MASK) >>
+		       AST_PTCR_CLK_CTRL_TYPEN_H;
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_EXT_CTRL) &
+		       AST_PTCR_CLK_CTRL_TYPEO_H_MASK) >>
+		       AST_PTCR_CLK_CTRL_TYPEO_H;
+		break;
+#endif
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_clock_div_h %d\n",
+			pwm_type);
+		break;
+	}
+	return tmp;
+}
+
+static void
+aspeed_set_pwm_clock_division_h(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+				u8 pwm_type, u8 div_high)
+{
+	if (div_high > 0xf)
+		return;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CLK_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEM_H_MASK) |
+		(div_high << AST_PTCR_CLK_CTRL_TYPEM_H), AST_PTCR_CLK_CTRL);
+		break;
+
+	case PWM_TYPE_N:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CLK_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEN_H_MASK) |
+		(div_high << AST_PTCR_CLK_CTRL_TYPEN_H), AST_PTCR_CLK_CTRL);
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+				       AST_PTCR_CLK_EXT_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEO_H_MASK) |
+		(div_high << AST_PTCR_CLK_CTRL_TYPEO_H),
+		AST_PTCR_CLK_EXT_CTRL);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_type %d\n",
+			pwm_type);
+		break;
+	}
+
+}
+
+static u8
+aspeed_get_pwm_clock_div_l(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_type)
+{
+	u8 tmp = 0;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_CTRL) &
+		      AST_PTCR_CLK_CTRL_TYPEM_L_MASK) >>
+		      AST_PTCR_CLK_CTRL_TYPEM_L;
+		break;
+
+	case PWM_TYPE_N:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_CTRL) &
+		      AST_PTCR_CLK_CTRL_TYPEN_L_MASK) >>
+		      AST_PTCR_CLK_CTRL_TYPEN_L;
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_EXT_CTRL) &
+		       AST_PTCR_CLK_CTRL_TYPEO_L_MASK) >>
+		       AST_PTCR_CLK_CTRL_TYPEO_L;
+		break;
+
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_clock_div_l %d\n",
+			pwm_type);
+		break;
+	}
+	return tmp;
+}
+
+static void
+aspeed_set_pwm_clock_division_l(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+				u8 pwm_type, u8 div_low)
+{
+	if (div_low > 0xf)
+		return;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CLK_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEM_L_MASK) |
+		(div_low << AST_PTCR_CLK_CTRL_TYPEM_L), AST_PTCR_CLK_CTRL);
+		break;
+
+	case PWM_TYPE_N:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CLK_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEN_L_MASK) |
+		(div_low << AST_PTCR_CLK_CTRL_TYPEN_L), AST_PTCR_CLK_CTRL);
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+				       AST_PTCR_CLK_EXT_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEO_L_MASK) |
+		(div_low << AST_PTCR_CLK_CTRL_TYPEO_L), AST_PTCR_CLK_EXT_CTRL);
+		break;
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_type %d\n",
+			pwm_type);
+		break;
+	}
+}
+
+static u8
+aspeed_get_pwm_clock_unit(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			  u8 pwm_type)
+{
+	u8 tmp = 0;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_CTRL) &
+		      AST_PTCR_CLK_CTRL_TYPEM_UNIT_MASK) >>
+		      AST_PTCR_CLK_CTRL_TYPEM_UNIT;
+		break;
+
+	case PWM_TYPE_N:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_CTRL) &
+		      AST_PTCR_CLK_CTRL_TYPEN_UNIT_MASK) >>
+		      AST_PTCR_CLK_CTRL_TYPEN_UNIT;
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CLK_EXT_CTRL) &
+		       AST_PTCR_CLK_CTRL_TYPEO_UNIT_MASK) >>
+		       AST_PTCR_CLK_CTRL_TYPEO_UNIT;
+		break;
+
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_clock_unit %d\n",
+			pwm_type);
+		break;
+	}
+
+	return tmp;
+}
+
+static void
+aspeed_set_pwm_clock_unit(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			  u8 pwm_type, u8 unit)
+{
+	if (unit > 0xff)
+		return;
+
+	switch (pwm_type) {
+	case PWM_TYPE_M:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CLK_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEM_UNIT_MASK) |
+		(unit << AST_PTCR_CLK_CTRL_TYPEM_UNIT),
+		AST_PTCR_CLK_CTRL);
+		break;
+
+	case PWM_TYPE_N:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CLK_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEN_UNIT_MASK) |
+		(unit << AST_PTCR_CLK_CTRL_TYPEN_UNIT),
+		AST_PTCR_CLK_CTRL);
+		break;
+
+#ifdef PWM_TYPE_O
+	case PWM_TYPE_O:
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+		(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+		AST_PTCR_CLK_EXT_CTRL) &
+		~AST_PTCR_CLK_CTRL_TYPEO_UNIT_MASK) |
+		(unit << AST_PTCR_CLK_CTRL_TYPEO_UNIT),
+		AST_PTCR_CLK_EXT_CTRL);
+		break;
+
+#endif
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_type %d\n",
+			pwm_type);
+		break;
+	}
+}
+
+static u32
+aspeed_get_pwm_clock(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+		     u8 pwm_type)
+{
+	u32 unit, div_low, div_high, clk_source;
+
+	unit = aspeed_get_pwm_clock_unit(aspeed_pwm_tacho, pwm_type);
+
+	div_high = aspeed_get_pwm_clock_div_h(aspeed_pwm_tacho, pwm_type);
+	div_high = (0x1 << div_high);
+
+	div_low = aspeed_get_pwm_clock_div_l(aspeed_pwm_tacho, pwm_type);
+	if (div_low == 0)
+		div_low = 1;
+	else
+		div_low = div_low*2;
+
+	if (AST_PTCR_CTRL_CLK_MCLK & aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+	    AST_PTCR_CTRL))
+		clk_source = aspeed_pwm_tacho->aspeed_pwm_data->get_pwm_clock();
+	else
+		clk_source = 24 * 1000 * 1000;
+
+	return (clk_source / (div_high * div_low * (unit + 1)));
+}
+
+static u8
+aspeed_get_pwm_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho, u8 pwm_ch)
+{
+	u8 tmp = 0;
+
+	switch (pwm_ch) {
+	case PWMA:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL) &
+		      AST_PTCR_CTRL_PMWA_EN) >> AST_PTCR_CTRL_PMWA;
+		break;
+
+	case PWMB:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL) &
+		      AST_PTCR_CTRL_PMWB_EN) >> AST_PTCR_CTRL_PMWB;
+		break;
+
+	case PWMC:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL) &
+		      AST_PTCR_CTRL_PMWC_EN) >> AST_PTCR_CTRL_PMWC;
+		break;
+
+	case PWMD:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL) &
+		      AST_PTCR_CTRL_PMWD_EN) >> AST_PTCR_CTRL_PMWD;
+		break;
+
+	case PWME:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CTRL_EXT) &
+		      AST_PTCR_CTRL_PMWE_EN) >> AST_PTCR_CTRL_PMWE;
+		break;
+
+	case PWMF:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CTRL_EXT) &
+		      AST_PTCR_CTRL_PMWF_EN) >> AST_PTCR_CTRL_PMWF;
+		break;
+
+	case PWMG:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CTRL_EXT) &
+		      AST_PTCR_CTRL_PMWG_EN) >> AST_PTCR_CTRL_PMWG;
+		break;
+
+	case PWMH:
+		tmp = (aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					     AST_PTCR_CTRL_EXT) &
+		      AST_PTCR_CTRL_PMWH_EN) >> AST_PTCR_CTRL_PMWH;
+		break;
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_type %d\n",
+			pwm_ch);
+		break;
+	}
+
+	return tmp;
+
+}
+
+static void
+aspeed_set_pwm_en(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho, u8 pwm_ch,
+		  u8 enable)
+{
+	switch (pwm_ch) {
+	case PWMA:
+		if (enable)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						     AST_PTCR_CTRL) |
+				AST_PTCR_CTRL_PMWA_EN, AST_PTCR_CTRL);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						      AST_PTCR_CTRL) &
+				~AST_PTCR_CTRL_PMWA_EN, AST_PTCR_CTRL);
+
+		break;
+
+	case PWMB:
+		if (enable)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL) |
+				AST_PTCR_CTRL_PMWB_EN), AST_PTCR_CTRL);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL) &
+				~AST_PTCR_CTRL_PMWB_EN), AST_PTCR_CTRL);
+		break;
+
+	case PWMC:
+		if (enable)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL) |
+				AST_PTCR_CTRL_PMWC_EN), AST_PTCR_CTRL);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL) &
+				~AST_PTCR_CTRL_PMWC_EN), AST_PTCR_CTRL);
+
+		break;
+
+	case PWMD:
+		if (enable)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL) |
+				AST_PTCR_CTRL_PMWD_EN), AST_PTCR_CTRL);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL) &
+				~AST_PTCR_CTRL_PMWD_EN), AST_PTCR_CTRL);
+
+		break;
+
+	case PWME:
+		if (enable)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL_EXT) |
+				AST_PTCR_CTRL_PMWE_EN), AST_PTCR_CTRL_EXT);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL_EXT) &
+				~AST_PTCR_CTRL_PMWE_EN), AST_PTCR_CTRL_EXT);
+
+		break;
+
+	case PWMF:
+		if (enable)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL_EXT) |
+				AST_PTCR_CTRL_PMWF_EN), AST_PTCR_CTRL_EXT);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL_EXT) &
+				~AST_PTCR_CTRL_PMWF_EN), AST_PTCR_CTRL_EXT);
+
+		break;
+
+	case PWMG:
+		if (enable)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL_EXT) |
+				AST_PTCR_CTRL_PMWG_EN), AST_PTCR_CTRL_EXT);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL_EXT) &
+				~AST_PTCR_CTRL_PMWG_EN), AST_PTCR_CTRL_EXT);
+
+		break;
+
+	case PWMH:
+		if (enable)
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL_EXT) |
+				AST_PTCR_CTRL_PMWH_EN), AST_PTCR_CTRL_EXT);
+		else
+			aspeed_pwm_tacho_write(aspeed_pwm_tacho,
+				(aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+						       AST_PTCR_CTRL_EXT) &
+				~AST_PTCR_CTRL_PMWH_EN), AST_PTCR_CTRL_EXT);
+
+		break;
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_type %d\n",
+			pwm_ch);
+		break;
+	}
+}
+
+static u8
+aspeed_get_pwm_type(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho, u8 pwm_ch)
+{
+	u8 tmp = 0;
+
+	switch (pwm_ch) {
+	case PWMA:
+		tmp = AST_PTCR_CTRL_GET_PWMA_TYPE(
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+			AST_PTCR_CTRL));
+		break;
+
+	case PWMB:
+		tmp = AST_PTCR_CTRL_GET_PWMB_TYPE(
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+			AST_PTCR_CTRL));
+		break;
+
+	case PWMC:
+		tmp = AST_PTCR_CTRL_GET_PWMC_TYPE(
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+			AST_PTCR_CTRL));
+		break;
+
+	case PWMD:
+		tmp = AST_PTCR_CTRL_GET_PWMD_TYPE(
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+			AST_PTCR_CTRL));
+		break;
+
+	case PWME:
+		tmp = AST_PTCR_CTRL_GET_PWME_TYPE(
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+			AST_PTCR_CTRL_EXT));
+		break;
+
+	case PWMF:
+		tmp = AST_PTCR_CTRL_GET_PWMF_TYPE(
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+			AST_PTCR_CTRL_EXT));
+		break;
+
+	case PWMG:
+		tmp = AST_PTCR_CTRL_GET_PWMG_TYPE(
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+			AST_PTCR_CTRL_EXT));
+		break;
+
+	case PWMH:
+		tmp = AST_PTCR_CTRL_GET_PWMH_TYPE(
+			aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+			AST_PTCR_CTRL_EXT));
+		break;
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel aspeed_get_pwm_type %d\n",
+			pwm_ch);
+		break;
+	}
+
+	return tmp;
+}
+
+static void
+aspeed_set_pwm_type(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+		    u8 pwm_ch, u8 type)
+{
+	u32 tmp1, tmp2;
+
+	if (type > 0x2)
+		return;
+
+	tmp1 = aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL);
+	tmp2 = aspeed_pwm_tacho_read(aspeed_pwm_tacho, AST_PTCR_CTRL_EXT);
+
+	switch (pwm_ch) {
+	case PWMA:
+		tmp1 &= ~AST_PTCR_CTRL_SET_PWMA_TYPE_MASK;
+		tmp1 |= AST_PTCR_CTRL_SET_PWMA_TYPE(type);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp1, AST_PTCR_CTRL);
+		break;
+
+	case PWMB:
+		tmp1 &= ~AST_PTCR_CTRL_SET_PWMB_TYPE_MASK;
+		tmp1 |= AST_PTCR_CTRL_SET_PWMB_TYPE(type);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp1, AST_PTCR_CTRL);
+		break;
+
+	case PWMC:
+		tmp1 &= ~AST_PTCR_CTRL_SET_PWMC_TYPE_MASK;
+		tmp1 |= AST_PTCR_CTRL_SET_PWMC_TYPE(type);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp1, AST_PTCR_CTRL);
+		break;
+	case PWMD:
+		tmp1 &= ~AST_PTCR_CTRL_SET_PWMD_TYPE_MASK;
+		tmp1 |= AST_PTCR_CTRL_SET_PWMD_TYPE(type);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp1, AST_PTCR_CTRL);
+		break;
+
+	case PWME:
+		tmp2 &= ~AST_PTCR_CTRL_SET_PWME_TYPE_MASK;
+		tmp2 |= AST_PTCR_CTRL_SET_PWME_TYPE(type);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp2,
+				       AST_PTCR_CTRL_EXT);
+		break;
+
+	case PWMF:
+		tmp2 &= ~AST_PTCR_CTRL_SET_PWMF_TYPE_MASK;
+		tmp2 |= AST_PTCR_CTRL_SET_PWMF_TYPE(type);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp2,
+				       AST_PTCR_CTRL_EXT);
+		break;
+
+	case PWMG:
+		tmp2 &= ~AST_PTCR_CTRL_SET_PWMG_TYPE_MASK;
+		tmp2 |= AST_PTCR_CTRL_SET_PWMG_TYPE(type);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp2,
+				       AST_PTCR_CTRL_EXT);
+		break;
+
+	case PWMH:
+		tmp2 &= ~AST_PTCR_CTRL_SET_PWMH_TYPE_MASK;
+		tmp2 |= AST_PTCR_CTRL_SET_PWMH_TYPE(type);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp2,
+				       AST_PTCR_CTRL_EXT);
+		break;
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error channel %d\n",
+			pwm_ch);
+		break;
+	}
+}
+
+static u8
+aspeed_get_pwm_duty_rising(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_ch)
+{
+	u32 tmp = 0;
+
+	switch (pwm_ch) {
+	case PWMA:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY0_CTRL);
+		tmp &= DUTY_CTRL0_PWMA_RISE_POINT_MASK;
+		break;
+
+	case PWMB:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY0_CTRL);
+		tmp &= DUTY_CTRL0_PWMB_RISE_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL0_PWMB_RISE_POINT);
+		break;
+
+	case PWMC:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY1_CTRL);
+		tmp &= DUTY_CTRL1_PWMC_RISE_POINT_MASK;
+		break;
+	case PWMD:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY1_CTRL);
+		tmp &= DUTY_CTRL1_PWMD_RISE_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL1_PWMD_RISE_POINT);
+		break;
+
+	case PWME:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY2_CTRL);
+		tmp &= DUTY_CTRL2_PWME_RISE_POINT_MASK;
+		break;
+
+	case PWMF:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY2_CTRL);
+		tmp &= DUTY_CTRL2_PWMF_RISE_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL2_PWMF_RISE_POINT);
+		break;
+
+	case PWMG:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY3_CTRL);
+		tmp &= DUTY_CTRL3_PWMG_RISE_POINT_MASK;
+		break;
+
+	case PWMH:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY3_CTRL);
+		tmp &= DUTY_CTRL3_PWMH_RISE_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL3_PWMH_RISE_POINT);
+		break;
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error pwm channel %d with duty R\n",
+			pwm_ch);
+		break;
+	}
+
+	return tmp;
+}
+
+static void
+aspeed_set_pwm_duty_rising(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			   u8 pwm_ch, u8 rising)
+{
+	u32 tmp = 0;
+	u32 pwm_type = aspeed_get_pwm_type(aspeed_pwm_tacho, pwm_ch);
+
+	if ((rising > 0xff) || (rising >
+	    aspeed_get_pwm_clock_unit(aspeed_pwm_tacho, pwm_type)))
+		return;
+
+	switch (pwm_ch) {
+	case PWMA:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY0_CTRL);
+		tmp &= ~DUTY_CTRL0_PWMA_RISE_POINT_MASK;
+		tmp |= rising;
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY0_CTRL);
+		break;
+
+	case PWMB:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY0_CTRL);
+		tmp &= ~DUTY_CTRL0_PWMB_RISE_POINT_MASK;
+		tmp |= (rising << DUTY_CTRL0_PWMB_RISE_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY0_CTRL);
+		break;
+
+	case PWMC:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY1_CTRL);
+		tmp &= ~DUTY_CTRL1_PWMC_RISE_POINT_MASK;
+		tmp |= rising;
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY1_CTRL);
+		break;
+
+	case PWMD:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY1_CTRL);
+		tmp &= ~DUTY_CTRL1_PWMD_RISE_POINT_MASK;
+		tmp |= (rising << DUTY_CTRL1_PWMD_RISE_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY1_CTRL);
+		break;
+
+	case PWME:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY2_CTRL);
+		tmp &= ~DUTY_CTRL2_PWME_RISE_POINT_MASK;
+		tmp |= rising;
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY2_CTRL);
+		break;
+
+	case PWMF:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY2_CTRL);
+		tmp &= ~DUTY_CTRL2_PWMF_RISE_POINT_MASK;
+		tmp |= (rising << DUTY_CTRL2_PWMF_RISE_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY2_CTRL);
+		break;
+
+	case PWMG:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY3_CTRL);
+		tmp &= ~DUTY_CTRL3_PWMG_RISE_POINT_MASK;
+		tmp |= rising;
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY3_CTRL);
+		break;
+
+	case PWMH:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY3_CTRL);
+		tmp &= ~DUTY_CTRL3_PWMH_RISE_POINT_MASK;
+		tmp |= (rising << DUTY_CTRL3_PWMH_RISE_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY3_CTRL);
+		break;
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error pwm channel %d with duty\n",
+			pwm_ch);
+		break;
+	}
+}
+
+static u8
+aspeed_get_pwm_duty_falling(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			    u8 pwm_ch)
+{
+	u32 tmp = 0;
+
+	switch (pwm_ch) {
+	case PWMA:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY0_CTRL);
+		tmp &= DUTY_CTRL0_PWMA_FALL_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL0_PWMA_FALL_POINT);
+		break;
+
+	case PWMB:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY0_CTRL);
+		tmp &= DUTY_CTRL0_PWMB_FALL_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL0_PWMB_FALL_POINT);
+		break;
+
+	case PWMC:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY1_CTRL);
+		tmp &= DUTY_CTRL1_PWMC_FALL_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL1_PWMC_FALL_POINT);
+		break;
+
+	case PWMD:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY1_CTRL);
+		tmp &= DUTY_CTRL1_PWMD_FALL_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL1_PWMD_FALL_POINT);
+		break;
+	case PWME:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY2_CTRL);
+		tmp &= DUTY_CTRL2_PWME_FALL_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL2_PWME_FALL_POINT);
+		break;
+
+	case PWMF:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY2_CTRL);
+		tmp &= DUTY_CTRL2_PWMF_FALL_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL2_PWMF_FALL_POINT);
+		break;
+
+	case PWMG:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY3_CTRL);
+		tmp &= DUTY_CTRL3_PWMG_FALL_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL3_PWMG_FALL_POINT);
+		break;
+
+	case PWMH:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY3_CTRL);
+		tmp &= DUTY_CTRL3_PWMH_FALL_POINT_MASK;
+		tmp = (tmp >> DUTY_CTRL3_PWMH_FALL_POINT);
+		break;
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error pwm channel %d with duty F\n",
+			pwm_ch);
+		break;
+	}
+
+	return tmp;
+}
+
+static void
+aspeed_set_pwm_duty_falling(struct aspeed_pwm_tacho_data *aspeed_pwm_tacho,
+			    u8 pwm_ch, u8 falling)
+{
+	u32 tmp = 0;
+	u32 pwm_type = aspeed_get_pwm_type(aspeed_pwm_tacho, pwm_ch);
+
+	if ((falling > 0xff) || (falling >
+	    aspeed_get_pwm_clock_unit(aspeed_pwm_tacho, pwm_type)))
+		return;
+
+	switch (pwm_ch) {
+	case PWMA:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY0_CTRL);
+		tmp &= ~DUTY_CTRL0_PWMA_FALL_POINT_MASK;
+		tmp |= (falling << DUTY_CTRL0_PWMA_FALL_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY0_CTRL);
+		break;
+
+	case PWMB:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY0_CTRL);
+		tmp &= ~DUTY_CTRL0_PWMB_FALL_POINT_MASK;
+		tmp |= (falling << DUTY_CTRL0_PWMB_FALL_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY0_CTRL);
+		break;
+
+	case PWMC:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY1_CTRL);
+		tmp &= ~DUTY_CTRL1_PWMC_FALL_POINT_MASK;
+		tmp |= (falling << DUTY_CTRL1_PWMC_FALL_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY1_CTRL);
+		break;
+
+	case PWMD:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY1_CTRL);
+		tmp &= ~DUTY_CTRL1_PWMD_FALL_POINT_MASK;
+		tmp |= (falling << DUTY_CTRL1_PWMD_FALL_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY1_CTRL);
+		break;
+
+	case PWME:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY2_CTRL);
+		tmp &= ~DUTY_CTRL2_PWME_FALL_POINT_MASK;
+		tmp |= (falling << DUTY_CTRL2_PWME_FALL_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY2_CTRL);
+		break;
+
+	case PWMF:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY2_CTRL);
+		tmp &= ~DUTY_CTRL2_PWMF_FALL_POINT_MASK;
+		tmp |= (falling << DUTY_CTRL2_PWMF_FALL_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY2_CTRL);
+		break;
+
+	case PWMG:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY3_CTRL);
+		tmp &= ~DUTY_CTRL3_PWMG_FALL_POINT_MASK;
+		tmp |= (falling << DUTY_CTRL3_PWMG_FALL_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY3_CTRL);
+		break;
+
+	case PWMH:
+		tmp = aspeed_pwm_tacho_read(aspeed_pwm_tacho,
+					    AST_PTCR_DUTY3_CTRL);
+		tmp &= ~DUTY_CTRL3_PWMH_FALL_POINT_MASK;
+		tmp |= (falling << DUTY_CTRL3_PWMH_FALL_POINT);
+		aspeed_pwm_tacho_write(aspeed_pwm_tacho, tmp,
+				       AST_PTCR_DUTY3_CTRL);
+		break;
+
+	default:
+		dev_err(aspeed_pwm_tacho->hwmon_dev, "error pwm channel %d with duty\n",
+			pwm_ch);
+		break;
+	}
+
+}
+
+/* PWM M/N/O Type sysfs
+ *
+ * Macro defining SENSOR_DEVICE_ATTR for a pwm sysfs entries.
+ * 0 - show/store unit
+ * 1 - show/store division_l
+ * 2 - show/store division_h
+ */
+static ssize_t
+aspeed_show_pwm_type_clock(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+
+	switch (sensor_attr->nr) {
+	case 0: /* unit : 0~256 */
+		return sprintf(buf, "%d (0~255)\n",
+			       aspeed_get_pwm_clock_unit(aspeed_pwm_tacho,
+						sensor_attr->index));
+
+	case 1: /* division_l */
+		return sprintf(buf, "%d (0~15)\n",
+			       aspeed_get_pwm_clock_div_l(aspeed_pwm_tacho,
+							  sensor_attr->index));
+
+	case 2: /* division_h */
+		return sprintf(buf, "%d (0~15)\n",
+			       aspeed_get_pwm_clock_div_h(aspeed_pwm_tacho,
+							  sensor_attr->index));
+
+	case 3: /* expect clock */
+		return sprintf(buf, "%d \n",
+			       aspeed_get_pwm_clock(aspeed_pwm_tacho,
+						    sensor_attr->index));
+
+	default:
+		return -EINVAL;
+	}
+
+	return sprintf(buf, "%d : %d\n", sensor_attr->nr, sensor_attr->index);
+}
+
+static ssize_t
+aspeed_store_pwm_type_clock(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	unsigned long input_val;
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+	int ret;
+
+	ret = kstrtoul(buf, 10, &input_val);
+	if (ret)
+		return ret;
+
+	switch (sensor_attr->nr) {
+	case 0: /* unit : 0~256 */
+		aspeed_set_pwm_clock_unit(aspeed_pwm_tacho, sensor_attr->index,
+					  input_val);
+		break;
+
+	case 1: /* division_l */
+		aspeed_set_pwm_clock_division_l(aspeed_pwm_tacho,
+						sensor_attr->index, input_val);
+		break;
+
+	case 2: /* division_h */
+		aspeed_set_pwm_clock_division_h(aspeed_pwm_tacho,
+						sensor_attr->index, input_val);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+/* attr
+ * 0 - show/store enable
+ * 1 - show/store type
+ * 2 - show/store falling
+ * 3 - show/store rising
+ */
+static ssize_t
+aspeed_show_pwm_speed(struct device *dev, struct device_attribute *attr,
+		      char *buf)
+{
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+
+	switch (sensor_attr->nr) {
+	case 0: /* enable, disable */
+		return sprintf(buf, "%d : %s\n",
+			       aspeed_get_pwm_en(aspeed_pwm_tacho,
+						 sensor_attr->index),
+			       aspeed_get_pwm_en(aspeed_pwm_tacho,
+						 sensor_attr->index) ?
+			       "Enable":"Disable");
+
+	case 1: /* pwm type M/N/O */
+		return sprintf(buf, "%d (0:M/1:N/2:O)\n",
+			       aspeed_get_pwm_type(aspeed_pwm_tacho,
+						   sensor_attr->index));
+
+	case 2: /* rising */
+		return sprintf(buf, "%x : unit limit (0~%d)\n",
+			       aspeed_get_pwm_duty_rising(aspeed_pwm_tacho,
+							  sensor_attr->index),
+			       aspeed_get_pwm_clock_unit(aspeed_pwm_tacho,
+					aspeed_get_pwm_type(aspeed_pwm_tacho,
+						sensor_attr->index)));
+
+	case 3: /* falling */
+		return sprintf(buf, "%x : unit limit (0~%d)\n",
+			       aspeed_get_pwm_duty_falling(aspeed_pwm_tacho,
+							   sensor_attr->index),
+			       aspeed_get_pwm_clock_unit(aspeed_pwm_tacho,
+					aspeed_get_pwm_type(aspeed_pwm_tacho,
+						sensor_attr->index)));
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t
+aspeed_store_pwm_speed(struct device *dev, struct device_attribute *attr,
+		       const char *buf, size_t count)
+{
+	unsigned long input_val;
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+	int ret;
+
+	ret = kstrtoul(buf, 10, &input_val);
+	if (ret)
+		return ret;
+
+	switch (sensor_attr->nr) {
+	case 0: /* enable, disable */
+		aspeed_set_pwm_en(aspeed_pwm_tacho, sensor_attr->index,
+				  input_val);
+		break;
+	case 1: /* pwm type M/N/O */
+		aspeed_set_pwm_type(aspeed_pwm_tacho, sensor_attr->index,
+				    input_val);
+		break;
+	case 2: /* rising */
+		aspeed_set_pwm_duty_rising(aspeed_pwm_tacho,
+					   sensor_attr->index, input_val);
+		break;
+	case 3: /* falling */
+		aspeed_set_pwm_duty_falling(aspeed_pwm_tacho,
+					    sensor_attr->index, input_val);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+/* Fan Type  */
+/* Fan M/N/O Type sysfs
+ * Macro defining SENSOR_DEVICE_ATTR for a pwm sysfs entries.
+ * 0 - show/store enable
+ * 1 - show/store mode
+ * 2 - show/store unit
+ * 3 - show/store division
+ * 4 - show/store limit
+ */
+static ssize_t
+aspeed_show_tacho_type(struct device *dev, struct device_attribute *attr,
+		       char *buf)
+{
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+
+	switch (sensor_attr->nr) {
+	case 0: /* enable, disable */
+		return sprintf(buf, "%d : %s\n",
+			aspeed_get_tacho_type_en(aspeed_pwm_tacho,
+						 sensor_attr->index),
+			aspeed_get_tacho_type_en(aspeed_pwm_tacho,
+						 sensor_attr->index) ?
+			"Enable":"Disable");
+
+	case 1: /* fan tacho mode */
+		switch (aspeed_get_tacho_type_mode(
+			aspeed_pwm_tacho, sensor_attr->index)) {
+		case FALL_EDGE:
+			return sprintf(buf, "0: falling\n");
+		case RISE_EDGE:
+			return sprintf(buf, "1: rising\n");
+		case BOTH_EDGE:
+			return sprintf(buf, "2: both\n");
+		default:
+			return sprintf(buf, "3: unknown\n");
+		}
+		break;
+
+	case 2: /* unit */
+		return sprintf(buf, "%d (0~65535)\n",
+			aspeed_get_tacho_type_unit(aspeed_pwm_tacho,
+						   sensor_attr->index));
+
+	case 3: /* division */
+		return sprintf(buf, "%d (0~7)\n",
+			aspeed_get_tacho_type_division(aspeed_pwm_tacho,
+						       sensor_attr->index));
+
+	case 4: /* limit */
+		return sprintf(buf, "%d (0~1048575)\n",
+			aspeed_get_tacho_type_limit(aspeed_pwm_tacho,
+						    sensor_attr->index));
+
+	case 5: /* measure period */
+		return sprintf(buf, "%d\n",
+			aspeed_get_tacho_measure_period(aspeed_pwm_tacho,
+							sensor_attr->index));
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t
+aspeed_store_tacho_type(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	unsigned long input_val;
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+	int ret;
+
+	ret = kstrtoul(buf, 10, &input_val);
+	if (ret)
+		return ret;
+
+	switch (sensor_attr->nr) {
+	case 0: /* enable, disable */
+		aspeed_set_tacho_type_en(aspeed_pwm_tacho, sensor_attr->index,
+					 input_val);
+		break;
+	case 1: /* fan tacho mode */
+		aspeed_set_tacho_type_mode(aspeed_pwm_tacho,
+					   sensor_attr->index, input_val);
+		break;
+
+	case 2: /* unit */
+		aspeed_set_tacho_type_unit(aspeed_pwm_tacho,
+					   sensor_attr->index, input_val);
+		break;
+
+	case 3: /* division */
+		aspeed_set_tacho_type_division(aspeed_pwm_tacho,
+					       sensor_attr->index, input_val);
+		break;
+
+	case 4: /* limit */
+		aspeed_set_tacho_type_limit(aspeed_pwm_tacho,
+					    sensor_attr->index, input_val);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return count;
+
+}
+
+/* fan detect */
+/* FAN sysfs
+ * Macro defining SENSOR_DEVICE_ATTR for a tacho sysfs entries.
+ *  - show/store enable
+ *  - show/store source
+ *  - show/store rpm
+ *  - show/store alarm
+ *  - show/store alarm_en
+*/
+static ssize_t
+aspeed_show_tacho_speed(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+
+	switch (sensor_attr->nr)  {
+	case 0: /* enable, disable */
+		return sprintf(buf, "%d : %s\n",
+			       aspeed_get_tacho_en(aspeed_pwm_tacho,
+						   sensor_attr->index),
+			       aspeed_get_tacho_en(aspeed_pwm_tacho,
+						   sensor_attr->index) ?
+			       "Enable":"Disable");
+
+	case 1: /* tacho source PWMA~H - 0~7 */
+		return sprintf(buf, "PWM%d (0~7)\n",
+			       aspeed_get_tacho_source(aspeed_pwm_tacho,
+						       sensor_attr->index));
+
+	case 2: /* rpm */
+		return sprintf(buf, "%d \n",
+			       aspeed_get_tacho_rpm(aspeed_pwm_tacho,
+						    sensor_attr->index));
+
+	case 3: /* alarm */
+		return sprintf(buf, "%d \n",
+			       aspeed_get_tacho_alarm(aspeed_pwm_tacho,
+						      sensor_attr->index));
+
+	case 4: /* alarm_en */
+		return sprintf(buf, "%d : %s\n",
+			       aspeed_get_tacho_alarm_en(aspeed_pwm_tacho,
+							 sensor_attr->index),
+			       aspeed_get_tacho_alarm_en(aspeed_pwm_tacho,
+							 sensor_attr->index) ?
+			       "Enable":"Disable");
+
+	default:
+		return -EINVAL;
+	}
+
+}
+
+static ssize_t
+aspeed_store_tacho_speed(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	unsigned long input_val;
+	struct sensor_device_attribute_2 *sensor_attr =
+						to_sensor_dev_attr_2(attr);
+	int ret;
+
+	ret = kstrtoul(buf, 10, &input_val);
+	if (ret)
+		return ret;
+
+	switch (sensor_attr->nr)  {
+	case 0: /* enable, disable */
+		aspeed_set_tacho_en(aspeed_pwm_tacho, sensor_attr->index,
+				    input_val);
+		break;
+
+	case 1: /* tacho source PWMA~H - 0~7 */
+		aspeed_set_tacho_source(aspeed_pwm_tacho, sensor_attr->index,
+					input_val);
+		break;
+
+	case 4: /* alarm_en */
+		aspeed_set_tacho_alarm_en(aspeed_pwm_tacho, sensor_attr->index,
+					  input_val);
+		break;
+
+	case 2: /* rpm */
+	case 3: /* alarm */
+	default:
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+/* sysfs attributes */
+
+/* CLK sysfs */
+static SENSOR_DEVICE_ATTR_2
+(clk_en, S_IRUGO | S_IWUSR, aspeed_show_clk, aspeed_store_clk, 0, 0);
+static SENSOR_DEVICE_ATTR_2
+(clk_source, S_IRUGO | S_IWUSR, aspeed_show_clk, aspeed_store_clk, 1, 0);
+
+static struct attribute *clk_attributes[] = {
+	&sensor_dev_attr_clk_source.dev_attr.attr,
+	&sensor_dev_attr_clk_en.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group clk_attribute_groups = {
+	.attrs = clk_attributes,
+};
+
+/* PWM M/N/O Type sysfs
+ * Macro defining SENSOR_DEVICE_ATTR for a pwm sysfs entries.
+ * 0 - show/store unit
+ * 1 - show/store division_l
+ * 2 - show/store division_h
+ */
+#define sysfs_pwm_type(type,  index) \
+static SENSOR_DEVICE_ATTR_2(pwm_type_##type##_unit, S_IRUGO | S_IWUSR, \
+	aspeed_show_pwm_type_clock, aspeed_store_pwm_type_clock, 0, index); \
+static SENSOR_DEVICE_ATTR_2(pwm_type_##type##_division_l, S_IRUGO | S_IWUSR, \
+	aspeed_show_pwm_type_clock, aspeed_store_pwm_type_clock, 1, index); \
+static SENSOR_DEVICE_ATTR_2(pwm_type_##type##_division_h, S_IRUGO | S_IWUSR, \
+	aspeed_show_pwm_type_clock, aspeed_store_pwm_type_clock, 2, index); \
+static SENSOR_DEVICE_ATTR_2(pwm_type_##type##_clk, S_IRUGO, \
+	aspeed_show_pwm_type_clock, NULL, 3, index); \
+static struct attribute *pwm_type_##type##_attributes[] = { \
+	&sensor_dev_attr_pwm_type_##type##_unit.dev_attr.attr, \
+	&sensor_dev_attr_pwm_type_##type##_division_l.dev_attr.attr, \
+	&sensor_dev_attr_pwm_type_##type##_division_h.dev_attr.attr, \
+	&sensor_dev_attr_pwm_type_##type##_clk.dev_attr.attr, \
+	NULL \
+}
+
+/*
+ * Create the needed functions for each pwm using the macro defined above
+ * (4 pwms are supported)
+ */
+sysfs_pwm_type(m, 0);
+sysfs_pwm_type(n, 1);
+#ifdef PWM_TYPE_O
+sysfs_pwm_type(o, 2);
+#endif
+
+static const struct attribute_group pwm_type_attribute_groups[] = {
+	{ .attrs = pwm_type_m_attributes },
+	{ .attrs = pwm_type_n_attributes },
+#ifdef PWM_TYPE_O
+	{ .attrs = pwm_type_o_attributes },
+#endif
+};
+
+/* PWM sysfs
+ * Macro defining SENSOR_DEVICE_ATTR for a pwm sysfs entries.
+ * 0 - show/store enable
+ * 1 - show/store type
+ * 2 - show/store rising
+ * 3 - show/store falling
+ */
+#define sysfs_pwm_speeds_num(index) \
+static SENSOR_DEVICE_ATTR_2(pwm##index##_en, S_IRUGO | S_IWUSR, \
+	aspeed_show_pwm_speed, aspeed_store_pwm_speed, 0, index); \
+static SENSOR_DEVICE_ATTR_2(pwm##index##_type, S_IRUGO | S_IWUSR, \
+	aspeed_show_pwm_speed, aspeed_store_pwm_speed, 1, index); \
+static SENSOR_DEVICE_ATTR_2(pwm##index##_rising, S_IRUGO | S_IWUSR, \
+	aspeed_show_pwm_speed, aspeed_store_pwm_speed, 2, index); \
+static SENSOR_DEVICE_ATTR_2(pwm##index##_falling, S_IRUGO | S_IWUSR, \
+	aspeed_show_pwm_speed, aspeed_store_pwm_speed, 3, index); \
+static struct attribute *pwm##index##_attributes[] = { \
+	&sensor_dev_attr_pwm##index##_en.dev_attr.attr, \
+	&sensor_dev_attr_pwm##index##_type.dev_attr.attr, \
+	&sensor_dev_attr_pwm##index##_rising.dev_attr.attr, \
+	&sensor_dev_attr_pwm##index##_falling.dev_attr.attr, \
+	NULL \
+}
+
+/*
+ * Create the needed functions for each pwm using the macro defined above
+ * (4 pwms are supported)
+ */
+sysfs_pwm_speeds_num(0);
+sysfs_pwm_speeds_num(1);
+sysfs_pwm_speeds_num(2);
+sysfs_pwm_speeds_num(3);
+sysfs_pwm_speeds_num(4);
+sysfs_pwm_speeds_num(5);
+sysfs_pwm_speeds_num(6);
+sysfs_pwm_speeds_num(7);
+
+static const struct attribute_group pwm_attribute_groups[] = {
+	{ .attrs = pwm0_attributes },
+	{ .attrs = pwm1_attributes },
+	{ .attrs = pwm2_attributes },
+	{ .attrs = pwm3_attributes },
+	{ .attrs = pwm4_attributes },
+	{ .attrs = pwm5_attributes },
+	{ .attrs = pwm6_attributes },
+	{ .attrs = pwm7_attributes },
+};
+
+/* Fan M/N/O Type sysfs
+ * Macro defining SENSOR_DEVICE_ATTR for a pwm sysfs entries.
+ *  0 - show/store enable
+ *  1 - show/store mode
+ *  2 - show/store unit
+ *  3 - show/store division
+ *  4 - show/store limit
+ */
+#define sysfs_tacho_type(type, index) \
+static SENSOR_DEVICE_ATTR_2(tacho_type_##type##_en, S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_type, aspeed_store_tacho_type, 0, index); \
+static SENSOR_DEVICE_ATTR_2(tacho_type_##type##_mode, S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_type, aspeed_store_tacho_type, 1, index); \
+static SENSOR_DEVICE_ATTR_2(tacho_type_##type##_unit, S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_type, aspeed_store_tacho_type, 2, index); \
+static SENSOR_DEVICE_ATTR_2(tacho_type_##type##_division, S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_type, aspeed_store_tacho_type, 3, index); \
+static SENSOR_DEVICE_ATTR_2(tacho_type_##type##_limit, S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_type, aspeed_store_tacho_type, 4, index); \
+static SENSOR_DEVICE_ATTR_2(tacho_type_##type##_measure_period, \
+	S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_type, aspeed_store_tacho_type, 5, index); \
+static struct attribute *tacho_type_##type##_attributes[] = { \
+	&sensor_dev_attr_tacho_type_##type##_en.dev_attr.attr, \
+	&sensor_dev_attr_tacho_type_##type##_mode.dev_attr.attr, \
+	&sensor_dev_attr_tacho_type_##type##_unit.dev_attr.attr, \
+	&sensor_dev_attr_tacho_type_##type##_division.dev_attr.attr, \
+	&sensor_dev_attr_tacho_type_##type##_limit.dev_attr.attr, \
+	&sensor_dev_attr_tacho_type_##type##_measure_period.dev_attr.attr, \
+	NULL \
+}
+
+/*
+ * Create the needed functions for each pwm using the macro defined above
+ * (4 pwms are supported)
+ */
+sysfs_tacho_type(m, 0);
+sysfs_tacho_type(n, 1);
+#ifdef PWM_TYPE_O
+sysfs_tacho_type(o, 2);
+#endif
+
+static const struct attribute_group tacho_type_attribute_groups[] = {
+	{ .attrs = tacho_type_m_attributes },
+	{ .attrs = tacho_type_n_attributes },
+#ifdef PWM_TYPE_O
+	{ .attrs = tacho_type_o_attributes },
+#endif
+};
+
+/* FAN sysfs
+ * Macro defining SENSOR_DEVICE_ATTR for a tacho sysfs entries.
+ *  - show/store enable
+ *  - show/store source
+ *  - show/store rpm
+ *  - show/store alarm
+ *  - show/store alarm_en
+ */
+#define sysfs_tacho_speeds_num(index) \
+static SENSOR_DEVICE_ATTR_2(tacho##index##_en, S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_speed, aspeed_store_tacho_speed, 0, index); \
+static SENSOR_DEVICE_ATTR_2(tacho##index##_source, S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_speed, aspeed_store_tacho_speed, 1, index); \
+static SENSOR_DEVICE_ATTR_2(tacho##index##_rpm, S_IRUGO, \
+	aspeed_show_tacho_speed, NULL, 2, index); \
+static SENSOR_DEVICE_ATTR_2(tacho##index##_alarm, S_IRUGO, \
+	aspeed_show_tacho_speed, aspeed_store_tacho_speed, 3, index); \
+static SENSOR_DEVICE_ATTR_2(tacho##index##_alarm_en, S_IRUGO | S_IWUSR, \
+	aspeed_show_tacho_speed, aspeed_store_tacho_speed, 4, index); \
+static struct attribute *tacho##index##_attributes[] = { \
+	&sensor_dev_attr_tacho##index##_en.dev_attr.attr, \
+	&sensor_dev_attr_tacho##index##_source.dev_attr.attr, \
+	&sensor_dev_attr_tacho##index##_rpm.dev_attr.attr, \
+	&sensor_dev_attr_tacho##index##_alarm.dev_attr.attr, \
+	&sensor_dev_attr_tacho##index##_alarm_en.dev_attr.attr, \
+	NULL \
+}
+
+/*
+ * Create the needed functions for each tacho using the macro defined above
+ * (4 tachos are supported)
+ */
+sysfs_tacho_speeds_num(0);
+sysfs_tacho_speeds_num(1);
+sysfs_tacho_speeds_num(2);
+sysfs_tacho_speeds_num(3);
+sysfs_tacho_speeds_num(4);
+sysfs_tacho_speeds_num(5);
+sysfs_tacho_speeds_num(6);
+sysfs_tacho_speeds_num(7);
+sysfs_tacho_speeds_num(8);
+sysfs_tacho_speeds_num(9);
+sysfs_tacho_speeds_num(10);
+sysfs_tacho_speeds_num(11);
+sysfs_tacho_speeds_num(12);
+sysfs_tacho_speeds_num(13);
+sysfs_tacho_speeds_num(14);
+sysfs_tacho_speeds_num(15);
+
+static const struct attribute_group tacho_attribute_groups[] = {
+	{ .attrs = tacho0_attributes },
+	{ .attrs = tacho1_attributes },
+	{ .attrs = tacho2_attributes },
+	{ .attrs = tacho3_attributes },
+	{ .attrs = tacho4_attributes },
+	{ .attrs = tacho5_attributes },
+	{ .attrs = tacho6_attributes },
+	{ .attrs = tacho7_attributes },
+	{ .attrs = tacho8_attributes },
+	{ .attrs = tacho9_attributes },
+	{ .attrs = tacho10_attributes },
+	{ .attrs = tacho11_attributes },
+	{ .attrs = tacho12_attributes },
+	{ .attrs = tacho13_attributes },
+	{ .attrs = tacho14_attributes },
+	{ .attrs = tacho15_attributes },
+};
+
+static int
+aspeed_pwm_tacho_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int i, j = 0;
+	int err = 0;
+
+	if (!of_device_is_compatible(pdev->dev.of_node,
+				     "aspeed,aspeed2500-pwm-fan"))
+		return -ENODEV;
+
+	aspeed_pwm_tacho = devm_kzalloc(&pdev->dev, sizeof(*aspeed_pwm_tacho),
+					GFP_KERNEL);
+	if (!aspeed_pwm_tacho) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	aspeed_pwm_tacho->aspeed_pwm_data = dev_get_platdata(&pdev->dev);
+	platform_set_drvdata(pdev, aspeed_pwm_tacho);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "cannot get IORESOURCE_MEM\n");
+		err = -ENOENT;
+		goto out;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res), res->name)) {
+		dev_err(&pdev->dev, "cannot reserved region\n");
+		err = -ENXIO;
+		goto out;
+	}
+
+	aspeed_pwm_tacho->reg_base = ioremap(res->start, resource_size(res));
+	if (!aspeed_pwm_tacho->reg_base) {
+		err = -EIO;
+		goto out_region;
+	}
+
+	aspeed_pwm_tacho->irq = platform_get_irq(pdev, 0);
+	if (aspeed_pwm_tacho->irq < 0) {
+		dev_err(&pdev->dev, "no irq specified\n");
+		err = -ENOENT;
+		goto out_region;
+	}
+
+	aspeed_pwm_tacho->groups[j++] = &clk_attribute_groups;
+
+	for (i = 0; i < PWM_CH_NUM; i++)
+		aspeed_pwm_tacho->groups[j++] = &pwm_attribute_groups[i];
+
+	for (i = 0; i < PWM_TYPE_NUM; i++)
+		aspeed_pwm_tacho->groups[j++] = &pwm_type_attribute_groups[i];
+
+	for (i = 0; i < TACHO_NUM; i++)
+		aspeed_pwm_tacho->groups[j++] =  &tacho_attribute_groups[i];
+
+	for (i = 0; i < PWM_TYPE_NUM; i++)
+		aspeed_pwm_tacho->groups[j++] =
+					&tacho_type_attribute_groups[i];
+
+	aspeed_pwm_tacho->groups[j] = NULL;
+
+	aspeed_pwm_tacho->hwmon_dev = devm_hwmon_device_register_with_groups(
+						&pdev->dev, "aspeed_pwm_tacho",
+						aspeed_pwm_tacho,
+						aspeed_pwm_tacho->groups);
+
+	if (PTR_ERR_OR_ZERO(aspeed_pwm_tacho->hwmon_dev)) {
+		dev_err(&pdev->dev, "cannot register hwmon\n");
+		err = PTR_ERR(aspeed_pwm_tacho->hwmon_dev);
+		goto out_region;
+	}
+
+	/* SCU Pin-MUX PWM & TACHO */
+	aspeed_scu_multi_func_reset(AST_SCU_FUN_PIN_CTRL3, 0xcfffff, 0xc000ff);
+
+	/* SCU PWM CTRL Reset */
+	aspeed_toggle_scu_reset(SCU_RESET_PWM, 3);
+
+	aspeed_pwm_taco_init();
+
+	dev_info(&pdev->dev, "aspeed pwm tacho: driver successfully loaded.\n");
+
+	return 0;
+
+out_region:
+	release_mem_region(res->start, res->end - res->start + 1);
+out:
+	dev_err(&pdev->dev, "aspeed pwm tacho: driver init failed (err=%d)!\n",
+		err);
+	return err;
+}
+
+static int aspeed_pwm_tacho_remove(struct platform_device *pdev)
+{
+	struct aspeed_pwm_tacho_data *aspeed_pwm_tacho =
+						platform_get_drvdata(pdev);
+	struct resource *res;
+
+	platform_set_drvdata(pdev, NULL);
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	iounmap(aspeed_pwm_tacho->reg_base);
+	release_mem_region(res->start, res->end - res->start + 1);
+	dev_info(&pdev->dev, "aspeed_pwm_tacho: driver unloaded.\n");
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int
+aspeed_pwm_tacho_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	dev_info(&pdev->dev, "aspeed_pwm_tacho_suspend\n");
+
+	return 0;
+}
+
+static int
+aspeed_pwm_tacho_resume(struct platform_device *pdev)
+{
+	aspeed_pwm_taco_init();
+	return 0;
+}
+
+#else
+#define aspeed_pwm_tacho_suspend        NULL
+#define aspeed_pwm_tacho_resume         NULL
+#endif
+
+static const struct of_device_id aspeed_pwm_fan_of_table[] = {
+	{ .compatible = "aspeed,aspeed2500-pwm-fan", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_pwm_fan_of_table);
+
+static struct platform_driver aspeed_pwm_tacho_driver = {
+	.remove		= aspeed_pwm_tacho_remove,
+	.suspend	= aspeed_pwm_tacho_suspend,
+	.resume		= aspeed_pwm_tacho_resume,
+	.driver		= {
+		.name   = KBUILD_MODNAME,
+		.owner  = THIS_MODULE,
+		.of_match_table = aspeed_pwm_fan_of_table,
+	},
+};
+module_platform_driver_probe(aspeed_pwm_tacho_driver, aspeed_pwm_tacho_probe);
+
+MODULE_AUTHOR("Ryan Chen <ryan_chen at aspeedtech.com>");
+MODULE_DESCRIPTION("ASPEED PWM TACHO driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:aspeed-pwm-fan");
-- 
2.1.4



More information about the openbmc mailing list