Home › Forum › Software discussion › Controlling PWM-fan
Tagged: pwm fan
Here is the patch to control PWM-fan connected to J10.
Tested with “Noctua NF-A4x20 PWM”.
——————
arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi | 51 ++++++
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 8
arch/arm64/configs/defconfig | 1
drivers/gpio/gpio-mvebu.c | 172 ++++++++++++++++++—
4 files changed, 212 insertions(+), 20 deletions(-)
——————
--- linux-5.1.20/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi.org 2019-07-26 16:13:08.000000000 +0900
+++ linux-5.1.20/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi 2019-07-28 09:25:13.361651661 +0900
@@ -101,6 +101,15 @@
pinctrl-names = "default";
pinctrl-0 = <&cp0_sfp_1g_pins &cp1_sfp_1g_pins>;
};
+
+ pwm_fan: pwm-fan {
+ /* J10 Fan header */
+ compatible = "pwm-fan";
+ #cooling-cells = <2>;
+ pwms = <&cp0_gpio2 16 40000>; /* 40000 ns is 25 kHz */
+ pinctrl-names = "default";
+ pinctrl-0 = <&cp0_fan_pwm_pins>;
+ };
};
&uart0 {
@@ -208,6 +217,10 @@
marvell,pins = "mpp47";
marvell,function = "gpio";
};
+ cp0_fan_pwm_pins: fan-pwm-pins {
+ marvell,pins = "mpp48";
+ marvell,function = "gpio";
+ };
cp0_sfp_1g_pins: sfp-1g-pins {
marvell,pins = "mpp51", "mpp53", "mpp54";
marvell,function = "gpio";
@@ -344,3 +357,41 @@
usb-phy = <&usb3h0_phy>;
status = "okay";
};
+
+&pwm_fan {
+ cooling-levels = <0
+ 8 9 10 11 12 13 14 15
+ 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+ 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
+ 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
+ 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
+ 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
+ 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
+ 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
+ 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
+ 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
+ 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
+ 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
+ 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
+ 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
+ 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
+ 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255>;
+ status = "okay";
+};
+
+&ap_thermal_cpu1 {
+ trips {
+ cpu_active: cpu-active {
+ temperature = <60000>;
+ hysteresis = <2000>;
+ type = "active";
+ };
+ };
+ cooling-maps {
+ fan-map {
+ trip = <&cpu_active>;
+ cooling-device = <&pwm_fan THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+};
+
--- linux-5.1.20/arch/arm64/boot/dts/marvell/armada-cp110.dtsi.org 2019-07-26 16:13:08.000000000 +0900
+++ linux-5.1.20/arch/arm64/boot/dts/marvell/armada-cp110.dtsi 2019-07-28 08:56:25.122842572 +0900
@@ -233,6 +233,10 @@
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&CP110_LABEL(pinctrl) 0 0 32>;
+ pwm-reg = <0x1f0 0x08>;
+ #pwm-cells = <2>;
+ clocks = <&CP110_LABEL(clk) 0 3>;
+ clock-names = "core";
interrupt-controller;
interrupts = <86 IRQ_TYPE_LEVEL_HIGH>,
<85 IRQ_TYPE_LEVEL_HIGH>,
@@ -248,6 +252,10 @@
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&CP110_LABEL(pinctrl) 0 32 31>;
+ pwm-reg = <0x1f8 0x08>;
+ #pwm-cells = <2>;
+ clocks = <&CP110_LABEL(clk) 0 3>;
+ clock-names = "core";
interrupt-controller;
interrupts = <82 IRQ_TYPE_LEVEL_HIGH>,
<81 IRQ_TYPE_LEVEL_HIGH>,
--- linux-5.1.20/arch/arm64/configs/defconfig.org 2019-07-26 16:13:08.000000000 +0900
+++ linux-5.1.20/arch/arm64/configs/defconfig 2019-07-28 08:56:25.126842754 +0900
@@ -779,3 +779,4 @@ CONFIG_DEBUG_KERNEL=y
# CONFIG_DEBUG_PREEMPT is not set
# CONFIG_FTRACE is not set
CONFIG_MEMTEST=y
+CONFIG_SENSORS_PWM_FAN=m
--- linux-5.1.20/drivers/gpio/gpio-mvebu.c.org 2019-07-26 16:13:08.000000000 +0900
+++ linux-5.1.20/drivers/gpio/gpio-mvebu.c 2019-07-28 08:56:25.126842754 +0900
@@ -35,6 +35,7 @@
#include <linux/bitops.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/consumer.h>
@@ -44,6 +45,7 @@
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/pinctrl/consumer.h>
@@ -68,6 +70,7 @@
/*
* PWM register offsets.
*/
+#define PWM_BLINK_DURATION_BASE 0x1f0
#define PWM_BLINK_ON_DURATION_OFF 0x0
#define PWM_BLINK_OFF_DURATION_OFF 0x4
@@ -94,6 +97,7 @@
struct mvebu_pwm {
void __iomem *membase;
+ u32 offset;
unsigned long clk_rate;
struct gpio_desc *gpiod;
struct pwm_chip chip;
@@ -292,6 +296,74 @@ static void __iomem *mvebu_pwmreg_blink_
return mvpwm->membase + PWM_BLINK_OFF_DURATION_OFF;
}
+static void mvebu_pwmreg_blink_duration(struct mvebu_pwm *mvpwm,
+ struct regmap **map, unsigned int *offset)
+{
+ *map = mvpwm->mvchip->regs;
+ *offset = mvpwm->offset;
+}
+
+static u32
+mvebu_pwmreg_read_blink_on_duration(struct mvebu_pwm *mvpwm)
+{
+ struct regmap *map;
+ unsigned int offset;
+ u32 val;
+
+ if (mvpwm->membase) {
+ val = readl_relaxed(mvebu_pwmreg_blink_on_duration(mvpwm));
+ } else {
+ mvebu_pwmreg_blink_duration(mvpwm, &map, &offset);
+ regmap_read(map, offset + PWM_BLINK_ON_DURATION_OFF, &val);
+ }
+ return val;
+}
+
+static void
+mvebu_pwmreg_write_blink_on_duration(struct mvebu_pwm *mvpwm, u32 val)
+{
+ struct regmap *map;
+ unsigned int offset;
+
+ if (mvpwm->membase) {
+ writel_relaxed(val, mvebu_pwmreg_blink_on_duration(mvpwm));
+ } else {
+ mvebu_pwmreg_blink_duration(mvpwm, &map, &offset);
+ regmap_write(map, offset + PWM_BLINK_ON_DURATION_OFF, val);
+ }
+}
+
+static u32
+mvebu_pwmreg_read_blink_off_duration(struct mvebu_pwm *mvpwm)
+{
+ struct regmap *map;
+ unsigned int offset;
+ u32 val;
+
+ if (mvpwm->membase) {
+ val = readl_relaxed(mvebu_pwmreg_blink_off_duration(mvpwm));
+ } else {
+ mvebu_pwmreg_blink_duration(mvpwm, &map, &offset);
+ regmap_read(map, offset + PWM_BLINK_OFF_DURATION_OFF, &val);
+ }
+
+ return val;
+}
+
+static void
+mvebu_pwmreg_write_blink_off_duration(struct mvebu_pwm *mvpwm, u32 val)
+{
+ struct regmap *map;
+ unsigned int offset;
+
+ if (mvpwm->membase) {
+ writel_relaxed(val, mvebu_pwmreg_blink_off_duration(mvpwm));
+ } else {
+ mvebu_pwmreg_blink_duration(mvpwm, &map, &offset);
+ regmap_write(map, offset + PWM_BLINK_OFF_DURATION_OFF, val);
+ }
+}
+
/*
* Functions implementing the gpio_chip methods
*/
@@ -661,7 +733,7 @@ static void mvebu_pwm_get_state(struct p
spin_lock_irqsave(&mvpwm->lock, flags);
val = (unsigned long long)
- readl_relaxed(mvebu_pwmreg_blink_on_duration(mvpwm));
+ mvebu_pwmreg_read_blink_on_duration(mvpwm);
val *= NSEC_PER_SEC;
do_div(val, mvpwm->clk_rate);
if (val > UINT_MAX)
@@ -672,7 +744,7 @@ static void mvebu_pwm_get_state(struct p
state->duty_cycle = 1;
val = (unsigned long long)
- readl_relaxed(mvebu_pwmreg_blink_off_duration(mvpwm));
+ mvebu_pwmreg_read_blink_off_duration(mvpwm);
val *= NSEC_PER_SEC;
do_div(val, mvpwm->clk_rate);
if (val < state->duty_cycle) {
@@ -726,8 +798,8 @@ static int mvebu_pwm_apply(struct pwm_ch
spin_lock_irqsave(&mvpwm->lock, flags);
- writel_relaxed(on, mvebu_pwmreg_blink_on_duration(mvpwm));
- writel_relaxed(off, mvebu_pwmreg_blink_off_duration(mvpwm));
+ mvebu_pwmreg_write_blink_on_duration(mvpwm, on);
+ mvebu_pwmreg_write_blink_off_duration(mvpwm, off);
if (state->enabled)
mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 1);
else
@@ -753,9 +825,9 @@ static void __maybe_unused mvebu_pwm_sus
regmap_read(mvchip->regs, GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset,
&mvpwm->blink_select);
mvpwm->blink_on_duration =
- readl_relaxed(mvebu_pwmreg_blink_on_duration(mvpwm));
+ mvebu_pwmreg_read_blink_on_duration(mvpwm);
mvpwm->blink_off_duration =
- readl_relaxed(mvebu_pwmreg_blink_off_duration(mvpwm));
+ mvebu_pwmreg_read_blink_off_duration(mvpwm);
}
static void __maybe_unused mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
@@ -764,19 +836,18 @@ static void __maybe_unused mvebu_pwm_res
regmap_write(mvchip->regs, GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset,
mvpwm->blink_select);
- writel_relaxed(mvpwm->blink_on_duration,
- mvebu_pwmreg_blink_on_duration(mvpwm));
- writel_relaxed(mvpwm->blink_off_duration,
- mvebu_pwmreg_blink_off_duration(mvpwm));
+ mvebu_pwmreg_write_blink_on_duration(mvpwm, mvpwm->blink_on_duration);
+ mvebu_pwmreg_write_blink_off_duration(mvpwm, mvpwm->blink_off_duration);
}
-static int mvebu_pwm_probe(struct platform_device *pdev,
+static int mvebu_pwm_probe_raw(struct platform_device *pdev,
struct mvebu_gpio_chip *mvchip,
int id)
{
struct device *dev = &pdev->dev;
struct mvebu_pwm *mvpwm;
struct resource *res;
+ const __be32 *base;
u32 set;
if (!of_device_is_compatible(mvchip->chip.of_node,
@@ -789,7 +860,7 @@ static int mvebu_pwm_probe(struct platfo
* for the first two GPIO chips. So if the resource is missing
* we can't treat it as an error.
*/
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm");
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res)
return 0;
@@ -800,12 +871,10 @@ static int mvebu_pwm_probe(struct platfo
* Use set A for lines of GPIO chip with id 0, B for GPIO chip
* with id 1. Don't allow further GPIO chips to be used for PWM.
*/
- if (id == 0)
- set = 0;
- else if (id == 1)
- set = U32_MAX;
- else
+ if (id >= 2)
return -EINVAL;
+ base = of_get_address(mvchip->chip.of_node, 1, NULL, NULL);
+ set = (be32_to_cpup(base) & 8) ? U32_MAX : 0;
regmap_write(mvchip->regs,
GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset, set);
@@ -841,6 +910,53 @@ static int mvebu_pwm_probe(struct platfo
return pwmchip_add(&mvpwm->chip);
}
+static int mvebu_pwm_probe_syscon(struct platform_device *pdev,
+ struct mvebu_gpio_chip *mvchip,
+ int id)
+{
+ struct device *dev = &pdev->dev;
+ struct mvebu_pwm *mvpwm;
+ u32 base = 0;
+ u32 set;
+
+ if (of_property_read_u32(pdev->dev.of_node, "pwm-reg", &base)) {
+// return -EINVAL;
+ base = 0x1f0 + 8 * (id % 2) ;
+ }
+
+ if (IS_ERR(mvchip->clk)) {
+ return PTR_ERR(mvchip->clk);
+ }
+
+ if (id == 0 || id >= 3) {
+ return -EINVAL;
+ }
+ set = (base & 8) ? U32_MAX : 0;
+ regmap_write(mvchip->regs,
+ GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset, set);
+
+ mvpwm = devm_kzalloc(dev, sizeof(struct mvebu_pwm), GFP_KERNEL);
+ if (!mvpwm)
+ return -ENOMEM;
+ mvchip->mvpwm = mvpwm;
+ mvpwm->mvchip = mvchip;
+ mvpwm->membase = NULL;
+ mvpwm->offset = base;
+ mvpwm->clk_rate = clk_get_rate(mvchip->clk);
+ if (!mvpwm->clk_rate) {
+ dev_err(dev, "failed to get clock rate\n");
+ return -EINVAL;
+ }
+
+ mvpwm->chip.dev = dev;
+ mvpwm->chip.ops = &mvebu_pwm_ops;
+ mvpwm->chip.npwm = mvchip->chip.ngpio;
+ mvpwm->chip.base = -1;
+
+ spin_lock_init(&mvpwm->lock);
+ return pwmchip_add(&mvpwm->chip);
+}
+
#ifdef CONFIG_DEBUG_FS
#include <linux/seq_file.h>
@@ -1132,8 +1248,12 @@ static int mvebu_gpio_probe(struct platf
mvchip->clk = devm_clk_get(&pdev->dev, NULL);
/* Not all SoCs require a clock.*/
- if (!IS_ERR(mvchip->clk))
+ if (IS_ERR(mvchip->clk) && PTR_ERR(mvchip->clk) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ }
+ if (!IS_ERR(mvchip->clk)) {
clk_prepare_enable(mvchip->clk);
+ }
mvchip->soc_variant = soc_variant;
mvchip->chip.label = dev_name(&pdev->dev);
@@ -1260,8 +1380,20 @@ static int mvebu_gpio_probe(struct platf
}
/* Some MVEBU SoCs have simple PWM support for GPIO lines */
- if (IS_ENABLED(CONFIG_PWM))
- return mvebu_pwm_probe(pdev, mvchip, id);
+ if (IS_ENABLED(CONFIG_PWM)) {
+ switch (soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_A8K:
+ err = mvebu_pwm_probe_syscon(pdev, mvchip, id);
+ break;
+ default:
+ err = mvebu_pwm_probe_raw(pdev, mvchip, id);
+ break;
+ }
+ if (err) {
+ dev_err(&pdev->dev, "no PWM available\n");
+ /* fallthrou */
+ }
+ }
return 0;
Hi,
where did you get this patch?
Cheers,
Technical specification tables can not be displayed on mobile. Please view on desktop