From 9370bb92b2d16684ee45cf24e879c93c509162da Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Thu, 19 Dec 2024 01:47:39 +0000
Subject: [PATCH] add wifi6 8852be driver

---
 kernel/drivers/hwmon/pwm-fan.c |  364 +++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 301 insertions(+), 63 deletions(-)

diff --git a/kernel/drivers/hwmon/pwm-fan.c b/kernel/drivers/hwmon/pwm-fan.c
index db0d15c..6298da5 100644
--- a/kernel/drivers/hwmon/pwm-fan.c
+++ b/kernel/drivers/hwmon/pwm-fan.c
@@ -1,42 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
 /*
  * pwm-fan.c - Hwmon driver for fans connected to PWM lines.
  *
  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
  *
  * Author: Kamil Debski <k.debski@samsung.com>
- *
- * 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; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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.
  */
 
 #include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
+#include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
+#include <linux/regulator/consumer.h>
 #include <linux/sysfs.h>
 #include <linux/thermal.h>
+#include <linux/timer.h>
+#include <soc/rockchip/rockchip_system_monitor.h>
 
 #define MAX_PWM 255
+
+struct thermal_trips {
+	int temp;
+	int state;
+};
 
 struct pwm_fan_ctx {
 	struct mutex lock;
 	struct pwm_device *pwm;
+	struct regulator *reg_en;
+
+	int irq;
+	atomic_t pulses;
+	unsigned int rpm;
+	u8 pulses_per_revolution;
+	ktime_t sample_start;
+	struct timer_list rpm_timer;
+
 	unsigned int pwm_value;
 	unsigned int pwm_fan_state;
 	unsigned int pwm_fan_max_state;
 	unsigned int *pwm_fan_cooling_levels;
 	struct thermal_cooling_device *cdev;
+	struct notifier_block thermal_nb;
+	struct thermal_trips *thermal_trips;
+	bool thermal_notifier_is_ok;
 };
+
+/* This handler assumes self resetting edge triggered interrupt. */
+static irqreturn_t pulse_handler(int irq, void *dev_id)
+{
+	struct pwm_fan_ctx *ctx = dev_id;
+
+	atomic_inc(&ctx->pulses);
+
+	return IRQ_HANDLED;
+}
+
+static void sample_timer(struct timer_list *t)
+{
+	struct pwm_fan_ctx *ctx = from_timer(ctx, t, rpm_timer);
+	unsigned int delta = ktime_ms_delta(ktime_get(), ctx->sample_start);
+	int pulses;
+
+	if (delta) {
+		pulses = atomic_read(&ctx->pulses);
+		atomic_sub(pulses, &ctx->pulses);
+		ctx->rpm = (unsigned int)(pulses * 1000 * 60) /
+			(ctx->pulses_per_revolution * delta);
+
+		ctx->sample_start = ktime_get();
+	}
+
+	mod_timer(&ctx->rpm_timer, jiffies + HZ);
+}
 
 static int  __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
 {
@@ -72,8 +112,8 @@
 	ctx->pwm_fan_state = i;
 }
 
-static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
-		       const char *buf, size_t count)
+static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
 {
 	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
 	unsigned long pwm;
@@ -90,23 +130,53 @@
 	return count;
 }
 
-static ssize_t show_pwm(struct device *dev,
-			struct device_attribute *attr, char *buf)
+static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
 {
 	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
 
 	return sprintf(buf, "%u\n", ctx->pwm_value);
 }
 
+static ssize_t rpm_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
 
-static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 0);
+	return sprintf(buf, "%u\n", ctx->rpm);
+}
+
+static SENSOR_DEVICE_ATTR_RW(pwm1, pwm, 0);
+static SENSOR_DEVICE_ATTR_RO(fan1_input, rpm, 0);
 
 static struct attribute *pwm_fan_attrs[] = {
 	&sensor_dev_attr_pwm1.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
 	NULL,
 };
 
