From da9ffbf4a0e7fecb93bcc8e747a09e9b59c1d64c Mon Sep 17 00:00:00 2001
From: "deepak.s" <deepak.s@adlinktech.com>
Date: Thu, 16 Oct 2025 10:45:59 +0530
Subject: [PATCH 2/9] LECMTKI1200: enable adlink config ethernet dsi panel
 support

Signed-off-by: deepak.s <deepak.s@adlinktech.com>
---
 arch/arm64/Kconfig.platforms                  |   5 +
 arch/arm64/boot/dts/mediatek/Makefile         |   4 +-
 drivers/gpu/drm/panel/panel-simple.c          |  36 +++++
 drivers/misc/Kconfig                          |   8 +
 drivers/misc/Makefile                         |   2 +
 drivers/misc/eeprom/at24.c                    |  47 +++++-
 drivers/misc/sku_pcb_info.c                   | 138 ++++++++++++++++++
 .../ethernet/stmicro/stmmac/stmmac_platform.c |  67 +++++++++
 drivers/net/phy/dp83867.c                     |  39 ++++-
 9 files changed, 341 insertions(+), 5 deletions(-)
 create mode 100644 drivers/misc/sku_pcb_info.c

diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index d7772a4c34fe..bc787f08c10c 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -164,6 +164,11 @@ config ARCH_MEDIATEK
 	  This enables support for MediaTek MT27xx, MT65xx, MT76xx
 	  & MT81xx ARMv8 SoCs
 
+config ARCH_ADLINKTECH
+       bool "Support Adlinktech board"
+       help
+         This enables support for Adlinktech boards
+
 config ARCH_MESON
 	bool "Amlogic Platforms"
 	select COMMON_CLK
diff --git a/arch/arm64/boot/dts/mediatek/Makefile b/arch/arm64/boot/dts/mediatek/Makefile
index 6900412303e3..d7da6e49612d 100644
--- a/arch/arm64/boot/dts/mediatek/Makefile
+++ b/arch/arm64/boot/dts/mediatek/Makefile
@@ -46,7 +46,6 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += mt8195-demo.dtb
 dtb-$(CONFIG_ARCH_MEDIATEK) += mt8195-evb.dtb
 dtb-$(CONFIG_ARCH_MEDIATEK) += mt8195-evb-ufs.dtb
 dtb-$(CONFIG_ARCH_MEDIATEK) += genio-700-evk.dtb
-dtb-$(CONFIG_ARCH_MEDIATEK) += genio-1200-evk.dtb
 dtb-$(CONFIG_ARCH_MEDIATEK) += genio-1200-evk-p1v1.dtb
 dtb-$(CONFIG_ARCH_MEDIATEK) += genio-1200-evk-ufs.dtb
 dtb-$(CONFIG_ARCH_MEDIATEK) += mt8365-evk.dtb
@@ -57,3 +56,6 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += genio-510-evk.dtb
 dtb-$(CONFIG_ARCH_MEDIATEK) += panel-boe-ne135fbm.dtbo
 dtb-$(CONFIG_ARCH_MEDIATEK) += display-dp.dtbo
 dtb-$(CONFIG_ARCH_MEDIATEK) += vcodec-vcu-mt8395.dtbo
+#dtb-$(CONFIG_ARCH_ADLINKTECH) += lec-mtk-i1200.dtb
+#dtb-$(CONFIG_ARCH_ADLINKTECH) += lec-mtk-i1200-ufs.dtb
+
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index b6e9e2ac863b..18188188c4e4 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -3992,7 +3992,38 @@ struct panel_desc_dsi {
 	enum mipi_dsi_pixel_format format;
 	unsigned int lanes;
 };
+#ifdef CONFIG_ARCH_ADLINKTECH
+static const struct drm_display_mode auo_b101uan01v7_mode = {
+	.clock = 148500,
+	.hdisplay = 1920,
+	.hsync_start = 1920 + 77,
+	.hsync_end = 1920 + 77 + 56,
+	.htotal = 1920 + 77 + 56 + 117,
+	.vdisplay = 1200,
+	.vsync_start = 1200 + 11,
+	.vsync_end = 1200 + 11 + 19,
+	.vtotal = 1200 + 11 + 19 + 19,
+};
 
