From 6fb280ac466893fb416ef65c8209e76f1c0f4cc4 Mon Sep 17 00:00:00 2001 From: coolsnowwolf Date: Fri, 18 Nov 2022 19:51:59 +0800 Subject: [PATCH] mediatek: add LED support for Xiaomi Redmi Router AX6000 --- .../mt7986a-xiaomi-redmi-router-ax6000.dts | 78 ++++++++ .../drivers/leds/leds-spi-single-wire.c | 183 ++++++++++++++++++ .../filogic/base-files/etc/board.d/01_leds | 18 ++ target/linux/mediatek/image/filogic.mk | 2 + target/linux/mediatek/modules.mk | 15 ++ ...fix-spi1-clock-dependency-for-mt7986.patch | 29 +++ ...or-xiaomi-redmi-ax6000-spi-led-panel.patch | 29 +++ 7 files changed, 354 insertions(+) create mode 100644 target/linux/mediatek/files-5.15/drivers/leds/leds-spi-single-wire.c create mode 100644 target/linux/mediatek/filogic/base-files/etc/board.d/01_leds create mode 100644 target/linux/mediatek/patches-5.15/930-spi-mediatek-fix-spi1-clock-dependency-for-mt7986.patch create mode 100644 target/linux/mediatek/patches-5.15/931-led-mediatek-add-support-for-xiaomi-redmi-ax6000-spi-led-panel.patch diff --git a/target/linux/mediatek/dts/mt7986a-xiaomi-redmi-router-ax6000.dts b/target/linux/mediatek/dts/mt7986a-xiaomi-redmi-router-ax6000.dts index ee493a751..7f8ce89e1 100644 --- a/target/linux/mediatek/dts/mt7986a-xiaomi-redmi-router-ax6000.dts +++ b/target/linux/mediatek/dts/mt7986a-xiaomi-redmi-router-ax6000.dts @@ -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>; diff --git a/target/linux/mediatek/files-5.15/drivers/leds/leds-spi-single-wire.c b/target/linux/mediatek/files-5.15/drivers/leds/leds-spi-single-wire.c new file mode 100644 index 000000000..9a59ca728 --- /dev/null +++ b/target/linux/mediatek/files-5.15/drivers/leds/leds-spi-single-wire.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022 Furong Xu + +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("single wire SPI LED driver for Xiaomi Redmi AX6000"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:leds-spi-single-wire"); diff --git a/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds b/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds new file mode 100644 index 000000000..45ede576f --- /dev/null +++ b/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds @@ -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 diff --git a/target/linux/mediatek/image/filogic.mk b/target/linux/mediatek/image/filogic.mk index 94c1e210f..7f133645c 100644 --- a/target/linux/mediatek/image/filogic.mk +++ b/target/linux/mediatek/image/filogic.mk @@ -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 diff --git a/target/linux/mediatek/modules.mk b/target/linux/mediatek/modules.mk index f46a6ad2f..e776047ed 100644 --- a/target/linux/mediatek/modules.mk +++ b/target/linux/mediatek/modules.mk @@ -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)) diff --git a/target/linux/mediatek/patches-5.15/930-spi-mediatek-fix-spi1-clock-dependency-for-mt7986.patch b/target/linux/mediatek/patches-5.15/930-spi-mediatek-fix-spi1-clock-dependency-for-mt7986.patch new file mode 100644 index 000000000..bcb716ab2 --- /dev/null +++ b/target/linux/mediatek/patches-5.15/930-spi-mediatek-fix-spi1-clock-dependency-for-mt7986.patch @@ -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: diff --git a/target/linux/mediatek/patches-5.15/931-led-mediatek-add-support-for-xiaomi-redmi-ax6000-spi-led-panel.patch b/target/linux/mediatek/patches-5.15/931-led-mediatek-add-support-for-xiaomi-redmi-ax6000-spi-led-panel.patch new file mode 100644 index 000000000..38b33dcf9 --- /dev/null +++ b/target/linux/mediatek/patches-5.15/931-led-mediatek-add-support-for-xiaomi-redmi-ax6000-spi-led-panel.patch @@ -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