-ATTRIBUTE_GROUPS(pwm_fan);
+static umode_t pwm_fan_attrs_visible(struct kobject *kobj, struct attribute *a,
+				     int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
+
+	/* Hide fan_input in case no interrupt is available  */
+	if (n == 1 && ctx->irq <= 0)
+		return 0;
+
+	return a->mode;
+}
+
+static const struct attribute_group pwm_fan_group = {
+	.attrs = pwm_fan_attrs,
+	.is_visible = pwm_fan_attrs_visible,
+};
+
+static const struct attribute_group *pwm_fan_groups[] = {
+	&pwm_fan_group,
+	NULL,
+};
 
 /* thermal cooling device callbacks */
 static int pwm_fan_get_max_state(struct thermal_cooling_device *cdev,
@@ -205,93 +275,234 @@
 	return 0;
 }
 
+static void pwm_fan_regulator_disable(void *data)
+{
+	regulator_disable(data);
+}
+
+static void pwm_fan_pwm_disable(void *__ctx)
+{
+	struct pwm_fan_ctx *ctx = __ctx;
+	pwm_disable(ctx->pwm);
+	del_timer_sync(&ctx->rpm_timer);
+}
+
+static int pwm_fan_get_thermal_trips(struct device *dev, char *porp_name,
+				     struct thermal_trips **trips)
+{
+	struct device_node *np = dev->of_node;
+	struct thermal_trips *thermal_trips;
+	const struct property *prop;
+	int count, i;
+
+	prop = of_find_property(np, porp_name, NULL);
+	if (!prop)
+		return -EINVAL;
+	if (!prop->value)
+		return -ENODATA;
+	count = of_property_count_u32_elems(np, porp_name);
+	if (count < 0)
+		return -EINVAL;
+	if (count % 2)
+		return -EINVAL;
+	thermal_trips = devm_kzalloc(dev,
+				     sizeof(*thermal_trips) * (count / 2 + 1),
+				     GFP_KERNEL);
+	if (!thermal_trips)
+		return -ENOMEM;
+
+	for (i = 0; i < count / 2; i++) {
+		of_property_read_u32_index(np, porp_name, 2 * i,
+					   &thermal_trips[i].temp);
+		of_property_read_u32_index(np, porp_name, 2 * i + 1,
+					   &thermal_trips[i].state);
+	}
+	thermal_trips[i].temp = 0;
+	thermal_trips[i].state = INT_MAX;
+
+	*trips = thermal_trips;
+
+	return 0;
+}
+
+static int pwm_fan_temp_to_state(struct pwm_fan_ctx *ctx, int temp)
+{
+	struct thermal_trips *trips = ctx->thermal_trips;
+	int i, state = 0;
+
+	for (i = 0; trips[i].state != INT_MAX; i++) {
+		if (temp >= trips[i].temp)
+			state = trips[i].state;
+	}
+
+	return state;
+}
+
+static int pwm_fan_thermal_notifier_call(struct notifier_block *nb,
+					 unsigned long event, void *data)
+{
+	struct pwm_fan_ctx *ctx = container_of(nb, struct pwm_fan_ctx, thermal_nb);
+	struct system_monitor_event_data *event_data = data;
+	int state, ret;
+
+	if (event != SYSTEM_MONITOR_CHANGE_TEMP)
+		return NOTIFY_OK;
+
+	state = pwm_fan_temp_to_state(ctx, event_data->temp);
+	if (state > ctx->pwm_fan_max_state)
+		return NOTIFY_BAD;
+	if (state == ctx->pwm_fan_state)
+		return NOTIFY_OK;
+
+	ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]);
+	if (ret)
+		return NOTIFY_BAD;
+
+	ctx->pwm_fan_state = state;
+
+	return NOTIFY_OK;
+}
+
+static int pwm_fan_register_thermal_notifier(struct device *dev,
+					     struct pwm_fan_ctx *ctx)
+{
+	if (pwm_fan_get_thermal_trips(dev, "rockchip,temp-trips",
+				      &ctx->thermal_trips))
+		return -EINVAL;
+
+	ctx->thermal_nb.notifier_call = pwm_fan_thermal_notifier_call;
+
+	return rockchip_system_monitor_register_notifier(&ctx->thermal_nb);
+}
+
 static int pwm_fan_probe(struct platform_device *pdev)
 {
 	struct thermal_cooling_device *cdev;
+	struct device *dev = &pdev->dev;
 	struct pwm_fan_ctx *ctx;
 	struct device *hwmon;
 	int ret;
 	struct pwm_state state = { };
+	u32 ppr = 2;
 
-	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
 	if (!ctx)
 		return -ENOMEM;
 
 	mutex_init(&ctx->lock);
 
-	ctx->pwm = devm_of_pwm_get(&pdev->dev, pdev->dev.of_node, NULL);
-	if (IS_ERR(ctx->pwm)) {
-		ret = PTR_ERR(ctx->pwm);
-
-		if (ret != -EPROBE_DEFER)
-			dev_err(&pdev->dev, "Could not get PWM: %d\n", ret);
-
-		return ret;
-	}
+	ctx->pwm = devm_of_pwm_get(dev, dev->of_node, NULL);
+	if (IS_ERR(ctx->pwm))
+		return dev_err_probe(dev, PTR_ERR(ctx->pwm), "Could not get PWM\n");
 
 	platform_set_drvdata(pdev, ctx);
 
+	ctx->irq = platform_get_irq_optional(pdev, 0);
+	if (ctx->irq == -EPROBE_DEFER)
+		return ctx->irq;
+
+	ctx->reg_en = devm_regulator_get_optional(dev, "fan");
+	if (IS_ERR(ctx->reg_en)) {
+		if (PTR_ERR(ctx->reg_en) != -ENODEV)
+			return PTR_ERR(ctx->reg_en);
+
+		ctx->reg_en = NULL;
+	} else {
+		ret = regulator_enable(ctx->reg_en);
+		if (ret) {
+			dev_err(dev, "Failed to enable fan supply: %d\n", ret);
+			return ret;
+		}
+		ret = devm_add_action_or_reset(dev, pwm_fan_regulator_disable,
+					       ctx->reg_en);
+		if (ret)
+			return ret;
+	}
+
 	ctx->pwm_value = MAX_PWM;
 
-	/* Set duty cycle to maximum allowed and enable PWM output */
 	pwm_init_state(ctx->pwm, &state);
+	/*
+	 * __set_pwm assumes that MAX_PWM * (period - 1) fits into an unsigned
+	 * long. Check this here to prevent the fan running at a too low
+	 * frequency.
+	 */
+	if (state.period > ULONG_MAX / MAX_PWM + 1) {
+		dev_err(dev, "Configured period too big\n");
+		return -EINVAL;
+	}
+
+	/* Set duty cycle to maximum allowed and enable PWM output */
 	state.duty_cycle = ctx->pwm->args.period - 1;
 	state.enabled = true;
 
 	ret = pwm_apply_state(ctx->pwm, &state);
 	if (ret) {
-		dev_err(&pdev->dev, "Failed to configure PWM\n");
+		dev_err(dev, "Failed to configure PWM: %d\n", ret);
 		return ret;
 	}
+	timer_setup(&ctx->rpm_timer, sample_timer, 0);
+	ret = devm_add_action_or_reset(dev, pwm_fan_pwm_disable, ctx);
+	if (ret)
+		return ret;
 
-	hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, "pwmfan",
-						       ctx, pwm_fan_groups);
-	if (IS_ERR(hwmon)) {
-		dev_err(&pdev->dev, "Failed to register hwmon device\n");
-		ret = PTR_ERR(hwmon);
-		goto err_pwm_disable;
+	of_property_read_u32(dev->of_node, "pulses-per-revolution", &ppr);
+	ctx->pulses_per_revolution = ppr;
+	if (!ctx->pulses_per_revolution) {
+		dev_err(dev, "pulses-per-revolution can't be zero.\n");
+		return -EINVAL;
 	}
 
-	ret = pwm_fan_of_get_cooling_data(&pdev->dev, ctx);
+	if (ctx->irq > 0) {
+		ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0,
+				       pdev->name, ctx);
+		if (ret) {
+			dev_err(dev, "Failed to request interrupt: %d\n", ret);
+			return ret;
+		}
+		ctx->sample_start = ktime_get();
+		mod_timer(&ctx->rpm_timer, jiffies + HZ);
+	}
+
+	hwmon = devm_hwmon_device_register_with_groups(dev, "pwmfan",
+						       ctx, pwm_fan_groups);
+	if (IS_ERR(hwmon)) {
+		dev_err(dev, "Failed to register hwmon device\n");
+		return PTR_ERR(hwmon);
+	}
+
+	ret = pwm_fan_of_get_cooling_data(dev, ctx);
 	if (ret)