+static const struct panel_desc_dsi auo_b101uan01v7 = {
+	.desc = {
+		.modes = &auo_b101uan01v7_mode,
+		.num_modes = 1,
+		.bpc = 8,
+		.size = {
+		    .width = 293,
+		    .height = 165,
+		},
+		.connector_type = DRM_MODE_CONNECTOR_DSI,
+	},
+	.flags = MIPI_DSI_MODE_VIDEO_HSE
+	    | MIPI_DSI_MODE_VIDEO
+	    | MIPI_DSI_MODE_VIDEO_SYNC_PULSE
+	    | MIPI_DSI_MODE_LPM,
+	.format = MIPI_DSI_FMT_RGB888,
+	.lanes = 4,
+};
+#endif
 static const struct drm_display_mode auo_b080uan01_mode = {
 	.clock = 154500,
 	.hdisplay = 1200,
@@ -4197,6 +4228,11 @@ static const struct panel_desc_dsi osd101t2045_53ts = {
 
 static const struct of_device_id dsi_of_match[] = {
 	{
+#ifdef CONFIG_ARCH_ADLINKTECH
+		.compatible = "auo,b101uan01v7",
+		.data = &auo_b101uan01v7
+	}, {
+#endif
 		.compatible = "auo,b080uan01",
 		.data = &auo_b080uan01
 	}, {
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6208afacd747..dfd8a71dc9aa 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -470,6 +470,14 @@ config HISI_HIKEY_USB
 	  switching between the dual-role USB-C port and the USB-A host ports
 	  using only one USB controller.
 
+config ADLINK_SKU_PCB_INFO
+	tristate "ADLINK SKU and PCB Information driver"
+	default n
+	help
+	  Say Y here if you want to enable a driver for reading SKU and PCB information.
+	  This is not linked to any specific hardware category and is a general driver.
+
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 5be7fe9d3687..4e2442b3516b 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -60,3 +60,5 @@ obj-$(CONFIG_UACCE)		+= uacce/
 obj-$(CONFIG_XILINX_SDFEC)	+= xilinx_sdfec.o
 obj-$(CONFIG_HISI_HIKEY_USB)	+= hisi_hikey_usb.o
 obj-$(CONFIG_HI6421V600_IRQ)	+= hi6421v600-irq.o
+obj-$(CONFIG_ADLINK_SKU_PCB_INFO) += sku_pcb_info.o
+
diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c
index 02bea4436943..0928a178ced3 100644
--- a/drivers/misc/eeprom/at24.c
+++ b/drivers/misc/eeprom/at24.c
@@ -39,7 +39,10 @@
 #define AT24_FLAG_MAC		BIT(2)
 /* Does not auto-rollover reads to the next slave address. */
 #define AT24_FLAG_NO_RDROL	BIT(1)
-
+#ifdef CONFIG_ARCH_ADLINKTECH
+#include <linux/sysfs.h>
+#define AT24_WP_OFFSET		128
+#endif
 /*
  * I2C EEPROMs from most vendors are inexpensive and mostly interchangeable.
  * Differences between different vendor product lines (like Atmel AT24C or
@@ -87,7 +90,9 @@ struct at24_data {
 	u32 byte_len;
 	u16 page_size;
 	u8 flags;
-
+#ifdef CONFIG_ARCH_ADLINKTECH
+	bool write_protect;
+#endif
 	struct nvmem_device *nvmem;
 	struct regulator *vcc_reg;
 	void (*read_post)(unsigned int off, char *buf, size_t count);
@@ -472,6 +477,13 @@ static int at24_write(void *priv, unsigned int off, void *val, size_t count)
 	int ret;
 
 	at24 = priv;
+#ifdef CONFIG_ARCH_ADLINKTECH
+	/* Check if write protection is enabled */
+	if (at24->write_protect && off < AT24_WP_OFFSET) {
+		printk("Write protection is enabled for the first 128 bytes.\n");
+		return -EPERM;
+	}
+#endif
 	dev = at24_base_client_dev(at24);
 
 	if (unlikely(!count))
@@ -609,7 +621,29 @@ static void at24_probe_temp_sensor(struct i2c_client *client)
 
 	i2c_new_client_device(client->adapter, &info);
 }
+ 
+#ifdef CONFIG_ARCH_ADLINKTECH
+static ssize_t write_protect_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct at24_data *at24 = i2c_get_clientdata(to_i2c_client(dev));
+	return sprintf(buf, "%u\n", at24->write_protect ? 1 : 0);
+}
+
+static ssize_t write_protect_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct at24_data *at24 = i2c_get_clientdata(to_i2c_client(dev));
+	unsigned long val;
+
+	if (kstrtoul(buf, 10, &val))
+		return -EINVAL;
+
+	at24->write_protect = val ? true : false;
+	return count;
+}
+
+static DEVICE_ATTR_RW(write_protect);
 
+#endif
 static int at24_probe(struct i2c_client *client)
 {
 	struct regmap_config regmap_config = { };
@@ -718,7 +752,14 @@ static int at24_probe(struct i2c_client *client)
 	at24->offset_adj = at24_get_offset_adj(flags, byte_len);
 	at24->client[0].client = client;
 	at24->client[0].regmap = regmap;
-
+#ifdef CONFIG_ARCH_ADLINKTECH
+    err = sysfs_create_file(&client->dev.kobj, &dev_attr_write_protect.attr);
+    if (err) {
+        dev_err(&client->dev, "Unable to create sysfs attribute for write protection\n");
+        return err;
+    }
+	at24->write_protect = true;
+#endif
 	at24->vcc_reg = devm_regulator_get(dev, "vcc");
 	if (IS_ERR(at24->vcc_reg))
 		return PTR_ERR(at24->vcc_reg);
diff --git a/drivers/misc/sku_pcb_info.c b/drivers/misc/sku_pcb_info.c
new file mode 100644
index 000000000000..9c7fd11e6b1c
--- /dev/null
+++ b/drivers/misc/sku_pcb_info.c
@@ -0,0 +1,138 @@
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/kobject.h>
+
+#define SKU_REG 0x10005220
+
+#define SKU_BIT0 0
+#define SKU_BIT1 1
+#define SKU_BIT2 2
+#define SKU_BIT3 3
+#define SKU_BIT4 4
+
+#define PCB_REG         0x10005230
+#define PCB_BIT0 20
+#define PCB_BIT1 21
+
+#define PCB_PUPD_REG    0x11F40030
+#define PCB_PUPD_BIT0   9
+#define PCB_PUPD_BIT1   8
+
+struct sku_info {
+    uint32_t sku_id;
+    char* sku_str;
+};
+
+struct sku_info skus[] = {
+    {0, "LEC-MTK-I1200-2G-64G-CT(000E)"},
+    {1, "LEC-MTK-I1200-4G-64G-CT(100E)"},
+    {2, "LEC-MTK-I1200-8G-64G-CT(200E)"},
+    {3, "LEC-MTK-I1200-2G-64G-BW-CT(300E)"},
+    {4, "LEC-MTK-I1200-4G-64G-BW-CT(400E)"},
+    {5, "LEC-MTK-I1200-8G-64G-BW-CT(500E)"},
+    {6, "LEC-MTK-I1200-4G-64G-BW-ER(600E)"},
+    {7, "LEC-MTK-I1200-8G-64G-BW-ER(700E)"},
+    {8, "LEC-MTK-I1200-8G-128G-BW-ER(800E)"}
+};
+
+static struct kobject *kobj_ref;
+
+static ssize_t pcb_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
+    void __iomem *pcb_reg;
+    void __iomem *pcb_pupd_reg;
+    uint32_t pcb_id_0, pcb_id_1;
+    uint32_t pcb_id;
+    uint32_t reg_value;
+
+    pcb_reg = ioremap(PCB_REG, sizeof(uint32_t));
+    pcb_pupd_reg = ioremap(PCB_PUPD_REG, sizeof(uint32_t));
+
+    reg_value = ioread32(pcb_pupd_reg);
+
+    reg_value &= ~(1 << PCB_PUPD_BIT0);
+    reg_value &= ~(1 << PCB_PUPD_BIT1);
+
+    iowrite32(reg_value, pcb_pupd_reg);
+
+    pcb_id_0 = (ioread32(pcb_reg) >> PCB_BIT0) & 0x1;
+    pcb_id_1 = (ioread32(pcb_reg) >> PCB_BIT1) & 0x1;
+
+    pcb_id = (pcb_id_1 << 1) | pcb_id_0;
+
+    iounmap(pcb_reg);
+    iounmap(pcb_pupd_reg);
+
+    switch (pcb_id) {
+    case 0:
+        return sprintf(buf, "PCB A1\n");
+    case 1:
+        return sprintf(buf, "PCB A2\n");
+    case 2:
+        return sprintf(buf, "PCB A3\n");
+    case 3:
+        return sprintf(buf, "PCB A4\n");
+    default:
+        return sprintf(buf, "Unknown PCB\n");
+    }
+}
+
+static ssize_t sku_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
+    void __iomem *sku_reg;
+    uint32_t sku_id_0, sku_id_1, sku_id_2, sku_id_3, sku_id_4;
+    uint32_t sku_id;
+    int i;
+
+    sku_reg = ioremap(SKU_REG, sizeof(uint32_t));
+
+    sku_id_0 = (ioread32(sku_reg) >> SKU_BIT0) & 0x1;
+    sku_id_1 = (ioread32(sku_reg) >> SKU_BIT1) & 0x1;
+    sku_id_2 = (ioread32(sku_reg) >> SKU_BIT2) & 0x1;
+    sku_id_3 = (ioread32(sku_reg) >> SKU_BIT3) & 0x1;
+    sku_id_4 = (ioread32(sku_reg) >> SKU_BIT4) & 0x1;
+
+    sku_id = (sku_id_4 << 4) | (sku_id_3 << 3) | (sku_id_2 << 2) | (sku_id_1 << 1) | sku_id_0;
+
+    for (i = 0; i < sizeof(skus)/sizeof(struct sku_info); i++) {
+        if (skus[i].sku_id == sku_id) {
+            iounmap(sku_reg);
+            return sprintf(buf, "%s\n", skus[i].sku_str);
+        }
+    }
+
+    iounmap(sku_reg);
+    return sprintf(buf, "Unknown SKU\n");
+}
+
+struct kobj_attribute pcb_attr = __ATTR_RO(pcb);
+struct kobj_attribute sku_attr = __ATTR_RO(sku);
+
+static int __init sysinfo_init(void)
+{
+    kobj_ref = kobject_create_and_add("sysinfo", kernel_kobj);
+    if (sysfs_create_file(kobj_ref, &pcb_attr.attr))
+    {
+        printk(KERN_INFO "Cannot create pcb sysfs file.\n");
+    }
+    if (sysfs_create_file(kobj_ref, &sku_attr.attr))
+    {
+        printk(KERN_INFO "Cannot create sku sysfs file.\n");
+    }
+    return 0;
+}
+
+static void __exit sysinfo_exit(void)
+{
+    kobject_put(kobj_ref);
+}
+
+module_init(sysinfo_init);
+module_exit(sysinfo_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tim Liang");
+MODULE_DESCRIPTION("A simple Linux driver for the SKU and PCB info.");
+MODULE_VERSION("1.0");
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
index 7d4b709698a8..88a997ce22a9 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
@@ -20,6 +20,57 @@
 
 #include "stmmac.h"
 #include "stmmac_platform.h"
+#include <linux/i2c.h>
+#include <linux/err.h>
+
+
+#ifdef CONFIG_ARCH_ADLINKTECH
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+int read_eeprom_mac_address(struct device *dev, unsigned char *mac)
+{
+    struct i2c_client *client;
+    struct device_node *eeprom_node;
+    int ret;
+
+    eeprom_node = of_parse_phandle(dev->of_node, "eeprom", 0);
+    if (!eeprom_node) {
+        dev_err(dev, "Failed to get EEPROM device node from device tree\n");
+        return -ENODEV;
+    }
+
+    client = of_find_i2c_device_by_node(eeprom_node);
+    if (!client) {
+        dev_err(dev, "Failed to get I2C client from device tree\n");
+        of_node_put(eeprom_node);
+        return -ENODEV;
+    } else {
+        dev_info(dev, "Successfully got I2C client from device tree\n");
+    }
+
+    ret = i2c_smbus_read_i2c_block_data(client, 0, 6, mac);
+    if (ret != 6) {
+        dev_err(dev, "Failed to read MAC address from EEPROM, ret: %d\n", ret);
+        ret = -EIO;
+    } else {
+        if (is_zero_ether_addr(mac) || is_broadcast_ether_addr(mac)) {
+            dev_err(dev, "Invalid MAC address read from EEPROM: %pM\n", mac);
+            ret = -EINVAL;
+        } else {
+            dev_info(dev, "Successfully read MAC address from EEPROM: %pM\n", mac);
+            ret = 0;
+        }
+    }
+
+    put_device(&client->dev);
+    of_node_put(eeprom_node);
+
+    return ret;
+}
+#endif
+
 
 #ifdef CONFIG_OF
 
@@ -425,6 +476,21 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac)
 	if (!plat)
 		return ERR_PTR(-ENOMEM);
 
