1433 lines
39 KiB
Diff
1433 lines
39 KiB
Diff
From 703e01ff0938c136fcb67c5a09a460fcb0254268 Mon Sep 17 00:00:00 2001
|
|
From: xuxiang <xuxiang@eswincomputing.com>
|
|
Date: Fri, 24 May 2024 13:24:28 +0800
|
|
Subject: [PATCH 030/219] feat:add fan,rtc,wdt,dw-spi,pwm
|
|
|
|
Changelogs:
|
|
1. support pwm, fan control
|
|
2. support dw-spi
|
|
3. support rtc, dw-wdt
|
|
---
|
|
.../dts/eswin/eswin-win2030-die0-soc.dtsi | 6 +-
|
|
arch/riscv/configs/win2030_defconfig | 4 +-
|
|
drivers/hwmon/Kconfig | 9 +
|
|
drivers/hwmon/Makefile | 1 +
|
|
drivers/hwmon/eswin-fan-control.c | 522 ++++++++++++++++++
|
|
drivers/pwm/Kconfig | 9 +
|
|
drivers/pwm/Makefile | 1 +
|
|
drivers/pwm/pwm-eswin.c | 370 +++++++++++++
|
|
drivers/rtc/Kconfig | 8 +
|
|
drivers/rtc/Makefile | 1 +
|
|
drivers/rtc/rtc-eswin.c | 323 +++++++++++
|
|
drivers/spi/spi-dw-mmio.c | 9 +
|
|
12 files changed, 1261 insertions(+), 2 deletions(-)
|
|
create mode 100644 drivers/hwmon/eswin-fan-control.c
|
|
create mode 100644 drivers/pwm/pwm-eswin.c
|
|
create mode 100644 drivers/rtc/rtc-eswin.c
|
|
|
|
diff --git a/arch/riscv/boot/dts/eswin/eswin-win2030-die0-soc.dtsi b/arch/riscv/boot/dts/eswin/eswin-win2030-die0-soc.dtsi
|
|
index 70f8a9d9af3b..430c5410bdda 100644
|
|
--- a/arch/riscv/boot/dts/eswin/eswin-win2030-die0-soc.dtsi
|
|
+++ b/arch/riscv/boot/dts/eswin/eswin-win2030-die0-soc.dtsi
|
|
@@ -1292,6 +1292,8 @@ fan_control: fan_control@50b50000 {
|
|
pulses-per-revolution = <1>;
|
|
pwm-minimun-period = <1000>;
|
|
pwms = <&pwm0 0 100000>;
|
|
+ pinctrl-names = "default";
|
|
+ pinctrl-0 = <&pinctrl_fan_tach_default>;
|
|
status = "disabled";
|
|
};
|
|
|
|
@@ -1510,13 +1512,15 @@ portd: gpio-port@3 {
|
|
|
|
pwm0: pwm@0x50818000 {
|
|
compatible = "eswin,pwm-eswin";
|
|
+ #pwm-cells = <2>;
|
|
reg = <0x0 0x50818000 0x0 0x4000>;
|
|
clock-names = "pwm","pclk";
|
|
clocks = <&d0_clock WIN2030_CLK_LSP_TIMER_PCLK>;
|
|
clock-frequency = <200000000>;
|
|
resets = <&d0_reset TIMER_RST_CTRL SW_TIMER_RST_N>;
|
|
reset-names = "pwmrst";
|
|
- #pwm-cells = <2>;
|
|
+ pinctrl-names = "default";
|
|
+ pinctrl-0 = <&pinctrl_pwm0_default>;
|
|
status = "disabled";
|
|
};
|
|
|
|
diff --git a/arch/riscv/configs/win2030_defconfig b/arch/riscv/configs/win2030_defconfig
|
|
index 0a03d049eff9..d0362cdd1b1e 100644
|
|
--- a/arch/riscv/configs/win2030_defconfig
|
|
+++ b/arch/riscv/configs/win2030_defconfig
|
|
@@ -247,7 +247,6 @@ CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
|
|
CONFIG_EDAC=y
|
|
CONFIG_EDAC_ESWIN=y
|
|
CONFIG_RTC_CLASS=y
|
|
-CONFIG_RTC_DRV_PCF8563=y
|
|
CONFIG_DMADEVICES=y
|
|
CONFIG_DW_AXI_DMAC=y
|
|
CONFIG_DMATEST=y
|
|
@@ -267,6 +266,9 @@ CONFIG_ARCH_ESWIN_EIC770X_SOC_FAMILY=y
|
|
CONFIG_EXTCON=y
|
|
CONFIG_MEMORY=y
|
|
CONFIG_PWM=y
|
|
+CONFIG_PWM_ESWIN=y
|
|
+CONFIG_SENSORS_ESWIN_FAN_CONTROL=y
|
|
+CONFIG_RTC_DRV_ESWIN=y
|
|
CONFIG_RESET_ESWIN_WIN2030=y
|
|
CONFIG_INTERCONNECT=y
|
|
CONFIG_EXT4_FS=y
|
|
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
|
|
index 14e8320b5c07..39185cb8e3df 100644
|
|
--- a/drivers/hwmon/Kconfig
|
|
+++ b/drivers/hwmon/Kconfig
|
|
@@ -312,6 +312,15 @@ config SENSORS_AXI_FAN_CONTROL
|
|
This driver can also be built as a module. If so, the module
|
|
will be called axi-fan-control
|
|
|
|
+config SENSORS_ESWIN_FAN_CONTROL
|
|
+ tristate "ESWIN FAN Control Core driver"
|
|
+ help
|
|
+ If you say yes here you get support for the Analog Devices
|
|
+ ESWIN FAN monitoring core.
|
|
+
|
|
+ This driver can also be built as a module. If so, the module
|
|
+ will be called eswin-fan-control
|
|
+
|
|
config SENSORS_K8TEMP
|
|
tristate "AMD Athlon64/FX or Opteron temperature sensor"
|
|
depends on X86 && PCI
|
|
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
|
|
index 6b5643ef316a..60f32f51eeb9 100644
|
|
--- a/drivers/hwmon/Makefile
|
|
+++ b/drivers/hwmon/Makefile
|
|
@@ -221,6 +221,7 @@ obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
|
|
obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
|
|
obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o
|
|
obj-$(CONFIG_SENSORS_PAC1934) += pac193x.o
|
|
+obj-$(CONFIG_SENSORS_ESWIN_FAN_CONTROL) += eswin-fan-control.o
|
|
|
|
obj-$(CONFIG_SENSORS_OCC) += occ/
|
|
obj-$(CONFIG_SENSORS_PECI) += peci/
|
|
diff --git a/drivers/hwmon/eswin-fan-control.c b/drivers/hwmon/eswin-fan-control.c
|
|
new file mode 100644
|
|
index 000000000000..9c8ab39dee30
|
|
--- /dev/null
|
|
+++ b/drivers/hwmon/eswin-fan-control.c
|
|
@@ -0,0 +1,522 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * ESWIN Fan Control CORE driver
|
|
+ *
|
|
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
|
|
+ * SPDX-License-Identifier: GPL-2.0
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, version 2.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ * Author: Han Min <hanmin@eswincomputing.com>
|
|
+ */
|
|
+
|
|
+#include <linux/bits.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/hwmon.h>
|
|
+#include <linux/hwmon-sysfs.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pwm.h>
|
|
+#include <linux/wait.h>
|
|
+
|
|
+#define FAN_PWM_DUTY 0x0
|
|
+#define FAN_PWM_PERIOD 0x1
|
|
+#define FAN_PWM_FREE 0x2
|
|
+
|
|
+/* register map */
|
|
+#define REG_FAN_INT 0x0
|
|
+#define REG_FAN_RPM 0x4
|
|
+
|
|
+/* wait for 50 times pwm period to trigger read interrupt */
|
|
+#define TIMEOUT(period) nsecs_to_jiffies(50*(period))
|
|
+
|
|
+struct eswin_fan_control_data {
|
|
+ struct reset_control *fan_rst;
|
|
+ struct clk *clk;
|
|
+ void __iomem *base;
|
|
+ struct device *hdev;
|
|
+ unsigned long clk_rate;
|
|
+ int pwm_id;
|
|
+ struct pwm_device *pwm;
|
|
+ wait_queue_head_t wq;
|
|
+ bool wait_flag;
|
|
+ int irq;
|
|
+ /* pwm minimum period */
|
|
+ u32 min_period;
|
|
+ /* pulses per revolution */
|
|
+ u32 ppr;
|
|
+ /* revolutions per minute */
|
|
+ u32 rpm;
|
|
+};
|
|
+
|
|
+static inline void fan_iowrite(const u32 val, const u32 reg,
|
|
+ const struct eswin_fan_control_data *ctl)
|
|
+{
|
|
+ iowrite32(val, ctl->base + reg);
|
|
+}
|
|
+
|
|
+static inline u32 fan_ioread(const u32 reg,
|
|
+ const struct eswin_fan_control_data *ctl)
|
|
+{
|
|
+ return ioread32(ctl->base + reg);
|
|
+}
|
|
+
|
|
+static ssize_t eswin_fan_pwm_ctl_show(struct device *dev, struct device_attribute *da, char *buf)
|
|
+{
|
|
+ struct eswin_fan_control_data *ctl = dev_get_drvdata(dev);
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
+ long temp = 0;
|
|
+
|
|
+ if (FAN_PWM_DUTY == attr->index) {
|
|
+ temp = pwm_get_duty_cycle(ctl->pwm);
|
|
+ }
|
|
+ else if (FAN_PWM_PERIOD == attr->index) {
|
|
+ temp = pwm_get_period(ctl->pwm);
|
|
+ }
|
|
+ else {
|
|
+ dev_err(dev, "get error attr index 0x%x\n", attr->index);
|
|
+ }
|
|
+
|
|
+ return sprintf(buf, "%lu\n", temp);
|
|
+}
|
|
+
|
|
+static ssize_t eswin_fan_pwm_ctl_store(struct device *dev, struct device_attribute *da,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct eswin_fan_control_data *ctl = dev_get_drvdata(dev);
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
|
+ struct pwm_state state;
|
|
+ int ret;
|
|
+
|
|
+ pwm_get_state(ctl->pwm, &state);
|
|
+
|
|
+ if (FAN_PWM_DUTY == attr->index) {
|
|
+ long val = 0;
|
|
+ ret = kstrtoul(buf, 10, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ state.duty_cycle = val;
|
|
+ }
|
|
+ else if (FAN_PWM_PERIOD == attr->index) {
|
|
+ long val = 0;
|
|
+ ret = kstrtoul(buf, 10, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ if (val >= ctl->min_period)
|
|
+ state.period = val;
|
|
+ else
|
|
+ dev_err(dev, "invalid pwm period!\n");
|
|
+ }
|
|
+ else {
|
|
+ dev_err(dev, "get error attr index 0x%x\n", attr->index);
|
|
+ }
|
|
+
|
|
+ pwm_apply_state(ctl->pwm, &state);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t eswin_fan_pwm_free_store(struct device *dev, struct device_attribute *da,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct eswin_fan_control_data *ctl = dev_get_drvdata(dev);
|
|
+ long val;
|
|
+ int ret;
|
|
+ ret = kstrtoul(buf, 10, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (val) {
|
|
+ pwm_put(ctl->pwm);
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static long eswin_fan_control_get_pwm_duty(const struct eswin_fan_control_data *ctl)
|
|
+{
|
|
+ struct pwm_state state;
|
|
+ int duty;
|
|
+
|
|
+ pwm_get_state(ctl->pwm, &state);
|
|
+ duty = pwm_get_relative_duty_cycle(&state, 100);
|
|
+
|
|
+ return duty;
|
|
+}
|
|
+
|
|
+static long eswin_fan_control_get_fan_rpm(struct eswin_fan_control_data *ctl)
|
|
+{
|
|
+ unsigned int val;
|
|
+ long period, timeout;
|
|
+ int ret;
|
|
+
|
|
+ ctl->wait_flag = false;
|
|
+ period = pwm_get_period(ctl->pwm);
|
|
+ timeout = TIMEOUT(period);
|
|
+ if(!timeout)
|
|
+ timeout = TIMEOUT(ctl->min_period);
|
|
+
|
|
+ val = fan_ioread(REG_FAN_INT, ctl);
|
|
+ val = val | 0x1;
|
|
+ fan_iowrite(val, REG_FAN_INT, ctl);
|
|
+
|
|
+ /* wair read interrupt */
|
|
+ ret = wait_event_interruptible_timeout(ctl->wq,
|
|
+ ctl->wait_flag,
|
|
+ timeout);
|
|
+
|
|
+ if (!ret){
|
|
+ /* timeout, set rpm to 0 */
|
|
+ ctl->rpm = 0;
|
|
+ }
|
|
+
|
|
+ if(ctl->rpm)
|
|
+ ctl->rpm = DIV_ROUND_CLOSEST(60 * ctl->clk_rate, ctl->ppr * ctl->rpm);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int eswin_fan_control_read_fan(struct device *dev, u32 attr, long *val)
|
|
+{
|
|
+ struct eswin_fan_control_data *ctl = dev_get_drvdata(dev);
|
|
+
|
|
+ switch (attr) {
|
|
+ case hwmon_fan_input:
|
|
+ if(!eswin_fan_control_get_fan_rpm(ctl)){
|
|
+ dev_err(dev, "wait read interrupt timeout!\n");
|
|
+ }
|
|
+ *val = ctl->rpm;
|
|
+ return 0;
|
|
+ default:
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int eswin_fan_control_read_pwm(struct device *dev, u32 attr, long *val)
|
|
+{
|
|
+ struct eswin_fan_control_data *ctl = dev_get_drvdata(dev);
|
|
+
|
|
+ switch (attr) {
|
|
+ case hwmon_pwm_input:
|
|
+ *val = eswin_fan_control_get_pwm_duty(ctl);
|
|
+ return 0;
|
|
+ default:
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int eswin_fan_control_set_pwm_duty(const long val, struct eswin_fan_control_data *ctl)
|
|
+{
|
|
+ struct pwm_state state;
|
|
+
|
|
+ pwm_get_state(ctl->pwm, &state);
|
|
+ pwm_set_relative_duty_cycle(&state, val, 100);
|
|
+ pwm_apply_state(ctl->pwm, &state);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_fan_control_write_pwm(struct device *dev, u32 attr, long val)
|
|
+{
|
|
+ struct eswin_fan_control_data *ctl = dev_get_drvdata(dev);
|
|
+
|
|
+ switch (attr) {
|
|
+ case hwmon_pwm_input:
|
|
+ if((val < 0)||(val > 100))
|
|
+ return -EINVAL;
|
|
+ else
|
|
+ return eswin_fan_control_set_pwm_duty(val, ctl);
|
|
+ default:
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_fan_control_read_labels(struct device *dev,
|
|
+ enum hwmon_sensor_types type,
|
|
+ u32 attr, int channel, const char **str)
|
|
+{
|
|
+ switch (type) {
|
|
+ case hwmon_fan:
|
|
+ *str = "FAN";
|
|
+ return 0;
|
|
+ default:
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int eswin_fan_control_read(struct device *dev,
|
|
+ enum hwmon_sensor_types type,
|
|
+ u32 attr, int channel, long *val)
|
|
+{
|
|
+ switch (type) {
|
|
+ case hwmon_fan:
|
|
+ return eswin_fan_control_read_fan(dev, attr, val);
|
|
+ case hwmon_pwm:
|
|
+ return eswin_fan_control_read_pwm(dev, attr, val);
|
|
+ default:
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int eswin_fan_control_write(struct device *dev,
|
|
+ enum hwmon_sensor_types type,
|
|
+ u32 attr, int channel, long val)
|
|
+{
|
|
+ switch (type) {
|
|
+ case hwmon_pwm:
|
|
+ return eswin_fan_control_write_pwm(dev, attr, val);
|
|
+ default:
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static umode_t eswin_fan_control_fan_is_visible(const u32 attr)
|
|
+{
|
|
+ switch (attr) {
|
|
+ case hwmon_fan_input:
|
|
+ case hwmon_fan_label:
|
|
+ return 0444;
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static umode_t eswin_fan_control_pwm_is_visible(const u32 attr)
|
|
+{
|
|
+ switch (attr) {
|
|
+ case hwmon_pwm_input:
|
|
+ return 0644;
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static umode_t eswin_fan_control_is_visible(const void *data,
|
|
+ enum hwmon_sensor_types type,
|
|
+ u32 attr, int channel)
|
|
+{
|
|
+ switch (type) {
|
|
+ case hwmon_fan:
|
|
+ return eswin_fan_control_fan_is_visible(attr);
|
|
+ case hwmon_pwm:
|
|
+ return eswin_fan_control_pwm_is_visible(attr);
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static irqreturn_t eswin_fan_control_irq_handler(int irq, void *data)
|
|
+{
|
|
+ struct eswin_fan_control_data *ctl = (struct eswin_fan_control_data *)data;
|
|
+ u32 status = 0;
|
|
+
|
|
+ status = fan_ioread(REG_FAN_INT, ctl);
|
|
+ if (0x3 == (status & 0x3)){
|
|
+ ctl->rpm = fan_ioread(REG_FAN_RPM, ctl);
|
|
+
|
|
+ /* clear interrupt */
|
|
+ fan_iowrite(0x5, REG_FAN_INT, ctl);
|
|
+
|
|
+ /* wake up fan_rpm read */
|
|
+ ctl->wait_flag = true;
|
|
+ wake_up_interruptible(&ctl->wq);
|
|
+ }
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int eswin_fan_control_init(struct eswin_fan_control_data *ctl,
|
|
+ const struct device_node *np)
|
|
+{
|
|
+ int ret;
|
|
+ /* get fan pulses per revolution */
|
|
+ ret = of_property_read_u32(np, "pulses-per-revolution", &ctl->ppr);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* 1, 2 and 4 are the typical and accepted values */
|
|
+ if (ctl->ppr != 1 && ctl->ppr != 2 && ctl->ppr != 4)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* get pwm minimum period */
|
|
+ ret = of_property_read_u32(np, "pwm-minimun-period", &ctl->min_period);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void eswin_fan_control_remove(void *data)
|
|
+{
|
|
+ int ret;
|
|
+ struct eswin_fan_control_data *ctl = data;
|
|
+ pwm_put(ctl->pwm);
|
|
+ ret = reset_control_assert(ctl->fan_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ clk_disable_unprepare(ctl->clk);
|
|
+}
|
|
+
|
|
+static const struct hwmon_channel_info *eswin_fan_control_info[] = {
|
|
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
|
|
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL),
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static const struct hwmon_ops eswin_fan_control_hwmon_ops = {
|
|
+ .is_visible = eswin_fan_control_is_visible,
|
|
+ .read = eswin_fan_control_read,
|
|
+ .write = eswin_fan_control_write,
|
|
+ .read_string = eswin_fan_control_read_labels,
|
|
+};
|
|
+
|
|
+static const struct hwmon_chip_info eswin_chip_info = {
|
|
+ .ops = &eswin_fan_control_hwmon_ops,
|
|
+ .info = eswin_fan_control_info,
|
|
+};
|
|
+
|
|
+static SENSOR_DEVICE_ATTR_RW(fan_pwm_duty, eswin_fan_pwm_ctl, FAN_PWM_DUTY);
|
|
+static SENSOR_DEVICE_ATTR_RW(fan_pwm_period, eswin_fan_pwm_ctl, FAN_PWM_PERIOD);
|
|
+static SENSOR_DEVICE_ATTR_WO(fan_pwm_free, eswin_fan_pwm_free, FAN_PWM_FREE);
|
|
+
|
|
+static struct attribute *eswin_fan_control_attrs[] = {
|
|
+ &sensor_dev_attr_fan_pwm_duty.dev_attr.attr,
|
|
+ &sensor_dev_attr_fan_pwm_period.dev_attr.attr,
|
|
+ &sensor_dev_attr_fan_pwm_free.dev_attr.attr,
|
|
+ NULL,
|
|
+};
|
|
+ATTRIBUTE_GROUPS(eswin_fan_control);
|
|
+
|
|
+static const struct of_device_id eswin_fan_control_of_match[] = {
|
|
+ { .compatible = "eswin-fan-control"},
|
|
+ {}
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, eswin_fan_control_of_match);
|
|
+
|
|
+static int eswin_fan_control_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct eswin_fan_control_data *ctl;
|
|
+ struct clk *clk;
|
|
+ const struct of_device_id *id;
|
|
+ const char *name = "eswin_fan_control";
|
|
+ struct pwm_state state;
|
|
+ int ret;
|
|
+
|
|
+ id = of_match_node(eswin_fan_control_of_match, pdev->dev.of_node);
|
|
+ if (!id)
|
|
+ return -EINVAL;
|
|
+
|
|
+ ctl = devm_kzalloc(&pdev->dev, sizeof(*ctl), GFP_KERNEL);
|
|
+ if (!ctl)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ctl->base = devm_platform_ioremap_resource(pdev, 0);
|
|
+
|
|
+ if (IS_ERR(ctl->base))
|
|
+ return PTR_ERR(ctl->base);
|
|
+
|
|
+ ctl->clk = devm_clk_get(&pdev->dev, "pclk");
|
|
+ if (IS_ERR(ctl->clk)) {
|
|
+ dev_err(&pdev->dev, "Couldn't get the clock for fan-controller\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(ctl->clk);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to enable clock for fan-controller\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ctl->clk_rate = clk_get_rate(ctl->clk);
|
|
+ if (!ctl->clk_rate)
|
|
+ return -EINVAL;
|
|
+
|
|
+ ctl->fan_rst = devm_reset_control_get_optional(&pdev->dev, "fan_rst");
|
|
+ if (IS_ERR_OR_NULL(ctl->fan_rst)) {
|
|
+ dev_err(&pdev->dev, "Failed to get fan_rst reset handle\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+ ret = reset_control_reset(ctl->fan_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+
|
|
+ init_waitqueue_head(&ctl->wq);
|
|
+
|
|
+ ctl->irq = platform_get_irq(pdev, 0);
|
|
+ if (ctl->irq < 0)
|
|
+ return ctl->irq;
|
|
+
|
|
+ ret = devm_request_threaded_irq(&pdev->dev, ctl->irq,
|
|
+ eswin_fan_control_irq_handler, NULL,
|
|
+ IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
|
|
+ pdev->driver_override, ctl);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to request an irq, %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = eswin_fan_control_init(ctl, pdev->dev.of_node);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to initialize device\n");
|
|
+ return ret;
|
|
+ }
|
|
+ ctl->pwm = pwm_get(&pdev->dev, NULL);
|
|
+ if (IS_ERR(ctl->pwm)) {
|
|
+ dev_dbg(&pdev->dev, "Unable to request PWM, trying legacy API\n");
|
|
+ }
|
|
+
|
|
+ if (IS_ERR(ctl->pwm)) {
|
|
+ ret = PTR_ERR(ctl->pwm);
|
|
+ dev_err(&pdev->dev, "Failed to request pwm device: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ pwm_enable(ctl->pwm);
|
|
+ pwm_init_state(ctl->pwm, &state);
|
|
+ state.duty_cycle = state.period/2;
|
|
+ ret = pwm_apply_state(ctl->pwm, &state);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "failed to apply initial PWM state: %d\n",
|
|
+ ret);
|
|
+ }
|
|
+
|
|
+ ret = devm_add_action_or_reset(&pdev->dev, eswin_fan_control_remove, ctl);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ctl->hdev = devm_hwmon_device_register_with_info(&pdev->dev,
|
|
+ name,
|
|
+ ctl,
|
|
+ &eswin_chip_info,
|
|
+ eswin_fan_control_groups);
|
|
+ dev_err(&pdev->dev, "eswin fan control init exit\n");
|
|
+ return PTR_ERR_OR_ZERO(ctl->hdev);
|
|
+}
|
|
+
|
|
+static struct platform_driver eswin_fan_control_driver = {
|
|
+ .driver = {
|
|
+ .name = "eswin_fan_control_driver",
|
|
+ .of_match_table = eswin_fan_control_of_match,
|
|
+ },
|
|
+ .probe = eswin_fan_control_probe,
|
|
+};
|
|
+module_platform_driver(eswin_fan_control_driver);
|
|
+
|
|
+MODULE_AUTHOR("Han Min <hanmin@eswincomputing.com>");
|
|
+MODULE_DESCRIPTION("ESWIN Fan Control CORE driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
|
|
index 8ebcddf91f7b..66e803d4b5a8 100644
|
|
--- a/drivers/pwm/Kconfig
|
|
+++ b/drivers/pwm/Kconfig
|
|
@@ -195,6 +195,15 @@ config PWM_DWC
|
|
To compile this driver as a module, choose M here: the module
|
|
will be called pwm-dwc.
|
|
|
|
+config PWM_ESWIN
|
|
+ tristate "DesignWare PWM Controller"
|
|
+ depends on PCI
|
|
+ help
|
|
+ PWM driver for Synopsys DWC PWM Controller.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the module
|
|
+ will be called pwm-eswin.
|
|
+
|
|
config PWM_EP93XX
|
|
tristate "Cirrus Logic EP93xx PWM support"
|
|
depends on ARCH_EP93XX || COMPILE_TEST
|
|
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
|
|
index c822389c2a24..4da24d757e1d 100644
|
|
--- a/drivers/pwm/Makefile
|
|
+++ b/drivers/pwm/Makefile
|
|
@@ -16,6 +16,7 @@ obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
|
|
obj-$(CONFIG_PWM_CRC) += pwm-crc.o
|
|
obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o
|
|
obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
|
|
+obj-$(CONFIG_PWM_ESWIN) += pwm-eswin.o
|
|
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
|
|
obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
|
|
obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o
|
|
diff --git a/drivers/pwm/pwm-eswin.c b/drivers/pwm/pwm-eswin.c
|
|
new file mode 100644
|
|
index 000000000000..139474716da5
|
|
--- /dev/null
|
|
+++ b/drivers/pwm/pwm-eswin.c
|
|
@@ -0,0 +1,370 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * ESWIN pwm driver
|
|
+ *
|
|
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
|
|
+ * SPDX-License-Identifier: GPL-2.0
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, version 2.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ * Author: zhangchunyun@eswincomputing.com
|
|
+ */
|
|
+
|
|
+#include <linux/bitops.h>
|
|
+#include <linux/export.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pwm.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/pinctrl/pinctrl.h>
|
|
+#include <linux/pinctrl/pinmux.h>
|
|
+#include <linux/pinctrl/pinconf.h>
|
|
+#include <linux/pinctrl/pinconf-generic.h>
|
|
+
|
|
+#define ESWIN_TIM_LD_CNT(n) ((n) * 0x14)
|
|
+#define ESWIN_TIM_LD_CNT2(n) (((n) * 4) + 0xb0)
|
|
+#define ESWIN_TIM_CUR_VAL(n) (((n) * 0x14) + 0x04)
|
|
+#define ESWIN_TIM_CTRL(n) (((n) * 0x14) + 0x08)
|
|
+#define ESWIN_TIM_EOI(n) (((n) * 0x14) + 0x0c)
|
|
+#define ESWIN_TIM_INT_STS(n) (((n) * 0x14) + 0x10)
|
|
+
|
|
+#define ESWIN_TIMERS_INT_STS 0xa0
|
|
+#define ESWIN_TIMERS_EOI 0xa4
|
|
+#define ESWIN_TIMERS_RAW_INT_STS 0xa8
|
|
+#define ESWIN_TIMERS_COMP_VERSION 0xac
|
|
+
|
|
+#define ESWIN_TIMERS_TOTAL 8
|
|
+#define NSEC_TO_SEC 1000000000
|
|
+
|
|
+/* Timer Control Register */
|
|
+#define ESWIN_TIM_CTRL_EN BIT(0)
|
|
+#define ESWIN_TIM_CTRL_MODE BIT(1)
|
|
+#define ESWIN_TIM_CTRL_MODE_FREE (0 << 1)
|
|
+#define ESWIN_TIM_CTRL_MODE_USER (1 << 1)
|
|
+#define ESWIN_TIM_CTRL_INT_MASK BIT(2)
|
|
+#define ESWIN_TIM_CTRL_PWM BIT(3)
|
|
+
|
|
+struct eswin_pwm_ctx {
|
|
+ u32 cnt;
|
|
+ u32 cnt2;
|
|
+ u32 ctrl;
|
|
+};
|
|
+
|
|
+struct eswin_pwm {
|
|
+ struct pwm_chip chip;
|
|
+ void __iomem *base;
|
|
+ struct clk *clk;
|
|
+ struct clk *pclk;
|
|
+ struct eswin_pwm_ctx ctx[ESWIN_TIMERS_TOTAL];
|
|
+ struct reset_control * pwm_rst;
|
|
+ u32 clk_period_ns;
|
|
+};
|
|
+
|
|
+#define to_eswin_pwm(p) (container_of((p), struct eswin_pwm, chip))
|
|
+
|
|
+static inline u32 eswin_pwm_readl(struct eswin_pwm *eswin, u32 offset)
|
|
+{
|
|
+ return readl(eswin->base + offset);
|
|
+}
|
|
+
|
|
+static inline void eswin_pwm_writel(struct eswin_pwm *eswin, u32 value, u32 offset)
|
|
+{
|
|
+ writel(value, eswin->base + offset);
|
|
+}
|
|
+
|
|
+static void __eswin_pwm_set_enable(struct eswin_pwm *eswin, int pwm, int enabled)
|
|
+{
|
|
+ u32 reg;
|
|
+
|
|
+ reg = eswin_pwm_readl(eswin, ESWIN_TIM_CTRL(pwm));
|
|
+
|
|
+ if (enabled)
|
|
+ reg |= ESWIN_TIM_CTRL_EN;
|
|
+ else
|
|
+ reg &= ~ESWIN_TIM_CTRL_EN;
|
|
+
|
|
+ eswin_pwm_writel(eswin, reg, ESWIN_TIM_CTRL(pwm));
|
|
+ reg = eswin_pwm_readl(eswin, ESWIN_TIM_CTRL(pwm));
|
|
+}
|
|
+
|
|
+static int __eswin_pwm_configure_timer(struct eswin_pwm *eswin,
|
|
+ struct pwm_device *pwm,
|
|
+ const struct pwm_state *state)
|
|
+{
|
|
+ u64 tmp;
|
|
+ u32 ctrl;
|
|
+ u32 high;
|
|
+ u32 low;
|
|
+
|
|
+ /*
|
|
+ ¦* Calculate width of low and high period in terms of input clock
|
|
+ ¦* periods and check are the result within HW limits between 1 and
|
|
+ ¦* 2^32 periods.
|
|
+ ¦*/
|
|
+
|
|
+ tmp = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, eswin->clk_period_ns);
|
|
+ if (tmp < 1 || tmp > (1ULL << 32))
|
|
+ return -ERANGE;
|
|
+ high = tmp - 1;
|
|
+
|
|
+ tmp = DIV_ROUND_CLOSEST_ULL(state->period - state->duty_cycle,
|
|
+ eswin->clk_period_ns);
|
|
+ if (tmp < 1 || tmp > (1ULL << 32))
|
|
+ return -ERANGE;
|
|
+ low = tmp - 1;
|
|
+ /*
|
|
+ ¦* Specification says timer usage flow is to disable timer, then
|
|
+ ¦* program it followed by enable. It also says Load Count is loaded
|
|
+ ¦* into timer after it is enabled - either after a disable or
|
|
+ ¦* a reset. Based on measurements it happens also without disable
|
|
+ ¦* whenever Load Count is updated. But follow the specification.
|
|
+ ¦*/
|
|
+ __eswin_pwm_set_enable(eswin, pwm->hwpwm, false);
|
|
+
|
|
+ /*
|
|
+ ¦* Write Load Count and Load Count 2 registers. Former defines the
|
|
+ ¦* width of low period and latter the width of high period in terms
|
|
+ ¦* multiple of input clock periods:
|
|
+ ¦* Width = ((Count + 1) * input clock period).
|
|
+ ¦*/
|
|
+ eswin_pwm_writel(eswin, low, ESWIN_TIM_LD_CNT(pwm->hwpwm));
|
|
+ eswin_pwm_writel(eswin, high, ESWIN_TIM_LD_CNT2(pwm->hwpwm));
|
|
+
|
|
+ /*
|
|
+ ¦* Set user-defined mode, timer reloads from Load Count registers
|
|
+ ¦* when it counts down to 0.
|
|
+ ¦* Set PWM mode, it makes output to toggle and width of low and high
|
|
+ ¦* periods are set by Load Count registers.
|
|
+ ¦*/
|
|
+ ctrl = ESWIN_TIM_CTRL_MODE_USER | ESWIN_TIM_CTRL_PWM;
|
|
+ eswin_pwm_writel(eswin, ctrl, ESWIN_TIM_CTRL(pwm->hwpwm));
|
|
+
|
|
+ /*
|
|
+ ¦* Enable timer. Output starts from low period.
|
|
+ ¦*/
|
|
+ __eswin_pwm_set_enable(eswin, pwm->hwpwm, state->enabled);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
+ const struct pwm_state *state)
|
|
+{
|
|
+ struct eswin_pwm *eswin = to_eswin_pwm(chip);
|
|
+ struct pwm_state curstate;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = clk_enable(eswin->pclk);
|
|
+
|
|
+ ret = clk_enable(eswin->clk);
|
|
+
|
|
+ pwm_get_state(pwm, &curstate);
|
|
+
|
|
+ __eswin_pwm_configure_timer(eswin, pwm, state);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
+ struct pwm_state *state)
|
|
+{
|
|
+ struct eswin_pwm *eswin = to_eswin_pwm(chip);
|
|
+ u64 duty, period;
|
|
+
|
|
+ pm_runtime_get_sync(chip->dev);
|
|
+
|
|
+ state->enabled = !!(eswin_pwm_readl(eswin,
|
|
+ ESWIN_TIM_CTRL(pwm->hwpwm)) & ESWIN_TIM_CTRL_EN);
|
|
+
|
|
+ duty = eswin_pwm_readl(eswin, ESWIN_TIM_LD_CNT(pwm->hwpwm));
|
|
+ duty += 1;
|
|
+ duty *= eswin->clk_period_ns;
|
|
+ state->duty_cycle = duty;
|
|
+
|
|
+ period = eswin_pwm_readl(eswin, ESWIN_TIM_LD_CNT2(pwm->hwpwm));
|
|
+ period += 1;
|
|
+ period *= eswin->clk_period_ns;
|
|
+ period += duty;
|
|
+ state->period = period;
|
|
+
|
|
+ state->polarity = PWM_POLARITY_INVERSED;
|
|
+
|
|
+ pm_runtime_put_sync(chip->dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static const struct pwm_ops eswin_pwm_ops = {
|
|
+ .apply = eswin_pwm_apply,
|
|
+ .get_state = eswin_pwm_get_state,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+static const struct of_device_id eswin_pwm_dt_ids[] = {
|
|
+ { .compatible = "eswin,pwm-eswin", },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, eswin_pwm_dt_ids);
|
|
+
|
|
+static int eswin_pwm_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct eswin_pwm *pc;
|
|
+ int ret, count;
|
|
+ struct resource *res;
|
|
+ int clk_rate;
|
|
+/* unsigned long *conf, *conf1, *conf2;
|
|
+ unsigned int val, val1, val2;
|
|
+
|
|
+ ret = of_property_read_u32_index(pdev->dev.of_node, "pinctrl-pwm", 0, &val);
|
|
+ if(ret){
|
|
+ dev_err(&pdev->dev, "Can't get pwm pin0\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32_index(pdev->dev.of_node, "pinctrl-pwm", 1, &val1);
|
|
+ if(ret){
|
|
+ dev_err(&pdev->dev, "Can't get pwm pin1\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32_index(pdev->dev.of_node, "pinctrl-pwm", 2, &val2);
|
|
+ if(ret){
|
|
+ dev_err(&pdev->dev, "Can't get pwm pin2\n");
|
|
+ return -1;
|
|
+ }
|
|
+ conf = (unsigned long *)(&val);
|
|
+ conf1 = (unsigned long *)(&val1);
|
|
+ conf2 = (unsigned long *)(&val2);
|
|
+
|
|
+ eswin_pinconf_cfg_set(NULL,147, conf,32);
|
|
+ eswin_pinconf_cfg_set(NULL,116, conf1,32);
|
|
+ eswin_pinconf_cfg_set(NULL,117, conf2,32);
|
|
+*/
|
|
+ pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
|
|
+ if (!pc)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (!res)
|
|
+ return -ENODEV;
|
|
+
|
|
+ pc->base = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (IS_ERR(pc->base))
|
|
+ return PTR_ERR(pc->base);
|
|
+
|
|
+ pc->clk = devm_clk_get(&pdev->dev, "pwm");
|
|
+ if (IS_ERR(pc->clk)) {
|
|
+ pc->clk = devm_clk_get(&pdev->dev, "pclk");
|
|
+ if (IS_ERR(pc->clk))
|
|
+ return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk),
|
|
+ "Can't get PWM clk\n");
|
|
+ }
|
|
+
|
|
+ count = of_count_phandle_with_args(pdev->dev.of_node,
|
|
+ "clocks", "#clock-cells");
|
|
+ if (count == 2)
|
|
+ pc->pclk = devm_clk_get(&pdev->dev, "pclk");
|
|
+ else
|
|
+ pc->pclk = pc->clk;
|
|
+
|
|
+ if (IS_ERR(pc->pclk)) {
|
|
+ ret = PTR_ERR(pc->pclk);
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Can't get APB clk: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ clk_rate = clk_get_rate(pc->pclk);
|
|
+ pc->clk_period_ns = DIV_ROUND_CLOSEST_ULL(NSEC_TO_SEC, clk_rate);
|
|
+ /* pwm reset init */
|
|
+ pc->pwm_rst = devm_reset_control_get_optional(&pdev->dev, "pwmrst");
|
|
+ if(IS_ERR_OR_NULL(pc->pwm_rst)) {
|
|
+ dev_err(&pdev->dev, "Failed to get pwmrst reset handle\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(pc->clk);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Can't prepare enable PWM clk: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(pc->pclk);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Can't prepare enable APB clk: %d\n", ret);
|
|
+ goto err_clk;
|
|
+ }
|
|
+
|
|
+ /* reset pwm */
|
|
+ ret = reset_control_assert(pc->pwm_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_deassert(pc->pwm_rst);
|
|
+ WARN_ON(0 != ret);
|
|
+
|
|
+ platform_set_drvdata(pdev, pc);
|
|
+
|
|
+ pc->chip.dev = &pdev->dev;
|
|
+ pc->chip.ops = &eswin_pwm_ops;
|
|
+ pc->chip.npwm = 3;
|
|
+
|
|
+ ret = pwmchip_add(&pc->chip);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
|
|
+ goto err_pclk;
|
|
+ }
|
|
+ dev_err(&pdev->dev, "eswin pwm init success \n");
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_pclk:
|
|
+ clk_disable_unprepare(pc->pclk);
|
|
+err_clk:
|
|
+ clk_disable_unprepare(pc->clk);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int eswin_pwm_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct eswin_pwm *pc = platform_get_drvdata(pdev);
|
|
+
|
|
+ pwmchip_remove(&pc->chip);
|
|
+
|
|
+ clk_disable_unprepare(pc->pclk);
|
|
+ clk_disable_unprepare(pc->clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver eswin_pwm_driver = {
|
|
+ .driver = {
|
|
+ .name = "eswin-pwm",
|
|
+ .of_match_table = eswin_pwm_dt_ids,
|
|
+ },
|
|
+ .probe = eswin_pwm_probe,
|
|
+ .remove = eswin_pwm_remove,
|
|
+};
|
|
+module_platform_driver(eswin_pwm_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("eswin SoC PWM driver");
|
|
+MODULE_AUTHOR("zhangchunyun@eswincomputing.com");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
|
|
index 92f46a6312c2..e6a5d8083520 100644
|
|
--- a/drivers/rtc/Kconfig
|
|
+++ b/drivers/rtc/Kconfig
|
|
@@ -1985,4 +1985,12 @@ config RTC_DRV_POLARFIRE_SOC
|
|
This driver can also be built as a module, if so, the module
|
|
will be called "rtc-mpfs".
|
|
|
|
+config RTC_DRV_ESWIN
|
|
+ tristate "eswin win2030 RTC"
|
|
+ help
|
|
+ If you say yes here you get support for the eswin win2030 real time
|
|
+ clock.
|
|
+
|
|
+ This driver can also be built as a module, if so, the module
|
|
+ will be called "rtc-eswin".
|
|
endif # RTC_CLASS
|
|
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
|
|
index fd209883ee2e..feff57496fb9 100644
|
|
--- a/drivers/rtc/Makefile
|
|
+++ b/drivers/rtc/Makefile
|
|
@@ -184,3 +184,4 @@ obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
|
|
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
|
|
obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o
|
|
obj-$(CONFIG_RTC_DRV_ZYNQMP) += rtc-zynqmp.o
|
|
+obj-$(CONFIG_RTC_DRV_ESWIN) += rtc-eswin.o
|
|
diff --git a/drivers/rtc/rtc-eswin.c b/drivers/rtc/rtc-eswin.c
|
|
new file mode 100644
|
|
index 000000000000..b4ca046d8ff8
|
|
--- /dev/null
|
|
+++ b/drivers/rtc/rtc-eswin.c
|
|
@@ -0,0 +1,323 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * ESWIN rtc driver
|
|
+ *
|
|
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
|
|
+ * SPDX-License-Identifier: GPL-2.0
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, version 2.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
+ *
|
|
+ * Author: zhangpengcheng@eswincomputing.com
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/rtc.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+
|
|
+#define RTC_INT_TO_U84 0xffff9fff
|
|
+/* RTC CSR Registers */
|
|
+#define RTC_CCVR 0x00
|
|
+#define RTC_CMR 0x04
|
|
+#define RTC_CLR 0x08
|
|
+#define RTC_CCR 0x0C
|
|
+#define RTC_CCR_IE BIT(0)
|
|
+#define RTC_CCR_MASK BIT(1)
|
|
+#define RTC_CCR_EN BIT(2)
|
|
+#define RTC_CCR_WEN BIT(3)
|
|
+#define RTC_CCR_PEN BIT(4)
|
|
+#define RTC_STAT 0x10
|
|
+#define RTC_STAT_BIT BIT(0)
|
|
+#define RTC_RSTAT 0x14
|
|
+#define RTC_EOI 0x18
|
|
+#define RTC_VER 0x1C
|
|
+#define RTC_CPSR 0x20
|
|
+#define RTC_CPCVR 0x24
|
|
+
|
|
+struct eswin_rtc_dev {
|
|
+ struct rtc_device *rtc;
|
|
+ struct device *dev;
|
|
+ unsigned long alarm_time;
|
|
+ void __iomem *csr_base;
|
|
+ struct clk *clk;
|
|
+ unsigned int irq_wake;
|
|
+ struct reset_control *rst_rtc;
|
|
+};
|
|
+
|
|
+static int eswin_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
|
+{
|
|
+ struct eswin_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
+ rtc_time64_to_tm(readl(pdata->csr_base + RTC_CCVR), tm);
|
|
+ return rtc_valid_tm(tm);
|
|
+}
|
|
+
|
|
+static int eswin_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
|
+{
|
|
+ struct eswin_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
+ unsigned long tr;
|
|
+
|
|
+ tr = rtc_tm_to_time64(tm);
|
|
+ writel(tr, pdata->csr_base + RTC_CLR);
|
|
+ readl(pdata->csr_base + RTC_CLR); /* Force a barrier */
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
+{
|
|
+ struct eswin_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
+ rtc_time64_to_tm(pdata->alarm_time, &alrm->time);
|
|
+ alrm->enabled = readl(pdata->csr_base + RTC_CCR) & RTC_CCR_IE;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_rtc_alarm_irq_enable(struct device *dev, u32 enabled)
|
|
+{
|
|
+ struct eswin_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
+ u32 ccr;
|
|
+
|
|
+ ccr = readl(pdata->csr_base + RTC_CCR);
|
|
+ if (enabled) {
|
|
+ ccr &= ~RTC_CCR_MASK;
|
|
+ ccr |= RTC_CCR_IE;
|
|
+ } else {
|
|
+ ccr &= ~RTC_CCR_IE;
|
|
+ ccr |= RTC_CCR_MASK;
|
|
+ }
|
|
+ writel(ccr, pdata->csr_base + RTC_CCR);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
+{
|
|
+ struct eswin_rtc_dev *pdata = dev_get_drvdata(dev);
|
|
+ unsigned long rtc_time;
|
|
+ unsigned long alarm_time;
|
|
+ rtc_time = readl(pdata->csr_base + RTC_CCVR);
|
|
+ alarm_time = rtc_tm_to_time64(&alrm->time);
|
|
+
|
|
+ pdata->alarm_time = alarm_time;
|
|
+ writel((u32) pdata->alarm_time, pdata->csr_base + RTC_CMR);
|
|
+
|
|
+ eswin_rtc_alarm_irq_enable(dev, alrm->enabled);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct rtc_class_ops eswin_rtc_ops = {
|
|
+ .read_time = eswin_rtc_read_time,
|
|
+ .set_time = eswin_rtc_set_time,
|
|
+ .read_alarm = eswin_rtc_read_alarm,
|
|
+ .set_alarm = eswin_rtc_set_alarm,
|
|
+ .alarm_irq_enable = eswin_rtc_alarm_irq_enable,
|
|
+};
|
|
+
|
|
+static irqreturn_t eswin_rtc_interrupt(int irq, void *id)
|
|
+{
|
|
+ struct eswin_rtc_dev *pdata = (struct eswin_rtc_dev *) id;
|
|
+ /* Check if interrupt asserted */
|
|
+ if (!(readl(pdata->csr_base + RTC_STAT) & RTC_STAT_BIT))
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ /* Clear interrupt */
|
|
+ readl(pdata->csr_base + RTC_EOI);
|
|
+
|
|
+ rtc_update_irq(pdata->rtc, 1, RTC_IRQF | RTC_AF);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int eswin_rtc_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct eswin_rtc_dev *pdata;
|
|
+ struct resource *res;
|
|
+ int ret;
|
|
+ int irq;
|
|
+ unsigned int reg_val;
|
|
+ unsigned int int_off;
|
|
+ unsigned int clk_freq;
|
|
+ struct regmap *regmap;
|
|
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
|
+ if (!pdata)
|
|
+ return -ENOMEM;
|
|
+ platform_set_drvdata(pdev, pdata);
|
|
+ pdata->dev = &pdev->dev;
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ pdata->csr_base = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (IS_ERR(pdata->csr_base))
|
|
+ return PTR_ERR(pdata->csr_base);
|
|
+
|
|
+ irq = platform_get_irq(pdev, 0);
|
|
+ if (irq < 0) {
|
|
+ dev_err(&pdev->dev, "No IRQ resource\n");
|
|
+ return irq;
|
|
+ }
|
|
+ ret = devm_request_irq(&pdev->dev, irq, eswin_rtc_interrupt, 0,
|
|
+ dev_name(&pdev->dev), pdata);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Could not request IRQ\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* update RTC interrupt to u84 */
|
|
+ regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "eswin,syscfg");
|
|
+ if (IS_ERR(regmap)) {
|
|
+ dev_err(&pdev->dev, "No syscfg phandle specified\n");
|
|
+ return PTR_ERR(regmap);
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32_index(pdev->dev.of_node, "eswin,syscfg", 1, &int_off);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "No rtc interrupt offset found\n");
|
|
+ return -1;
|
|
+ }
|
|
+ regmap_read(regmap, int_off, ®_val);
|
|
+ reg_val &= (RTC_INT_TO_U84);
|
|
+ regmap_write(regmap, int_off, reg_val);
|
|
+
|
|
+ ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &clk_freq);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "No rtc clock-frequency found\n");
|
|
+ }
|
|
+ /* rtc reset init*/
|
|
+ pdata->rst_rtc = devm_reset_control_get_optional(&pdev->dev, "rtcrst");
|
|
+ if (IS_ERR_OR_NULL(pdata->rst_rtc)) {
|
|
+ dev_err(&pdev->dev, "Failed to get rtcrst reset handle\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ /* get RTC clock */
|
|
+ pdata->clk = devm_clk_get(&pdev->dev, "rtcclk");
|
|
+ if (IS_ERR(pdata->clk)) {
|
|
+ dev_err(&pdev->dev, "Couldn't get the clock for RTC\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ /* Enable the clock */
|
|
+ clk_prepare_enable(pdata->clk);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "failed to enable RTC clock: %d\n", ret);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ /* reset rtc */
|
|
+ ret = reset_control_assert(pdata->rst_rtc);
|
|
+ WARN_ON(0 != ret);
|
|
+ ret = reset_control_deassert(pdata->rst_rtc);
|
|
+ WARN_ON(0 != ret);
|
|
+
|
|
+ /* Turn on the clock and the crystal */
|
|
+ reg_val = readl(pdata->csr_base + RTC_CCR);
|
|
+ writel(RTC_CCR_EN | reg_val, pdata->csr_base + RTC_CCR);
|
|
+
|
|
+ /* Turn on the prescaler and set the value */
|
|
+ writel(clk_freq, pdata->csr_base + RTC_CPSR);
|
|
+ reg_val = readl(pdata->csr_base + RTC_CCR);
|
|
+ writel(RTC_CCR_PEN | reg_val, pdata->csr_base + RTC_CCR);
|
|
+
|
|
+ device_init_wakeup(&pdev->dev, 1);
|
|
+
|
|
+ pdata->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
|
|
+ &eswin_rtc_ops, THIS_MODULE);
|
|
+ if (IS_ERR(pdata->rtc)) {
|
|
+ clk_disable_unprepare(pdata->clk);
|
|
+ return PTR_ERR(pdata->rtc);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_rtc_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct eswin_rtc_dev *pdata = platform_get_drvdata(pdev);
|
|
+
|
|
+ eswin_rtc_alarm_irq_enable(&pdev->dev, 0);
|
|
+ device_init_wakeup(&pdev->dev, 0);
|
|
+ clk_disable_unprepare(pdata->clk);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+static int eswin_rtc_suspend(struct device *dev)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct eswin_rtc_dev *pdata = platform_get_drvdata(pdev);
|
|
+ int irq;
|
|
+
|
|
+ irq = platform_get_irq(pdev, 0);
|
|
+ if (device_may_wakeup(&pdev->dev)) {
|
|
+ if (!enable_irq_wake(irq))
|
|
+ pdata->irq_wake = 1;
|
|
+ } else {
|
|
+ eswin_rtc_alarm_irq_enable(dev, 0);
|
|
+ clk_disable(pdata->clk);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int eswin_rtc_resume(struct device *dev)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct eswin_rtc_dev *pdata = platform_get_drvdata(pdev);
|
|
+ int irq;
|
|
+
|
|
+ irq = platform_get_irq(pdev, 0);
|
|
+ if (device_may_wakeup(&pdev->dev)) {
|
|
+ if (pdata->irq_wake) {
|
|
+ disable_irq_wake(irq);
|
|
+ pdata->irq_wake = 0;
|
|
+ }
|
|
+ } else {
|
|
+ clk_enable(pdata->clk);
|
|
+ eswin_rtc_alarm_irq_enable(dev, 1);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(eswin_rtc_pm_ops, eswin_rtc_suspend, eswin_rtc_resume);
|
|
+
|
|
+#ifdef CONFIG_OF
|
|
+static const struct of_device_id eswin_rtc_of_match[] = {
|
|
+ {.compatible = "eswin,win2030-rtc" },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, eswin_rtc_of_match);
|
|
+#endif
|
|
+
|
|
+static struct platform_driver eswin_rtc_driver = {
|
|
+ .probe = eswin_rtc_probe,
|
|
+ .remove = eswin_rtc_remove,
|
|
+ .driver = {
|
|
+ .name = "eswin-rtc",
|
|
+ .pm = &eswin_rtc_pm_ops,
|
|
+ .of_match_table = of_match_ptr(eswin_rtc_of_match),
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(eswin_rtc_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("eswin win2030 RTC driver");
|
|
+MODULE_AUTHOR("zhangpengcheng@eswin.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c
|
|
index 805264c9c65c..39c1ae316ac0 100644
|
|
--- a/drivers/spi/spi-dw-mmio.c
|
|
+++ b/drivers/spi/spi-dw-mmio.c
|
|
@@ -210,6 +210,14 @@ static int dw_spi_alpine_init(struct platform_device *pdev,
|
|
return 0;
|
|
}
|
|
|
|
+static int dw_spi_eswin_init(struct platform_device *pdev,
|
|
+ struct dw_spi_mmio *dwsmmio)
|
|
+{
|
|
+ dw_spi_dma_setup_generic(&dwsmmio->dws);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int dw_spi_pssi_init(struct platform_device *pdev,
|
|
struct dw_spi_mmio *dwsmmio)
|
|
{
|
|
@@ -432,6 +440,7 @@ static const struct of_device_id dw_spi_mmio_of_match[] = {
|
|
{ .compatible = "microchip,sparx5-spi", dw_spi_mscc_sparx5_init},
|
|
{ .compatible = "canaan,k210-spi", dw_spi_canaan_k210_init},
|
|
{ .compatible = "amd,pensando-elba-spi", .data = dw_spi_elba_init},
|
|
+ { .compatible = "snps,eic770x-spi", .data = dw_spi_eswin_init},
|
|
{ /* end of table */}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, dw_spi_mmio_of_match);
|
|
--
|
|
2.47.0
|
|
|