mediatek: add LED support for Xiaomi Redmi Router AX6000

This commit is contained in:
coolsnowwolf 2022-11-18 19:51:59 +08:00
parent a01f34b7be
commit 6fb280ac46
7 changed files with 354 additions and 0 deletions

View File

@ -12,6 +12,10 @@
aliases {
serial0 = &uart0;
led-boot = &led_status_green;
led-failsafe = &led_status_yellow;
led-running = &led_status_white;
led-upgrade = &led_status_blue;
};
chosen {
@ -95,6 +99,13 @@
};
};
spi_led_pins: spic-pins-29-to-32 {
mux {
function = "spi";
groups = "spi1_2";
};
};
wf_2g_5g_pins: wf_2g_5g-pins {
mux {
function = "wifi";
@ -205,6 +216,73 @@
};
};
&spi1 {
pinctrl-names = "default";
pinctrl-0 = <&spi_led_pins>;
cs-gpios = <0>, <0>;
status = "okay";
miwifi-hm0807a-led@0x00 {
compatible = "xiaomi,HM0807A";
reg = <0x00>;
spi-max-frequency = <3000000>;
status_led@0 {
led@0 {
label = "red:status";
default-state = "on";
};
led_status_blue: led@1 {
label = "blue:status";
default-state = "off";
};
led_status_yellow: led@2 {
label = "yellow:status";
default-state = "off";
};
led_status_white: led@3 {
label = "white:status";
default-state = "off";
};
led_status_green: led@4 {
label = "green:status";
default-state = "off";
};
};
network_led@1 {
led@0 {
label = "red:network";
default-state = "off";
};
led@1 {
label = "blue:network";
default-state = "off";
};
led@2 {
label = "yellow:network";
default-state = "off";
};
led@3 {
label = "white:network";
default-state = "off";
};
led@4 {
label = "green:network";
default-state = "off";
};
};
};
};
&switch {
ports {
#address-cells = <1>;

View File

@ -0,0 +1,183 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2022 Furong Xu <xfr@outlook.com>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/spi/spi.h>
#include <linux/mutex.h>
#include <uapi/linux/uleds.h>
#define LED_COLOR_MAX 5
static unsigned char *led_color_table[LED_COLOR_MAX + 1] = {
"\x92\x49\x24\x92\x49\x24\x92\x49\x24", /* LED OFF */
"\xdb\x6d\x36\x92\x49\x24\x92\x49\x24", /* LED RED */
"\x92\x49\x24\x92\x49\x24\xdb\x6d\x36", /* LED BLUE */
"\xdb\x6d\x36\x9a\x49\x34\x92\x49\x24", /* LED YELLOW */
"\xda\x4d\x36\xdb\x6d\x36\xda\x49\x24", /* LED WHITE */
"\x92\x49\x24\xdb\x6d\x36\x92\x49\x24", /* LED GREEN */
};
struct spi_single_wire_chipdef {
unsigned char chip_num;
unsigned char led_ctrl_cmd_len;
};
struct spi_single_wire_led {
struct led_classdev ldev;
struct spi_device *spi;
char name[LED_MAX_NAME_SIZE];
struct mutex mutex;
const struct spi_single_wire_chipdef *cdef;
unsigned char *led_ctrl_cmd;
unsigned char chip_id;
unsigned char color_id;
};
static struct spi_single_wire_chipdef miwifi_hm0807a_led = {
.chip_num = 2, /* Redmi AX6000 has 2 LED chips */
.led_ctrl_cmd_len = 9, /* 9 bytes per LED chip */
};
static const struct spi_device_id spi_ids[] = {
{ .name = "HM0807A" },
{},
};
static const struct of_device_id spi_single_wire_dt_ids[] = {
{ .compatible = "xiaomi,HM0807A", .data = &miwifi_hm0807a_led },
{},
};
MODULE_DEVICE_TABLE(of, spi_single_wire_dt_ids);
static int spi_brightness_set_blocking(struct led_classdev *dev,
enum led_brightness brightness)
{
struct spi_single_wire_led *led = container_of(dev, struct spi_single_wire_led, ldev);
unsigned char *ctrl_cmd_head;
unsigned char *ctrl_cmd_payload;
unsigned char ctrl_cmd_len = led->cdef->chip_num * led->cdef->led_ctrl_cmd_len;
int ret = 0;
ctrl_cmd_head = kzalloc(ctrl_cmd_len + 2, GFP_KERNEL); /* 2 bytes pad is required */
if (!ctrl_cmd_head)
return -ENOMEM;
mutex_lock(&led->mutex);
ctrl_cmd_payload = ctrl_cmd_head + 1; /* skip the SOF byte */
if (brightness == LED_OFF) {
memcpy(led->led_ctrl_cmd, led_color_table[0], led->cdef->led_ctrl_cmd_len);
} else {
memcpy(led->led_ctrl_cmd, led_color_table[led->color_id + 1], led->cdef->led_ctrl_cmd_len);
}
memcpy(ctrl_cmd_payload, led->led_ctrl_cmd - led->cdef->led_ctrl_cmd_len * led->chip_id,
led->cdef->led_ctrl_cmd_len * led->cdef->chip_num);
ret = spi_write(led->spi, ctrl_cmd_head, ctrl_cmd_len + 2); /* 2 bytes pad */
mutex_unlock(&led->mutex);
kfree(ctrl_cmd_head);
return ret;
}
static int spi_single_wire_probe(struct spi_device *spi)
{
struct spi_single_wire_led *led;
const struct spi_single_wire_chipdef *cdef;
struct device *dev = &spi->dev;
struct device_node *np = dev_of_node(dev);
struct device_node *chip_node;
struct device_node *color_node;
const char *color_name = "";
unsigned char *chip_cmd_buffer;
const char *state;
unsigned char cur_chip_id = 0;
unsigned char cur_color_id = 0;
int ret;
int chip_count;
cdef = device_get_match_data(dev);
if (!cdef)
return -ENODEV;
chip_count = of_get_available_child_count(np);
dev_info(dev, "Device has %d LED chip(s)\n", chip_count);
if (chip_count == 0 || chip_count != cdef->chip_num)
return -ENODEV;
chip_cmd_buffer = devm_kzalloc(dev, cdef->led_ctrl_cmd_len * cdef->chip_num, GFP_KERNEL);
if (!chip_cmd_buffer)
return -ENOMEM;
for_each_available_child_of_node(np, chip_node) {
for_each_available_child_of_node(chip_node, color_node) {
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
/* all LEDs of one chip share one cmd_buffer */
led->led_ctrl_cmd = chip_cmd_buffer + cdef->led_ctrl_cmd_len * cur_chip_id;
of_property_read_string(color_node, "label", &color_name);
snprintf(led->name, sizeof(led->name), "%s", color_name);
led->spi = spi;
mutex_init(&led->mutex);
led->cdef = cdef;
led->chip_id = cur_chip_id;
led->color_id = cur_color_id;
led->ldev.name = led->name;
led->ldev.brightness = LED_OFF;
led->ldev.max_brightness = LED_FULL;
led->ldev.brightness_set_blocking = spi_brightness_set_blocking;
state = of_get_property(color_node, "default-state", NULL);
if (state) {
if (!strcmp(state, "on")) {
led->ldev.brightness = led->ldev.max_brightness;
}
/* default to LED_OFF already */
}
spi_brightness_set_blocking(&led->ldev, led->ldev.brightness);
ret = devm_led_classdev_register(&spi->dev, &led->ldev);
if (ret) {
mutex_destroy(&led->mutex);
return ret;
}
spi_set_drvdata(spi, led);
cur_color_id++;
}
cur_chip_id++;
cur_color_id = 0;
}
return 0;
}
static int spi_single_wire_remove(struct spi_device *spi)
{
/* module should never be removed from kernel */
return 0;
}
static struct spi_driver spi_single_wire_led_driver = {
.probe = spi_single_wire_probe,
.remove = spi_single_wire_remove,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = spi_single_wire_dt_ids,
},
.id_table = spi_ids,
};
module_spi_driver(spi_single_wire_led_driver);
MODULE_AUTHOR("Furong Xu <xfr@outlook.com>");
MODULE_DESCRIPTION("single wire SPI LED driver for Xiaomi Redmi AX6000");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("spi:leds-spi-single-wire");

View File

@ -0,0 +1,18 @@
. /lib/functions/leds.sh
. /lib/functions/uci-defaults.sh
board=$(board_name)
board_config_update
case $board in
xiaomi,redmi-router-ax6000)
ucidef_set_led_netdev "wan" "wan" "blue:network" "wan"
ucidef_set_led_heartbeat "status" "status" "white:status"
;;
esac
board_config_flush
exit 0