+#ifdef CONFIG_ARCH_ADLINKTECH
+    if (read_eeprom_mac_address(&pdev->dev, mac)) {
+        rc = of_get_mac_address(np, mac);
+        if (rc) {
+            if (rc == -EPROBE_DEFER)
+                return ERR_PTR(rc);
+
+            eth_zero_addr(mac);
+        } else {
+            dev_info(&pdev->dev, "Successfully read MAC address from device tree: %pM\n", mac);
+        }
+    } else {
+        dev_info(&pdev->dev, "Successfully read MAC address from EEPROM: %pM\n", mac);
+    }
+#else
 	rc = of_get_mac_address(np, mac);
 	if (rc) {
 		if (rc == -EPROBE_DEFER)
@@ -432,6 +498,7 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac)
 
 		eth_zero_addr(mac);
 	}
+#endif
 
 	phy_mode = device_get_phy_mode(&pdev->dev);
 	if (phy_mode < 0)
diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c
index 76ca43108d99..5ad940191f13 100644
--- a/drivers/net/phy/dp83867.c
+++ b/drivers/net/phy/dp83867.c
@@ -56,7 +56,11 @@
 
 #define DP83867_SW_RESET	BIT(15)
 #define DP83867_SW_RESTART	BIT(14)
-
+/* Extended LED Registers */
+#if defined(CONFIG_ARCH_ADLINKTECH)
+#define DP83867_LEDCR1		0x0018
+#define DP83867_LEDCR2		0x0019
+#endif
 /* MICR Interrupt bits */
 #define MII_DP83867_MICR_AN_ERR_INT_EN		BIT(15)
 #define MII_DP83867_MICR_SPEED_CHNG_INT_EN	BIT(14)