-		goto err_pwm_disable;
+		return ret;
 
 	ctx->pwm_fan_state = ctx->pwm_fan_max_state;
+	if (IS_REACHABLE(CONFIG_ROCKCHIP_SYSTEM_MONITOR) &&
+	    of_find_property(dev->of_node, "rockchip,temp-trips", NULL)) {
+		ret = pwm_fan_register_thermal_notifier(dev, ctx);
+		if (ret)
+			dev_err(dev, "Failed to register thermal notifier: %d\n", ret);
+		else
+			ctx->thermal_notifier_is_ok = true;
+		return 0;
+	}
 	if (IS_ENABLED(CONFIG_THERMAL)) {
-		cdev = thermal_of_cooling_device_register(pdev->dev.of_node,
-							  "pwm-fan", ctx,
-							  &pwm_fan_cooling_ops);
+		cdev = devm_thermal_of_cooling_device_register(dev,
+			dev->of_node, "pwm-fan", ctx, &pwm_fan_cooling_ops);
 		if (IS_ERR(cdev)) {
-			dev_err(&pdev->dev,
-				"Failed to register pwm-fan as cooling device");
 			ret = PTR_ERR(cdev);
-			goto err_pwm_disable;
+			dev_err(dev,
+				"Failed to register pwm-fan as cooling device: %d\n",
+				ret);
+			return ret;
 		}
 		ctx->cdev = cdev;
 		thermal_cdev_update(cdev);
 	}
 
 	return 0;
