[PATCH linux v3 2/3] drivers: hwmon: Hwmon driver for ASPEED AST2400/2500 PWM support

Jaghathiswari Rankappagounder Natarajan jaghu at google.com
Thu Nov 24 20:26:49 AEDT 2016


The ASPEED AST2400/2500 PWM controller supports 8 PWM output ports.
PWM clock types M, N and 0 are three types just to have three independent PWM
sources. Each port can be assigned a different PWM clock type.
The device driver matches on the device tree node. The configuration values
are read from the device tree and written to the respective registers.
The driver provides a sysfs entry "pwm" through which the user can
configure the duty-cycle value (ranging from 0 to 100 percent).

Signed-off-by: Jaghathiswari Rankappagounder Natarajan <jaghu at google.com>
---
v2:
- Merged the drivers for PWM controller and PWM device as one PWM driver.

v3:
- Mentioned that this driver is supported for AST2400 and AST2500.
- Provided explanation of why this is hwmon driver.
- Entered changelog in the correct place.
- Removed name at the top of the file.
- Corrected style of multi-line comments.
- Added GPL version 2 or later.
- Changed prefix as "aspeed".
- Changed val to bool as mentioned in comment.
- Removed switch statements and added a representation that would allow
-  to look up the values without requiring a switch statement.
- Changed some variable names.
- Added enum for pwm_port and pwm_clock_type.
- Replaced hwmon name as "aspeed_pwm".
- Removed struct ast_pwm_controller_data allocation in the probe function.
- Used struct clk and related logic.
- Removed remove function.
- Moved header file content to c file.
- Removed some duplicate definitions not needed.
- Used BIT(x).
- Removed guarding () for integers in #define statements. Retained guarding
-  for complex calculations; ow getting error when running checkpatch.pl script.
- Added documentation for hwmon driver.

 Documentation/hwmon/aspeed-pwm |  18 ++
 drivers/hwmon/Kconfig          |  13 +
 drivers/hwmon/Makefile         |   3 +-
 drivers/hwmon/aspeed-pwm.c     | 660 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 692 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/hwmon/aspeed-pwm
 create mode 100644 drivers/hwmon/aspeed-pwm.c

diff --git a/Documentation/hwmon/aspeed-pwm b/Documentation/hwmon/aspeed-pwm
new file mode 100644
index 0000000..f1affe0
--- /dev/null
+++ b/Documentation/hwmon/aspeed-pwm
@@ -0,0 +1,18 @@
+Kernel driver aspeed-pwm
+========================
+
+Supported chips:
+	ASPEED AST2400/2500
+
+Authors:
+	<jaghu at google.com>
+
+Description:
+------------
+The ASPEED AST2400/2500 PWM controller supports 8 PWM output ports.
+PWM clock types M, N and 0 are three types just to have three independent PWM
+sources. Each port can be assigned a different PWM clock type.
+The device driver matches on the device tree node. The configuration values
+are read from the device tree and written to the respective registers.
+The driver provides a sysfs entry "pwm1" through which the user can
+configure the duty-cycle value (ranging from 0 to 255; 255 is 100 percent).
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index d4de8d5..7f75b01 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -341,6 +341,19 @@ config SENSORS_ASB100
 	  This driver can also be built as a module.  If so, the module
 	  will be called asb100.

+config SENSORS_ASPEED_PWM
+	tristate "ASPEED AST2400/AST2500 PWM driver"
+	help
+	  This driver provides support for ASPEED AST2400/AST2500 PWM
+	  controller and output ports. The ASPEED PWM controller can support 8
+	  PWM outputs. PWM clock types M, N and 0 are three types just to have three
+	  independent PWM sources. Each could be assigned to the 8 PWM port
+	  with its own settings. The user can configure duty cycle through
+	  the exposed hwmon sysfs entry "pwm".
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called aspeed-pwm.
+
 config SENSORS_ATXP1
 	tristate "Attansic ATXP1 VID controller"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 4455478..f39c057 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SENSORS_ADT7475)	+= adt7475.o
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_ARM_SCPI)	+= scpi-hwmon.o
 obj-$(CONFIG_SENSORS_ASC7621)	+= asc7621.o
+obj-$(CONFIG_SENSORS_ASPEED_PWM)	+= aspeed-pwm.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
@@ -164,8 +165,6 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
 obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
 obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
-
 obj-$(CONFIG_PMBUS)		+= pmbus/

 ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