View File

@ -143,6 +143,8 @@ define Device/xiaomi_redmi-router-ax6000
DEVICE_MODEL := Redmi Router AX6000
DEVICE_DTS := mt7986a-xiaomi-redmi-router-ax6000
DEVICE_DTS_DIR := ../dts
DEVICE_PACKAGES := kmod-leds-spi-single-wire
KERNEL_LOADADDR := 0x48000000
UBINIZE_OPTS := -E 5
BLOCKSIZE := 128k
PAGESIZE := 2048

View File

@ -37,3 +37,18 @@ define KernelPackage/iio-mt6577-auxadc
$(call AddDepends/iio)
endef
$(eval $(call KernelPackage,iio-mt6577-auxadc))
define KernelPackage/leds-spi-single-wire
SUBMENU:=LED modules
TITLE:=SPI single wire LED support
DEPENDS:=@(TARGET_mediatek_filogic)
KCONFIG:=CONFIG_LEDS_SPI_SINGLE_WIRE
FILES:= $(LINUX_DIR)/drivers/leds/leds-spi-single-wire.ko
AUTOLOAD:=$(call AutoLoad,60,leds-spi-single-wire,1)
endef
define KernelPackage/leds-spi-single-wire/description
LED support for SPI LED controller with a single wire (MOSI)
endef
$(eval $(call KernelPackage,leds-spi-single-wire))