-
-err_pwm_disable:
-	state.enabled = false;
-	pwm_apply_state(ctx->pwm, &state);
-
-	return ret;
 }
 
-static int pwm_fan_remove(struct platform_device *pdev)
-{
-	struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev);
-
-	thermal_cooling_device_unregister(ctx->cdev);
-	if (ctx->pwm_value)
-		pwm_disable(ctx->pwm);
-	return 0;
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int pwm_fan_suspend(struct device *dev)
+static int pwm_fan_disable(struct device *dev)
 {
 	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
 	struct pwm_args args;
@@ -299,7 +510,7 @@
 
 	pwm_get_args(ctx->pwm, &args);
 
-	if (ctx->pwm_value) {
+	if (ctx->pwm_value || ctx->thermal_notifier_is_ok) {
 		ret = pwm_config(ctx->pwm, 0, args.period);
 		if (ret < 0)
 			return ret;
@@ -307,7 +518,26 @@
 		pwm_disable(ctx->pwm);
 	}
 
+	if (ctx->reg_en) {
+		ret = regulator_disable(ctx->reg_en);
+		if (ret) {
+			dev_err(dev, "Failed to disable fan supply: %d\n", ret);
+			return ret;
+		}
+	}
+
 	return 0;
+}
+
+static void pwm_fan_shutdown(struct platform_device *pdev)
+{
+	pwm_fan_disable(&pdev->dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pwm_fan_suspend(struct device *dev)
+{
+	return pwm_fan_disable(dev);
 }
 
 static int pwm_fan_resume(struct device *dev)
@@ -317,7 +547,15 @@
 	unsigned long duty;
 	int ret;
 
-	if (ctx->pwm_value == 0)
+	if (ctx->reg_en) {
+		ret = regulator_enable(ctx->reg_en);
+		if (ret) {
+			dev_err(dev, "Failed to enable fan supply: %d\n", ret);
+			return ret;
+		}
+	}
+
+	if (ctx->pwm_value == 0 && !ctx->thermal_notifier_is_ok)
 		return 0;
 
 	pwm_get_args(ctx->pwm, &pargs);
@@ -339,7 +577,7 @@
 
 static struct platform_driver pwm_fan_driver = {
 	.probe		= pwm_fan_probe,
-	.remove		= pwm_fan_remove,
+	.shutdown	= pwm_fan_shutdown,
 	.driver	= {
 		.name		= "pwm-fan",
 		.pm		= &pwm_fan_pm,

--
Gitblit v1.6.2