-
diff --git a/drivers/hwmon/aspeed-pwm.c b/drivers/hwmon/aspeed-pwm.c
new file mode 100644
index 0000000..1c96bbe
--- /dev/null
+++ b/drivers/hwmon/aspeed-pwm.c
@@ -0,0 +1,660 @@
+/*
+ * Copyright (c) 2016 Google, 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/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>
+
+/* ASPEED PWM & FAN Register Definition */
+#define ASPEED_PTCR_CTRL		0x00
+#define ASPEED_PTCR_CLK_CTRL		0x04
+#define ASPEED_PTCR_DUTY0_CTRL		0x08
+#define ASPEED_PTCR_DUTY1_CTRL		0x0c
+#define ASPEED_PTCR_CTRL_EXT		0x40
+#define ASPEED_PTCR_CLK_CTRL_EXT	0x44
+#define ASPEED_PTCR_DUTY2_CTRL		0x48
+#define ASPEED_PTCR_DUTY3_CTRL		0x4c
+
+#define DUTY_CTRL_PWM2_FALL_POINT	24
+#define DUTY_CTRL_PWM2_RISE_POINT	16
+#define DUTY_CTRL_PWM1_FALL_POINT	8
+#define DUTY_CTRL_PWM1_RISE_POINT	0
+
+/* ASPEED_PTCR_CTRL : 0x00 - General Control Register */
+#define ASPEED_PTCR_CTRL_SET_PWMD_TYPE_PART1	15
+#define ASPEED_PTCR_CTRL_SET_PWMD_TYPE_PART2	6
+#define ASPEED_PTCR_CTRL_SET_PWMD_TYPE_MASK	(BIT(7) | BIT(15))
+
+#define ASPEED_PTCR_CTRL_SET_PWMC_TYPE_PART1	14
+#define ASPEED_PTCR_CTRL_SET_PWMC_TYPE_PART2	5
+#define ASPEED_PTCR_CTRL_SET_PWMC_TYPE_MASK	(BIT(6) | BIT(14))
+
+#define ASPEED_PTCR_CTRL_SET_PWMB_TYPE_PART1	13
+#define ASPEED_PTCR_CTRL_SET_PWMB_TYPE_PART2	4
+#define ASPEED_PTCR_CTRL_SET_PWMB_TYPE_MASK	(BIT(5) | BIT(13))
+
+#define ASPEED_PTCR_CTRL_SET_PWMA_TYPE_PART1	12
+#define ASPEED_PTCR_CTRL_SET_PWMA_TYPE_PART2	3
+#define ASPEED_PTCR_CTRL_SET_PWMA_TYPE_MASK	(BIT(4) | BIT(12))
+
+#define	ASPEED_PTCR_CTRL_PWMD_EN	(0x1 << 11)
+#define	ASPEED_PTCR_CTRL_PWMC_EN	(0x1 << 10)
+#define	ASPEED_PTCR_CTRL_PWMB_EN	(0x1 << 9)
+#define	ASPEED_PTCR_CTRL_PWMA_EN	(0x1 << 8)
+
+/*0:24Mhz, 1:MCLK */
+#define	ASPEED_PTCR_CTRL_CLK_SRC	0x2
+#define	ASPEED_PTCR_CTRL_CLK_EN		0x1
+
+/* ASPEED_PTCR_CLK_CTRL : 0x04 - Clock Control Register */
+/* TYPE N */
+#define ASPEED_PTCR_CLK_CTRL_TYPEN_UNIT		24
+#define ASPEED_PTCR_CLK_CTRL_TYPEN_UNIT_MASK	(0xff << 24)
+#define ASPEED_PTCR_CLK_CTRL_TYPEN_H		20
+#define ASPEED_PTCR_CLK_CTRL_TYPEN_H_MASK	(0xf << 20)
+#define ASPEED_PTCR_CLK_CTRL_TYPEN_L		16
+#define ASPEED_PTCR_CLK_CTRL_TYPEN_L_MASK	(0xf << 16)
+/* TYPE M */
+#define ASPEED_PTCR_CLK_CTRL_TYPEM_UNIT		8
+#define ASPEED_PTCR_CLK_CTRL_TYPEM_UNIT_MASK	(0xff << 8)
+#define ASPEED_PTCR_CLK_CTRL_TYPEM_H		4
+#define ASPEED_PTCR_CLK_CTRL_TYPEM_H_MASK	(0xf << 4)
+#define ASPEED_PTCR_CLK_CTRL_TYPEM_L		0
+#define ASPEED_PTCR_CLK_CTRL_TYPEM_L_MASK	0xf
+
+/* ASPEED_PTCR_CTRL_EXT : 0x40 - General Control Extension #1 Register */
+#define ASPEED_PTCR_CTRL_SET_PWMH_TYPE_PART1	15
+#define ASPEED_PTCR_CTRL_SET_PWMH_TYPE_PART2	6
+#define ASPEED_PTCR_CTRL_SET_PWMH_TYPE_MASK	(BIT(7) | BIT(15))
+
+#define ASPEED_PTCR_CTRL_SET_PWMG_TYPE_PART1	14
+#define ASPEED_PTCR_CTRL_SET_PWMG_TYPE_PART2	5
+#define ASPEED_PTCR_CTRL_SET_PWMG_TYPE_MASK	(BIT(6) | BIT(14))
+
+
+#define ASPEED_PTCR_CTRL_SET_PWMF_TYPE_PART1	13
+#define ASPEED_PTCR_CTRL_SET_PWMF_TYPE_PART2	4
+#define ASPEED_PTCR_CTRL_SET_PWMF_TYPE_MASK	(BIT(5) | BIT(13))
+
+#define ASPEED_PTCR_CTRL_SET_PWME_TYPE_PART1	12
+#define ASPEED_PTCR_CTRL_SET_PWME_TYPE_PART2	3
+#define ASPEED_PTCR_CTRL_SET_PWME_TYPE_MASK	(BIT(4) | BIT(12))
+
+#define	ASPEED_PTCR_CTRL_PWMH_EN	(0x1 << 11)
+#define	ASPEED_PTCR_CTRL_PWMG_EN	(0x1 << 10)
+#define	ASPEED_PTCR_CTRL_PWMF_EN	(0x1 << 9)
+#define	ASPEED_PTCR_CTRL_PWME_EN	(0x1 << 8)
+
+/* ASPEED_PTCR_CLK_EXT_CTRL : 0x44 - Clock Control Extension #1 Register */
+/* TYPE O */
+#define ASPEED_PTCR_CLK_CTRL_TYPEO_UNIT		8
+#define ASPEED_PTCR_CLK_CTRL_TYPEO_UNIT_MASK	(0xff << 8)
+#define ASPEED_PTCR_CLK_CTRL_TYPEO_H		4
+#define ASPEED_PTCR_CLK_CTRL_TYPEO_H_MASK	(0xf << 4)
+#define ASPEED_PTCR_CLK_CTRL_TYPEO_L		0
+#define ASPEED_PTCR_CLK_CTRL_TYPEO_L_MASK	0xf
+
+#define PWM_MAX 255
+
+#define MAX_HIGH_LOW_BIT 15
+
+struct aspeed_pwm_port_data {
+	u8 pwm_port;
+	u8 pwm_enable;
+	u8 pwm_type;
+	u8 pwm_fan_ctrl;
+	void __iomem *base;
+};
+
+enum pwm_clock_type { TYPEM, TYPEN, TYPEO };
+
+struct pwm_clock_type_params {
+	u32 l_value;
+	u32 l_mask;
+	u32 h_value;
+	u32 h_mask;
+	u32 unit_value;
+	u32 unit_mask;
+	u32 ctrl_reg_offset;
+};
+
+static const struct pwm_clock_type_params pwm_clock_type_params[] = {
+	[TYPEM] = {
+		.l_value = ASPEED_PTCR_CLK_CTRL_TYPEM_L,
+		.l_mask = ASPEED_PTCR_CLK_CTRL_TYPEM_L_MASK,
+		.h_value = ASPEED_PTCR_CLK_CTRL_TYPEM_H,
+		.h_mask = ASPEED_PTCR_CLK_CTRL_TYPEM_H_MASK,
+		.unit_value = ASPEED_PTCR_CLK_CTRL_TYPEM_UNIT,
+		.unit_mask = ASPEED_PTCR_CLK_CTRL_TYPEM_UNIT_MASK,
+		.ctrl_reg_offset = ASPEED_PTCR_CLK_CTRL,
+	},
+	[TYPEN] = {
+		.l_value = ASPEED_PTCR_CLK_CTRL_TYPEN_L,
+		.l_mask = ASPEED_PTCR_CLK_CTRL_TYPEN_L_MASK,
+		.h_value = ASPEED_PTCR_CLK_CTRL_TYPEN_H,
+		.h_mask = ASPEED_PTCR_CLK_CTRL_TYPEN_H_MASK,
+		.unit_value = ASPEED_PTCR_CLK_CTRL_TYPEN_UNIT,
+		.unit_mask = ASPEED_PTCR_CLK_CTRL_TYPEN_UNIT_MASK,
+		.ctrl_reg_offset = ASPEED_PTCR_CLK_CTRL,
+	},
+	[TYPEO] = {
+		.l_value = ASPEED_PTCR_CLK_CTRL_TYPEO_L,
+		.l_mask = ASPEED_PTCR_CLK_CTRL_TYPEO_L_MASK,
+		.h_value = ASPEED_PTCR_CLK_CTRL_TYPEO_H,
+		.h_mask = ASPEED_PTCR_CLK_CTRL_TYPEO_H_MASK,
+		.unit_value = ASPEED_PTCR_CLK_CTRL_TYPEO_UNIT,
+		.unit_mask = ASPEED_PTCR_CLK_CTRL_TYPEO_UNIT_MASK,
+		.ctrl_reg_offset = ASPEED_PTCR_CLK_CTRL_EXT,
+	}
+};
+
+enum pwm_port { PWMA, PWMB, PWMC, PWMD, PWME, PWMF, PWMG, PWMH };
+
+struct pwm_port_params {
+	u32 pwm_en;
+	u32 ctrl_reg_offset;
+	u32 pwm_type_part1;
+	u32 pwm_type_part2;
+	u32 pwm_type_mask;
+	u32 duty_ctrl_rise_point;
+	u32 duty_ctrl_fall_point;
+	u32 duty_ctrl_reg_offset;
+	u8 duty_ctrl_calc_type;
+};
+
+static const struct pwm_port_params pwm_port_params[] = {
+	[PWMA] = {
+		.pwm_en = ASPEED_PTCR_CTRL_PWMA_EN,
+		.ctrl_reg_offset = ASPEED_PTCR_CTRL,
+		.pwm_type_part1 = ASPEED_PTCR_CTRL_SET_PWMA_TYPE_PART1,
+		.pwm_type_part2 = ASPEED_PTCR_CTRL_SET_PWMA_TYPE_PART2,
+		.pwm_type_mask = ASPEED_PTCR_CTRL_SET_PWMA_TYPE_MASK,
+		.duty_ctrl_rise_point = DUTY_CTRL_PWM1_RISE_POINT,
+		.duty_ctrl_fall_point = DUTY_CTRL_PWM1_FALL_POINT,
+		.duty_ctrl_reg_offset = ASPEED_PTCR_DUTY0_CTRL,
+		.duty_ctrl_calc_type = 0,
+	},
+	[PWMB] = {
+		.pwm_en = ASPEED_PTCR_CTRL_PWMB_EN,
+		.ctrl_reg_offset = ASPEED_PTCR_CTRL,
+		.pwm_type_part1 = ASPEED_PTCR_CTRL_SET_PWMB_TYPE_PART1,
+		.pwm_type_part2 = ASPEED_PTCR_CTRL_SET_PWMB_TYPE_PART2,
+		.pwm_type_mask = ASPEED_PTCR_CTRL_SET_PWMB_TYPE_MASK,
+		.duty_ctrl_rise_point = DUTY_CTRL_PWM2_RISE_POINT,
+		.duty_ctrl_fall_point = DUTY_CTRL_PWM2_FALL_POINT,
+		.duty_ctrl_reg_offset = ASPEED_PTCR_DUTY0_CTRL,
+		.duty_ctrl_calc_type = 1,
+	},
+	[PWMC] = {
+		.pwm_en = ASPEED_PTCR_CTRL_PWMC_EN,
+		.ctrl_reg_offset = ASPEED_PTCR_CTRL,
+		.pwm_type_part1 = ASPEED_PTCR_CTRL_SET_PWMC_TYPE_PART1,
+		.pwm_type_part2 = ASPEED_PTCR_CTRL_SET_PWMC_TYPE_PART2,
+		.pwm_type_mask = ASPEED_PTCR_CTRL_SET_PWMC_TYPE_MASK,
+		.duty_ctrl_rise_point = DUTY_CTRL_PWM1_RISE_POINT,
+		.duty_ctrl_fall_point = DUTY_CTRL_PWM1_FALL_POINT,
+		.duty_ctrl_reg_offset = ASPEED_PTCR_DUTY1_CTRL,
+		.duty_ctrl_calc_type = 0,
+	},
+	[PWMD] = {
+		.pwm_en = ASPEED_PTCR_CTRL_PWMD_EN,
+		.ctrl_reg_offset = ASPEED_PTCR_CTRL,
+		.pwm_type_part1 = ASPEED_PTCR_CTRL_SET_PWMD_TYPE_PART1,
+		.pwm_type_part2 = ASPEED_PTCR_CTRL_SET_PWMD_TYPE_PART2,
+		.pwm_type_mask = ASPEED_PTCR_CTRL_SET_PWMD_TYPE_MASK,
+		.duty_ctrl_rise_point = DUTY_CTRL_PWM2_RISE_POINT,
+		.duty_ctrl_fall_point = DUTY_CTRL_PWM2_FALL_POINT,
+		.duty_ctrl_reg_offset = ASPEED_PTCR_DUTY1_CTRL,
+		.duty_ctrl_calc_type = 1,
+	},
+	[PWME] = {
+		.pwm_en = ASPEED_PTCR_CTRL_PWME_EN,
+		.ctrl_reg_offset = ASPEED_PTCR_CTRL_EXT,
+		.pwm_type_part1 = ASPEED_PTCR_CTRL_SET_PWME_TYPE_PART1,
+		.pwm_type_part2 = ASPEED_PTCR_CTRL_SET_PWME_TYPE_PART2,
+		.pwm_type_mask = ASPEED_PTCR_CTRL_SET_PWME_TYPE_MASK,
+		.duty_ctrl_rise_point = DUTY_CTRL_PWM1_RISE_POINT,
+		.duty_ctrl_fall_point = DUTY_CTRL_PWM1_FALL_POINT,
+		.duty_ctrl_reg_offset = ASPEED_PTCR_DUTY2_CTRL,
+		.duty_ctrl_calc_type = 0,
+	},
+	[PWMF] = {
+		.pwm_en = ASPEED_PTCR_CTRL_PWMF_EN,
+		.ctrl_reg_offset = ASPEED_PTCR_CTRL_EXT,
+		.pwm_type_part1 = ASPEED_PTCR_CTRL_SET_PWMF_TYPE_PART1,
+		.pwm_type_part2 = ASPEED_PTCR_CTRL_SET_PWMF_TYPE_PART2,
+		.pwm_type_mask = ASPEED_PTCR_CTRL_SET_PWMF_TYPE_MASK,
+		.duty_ctrl_rise_point = DUTY_CTRL_PWM2_RISE_POINT,
+		.duty_ctrl_fall_point = DUTY_CTRL_PWM2_FALL_POINT,
+		.duty_ctrl_reg_offset = ASPEED_PTCR_DUTY2_CTRL,
+		.duty_ctrl_calc_type = 1,
+	},
+	[PWMG] = {
+		.pwm_en = ASPEED_PTCR_CTRL_PWMG_EN,
+		.ctrl_reg_offset = ASPEED_PTCR_CTRL_EXT,
+		.pwm_type_part1 = ASPEED_PTCR_CTRL_SET_PWMG_TYPE_PART1,
+		.pwm_type_part2 = ASPEED_PTCR_CTRL_SET_PWMG_TYPE_PART2,
+		.pwm_type_mask = ASPEED_PTCR_CTRL_SET_PWMG_TYPE_MASK,
+		.duty_ctrl_rise_point = DUTY_CTRL_PWM1_RISE_POINT,
+		.duty_ctrl_fall_point = DUTY_CTRL_PWM1_FALL_POINT,
+		.duty_ctrl_reg_offset = ASPEED_PTCR_DUTY3_CTRL,
+		.duty_ctrl_calc_type = 0,
+	},
+	[PWMH] = {
+		.pwm_en = ASPEED_PTCR_CTRL_PWMH_EN,
+		.ctrl_reg_offset = ASPEED_PTCR_CTRL_EXT,
+		.pwm_type_part1 = ASPEED_PTCR_CTRL_SET_PWMH_TYPE_PART1,
+		.pwm_type_part2 = ASPEED_PTCR_CTRL_SET_PWMH_TYPE_PART2,
+		.pwm_type_mask = ASPEED_PTCR_CTRL_SET_PWMH_TYPE_MASK,
+		.duty_ctrl_rise_point = DUTY_CTRL_PWM2_RISE_POINT,
+		.duty_ctrl_fall_point = DUTY_CTRL_PWM2_FALL_POINT,
+		.duty_ctrl_reg_offset = ASPEED_PTCR_DUTY3_CTRL,
+		.duty_ctrl_calc_type = 1,
+	}
+};
+
+static inline void
+aspeed_pwm_write(void __iomem *base, u32 val, u32 reg)
+{
+	writel(val, base + reg);
+}
+
+static inline u32
+aspeed_pwm_read(void __iomem *base, u32 reg)
+{
+	u32 val = readl(base + reg);
+	return val;
+}
+
+static void
+aspeed_set_pwm_clock_enable(void __iomem *base, bool val)
+{
+	u32 reg_value = aspeed_pwm_read(base, ASPEED_PTCR_CTRL);
+
+	if (val)
+		reg_value |= ASPEED_PTCR_CTRL_CLK_EN;
+	else
+		reg_value &= ~ASPEED_PTCR_CTRL_CLK_EN;
+
+	aspeed_pwm_write(base, reg_value, ASPEED_PTCR_CTRL);
+}
+
+static void
+aspeed_set_pwm_clock_source(void __iomem *base, bool val)
+{
+	u32 reg_value = aspeed_pwm_read(base, ASPEED_PTCR_CTRL);
+
+	if (val)
+		reg_value |= ASPEED_PTCR_CTRL_CLK_SRC;
+	else
+		reg_value &= ~ASPEED_PTCR_CTRL_CLK_SRC;
+
+	aspeed_pwm_write(base, reg_value, ASPEED_PTCR_CTRL);
+}
+
+static void
+aspeed_set_pwm_clock_division_h(void __iomem *base, u8 pwm_clock_type,
+		u8 div_high)
+{
+	u32 reg_offset = pwm_clock_type_params[pwm_clock_type].ctrl_reg_offset;
+	u32 reg_value = aspeed_pwm_read(base, reg_offset);
+
+	reg_value &= ~pwm_clock_type_params[pwm_clock_type].h_mask;
+	reg_value |= div_high << pwm_clock_type_params[pwm_clock_type].h_value;
+
+	aspeed_pwm_write(base, reg_value, reg_offset);
+}
+
+static void
+aspeed_set_pwm_clock_division_l(void __iomem *base, u8 pwm_clock_type,
+		u8 div_low)
+{
+	u32 reg_offset = pwm_clock_type_params[pwm_clock_type].ctrl_reg_offset;
+	u32 reg_value = aspeed_pwm_read(base, reg_offset);
+
+	reg_value &= ~pwm_clock_type_params[pwm_clock_type].l_mask;
+	reg_value |= div_low << pwm_clock_type_params[pwm_clock_type].l_value;
+
+	aspeed_pwm_write(base, reg_value, reg_offset);
+}
+
+static void
+aspeed_set_pwm_clock_unit(void __iomem *base, u8 pwm_clock_type,
+		u8 unit)
+{
+	u32 reg_offset = pwm_clock_type_params[pwm_clock_type].ctrl_reg_offset;
+	u32 reg_value = aspeed_pwm_read(base, reg_offset);
+
+	reg_value &= ~pwm_clock_type_params[pwm_clock_type].unit_mask;
+	reg_value |= unit << pwm_clock_type_params[pwm_clock_type].unit_value;
+
+	aspeed_pwm_write(base, reg_value, reg_offset);
+}
+
+static void
+aspeed_set_pwm_enable(struct aspeed_pwm_port_data *priv, bool enable)
+{
+	u8 pwm_port = priv->pwm_port;
+
+	u32 reg_offset = pwm_port_params[pwm_port].ctrl_reg_offset;
+	u32 reg_value = aspeed_pwm_read(priv->base, reg_offset);
+
+	if (enable)
+		reg_value |= pwm_port_params[pwm_port].pwm_en;
+	else
+		reg_value &= ~pwm_port_params[pwm_port].pwm_en;
+
+	aspeed_pwm_write(priv->base, reg_value, reg_offset);
+}
+
+static void
+aspeed_set_pwm_type(struct aspeed_pwm_port_data *priv, u8 type)
+{
+	u8 pwm_port = priv->pwm_port;
+
+	u32 reg_offset = pwm_port_params[pwm_port].ctrl_reg_offset;
+	u32 reg_value = aspeed_pwm_read(priv->base, reg_offset);
+
+	reg_value &= ~pwm_port_params[pwm_port].pwm_type_mask;
+	reg_value |= (type & 0x1) <<
+		     pwm_port_params[pwm_port].pwm_type_part1;
+	reg_value |= (type & 0x2) <<
+		     pwm_port_params[pwm_port].pwm_type_part2;
+
+	aspeed_pwm_write(priv->base, reg_value, reg_offset);
+}
+
+static void
+aspeed_set_pwm_duty_rising(struct aspeed_pwm_port_data *priv, u8 rising)
+{
+	u8 pwm_port = priv->pwm_port;
+
+	u32 reg_offset = pwm_port_params[pwm_port].duty_ctrl_reg_offset;
+	u32 reg_value = aspeed_pwm_read(priv->base, reg_offset);
+
+	reg_value &= ~(0xFF << pwm_port_params[pwm_port].duty_ctrl_rise_point);
+
+	if (pwm_port_params[pwm_port].duty_ctrl_calc_type == 0) {
+		reg_value |= rising;
+	} else if (pwm_port_params[pwm_port].duty_ctrl_calc_type == 1) {
+		reg_value |= (rising <<
+			     pwm_port_params[pwm_port].duty_ctrl_rise_point);
+	}
+
+	aspeed_pwm_write(priv->base, reg_value, reg_offset);
+}
+
+static void
+aspeed_set_pwm_duty_falling(struct aspeed_pwm_port_data *priv, u8 falling)
+{
+	u8 pwm_port = priv->pwm_port;
+
+	u32 reg_offset = pwm_port_params[pwm_port].duty_ctrl_reg_offset;
+	u32 reg_value = aspeed_pwm_read(priv->base, reg_offset);
+
+	reg_value &= ~(0xFF << pwm_port_params[pwm_port].duty_ctrl_fall_point);
+	reg_value |= (falling <<
+		     pwm_port_params[pwm_port].duty_ctrl_fall_point);
+
+	aspeed_pwm_write(priv->base, reg_value, reg_offset);
+}
+
+static u8
+aspeed_get_pwm_clock_unit(struct aspeed_pwm_port_data *priv, u8 pwm_clock_type)
+{
+	u32 reg_offset = pwm_clock_type_params[pwm_clock_type].ctrl_reg_offset;
+	u32 reg_value = aspeed_pwm_read(priv->base, reg_offset);
+	u8 period;
+
+	reg_value &= pwm_clock_type_params[pwm_clock_type].unit_mask;
+	period = reg_value >> pwm_clock_type_params[pwm_clock_type].unit_value;
+
+	return period;
+}
+
+static void
+aspeed_set_pwm_fan_ctrl(struct aspeed_pwm_port_data *priv, u8 fan_ctrl)
+{
+	u16 period;
+	u16 dc_time_on;
+
+	period = aspeed_get_pwm_clock_unit(priv, priv->pwm_type);
+	period += 1;
+	dc_time_on = (fan_ctrl * period) / PWM_MAX;
+
+	if (dc_time_on == 0) {
+		aspeed_set_pwm_enable(priv, 0);
+	} else {
+		if (dc_time_on == period)
+			dc_time_on = 0;
+
+		aspeed_set_pwm_duty_rising(priv, 0);
+		aspeed_set_pwm_duty_falling(priv, dc_time_on);
+		aspeed_set_pwm_enable(priv, 1);
+	}
+}
+
+static ssize_t
+set_pwm(struct device *dev, struct device_attribute *attr, const char *buf,
+		size_t count)
+{
+	struct aspeed_pwm_port_data *priv = dev_get_drvdata(dev);
+	u8 fan_ctrl;
+	int ret;
+
+	ret = kstrtou8(buf, 10, &fan_ctrl);
+	if (ret)
+		return -EINVAL;
+
+	if (fan_ctrl < 0 || fan_ctrl > PWM_MAX)
+		return -EINVAL;
+
+	if (priv->pwm_fan_ctrl == fan_ctrl)
+		return count;
+
+	priv->pwm_fan_ctrl = fan_ctrl;
+	aspeed_set_pwm_fan_ctrl(priv, fan_ctrl);
+
+	return count;
+}
+
+static ssize_t
+show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct aspeed_pwm_port_data *priv = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", priv->pwm_fan_ctrl);
+}
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 0);
+
+static struct attribute *pwm_dev_attrs[] = {
+	&sensor_dev_attr_pwm1.dev_attr.attr,
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(pwm_dev);
+
+static int
+aspeed_create_pwm_port(struct device *dev, struct device_node *child,
+		void __iomem *base)
+{
+	struct device *hwmon;
+	u8 val;
+	struct aspeed_pwm_port_data *priv;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	hwmon = devm_hwmon_device_register_with_groups(dev, "aspeed_pwm",
+						      priv, pwm_dev_groups);
+	if (IS_ERR(hwmon)) {
+		dev_err(dev, "Failed to register hwmon device\n");
+		return PTR_ERR(hwmon);
+	}
+	priv->base = base;
+
+	of_property_read_u8(child, "pwm_port", &val);
+	priv->pwm_port = val;
+
+	of_property_read_u8(child, "pwm_enable", &val);
+	priv->pwm_enable = val;
+	aspeed_set_pwm_enable(priv, val);
+
+	of_property_read_u8(child, "pwm_type", &val);
+	priv->pwm_type = val;
+	aspeed_set_pwm_type(priv, val);
+
+	of_property_read_u8(child, "pwm_fan_ctrl", &val);
+	priv->pwm_fan_ctrl = val;
+	aspeed_set_pwm_fan_ctrl(priv, val);
+
+	return 0;
+}
+
+/*
+ * If the clock type is type M then :
+ * The PWM frequency = 24MHz / (type M clock division L bit *
+ * type M clock division H bit * (type M PWM period bit + 1))
+ * Calculate type M clock division L bit and H bits given the other values
+ */
+static bool
+set_clock_values(u32 pwm_frequency, u32 period, u8 type, void __iomem *base)
+{
+	u32 src_frequency = 24000000;
+	u32 divisor = src_frequency / pwm_frequency;
+	u32 clock_divisor = divisor / (period + 1);
+	u32 tmp_clock_divisor;
+	u8 low;
+	u8 high;
+	u32 calc_high;
+	u32 calc_low;
+
+	for (high = 0; high <= MAX_HIGH_LOW_BIT; high += 1) {
+		for (low = 0; low <= MAX_HIGH_LOW_BIT; low += 1) {
+			calc_high = 0x1 << high;
+			calc_low = (low == 0 ? 1 : (2 * low));
+			tmp_clock_divisor = calc_high * calc_low;
+			if (tmp_clock_divisor >= clock_divisor)
+				goto set_value;
+		}
+	}
+
+	return false;
+
+set_value:
+	aspeed_set_pwm_clock_division_h(base, type, high);
+	aspeed_set_pwm_clock_division_l(base, type, low);
+	aspeed_set_pwm_clock_unit(base, type, period);
+
+	return true;
+}
+
+static int
+aspeed_pwm_probe(struct platform_device *pdev)
+{
+	u32 period;
+	struct device_node *np, *child;
+	struct resource *res;
+	void __iomem *base;
+	bool success;
+	struct clk *typem_clk, *typen_clk, *typeo_clk;
+	u32 pwm_typem_freq, pwm_typen_freq, pwm_typeo_freq;
+	int err;
+
+	np = pdev->dev.of_node;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL)
+		return -ENOENT;
+
+	base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+	if (!base)
+		return -ENOMEM;
+
+	/* Enable PWM clock */
+	aspeed_set_pwm_clock_enable(base, 1);
+	/* Select clock source as 24MHz */
+	aspeed_set_pwm_clock_source(base, 0);
+
+	typem_clk = of_clk_get(np, 0);
+	if (!IS_ERR(typem_clk))
+		pwm_typem_freq = clk_get_rate(typem_clk);
+	else
+		return -EINVAL;
+
+	typen_clk = of_clk_get(np, 1);
+	if (!IS_ERR(typen_clk))
+		pwm_typen_freq = clk_get_rate(typen_clk);
+
+	typeo_clk = of_clk_get(np, 2);
+	if (!IS_ERR(typeo_clk))
+		pwm_typeo_freq = clk_get_rate(typeo_clk);
+
+	err = of_property_read_u32(np, "pwm_typem_period", &period);
+	if (!err) {
+		success = set_clock_values(pwm_typem_freq, period, TYPEM, base);
+		if (!success)
+			return -EINVAL;
+	} else {
+		return -EINVAL;
+	}
+
+	err = of_property_read_u32(np, "pwm_typen_period", &period);
+	if (!err) {
+		success = set_clock_values(pwm_typen_freq, period, TYPEN, base);
+		if (!success)
+			return -EINVAL;
+	}
+
+	err = of_property_read_u32(np, "pwm_typeo_period", &period);
+	if (!err) {
+		success = set_clock_values(pwm_typeo_freq, period, TYPEO, base);
+		if (!success)
+			return -EINVAL;
+	}
+
+	for_each_child_of_node(np, child) {
+		aspeed_create_pwm_port(&pdev->dev,
+				child, base);
+		of_node_put(child);
+	}
+	of_node_put(np);
+
+	return 0;
+}
+
+static const struct of_device_id of_pwm_match_table[] = {
+	{ .compatible = "aspeed,ast2400-pwm", },
+	{ .compatible = "aspeed,ast2500-pwm", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, of_pwm_match_table);
+
+static struct platform_driver aspeed_pwm_driver = {
+	.probe		= aspeed_pwm_probe,
+	.driver		= {
+		.name	= "aspeed_pwm",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_pwm_match_table,
+	},
+};
+
+module_platform_driver(aspeed_pwm_driver);
+
+MODULE_AUTHOR("Jaghathiswari Rankappagounder Natarajan <jaghu at google.com>");
+MODULE_DESCRIPTION("ASPEED PWM device driver");
+MODULE_LICENSE("GPL");
--
2.8.0.rc3.226.g39d4020



More information about the openbmc mailing list