@@ -813,6 +817,39 @@ static int dp83867_config_init(struct phy_device *phydev)
 	if (dp83867->port_mirroring != DP83867_PORT_MIRROING_KEEP)
 		dp83867_config_port_mirroring(phydev);
 
+#if defined(CONFIG_ARCH_ADLINKTECH)
+	/* LED Configuration for LEC-BASE 2.0 Board */
+	/* Set LED pins' function - set LED_0 1011, LED_1 0110, LED_2 0101, LED_GPIO 1111 */
+	/* LED_0    1011: Link established, blink for transmit or receive activity */
+	/* LED_1    0110: 100 BTX link established */
+	/* LED_2    0101: 1000BT link established */
+	/* LED_GPIO 1111: Reserved */
+	printk("%s: DP83867_LED config\n",__func__);
+	val = phy_read(phydev, DP83867_LEDCR1);
+	if (val < 0)
+		return val;
+
+	if ((val & 0xFFFF) != 0xF56B) {
+		val &= ~0xFFFF;
+		val |= 0xF56B;
+		ret = phy_write(phydev, DP83867_LEDCR1, val);
+		if (ret)
+			return ret;
+	}
+
+	/* Control LED outputs' ability - set LED_0, LED_1, LED_2 active low, LED_GPIO reserve */
+	val = phy_read(phydev, DP83867_LEDCR2);
+	if (val < 0)
+		return val;
+
+	if ((val & 0xFFFF) != 0x0000) {
+		val &= ~0xFFFF;
+		val |= 0x0000;
+		ret = phy_write(phydev, DP83867_LEDCR2, val);
+		if (ret)
+			return ret;
+	}
+#endif
 	/* Clock output selection if muxing property is set */
 	if (dp83867->set_clk_output) {
 		u16 mask = DP83867_IO_MUX_CFG_CLK_O_DISABLE;
-- 
2.43.0