View File

@ -0,0 +1,29 @@
--- a/drivers/spi/spi-mt65xx.c
+++ b/drivers/spi/spi-mt65xx.c
@@ -1223,10 +1223,16 @@
goto err_disable_spi_hclk;
}
+ ret = clk_prepare_enable(mdata->sel_clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to enable sel_clk (%d)\n", ret);
+ goto err_disable_spi_clk;
+ }
+
ret = clk_set_parent(mdata->sel_clk, mdata->parent_clk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to clk_set_parent (%d)\n", ret);
- goto err_disable_spi_clk;
+ goto err_disable_spi_sel_clk;
}
mdata->spi_clk_hz = clk_get_rate(mdata->spi_clk);
@@ -1277,6 +1283,8 @@
err_disable_runtime_pm:
pm_runtime_disable(&pdev->dev);
+err_disable_spi_sel_clk:
+ clk_disable_unprepare(mdata->sel_clk);
err_disable_spi_clk:
clk_disable_unprepare(mdata->spi_clk);
err_disable_spi_hclk:

View File

@ -0,0 +1,29 @@
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -93,6 +93,7 @@
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
obj-$(CONFIG_LEDS_EL15203000) += leds-el15203000.o
obj-$(CONFIG_LEDS_SPI_BYTE) += leds-spi-byte.o
+obj-$(CONFIG_LEDS_SPI_SINGLE_WIRE) += leds-spi-single-wire.o
# LED Userspace Drivers
obj-$(CONFIG_LEDS_USER) += uleds.o
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -818,6 +818,16 @@
for controlling the brightness. Currently the following controller is
supported: Ubiquiti airCube ISP microcontroller based LED controller.
+config LEDS_SPI_SINGLE_WIRE
+ tristate "LED support for SPI LED controller with a single wire (MOSI)"
+ depends on LEDS_CLASS
+ depends on SPI
+ depends on OF
+ help
+ This option enables support for LED controller which use a single wire
+ for controlling the brightness. Currently the following controller is
+ supported: Xiaomi Redmi AX6000
+
config LEDS_TI_LMU_COMMON
tristate "LED driver for TI LMU"
depends on LEDS_CLASS