1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter.git synced 2025-03-09 15:40:20 +00:00

Update kernel 6.12 patches

This commit is contained in:
Ycarus (Yannick Chabanois) 2025-02-06 14:25:19 +01:00
parent 94a20cce9c
commit 4ca673613f
62 changed files with 7522 additions and 780 deletions

View file

@ -19,8 +19,8 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
+
#endif /* __KERNEL__ */
/*
@@ -314,6 +316,4 @@ static inline void *offset_to_ptr(const
/**
@@ -329,6 +331,4 @@ static inline void *offset_to_ptr(const
*/
#define prevent_tail_call_optimization() mb()

View file

@ -1,82 +0,0 @@
From: Tobias Wolf <dev-NTEO@vplace.de>
Subject: mm: Fix alloc_node_mem_map with ARCH_PFN_OFFSET calculation
An rt288x (ralink) based router (Belkin F5D8235 v1) does not boot with any
kernel beyond version 4.3 resulting in:
BUG: Bad page state in process swapper pfn:086ac
bisect resulted in:
a1c34a3bf00af2cede839879502e12dc68491ad5 is the first bad commit
commit a1c34a3bf00af2cede839879502e12dc68491ad5
Author: Laura Abbott <laura@labbott.name>
Date: Thu Nov 5 18:48:46 2015 -0800
mm: Don't offset memmap for flatmem
Srinivas Kandagatla reported bad page messages when trying to remove the
bottom 2MB on an ARM based IFC6410 board
BUG: Bad page state in process swapper pfn:fffa8
page:ef7fb500 count:0 mapcount:0 mapping: (null) index:0x0
flags: 0x96640253(locked|error|dirty|active|arch_1|reclaim|mlocked)
page dumped because: PAGE_FLAGS_CHECK_AT_FREE flag(s) set
bad because of flags:
flags: 0x200041(locked|active|mlocked)
Modules linked in:
CPU: 0 PID: 0 Comm: swapper Not tainted 3.19.0-rc3-00007-g412f9ba-dirty
#816
Hardware name: Qualcomm (Flattened Device Tree)
unwind_backtrace
show_stack
dump_stack
bad_page
free_pages_prepare
free_hot_cold_page
__free_pages
free_highmem_page
mem_init
start_kernel
Disabling lock debugging due to kernel taint
[...]
:040000 040000 2de013c372345fd471cd58f0553c9b38b0ef1cc4
0a8156f848733dfa21e16c196dfb6c0a76290709 M mm
This fix for ARM does not account ARCH_PFN_OFFSET for mem_map as later used by
page_to_pfn anymore.
The following output was generated with two hacked in printk statements:
printk("before %p vs. %p or %p\n", mem_map, mem_map - offset, mem_map -
(pgdat->node_start_pfn - ARCH_PFN_OFFSET));
if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
mem_map -= offset + (pgdat->node_start_pfn - ARCH_PFN_OFFSET);
printk("after %p\n", mem_map);
Output:
[ 0.000000] before 8861b280 vs. 8861b280 or 8851b280
[ 0.000000] after 8851b280
As seen in the first line mem_map with subtraction of offset does not equal the
mem_map after subtraction of ARCH_PFN_OFFSET.
After adding the offset of ARCH_PFN_OFFSET as well to mem_map as the
previously calculated offset is zero for the named platform it is able to boot
4.4 and 4.9-rc7 again.
Signed-off-by: Tobias Wolf <dev-NTEO@vplace.de>
---
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1632,7 +1632,7 @@ static void __init alloc_node_mem_map(st
if (pgdat == NODE_DATA(0)) {
mem_map = NODE_DATA(0)->node_mem_map;
if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
- mem_map -= offset;
+ mem_map -= offset + (pgdat->node_start_pfn - ARCH_PFN_OFFSET);
}
#endif
}

View file

@ -0,0 +1,165 @@
From: Felix Fietkau <nbd@nbd.name>
Subject: kernel: add a config option for keeping the kallsyms table uncompressed, saving ~9kb kernel size after lzma on ar71xx
[john@phrozen.org: added to my upstream queue 30.12.2016]
lede-commit: e0e3509b5ce2ccf93d4d67ea907613f5f7ec2eed
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
init/Kconfig | 11 +++++++++++
kernel/kallsyms.c | 8 ++++++++
scripts/kallsyms.c | 12 ++++++++++++
scripts/link-vmlinux.sh | 4 ++++
4 files changed, 35 insertions(+)
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1525,6 +1525,17 @@ config SYSCTL_ARCH_UNALIGN_ALLOW
the unaligned access emulation.
see arch/parisc/kernel/unaligned.c for reference
+config KALLSYMS_UNCOMPRESSED
+ bool "Keep kallsyms uncompressed"
+ depends on KALLSYMS
+ help
+ Normally kallsyms contains compressed symbols (using a token table),
+ reducing the uncompressed kernel image size. Keeping the symbol table
+ uncompressed significantly improves the size of this part in compressed
+ kernel images.
+
+ Say N unless you need compressed kernel images to be small.
+
config HAVE_PCSPKR_PLATFORM
bool
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -69,6 +69,11 @@ static unsigned int kallsyms_expand_symb
* For every byte on the compressed symbol data, copy the table
* entry for that byte.
*/
+#ifdef CONFIG_KALLSYMS_UNCOMPRESSED
+ memcpy(result, data + 1, len - 1);
+ result += len - 1;
+ len = 0;
+#endif
while (len) {
tptr = &kallsyms_token_table[kallsyms_token_index[*data]];
data++;
@@ -101,6 +106,9 @@ tail:
*/
static char kallsyms_get_symbol_type(unsigned int off)
{
+#ifdef CONFIG_KALLSYMS_UNCOMPRESSED
+ return kallsyms_names[off + 1];
+#endif
/*
* Get just the first code, look it up in the token table,
* and return the first char from this token.
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -62,6 +62,7 @@ static struct addr_range percpu_range =
static struct sym_entry **table;
static unsigned int table_size, table_cnt;
static int all_symbols;
+static int uncompressed;
static int absolute_percpu;
static int token_profit[0x10000];
@@ -412,12 +413,14 @@ static void write_src(void)
for (k = 0; k < table[i]->len; k++)
printf(", 0x%02x", table[i]->sym[k]);
- /*
- * Now that we wrote out the compressed symbol name, restore the
- * original name and print it in the comment.
- */
- expand_symbol(table[i]->sym, table[i]->len, buf);
- strcpy((char *)table[i]->sym, buf);
+ if (!uncompressed) {
+ /*
+ * Now that we wrote out the compressed symbol name, restore the
+ * original name and print it in the comment.
+ */
+ expand_symbol(table[i]->sym, table[i]->len, buf);
+ strcpy((char *)table[i]->sym, buf);
+ }
printf("\t/* %s */\n", table[i]->sym);
}
printf("\n");
@@ -429,20 +432,22 @@ static void write_src(void)
free(markers);
- output_label("kallsyms_token_table");
- off = 0;
- for (i = 0; i < 256; i++) {
- best_idx[i] = off;
- expand_symbol(best_table[i], best_table_len[i], buf);
- printf("\t.asciz\t\"%s\"\n", buf);
- off += strlen(buf) + 1;
+ if (!uncompressed) {
+ output_label("kallsyms_token_table");
+ off = 0;
+ for (i = 0; i < 256; i++) {
+ best_idx[i] = off;
+ expand_symbol(best_table[i], best_table_len[i], buf);
+ printf("\t.asciz\t\"%s\"\n", buf);
+ off += strlen(buf) + 1;
+ }
+ printf("\n");
+
+ output_label("kallsyms_token_index");
+ for (i = 0; i < 256; i++)
+ printf("\t.short\t%d\n", best_idx[i]);
+ printf("\n");
}
- printf("\n");
-
- output_label("kallsyms_token_index");
- for (i = 0; i < 256; i++)
- printf("\t.short\t%d\n", best_idx[i]);
- printf("\n");
output_label("kallsyms_offsets");
@@ -532,6 +537,9 @@ static unsigned char *find_token(unsigne
{
int i;
+ if (uncompressed)
+ return NULL;
+
for (i = 0; i < len - 1; i++) {
if (str[i] == token[0] && str[i+1] == token[1])
return &str[i];
@@ -604,6 +612,9 @@ static void optimize_result(void)
{
int i, best;
+ if (uncompressed)
+ return;
+
/* using the '\0' symbol last allows compress_symbols to use standard
* fast string functions */
for (i = 255; i >= 0; i--) {
@@ -763,6 +774,7 @@ int main(int argc, char **argv)
static const struct option long_options[] = {
{"all-symbols", no_argument, &all_symbols, 1},
{"absolute-percpu", no_argument, &absolute_percpu, 1},
+ {"uncompressed", no_argument, &uncompressed, 1},
{},
};
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -144,6 +144,10 @@ kallsyms()
kallsymopt="${kallsymopt} --absolute-percpu"
fi
+ if is_enabled CONFIG_KALLSYMS_UNCOMPRESSED; then
+ kallsymopt="${kallsymopt} --uncompressed"
+ fi
+
info KSYMS "${2}.S"
scripts/kallsyms ${kallsymopt} "${1}" > "${2}.S"

View file

@ -156,7 +156,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+device_initcall(blk_notifications_init);
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1689,4 +1689,15 @@ static inline bool bdev_can_atomic_write
@@ -1690,4 +1690,15 @@ static inline bool bdev_can_atomic_write
#define DEFINE_IO_COMP_BATCH(name) struct io_comp_batch name = { }

View file

@ -30,7 +30,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
#define PACKET_FANOUT_LB 1
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -1925,6 +1925,7 @@ static int packet_rcv_spkt(struct sk_buf
@@ -1911,6 +1911,7 @@ static int packet_rcv_spkt(struct sk_buf
{
struct sock *sk;
struct sockaddr_pkt *spkt;
@ -38,7 +38,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
/*
* When we registered the protocol we saved the socket in the data
@@ -1932,6 +1933,7 @@ static int packet_rcv_spkt(struct sk_buf
@@ -1918,6 +1919,7 @@ static int packet_rcv_spkt(struct sk_buf
*/
sk = pt->af_packet_priv;
@ -46,7 +46,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
/*
* Yank back the headers [hope the device set this
@@ -1944,7 +1946,7 @@ static int packet_rcv_spkt(struct sk_buf
@@ -1930,7 +1932,7 @@ static int packet_rcv_spkt(struct sk_buf
* so that this procedure is noop.
*/
@ -55,7 +55,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
goto out;
if (!net_eq(dev_net(dev), sock_net(sk)))
@@ -2189,12 +2191,12 @@ static int packet_rcv(struct sk_buff *sk
@@ -2175,12 +2177,12 @@ static int packet_rcv(struct sk_buff *sk
int skb_len = skb->len;
unsigned int snaplen, res;
@ -71,7 +71,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (!net_eq(dev_net(dev), sock_net(sk)))
goto drop;
@@ -2318,12 +2320,12 @@ static int tpacket_rcv(struct sk_buff *s
@@ -2304,12 +2306,12 @@ static int tpacket_rcv(struct sk_buff *s
BUILD_BUG_ON(TPACKET_ALIGN(sizeof(*h.h2)) != 32);
BUILD_BUG_ON(TPACKET_ALIGN(sizeof(*h.h3)) != 48);
@ -87,7 +87,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (!net_eq(dev_net(dev), sock_net(sk)))
goto drop;
@@ -3444,6 +3446,7 @@ static int packet_create(struct net *net
@@ -3430,6 +3432,7 @@ static int packet_create(struct net *net
mutex_init(&po->pg_vec_lock);
po->rollover = NULL;
po->prot_hook.func = packet_rcv;
@ -95,7 +95,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (sock->type == SOCK_PACKET)
po->prot_hook.func = packet_rcv_spkt;
@@ -4111,6 +4114,16 @@ packet_setsockopt(struct socket *sock, i
@@ -4097,6 +4100,16 @@ packet_setsockopt(struct socket *sock, i
packet_sock_flag_set(po, PACKET_SOCK_QDISC_BYPASS, val);
return 0;
}
@ -112,7 +112,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
default:
return -ENOPROTOOPT;
}
@@ -4173,6 +4186,13 @@ static int packet_getsockopt(struct sock
@@ -4159,6 +4172,13 @@ static int packet_getsockopt(struct sock
case PACKET_COPY_THRESH:
val = READ_ONCE(pkt_sk(sk)->copy_thresh);
break;

View file

@ -96,7 +96,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
}
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -2525,7 +2525,7 @@ void sk_setup_caps(struct sock *sk, stru
@@ -2528,7 +2528,7 @@ void sk_setup_caps(struct sock *sk, stru
if (sk_is_tcp(sk))
sk->sk_route_caps |= NETIF_F_GSO;
if (sk->sk_route_caps & NETIF_F_GSO)
@ -107,7 +107,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
if (sk_can_gso(sk)) {
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2012,7 +2012,7 @@ void ieee80211_color_collision_detection
@@ -2010,7 +2010,7 @@ void ieee80211_color_collision_detection
/* interface handling */
#define MAC80211_SUPPORTED_FEATURES_TX (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | \
NETIF_F_HW_CSUM | NETIF_F_SG | \

View file

@ -18,7 +18,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -8607,7 +8607,7 @@ static int nft_register_flowtable_net_ho
@@ -8615,7 +8615,7 @@ static int nft_register_flowtable_net_ho
err = flowtable->data.type->setup(&flowtable->data,
hook->ops.dev,
FLOW_BLOCK_BIND);

View file

@ -10,12 +10,12 @@ the PHY.
Reported-by: Yevhen Kolomeiko <jarvis2709@gmail.com>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/realtek.c | 6 ++++++
drivers/net/phy/realtek/realtek_main.c | 6 ++++++
1 file changed, 6 insertions(+)
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -1375,6 +1375,7 @@ static struct phy_driver realtek_drvs[]
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1430,6 +1430,7 @@ static struct phy_driver realtek_drvs[]
}, {
.name = "RTL8226 2.5Gbps PHY",
.match_phy_device = rtl8226_match_phy_device,
@ -23,15 +23,15 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.read_status = rtl822x_read_status,
@@ -1387,6 +1388,7 @@ static struct phy_driver realtek_drvs[]
@@ -1440,6 +1441,7 @@ static struct phy_driver realtek_drvs[]
}, {
PHY_ID_MATCH_EXACT(0x001cc840),
.match_phy_device = rtl8221b_match_phy_device,
.name = "RTL8226B_RTL8221B 2.5Gbps PHY",
+ .soft_reset = genphy_soft_reset,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.config_init = rtl822xb_config_init,
@@ -1401,6 +1403,7 @@ static struct phy_driver realtek_drvs[]
@@ -1452,6 +1454,7 @@ static struct phy_driver realtek_drvs[]
}, {
PHY_ID_MATCH_EXACT(0x001cc838),
.name = "RTL8226-CG 2.5Gbps PHY",
@ -39,7 +39,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.read_status = rtl822x_read_status,
@@ -1411,6 +1414,7 @@ static struct phy_driver realtek_drvs[]
@@ -1462,6 +1465,7 @@ static struct phy_driver realtek_drvs[]
}, {
PHY_ID_MATCH_EXACT(0x001cc848),
.name = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
@ -47,35 +47,35 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.config_init = rtl822xb_config_init,
@@ -1423,6 +1427,7 @@ static struct phy_driver realtek_drvs[]
@@ -1474,6 +1478,7 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
.name = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
+ .soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.config_init = rtl822xb_config_init,
@@ -1435,6 +1440,7 @@ static struct phy_driver realtek_drvs[]
@@ -1487,6 +1492,7 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
.name = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
+ .soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
.config_init = rtl822xb_config_init,
.get_rate_matching = rtl822xb_get_rate_matching,
.get_features = rtl822x_c45_get_features,
@@ -1445,6 +1451,7 @@ static struct phy_driver realtek_drvs[]
@@ -1498,6 +1504,7 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vn_cg_c22_match_phy_device,
.name = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
+ .soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
.config_init = rtl822xb_config_init,
@@ -1457,6 +1464,7 @@ static struct phy_driver realtek_drvs[]
@@ -1511,6 +1518,7 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vn_cg_c45_match_phy_device,
.name = "RTL8221B-VN-CG 2.5Gbps PHY (C45)",
+ .soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
.config_init = rtl822xb_config_init,
.get_rate_matching = rtl822xb_get_rate_matching,
.get_features = rtl822x_c45_get_features,

View file

@ -15,12 +15,12 @@ Reported-by: Yevhen Kolomeiko <jarvis2709@gmail.com>
Tested-by: Yevhen Kolomeiko <jarvis2709@gmail.com>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/realtek.c | 27 +++++++++++++++++++++++++--
drivers/net/phy/realtek/realtek_main.c | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -814,8 +814,8 @@ static int rtl822x_write_mmd(struct phy_
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -834,8 +834,8 @@ static int rtl822x_probe(struct phy_devi
static int rtl822xb_config_init(struct phy_device *phydev)
{
bool has_2500, has_sgmii;
@ -30,7 +30,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
has_2500 = test_bit(PHY_INTERFACE_MODE_2500BASEX,
phydev->host_interfaces) ||
@@ -865,7 +865,29 @@ static int rtl822xb_config_init(struct p
@@ -885,7 +885,29 @@ static int rtl822xb_config_init(struct p
if (ret < 0)
return ret;

View file

@ -13,12 +13,12 @@ rtl821x_write_page instead of 3 individually locked MDIO bus operations.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/realtek.c | 8 +++++---
drivers/net/phy/realtek/realtek_main.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -1092,9 +1092,11 @@ static bool rtlgen_supports_2_5gbps(stru
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1111,9 +1111,11 @@ static bool rtlgen_supports_2_5gbps(stru
{
int val;

View file

@ -1,100 +0,0 @@
From 9155098547fb1172d4fa536f3f6bc9d42f59d08c Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Sat, 22 Apr 2023 03:26:01 +0100
Subject: [PATCH] net: phy: realtek: setup ALDPS on RTL822x
Setup Link Down Power Saving Mode according the DTS property
just like for RTL821x 1GE PHYs.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/realtek.c | 11 +++++++++++
1 file changed, 11 insertions(+)
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -80,6 +80,10 @@
#define RTL822X_VND2_GANLPAR 0xa414
+#define RTL8221B_PHYCR1 0xa430
+#define RTL8221B_PHYCR1_ALDPS_EN BIT(2)
+#define RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN BIT(12)
+
#define RTL8366RB_POWER_SAVE 0x15
#define RTL8366RB_POWER_SAVE_ON BIT(12)
@@ -1152,6 +1156,25 @@ static int rtl8251b_c45_match_phy_device
return rtlgen_is_c45_match(phydev, RTL_8251B, true);
}
+static int rtl822x_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL8221B_PHYCR1);
+ if (val < 0)
+ return val;
+
+ if (of_property_read_bool(dev->of_node, "realtek,aldps-enable"))
+ val |= RTL8221B_PHYCR1_ALDPS_EN | RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN;
+ else
+ val &= ~(RTL8221B_PHYCR1_ALDPS_EN | RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN);
+
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL8221B_PHYCR1, val);
+
+ return 0;
+}
+
static int rtlgen_resume(struct phy_device *phydev)
{
int ret = genphy_resume(phydev);
@@ -1427,6 +1450,7 @@ static struct phy_driver realtek_drvs[]
}, {
PHY_ID_MATCH_EXACT(0x001cc838),
.name = "RTL8226-CG 2.5Gbps PHY",
+ .probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
@@ -1438,6 +1462,7 @@ static struct phy_driver realtek_drvs[]
}, {
PHY_ID_MATCH_EXACT(0x001cc848),
.name = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY",
+ .probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
@@ -1451,6 +1476,7 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
.name = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
+ .probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
@@ -1464,6 +1490,7 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
.name = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
+ .probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.config_init = rtl822xb_config_init,
.get_rate_matching = rtl822xb_get_rate_matching,
@@ -1475,6 +1502,7 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vn_cg_c22_match_phy_device,
.name = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
+ .probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.get_features = rtl822x_get_features,
.config_aneg = rtl822x_config_aneg,
@@ -1488,6 +1516,7 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vn_cg_c45_match_phy_device,
.name = "RTL8221B-VN-CG 2.5Gbps PHY (C45)",
+ .probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.config_init = rtl822xb_config_init,
.get_rate_matching = rtl822xb_get_rate_matching,

View file

@ -0,0 +1,42 @@
From 9155098547fb1172d4fa536f3f6bc9d42f59d08c Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Sat, 22 Apr 2023 03:26:01 +0100
Subject: [PATCH] net: phy: realtek: setup ALDPS on RTL822x
Setup Link Down Power Saving Mode according the DTS property
just like for RTL821x 1GE PHYs.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/realtek/realtek_main.c | 11 +++++++++++
1 file changed, 11 insertions(+)
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -82,6 +82,10 @@
#define RTL822X_VND2_GANLPAR 0xa414
+#define RTL8221B_PHYCR1 0xa430
+#define RTL8221B_PHYCR1_ALDPS_EN BIT(2)
+#define RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN BIT(12)
+
#define RTL8366RB_POWER_SAVE 0x15
#define RTL8366RB_POWER_SAVE_ON BIT(12)
@@ -889,6 +893,15 @@ static int rtl822xb_config_init(struct p
if (ret < 0)
return ret;
+ if (of_property_read_bool(phydev->mdio.dev.of_node, "realtek,aldps-enable"))
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, RTL8221B_PHYCR1,
+ RTL8221B_PHYCR1_ALDPS_EN | RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN);
+ else
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, RTL8221B_PHYCR1,
+ RTL8221B_PHYCR1_ALDPS_EN | RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN);
+ if (ret < 0)
+ return ret;
+
/* Disable SGMII AN */
ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x7588, 0x2);
if (ret < 0)

View file

@ -12,9 +12,9 @@ over the implemented MMDs.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
[forward-port by @namiltd]
Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -1120,10 +1120,32 @@ static int rtl8226_match_phy_device(stru
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1166,10 +1166,32 @@ static int rtl8226_match_phy_device(stru
static int rtlgen_is_c45_match(struct phy_device *phydev, unsigned int id,
bool is_c45)
{
@ -49,4 +49,4 @@ Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
+ }
}
static int rtl8221b_vb_cg_c22_match_phy_device(struct phy_device *phydev)
static int rtl8221b_match_phy_device(struct phy_device *phydev)

View file

@ -7,12 +7,12 @@ This commit introduces interrupt support for RTL8221B.
Signed-off-by: Jianhui Zhao <zhaojh329@gmail.com>
---
drivers/net/phy/realtek.c | 47 +++++++++++++++++++++++++++++++++++++++
drivers/net/phy/realtek/realtek_main.c | 47 +++++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -1332,6 +1332,51 @@ static irqreturn_t rtl9000a_handle_inter
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1377,6 +1377,51 @@ static irqreturn_t rtl9000a_handle_inter
return IRQ_HANDLED;
}
@ -64,39 +64,39 @@ Signed-off-by: Jianhui Zhao <zhaojh329@gmail.com>
static struct phy_driver realtek_drvs[] = {
{
PHY_ID_MATCH_EXACT(0x00008201),
@@ -1498,6 +1543,8 @@ static struct phy_driver realtek_drvs[]
@@ -1537,6 +1582,8 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vb_cg_c22_match_phy_device,
.name = "RTL8221B-VB-CG 2.5Gbps PHY (C22)",
+ .config_intr = rtl8221b_config_intr,
+ .handle_interrupt = rtl8221b_handle_interrupt,
.probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
.get_features = rtl822x_get_features,
@@ -1512,6 +1559,8 @@ static struct phy_driver realtek_drvs[]
@@ -1551,6 +1598,8 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vb_cg_c45_match_phy_device,
.name = "RTL8221B-VB-CG 2.5Gbps PHY (C45)",
+ .config_intr = rtl8221b_config_intr,
+ .handle_interrupt = rtl8221b_handle_interrupt,
.probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
.config_init = rtl822xb_config_init,
@@ -1524,6 +1573,8 @@ static struct phy_driver realtek_drvs[]
@@ -1563,6 +1612,8 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vn_cg_c22_match_phy_device,
.name = "RTL8221B-VM-CG 2.5Gbps PHY (C22)",
+ .config_intr = rtl8221b_config_intr,
+ .handle_interrupt = rtl8221b_handle_interrupt,
.probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
.get_features = rtl822x_get_features,
@@ -1538,6 +1589,8 @@ static struct phy_driver realtek_drvs[]
@@ -1577,6 +1628,8 @@ static struct phy_driver realtek_drvs[]
}, {
.match_phy_device = rtl8221b_vn_cg_c45_match_phy_device,
.name = "RTL8221B-VN-CG 2.5Gbps PHY (C45)",
+ .config_intr = rtl8221b_config_intr,
+ .handle_interrupt = rtl8221b_handle_interrupt,
.probe = rtl822x_probe,
.soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
.config_init = rtl822xb_config_init,

View file

@ -0,0 +1,27 @@
From 1addfb042a9d27788a0fb2c2935045b56fd8560e Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 23 Jan 2025 03:25:29 +0000
Subject: [PATCH] net: phy: realtek: mark existing MMDs as present
When using Clause-45 mode to access RealTek RTL8221B 2.5G PHYs some
versions of the PHY fail to report the MMDs present on the PHY.
Mark MMDs PMAPMD, PCS and AN which are always existing according to
the datasheet as present to fix that.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/phy/realtek/realtek_main.c | 3 +++
1 file changed, 3 insertions(+)
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1043,6 +1043,9 @@ static int rtl822x_c45_get_features(stru
linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT,
phydev->supported);
+ phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_PCS |
+ MDIO_DEVS_AN;
+
return genphy_c45_pma_read_abilities(phydev);
}

View file

@ -0,0 +1,58 @@
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 30 Jan 2025 05:33:12 +0000
Subject: [PATCH] net: phy: realtek: work around broken SerDes
For still unknown reasons the SerDes init sequence may sometimes
time out because a self-clearing bit never clears, indicating the
PHY has entered an unrecoverable error state.
Work-around the issue by triggering a hardware reset and retry the
setup sequence while warning the user that this has happened.
This is really more of a work-around than a fix, and should be
replaced by a better actual fix in future (hopefully).
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -923,6 +923,22 @@ static int rtl822xb_config_init(struct p
return 0;
}
+static int rtl822xb_config_init_war(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = rtl822xb_config_init(phydev);
+
+ if (ret == -ETIMEDOUT) {
+ phydev_warn(phydev, "SerDes setup timed out, retrying\n");
+ phy_device_reset(phydev, 1);
+ phy_device_reset(phydev, 0);
+ ret = rtl822xb_config_init(phydev);
+ }
+
+ return ret;
+}
+
static int rtl822xb_get_rate_matching(struct phy_device *phydev,
phy_interface_t iface)
{
@@ -1605,7 +1621,7 @@ static struct phy_driver realtek_drvs[]
.handle_interrupt = rtl8221b_handle_interrupt,
.soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
- .config_init = rtl822xb_config_init,
+ .config_init = rtl822xb_config_init_war,
.get_rate_matching = rtl822xb_get_rate_matching,
.get_features = rtl822x_c45_get_features,
.config_aneg = rtl822x_c45_config_aneg,
@@ -1635,7 +1651,7 @@ static struct phy_driver realtek_drvs[]
.handle_interrupt = rtl8221b_handle_interrupt,
.soft_reset = genphy_soft_reset,
.probe = rtl822x_probe,
- .config_init = rtl822xb_config_init,
+ .config_init = rtl822xb_config_init_war,
.get_rate_matching = rtl822xb_get_rate_matching,
.get_features = rtl822x_c45_get_features,
.config_aneg = rtl822x_c45_config_aneg,

View file

@ -0,0 +1,27 @@
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 30 Jan 2025 05:38:31 +0000
Subject: [PATCH] net: phy: realtek: disable MDIO broadcast
RealTek's PHYs by default also listen on MDIO address 0 which is defined
as broadcast address. This can lead to problems if there is an actual PHY
(such as MT7981 built-in PHY) present at this address, as accessing that
PHY may then confuse the RealTek PHY.
Disabled listening on the MDIO broadcast address to avoid such problems.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -849,6 +849,11 @@ static int rtl822xb_config_init(struct p
phydev->host_interfaces) ||
phydev->interface == PHY_INTERFACE_MODE_SGMII;
+ /* disable listening on MDIO broadcast address (0) */
+ ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, 0xa430, BIT(13));
+ if (ret < 0)
+ return ret;
+
/* fill in possible interfaces */
__assign_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->possible_interfaces,
has_2500);

View file

@ -0,0 +1,104 @@
From: Felix Fietkau <nbd@nbd.name>
Subject: [PATCH net-next 3/4] net: ethernet: mtk_eth_soc: reduce rx ring size for older chipsets
Date: Tue, 15 Oct 2024 13:09:37 +0200
Commit c57e55819443 ("net: ethernet: mtk_eth_soc: handle dma buffer
size soc specific") resolved some tx timeout issues by bumping FQ and
tx ring sizes from 512 to 2048 entries (the value used in the MediaTek
SDK), however it also changed the rx ring size for all chipsets in the
same way.
Based on a few tests, it seems that a symmetric rx/tx ring size of 2048
really only makes sense on MT7988, which is capable of 10G ethernet links.
Older chipsets are typically deployed in systems that are more memory
constrained and don't actually need the larger rings to handle received
packets.
In order to reduce wasted memory set the ring size based on the SoC to
the following values:
- 2048 on MT7988
- 1024 on MT7986
- 512 (previous value) on everything else, except:
- 256 on RT5350 (the oldest supported chipset)
Fixes: c57e55819443 ("net: ethernet: mtk_eth_soc: handle dma buffer size soc specific")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -5387,7 +5387,7 @@ static const struct mtk_soc_data mt2701_
.desc_size = sizeof(struct mtk_rx_dma),
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
- .dma_size = MTK_DMA_SIZE(2K),
+ .dma_size = MTK_DMA_SIZE(512),
.dma_max_len = MTK_TX_DMA_BUF_LEN,
.dma_len_offset = 16,
},
@@ -5415,7 +5415,7 @@ static const struct mtk_soc_data mt7621_
.desc_size = sizeof(struct mtk_rx_dma),
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
- .dma_size = MTK_DMA_SIZE(2K),
+ .dma_size = MTK_DMA_SIZE(512),
.dma_max_len = MTK_TX_DMA_BUF_LEN,
.dma_len_offset = 16,
},
@@ -5445,7 +5445,7 @@ static const struct mtk_soc_data mt7622_
.desc_size = sizeof(struct mtk_rx_dma),
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
- .dma_size = MTK_DMA_SIZE(2K),
+ .dma_size = MTK_DMA_SIZE(512),
.dma_max_len = MTK_TX_DMA_BUF_LEN,
.dma_len_offset = 16,
},
@@ -5474,7 +5474,7 @@ static const struct mtk_soc_data mt7623_
.desc_size = sizeof(struct mtk_rx_dma),
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
- .dma_size = MTK_DMA_SIZE(2K),
+ .dma_size = MTK_DMA_SIZE(512),
.dma_max_len = MTK_TX_DMA_BUF_LEN,
.dma_len_offset = 16,
},
@@ -5500,7 +5500,7 @@ static const struct mtk_soc_data mt7629_
.desc_size = sizeof(struct mtk_rx_dma),
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
- .dma_size = MTK_DMA_SIZE(2K),
+ .dma_size = MTK_DMA_SIZE(512),
.dma_max_len = MTK_TX_DMA_BUF_LEN,
.dma_len_offset = 16,
},
@@ -5532,7 +5532,7 @@ static const struct mtk_soc_data mt7981_
.dma_l4_valid = RX_DMA_L4_VALID_V2,
.dma_max_len = MTK_TX_DMA_BUF_LEN,
.dma_len_offset = 16,
- .dma_size = MTK_DMA_SIZE(2K),
+ .dma_size = MTK_DMA_SIZE(512),
},
};
@@ -5562,7 +5562,7 @@ static const struct mtk_soc_data mt7986_
.dma_l4_valid = RX_DMA_L4_VALID_V2,
.dma_max_len = MTK_TX_DMA_BUF_LEN,
.dma_len_offset = 16,
- .dma_size = MTK_DMA_SIZE(2K),
+ .dma_size = MTK_DMA_SIZE(1K),
},
};
@@ -5615,7 +5615,7 @@ static const struct mtk_soc_data rt5350_
.dma_l4_valid = RX_DMA_L4_VALID_PDMA,
.dma_max_len = MTK_TX_DMA_BUF_LEN,
.dma_len_offset = 16,
- .dma_size = MTK_DMA_SIZE(2K),
+ .dma_size = MTK_DMA_SIZE(256),
},
};

View file

@ -0,0 +1,43 @@
From: Danila Romanov <pervokur@gmail.com>
Date: Wed, 22 Jan 2025 06:48:45 +0100
Subject: [PATCH] net: ethernet: mtk_eth_soc: do not enable page pool stats by
default
There is no reason for it to be enabled by default.
Align mtk_eth_soc driver to mt76 driver.
This option incurs additional CPU cost in allocation and recycle paths
and additional memory cost to store the statistics.
Signed-off-by: Danila Romanov <pervokur@gmail.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
--- a/drivers/net/ethernet/mediatek/Kconfig
+++ b/drivers/net/ethernet/mediatek/Kconfig
@@ -26,7 +26,6 @@ config NET_MEDIATEK_SOC
select PHYLINK
select DIMLIB
select PAGE_POOL
- select PAGE_POOL_STATS
select PCS_MTK_LYNXI
select REGMAP_MMIO
help
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -4552,6 +4552,7 @@ static int mtk_get_sset_count(struct net
static void mtk_ethtool_pp_stats(struct mtk_eth *eth, u64 *data)
{
+#ifdef CONFIG_PAGE_POOL_STATS
struct page_pool_stats stats = {};
int i;
@@ -4564,6 +4565,7 @@ static void mtk_ethtool_pp_stats(struct
page_pool_get_stats(ring->page_pool, &stats);
}
page_pool_ethtool_stats_get(data, &stats);
+#endif
}
static void mtk_get_ethtool_stats(struct net_device *dev,

View file

@ -0,0 +1,248 @@
From fd0e523037439520813db7c57df5bd37cdf40f7e Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Mon, 3 Feb 2025 00:10:18 +0100
Subject: [PATCH 1/2] nvmem: core: generalize "mac-base" cells handling
Generalize support of "mac-base" nvmem cells and provide a GPL symbol to
permit also other NVMEM layout driver to parse mac-base cells.
It's VERY COMMON for some specially formatted NVMEM to expose a mac
address in ASCII format or HEX format hence prevent code duplication by
exposing a common helper.
Such helper will change the nvmem_info_cell and apply the correct post
process function to correctly parse the mac address.
Since the API requires OF and is only related to layout, move the
function in layouts.c to correctly handle non-OF NVMEM.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/nvmem/core.c | 79 +--------------------------------
drivers/nvmem/layouts.c | 80 ++++++++++++++++++++++++++++++++++
include/linux/nvmem-provider.h | 4 ++
3 files changed, 86 insertions(+), 77 deletions(-)
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -7,12 +7,9 @@
*/
#include <linux/device.h>
-#include <linux/ctype.h>
-#include <linux/etherdevice.h>
#include <linux/export.h>
#include <linux/fs.h>
#include <linux/idr.h>
-#include <linux/if_ether.h>
#include <linux/init.h>
#include <linux/kref.h>
#include <linux/module.h>
@@ -800,62 +797,6 @@ static int nvmem_validate_keepouts(struc
return 0;
}
-static int nvmem_mac_base_raw_read(void *context, const char *id, int index, unsigned int offset,
- void *buf, size_t bytes)
-{
- if (WARN_ON(bytes != ETH_ALEN))
- return -EINVAL;
-
- if (index)
- eth_addr_add(buf, index);
-
- return 0;
-}
-
-static int nvmem_mac_base_ascii_read(void *context, const char *id, int index, unsigned int offset,
- void *buf, size_t bytes)
-{
- u8 mac[ETH_ALEN];
-
- if (WARN_ON(bytes != 3 * ETH_ALEN - 1))
- return -EINVAL;
-
- if (!mac_pton(buf, mac))
- return -EINVAL;
-
- if (index)
- eth_addr_add(mac, index);
-
- ether_addr_copy(buf, mac);
-
- return 0;
-}
-
-static int nvmem_mac_base_hex_read(void *context, const char *id, int index, unsigned int offset,
- void *buf, size_t bytes)
-{
- u8 mac[ETH_ALEN], *hexstr;
- int i;
-
- if (WARN_ON(bytes != 2 * ETH_ALEN))
- return -EINVAL;
-
- hexstr = (u8 *)buf;
- for (i = 0; i < ETH_ALEN; i++) {
- if (!isxdigit(hexstr[i * 2]) || !isxdigit(hexstr[i * 2 + 1]))
- return -EINVAL;
-
- mac[i] = (hex_to_bin(hexstr[i * 2]) << 4) | hex_to_bin(hexstr[i * 2 + 1]);
- }
-
- if (index)
- eth_addr_add(mac, index);
-
- ether_addr_copy(buf, mac);
-
- return 0;
-}
-
static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
{
struct device *dev = &nvmem->dev;
@@ -895,24 +836,8 @@ static int nvmem_add_cells_from_dt(struc
if (nvmem->fixup_dt_cell_info)
nvmem->fixup_dt_cell_info(nvmem, &info);
- if (of_device_is_compatible(np, "fixed-layout")) {
- if (of_device_is_compatible(child, "mac-base")) {
- if (info.bytes == ETH_ALEN) {
- info.raw_len = info.bytes;
- info.bytes = ETH_ALEN;
- info.read_post_process = nvmem_mac_base_raw_read;
- } else if (info.bytes == 2 * ETH_ALEN) {
- info.raw_len = info.bytes;
- info.bytes = ETH_ALEN;
- info.read_post_process = nvmem_mac_base_hex_read;
- } else if (info.bytes == 3 * ETH_ALEN - 1) {
- info.raw_len = info.bytes;
- info.bytes = ETH_ALEN;
- info.read_post_process = nvmem_mac_base_ascii_read;
- }
-
- }
- }
+ if (of_device_is_compatible(np, "fixed-layout"))
+ nvmem_layout_parse_mac_base(&info);
ret = nvmem_add_one_cell(nvmem, &info);
kfree(info.name);
--- a/drivers/nvmem/layouts.c
+++ b/drivers/nvmem/layouts.c
@@ -6,8 +6,11 @@
* Author: Miquel Raynal <miquel.raynal@bootlin.com
*/
+#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/if_ether.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
#include <linux/of.h>
@@ -21,6 +24,83 @@
#define to_nvmem_layout_device(_dev) \
container_of((_dev), struct nvmem_layout, dev)
+static int nvmem_mac_base_raw_read(void *context, const char *id, int index, unsigned int offset,
+ void *buf, size_t bytes)
+{
+ if (WARN_ON(bytes != ETH_ALEN))
+ return -EINVAL;
+
+ if (index)
+ eth_addr_add(buf, index);
+
+ return 0;
+}
+
+static int nvmem_mac_base_ascii_read(void *context, const char *id, int index, unsigned int offset,
+ void *buf, size_t bytes)
+{
+ u8 mac[ETH_ALEN];
+
+ if (WARN_ON(bytes != 3 * ETH_ALEN - 1))
+ return -EINVAL;
+
+ if (!mac_pton(buf, mac))
+ return -EINVAL;
+
+ if (index)
+ eth_addr_add(mac, index);
+
+ ether_addr_copy(buf, mac);
+
+ return 0;
+}
+
+static int nvmem_mac_base_hex_read(void *context, const char *id, int index, unsigned int offset,
+ void *buf, size_t bytes)
+{
+ u8 mac[ETH_ALEN], *hexstr;
+ int i;
+
+ if (WARN_ON(bytes != 2 * ETH_ALEN))
+ return -EINVAL;
+
+ hexstr = (u8 *)buf;
+ for (i = 0; i < ETH_ALEN; i++) {
+ if (!isxdigit(hexstr[i * 2]) || !isxdigit(hexstr[i * 2 + 1]))
+ return -EINVAL;
+
+ mac[i] = (hex_to_bin(hexstr[i * 2]) << 4) | hex_to_bin(hexstr[i * 2 + 1]);
+ }
+
+ if (index)
+ eth_addr_add(mac, index);
+
+ ether_addr_copy(buf, mac);
+
+ return 0;
+}
+
+void nvmem_layout_parse_mac_base(struct nvmem_cell_info *info)
+{
+ if (!of_device_is_compatible(info->np, "mac-base"))
+ return;
+
+ if (info->bytes == ETH_ALEN) {
+ info->raw_len = info->bytes;
+ info->bytes = ETH_ALEN;
+ info->read_post_process = nvmem_mac_base_raw_read;
+ } else if (info->bytes == 2 * ETH_ALEN) {
+ info->raw_len = info->bytes;
+ info->bytes = ETH_ALEN;
+ info->read_post_process = nvmem_mac_base_hex_read;
+ } else if (info->bytes == 3 * ETH_ALEN - 1) {
+ info->raw_len = info->bytes;
+ info->bytes = ETH_ALEN;
+ info->read_post_process = nvmem_mac_base_ascii_read;
+ }
+}
+EXPORT_SYMBOL_GPL(nvmem_layout_parse_mac_base);
+
static int nvmem_layout_bus_match(struct device *dev, const struct device_driver *drv)
{
return of_driver_match_device(dev, drv);
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -242,6 +242,8 @@ static inline void nvmem_layout_unregist
#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF)
+void nvmem_layout_parse_mac_base(struct nvmem_cell_info *info);
+
/**
* of_nvmem_layout_get_container() - Get OF node of layout container
*
@@ -254,6 +256,8 @@ struct device_node *of_nvmem_layout_get_
#else /* CONFIG_NVMEM && CONFIG_OF */
+static inline void nvmem_layout_parse_mac_base(struct nvmem_cell_info *info) {}
+
static inline struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
{
return NULL;

View file

@ -0,0 +1,204 @@
From 38287e8ec5c0281377fc70f11f20bcd9986a05f5 Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Mon, 3 Feb 2025 00:36:12 +0100
Subject: [PATCH 2/2] nvmem: layouts: add support for ascii-env driver
Add support for simple ASCII envirorment driver for NVMEM layouts.
It's very common for devices to store simple text file format in
partition for environment varibles. The most common pattern is variable
name, a delimiter and variable value all separated by a new line
character (\n).
This driver adds support for exporting such data and expose NVMEM cells
so they can be referenced by other drivers. This driver also supports
parsing mac-base NVMEM cells to parse ASCII or HEX mac address.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/nvmem/layouts/Kconfig | 13 +++
drivers/nvmem/layouts/Makefile | 1 +
drivers/nvmem/layouts/ascii-env.c | 140 ++++++++++++++++++++++++++++++
3 files changed, 154 insertions(+)
create mode 100644 drivers/nvmem/layouts/ascii-env.c
--- a/drivers/nvmem/layouts/Kconfig
+++ b/drivers/nvmem/layouts/Kconfig
@@ -37,6 +37,19 @@ config NVMEM_LAYOUT_U_BOOT_ENV
If unsure, say N.
+config NVMEM_LAYOUT_ASCII_ENV
+ tristate "ASCII environment variables layout"
+ help
+ It's very common for devices to store simple text file format in
+ partition for environment varibles. The most common pattern is variable
+ name, a delimiter and variable value all separated by a new line
+ character (\n).
+ This driver adds support for exporting such data and expose NVMEM cells
+ so they can be referenced by other drivers. This driver also supports
+ parsing mac-base NVMEM cells to parse ASCII or HEX mac address.
+
+ If unsure, say N.
+
endmenu
endif
--- a/drivers/nvmem/layouts/Makefile
+++ b/drivers/nvmem/layouts/Makefile
@@ -6,3 +6,4 @@
obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o
obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o
+obj-$(CONFIG_NVMEM_LAYOUT_ASCII_ENV) += ascii-env.o
--- /dev/null
+++ b/drivers/nvmem/layouts/ascii-env.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
+ *
+ * This borrow some parse logic from u-boot-env.
+ */
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+struct ascii_env_match_data {
+ const char delim;
+};
+
+/*
+ * Parse a buffer as an ASCII text with name delimiter value and each pattern separated
+ * with a new line char '\n'
+ * Example: (delimiter '=')
+ * name=value\nname2=value2\n
+ * 2 Cell:
+ * - name: value
+ * - name2: value2
+ */
+static int ascii_env_parse_cells(struct device *dev, struct nvmem_device *nvmem, uint8_t *buf,
+ size_t data_len, const char delim)
+{
+ char *var, *value, *eq, *lf;
+ char *data = buf;
+
+ /*
+ * Warning the inner loop take care of replacing '\n'
+ * with '\0', hence we can use strlen on value.
+ */
+ for (var = data; var < data + data_len && *var;
+ var = value + strlen(value) + 1) {
+ struct nvmem_cell_info info = {};
+ struct device_node *child;
+ const char *label;
+
+ eq = strchr(var, delim);
+ if (!eq)
+ break;
+ *eq = '\0';
+ value = eq + 1;
+
+ /* Replace '\n' with '\0' to use strlen for value */
+ lf = strchr(value, '\n');
+ if (!lf)
+ break;
+ *lf = '\0';
+
+ info.name = devm_kstrdup(dev, var, GFP_KERNEL);
+ if (!info.name)
+ return -ENOMEM;
+ info.offset = value - data;
+ info.bytes = strlen(value);
+ info.np = of_get_child_by_name(dev->of_node, info.name);
+ for_each_child_of_node(dev->of_node, child) {
+ if (!of_property_read_string(child, "label", &label) &&
+ !strncmp(info.name, label, info.bytes))
+ info.np = child;
+ else if (of_node_name_eq(child, info.name))
+ info.np = child;
+ }
+
+ nvmem_layout_parse_mac_base(&info);
+
+ nvmem_add_one_cell(nvmem, &info);
+ }
+
+ return 0;
+}
+
+static int ascii_env_add_cells(struct nvmem_layout *layout)
+{
+ struct nvmem_device *nvmem = layout->nvmem;
+ const struct ascii_env_match_data *data;
+ struct device *dev = &layout->dev;
+ size_t dev_size;
+ uint8_t *buf;
+ int bytes;
+ int ret;
+
+ /* Get the delimiter for name value pattern */
+ data = device_get_match_data(dev);
+
+ dev_size = nvmem_dev_size(nvmem);
+
+ buf = kzalloc(dev_size, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ bytes = nvmem_device_read(nvmem, 0, dev_size, buf);
+ if (bytes < 0) {
+ ret = bytes;
+ goto err_kfree;
+ } else if (bytes != dev_size) {
+ ret = -EIO;
+ goto err_kfree;
+ }
+
+ buf[dev_size - 1] = '\0';
+ ret = ascii_env_parse_cells(dev, nvmem, buf, dev_size, data->delim);
+
+err_kfree:
+ kfree(buf);
+err_out:
+ return ret;
+}
+
+static int ascii_env_probe(struct nvmem_layout *layout)
+{
+ layout->add_cells = ascii_env_add_cells;
+
+ return nvmem_layout_register(layout);
+}
+
+static void ascii_env_remove(struct nvmem_layout *layout)
+{
+ nvmem_layout_unregister(layout);
+}
+
+static const struct ascii_env_match_data ascii_env_eq = {
+ .delim = '=',
+};
+
+static const struct of_device_id ascii_env_of_match_table[] = {
+ { .compatible = "ascii-eq-delim-env", .data = &ascii_env_eq, },
+ {},
+};
+
+static struct nvmem_layout_driver ascii_env_layout = {
+ .driver = {
+ .name = "ascii-env-layout",
+ .of_match_table = ascii_env_of_match_table,
+ },
+ .probe = ascii_env_probe,
+ .remove = ascii_env_remove,
+};
+module_nvmem_layout_driver(ascii_env_layout);
+
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(of, ascii_env_of_match_table);
+MODULE_DESCRIPTION("NVMEM layout driver for ASCII environment variables");

View file

@ -0,0 +1,181 @@
From: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
To: Pavel Machek <pavel@ucw.cz>, Lee Jones <lee@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Jonathan Corbet <corbet@lwn.net>,
Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>,
linux-leds@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org
Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Subject: [PATCH v11 2/3] dt-bindings: leds: Add LED1202 LED Controller
Date: Wed, 18 Dec 2024 18:33:58 +0000 [thread overview]
Message-ID: <20241218183401.41687-3-vicentiu.galanopulo@remote-tech.co.uk> (raw)
In-Reply-To: <20241218183401.41687-1-vicentiu.galanopulo@remote-tech.co.uk>
The LED1202 is a 12-channel low quiescent current LED driver with:
* Supply range from 2.6 V to 5 V
* 20 mA current capability per channel
* 1.8 V compatible I2C control interface
* 8-bit analog dimming individual control
* 12-bit local PWM resolution
* 8 programmable patterns
If the led node is present in the controller then the channel is
set to active.
Signed-off-by: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
v1: https://lore.kernel.org/lkml/ZnCnnQfwuRueCIQ0@admins-Air/T/
v2: https://lore.kernel.org/all/ZniNdGgKyUMV-hjq@admins-Air/T/
v3: https://lore.kernel.org/all/ZniNdGgKyUMV-hjq@admins-Air/T/
Changes in v4:
- remove label property, use devm_led_classdev_register_ext instead
Changes in v3:
- remove active property
Changes in v2:
- renamed label to remove color from it
- add color property for each node
- add function and function-enumerator property for each node
.../devicetree/bindings/leds/st,led1202.yaml | 132 ++++++++++++++++++
1 file changed, 132 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/st,led1202.yaml
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/st,led1202.yaml
@@ -0,0 +1,132 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/st,led1202.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ST LED1202 LED controllers
+
+maintainers:
+ - Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
+
+description: |
+ The LED1202 is a 12-channel low quiescent current LED controller
+ programmable via I2C; The output current can be adjusted separately
+ for each channel by 8-bit analog and 12-bit digital dimming control.
+ Datasheet available at
+ https://www.st.com/en/power-management/led1202.html
+
+properties:
+ compatible:
+ const: st,led1202
+
+ reg:
+ maxItems: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^led@[0-9a-f]$":
+ type: object
+ $ref: common.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 11
+
+ required:
+ - reg
+
+required:
+ - compatible
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@58 {
+ compatible = "st,led1202";
+ reg = <0x58>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@0 {
+ reg = <0x0>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_RED>;
+ function-enumerator = <1>;
+ };
+
+ led@1 {
+ reg = <0x1>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_GREEN>;
+ function-enumerator = <2>;
+ };
+
+ led@2 {
+ reg = <0x2>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_BLUE>;
+ function-enumerator = <3>;
+ };
+
+ led@3 {
+ reg = <0x3>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_RED>;
+ function-enumerator = <4>;
+ };
+
+ led@4 {
+ reg = <0x4>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_GREEN>;
+ function-enumerator = <5>;
+ };
+
+ led@5 {
+ reg = <0x5>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_BLUE>;
+ function-enumerator = <6>;
+ };
+
+ led@6 {
+ reg = <0x6>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_RED>;
+ function-enumerator = <7>;
+ };
+
+ led@7 {
+ reg = <0x7>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_GREEN>;
+ function-enumerator = <8>;
+ };
+
+ led@8 {
+ reg = <0x8>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_BLUE>;
+ function-enumerator = <9>;
+ };
+ };
+ };
+...

View file

@ -0,0 +1,513 @@
From: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
To: Pavel Machek <pavel@ucw.cz>, Lee Jones <lee@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Jonathan Corbet <corbet@lwn.net>,
Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>,
linux-leds@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org
Subject: [PATCH v11 3/3] leds: Add LED1202 I2C driver
Date: Wed, 18 Dec 2024 18:33:59 +0000 [thread overview]
Message-ID: <20241218183401.41687-4-vicentiu.galanopulo@remote-tech.co.uk> (raw)
In-Reply-To: <20241218183401.41687-1-vicentiu.galanopulo@remote-tech.co.uk>
The output current can be adjusted separately for each channel by 8-bit
analog (current sink input) and 12-bit digital (PWM) dimming control. The
LED1202 implements 12 low-side current generators with independent dimming
control.
Internal volatile memory allows the user to store up to 8 different patterns,
each pattern is a particular output configuration in terms of PWM
duty-cycle (on 4096 steps). Analog dimming (on 256 steps) is per channel but
common to all patterns. Each device tree LED node will have a corresponding
entry in /sys/class/leds with the label name. The brightness property
corresponds to the per channel analog dimming, while the patterns[1-8] to the
PWM dimming control.
Signed-off-by: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
---
Changes in v10:
- update description help in Kconfig
- move st1202_led and st1202_chip into one line, declaration and definition
Changes in v9:
- log errors directly in st1202_write_reg and st1202_read_reg
- use mutex guards instead of lock/unlock
- remove i2c_set_clientdata
Changes in v7:
- fix st1202_brightness_get() error: uninitialized symbol 'value'
Changes in v6:
- fix build error
Changes in v5:
- remove unused macros
- switch to using devm_led_classdev_register_ext (struct st1202_led update)
- add prescalar_to_milliseconds (convert [22..5660]ms to [0..255] reg value)
- remove register range check in dt_init (range protected by yaml)
- address all review comments in v4
Changes in v4:
- Remove attributes/extended attributes implementation
- Use /sys/class/leds/<led>/hw_pattern (Pavel suggestion)
- Implement review findings of Christophe JAILLET
Changes in v3:
- Rename all ll1202 to st1202, including driver file name
- Convert all magic numbers to defines
- Refactor the show/store callbacks as per Lee's and Thomas's review
- Remove ll1202_get_channel and use dev_ext_attributes instead
- Log all error values for all the functions
- Use sysfs_emit for show callbacks
Changes in v2:
- Fix build error for device_attribute modes
drivers/leds/Kconfig | 10 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-st1202.c | 416 +++++++++++++++++++++++++++++++++++++
3 files changed, 427 insertions(+)
create mode 100644 drivers/leds/leds-st1202.c
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -931,6 +931,16 @@ config LEDS_LM36274
Say Y to enable the LM36274 LED driver for TI LMU devices.
This supports the LED device LM36274.
+config LEDS_ST1202
+ tristate "LED Support for STMicroelectronics LED1202 I2C chips"
+ depends on LEDS_CLASS
+ depends on I2C
+ depends on OF
+ select LEDS_TRIGGERS
+ help
+ Say Y to enable support for LEDs connected to LED1202
+ LED driver chips accessed via the I2C bus.
+
config LEDS_TPS6105X
tristate "LED support for TI TPS6105X"
depends on LEDS_CLASS
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o
+obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o
obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o
--- /dev/null
+++ b/drivers/leds/leds-st1202.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED driver for STMicroelectronics LED1202 chip
+ *
+ * Copyright (C) 2024 Remote-Tech Ltd. UK
+ */
+
+#include <linux/cleanup.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#define ST1202_CHAN_DISABLE_ALL 0x00
+#define ST1202_CHAN_ENABLE_HIGH 0x03
+#define ST1202_CHAN_ENABLE_LOW 0x02
+#define ST1202_CONFIG_REG 0x04
+/* PATS: Pattern sequence feature enable */
+#define ST1202_CONFIG_REG_PATS BIT(7)
+/* PATSR: Pattern sequence runs (self-clear when sequence is finished) */
+#define ST1202_CONFIG_REG_PATSR BIT(6)
+#define ST1202_CONFIG_REG_SHFT BIT(3)
+#define ST1202_DEV_ENABLE 0x01
+#define ST1202_DEV_ENABLE_ON BIT(0)
+#define ST1202_DEV_ENABLE_RESET BIT(7)
+#define ST1202_DEVICE_ID 0x00
+#define ST1202_ILED_REG0 0x09
+#define ST1202_MAX_LEDS 12
+#define ST1202_MAX_PATTERNS 8
+#define ST1202_MILLIS_PATTERN_DUR_MAX 5660
+#define ST1202_MILLIS_PATTERN_DUR_MIN 22
+#define ST1202_PATTERN_DUR 0x16
+#define ST1202_PATTERN_PWM 0x1E
+#define ST1202_PATTERN_REP 0x15
+
+struct st1202_led {
+ struct fwnode_handle *fwnode;
+ struct led_classdev led_cdev;
+ struct st1202_chip *chip;
+ bool is_active;
+ int led_num;
+};
+
+struct st1202_chip {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct st1202_led leds[ST1202_MAX_LEDS];
+};
+
+static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct st1202_led, led_cdev);
+}
+
+static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val)
+{
+ struct device *dev = &chip->client->dev;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(chip->client, reg);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read register [0x%x]: %d\n", reg, ret);
+ return ret;
+ }
+
+ *val = (uint8_t)ret;
+ return 0;
+}
+
+static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
+{
+ struct device *dev = &chip->client->dev;
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+ if (ret != 0)
+ dev_err(dev, "Failed to write %d to register [0x%x]: %d\n", val, reg, ret);
+
+ return ret;
+}
+
+static uint8_t st1202_prescalar_to_miliseconds(unsigned int value)
+{
+ return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1;
+}
+
+static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
+ int pattern, unsigned int value)
+{
+ u8 value_l, value_h;
+ int ret;
+
+ value_l = (u8)value;
+ value_h = (u8)(value >> 8);
+
+ /*
+ * Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh),
+ * where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh)
+ * and y is the pattern number in hexadecimal (y = 00h .. 07h)
+ */
+ ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),
+ value_l);
+ if (ret != 0)
+ return ret;
+
+ /*
+ * Datasheet: Register address high = 1Eh + 01h + 2(xh) +18h*(yh),
+ * where x is the channel number in hexadecimal (x = 00h .. 0Bh)
+ * and y is the pattern number in hexadecimal (y = 00h .. 07h)
+ */
+ ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + 0x1 + (led_num * 2) + 0x18 * pattern),
+ value_h);
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern,
+ unsigned int value)
+{
+ return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern),
+ st1202_prescalar_to_miliseconds(value));
+}
+
+static void st1202_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct st1202_led *led = cdev_to_st1202_led(led_cdev);
+ struct st1202_chip *chip = led->chip;
+
+ guard(mutex)(&chip->lock);
+
+ st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value);
+}
+
+static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev)
+{
+ struct st1202_led *led = cdev_to_st1202_led(led_cdev);
+ struct st1202_chip *chip = led->chip;
+ u8 value = 0;
+
+ guard(mutex)(&chip->lock);
+
+ st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value);
+
+ return value;
+}
+
+static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
+{
+ u8 chan_low, chan_high;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ if (led_num <= 7) {
+ ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low);
+ if (ret < 0)
+ return ret;
+
+ chan_low = active ? chan_low | BIT(led_num) : chan_low & ~BIT(led_num);
+
+ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, chan_low);
+ if (ret < 0)
+ return ret;
+
+ } else {
+ ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_HIGH, &chan_high);
+ if (ret < 0)
+ return ret;
+
+ chan_high = active ? chan_high | (BIT(led_num) >> 8) :
+ chan_high & ~(BIT(led_num) >> 8);
+
+ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, chan_high);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
+{
+ struct st1202_led *led = cdev_to_st1202_led(ldev);
+ struct st1202_chip *chip = led->chip;
+
+ return st1202_channel_set(chip, led->led_num, value == LED_OFF ? false : true);
+}
+
+static int st1202_led_pattern_clear(struct led_classdev *ldev)
+{
+ struct st1202_led *led = cdev_to_st1202_led(ldev);
+ struct st1202_chip *chip = led->chip;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) {
+ ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF);
+ if (ret != 0)
+ return ret;
+
+ ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN);
+ if (ret != 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int st1202_led_pattern_set(struct led_classdev *ldev,
+ struct led_pattern *pattern,
+ u32 len, int repeat)
+{
+ struct st1202_led *led = cdev_to_st1202_led(ldev);
+ struct st1202_chip *chip = led->chip;
+ int ret;
+
+ if (len > ST1202_MAX_PATTERNS)
+ return -EINVAL;
+
+ guard(mutex)(&chip->lock);
+
+ for (int patt = 0; patt < len; patt++) {
+ if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN ||
+ pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX)
+ return -EINVAL;
+
+ ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness);
+ if (ret != 0)
+ return ret;
+
+ ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t);
+ if (ret != 0)
+ return ret;
+ }
+
+ ret = st1202_write_reg(chip, ST1202_PATTERN_REP, repeat);
+ if (ret != 0)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR |
+ ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT));
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+static int st1202_dt_init(struct st1202_chip *chip)
+{
+ struct device *dev = &chip->client->dev;
+ struct st1202_led *led;
+ int err, reg;
+
+ for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
+ struct led_init_data init_data = {};
+
+ err = of_property_read_u32(child, "reg", &reg);
+ if (err)
+ return dev_err_probe(dev, err, "Invalid register\n");
+
+ led = &chip->leds[reg];
+ led->is_active = true;
+ led->fwnode = of_fwnode_handle(child);
+
+ led->led_cdev.max_brightness = U8_MAX;
+ led->led_cdev.brightness_set_blocking = st1202_led_set;
+ led->led_cdev.pattern_set = st1202_led_pattern_set;
+ led->led_cdev.pattern_clear = st1202_led_pattern_clear;
+ led->led_cdev.default_trigger = "pattern";
+
+ init_data.fwnode = led->fwnode;
+ init_data.devicename = "st1202";
+ init_data.default_label = ":";
+
+ err = devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data);
+ if (err < 0)
+ return dev_err_probe(dev, err, "Failed to register LED class device\n");
+
+ led->led_cdev.brightness_set = st1202_brightness_set;
+ led->led_cdev.brightness_get = st1202_brightness_get;
+ }
+
+ return 0;
+}
+
+static int st1202_setup(struct st1202_chip *chip)
+{
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ /*
+ * Once the supply voltage is applied, the LED1202 executes some internal checks,
+ * afterwords it stops the oscillator and puts the internal LDO in quiescent mode.
+ * To start the device, EN bit must be set inside the “Device Enable” register at
+ * address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters
+ * from the internal non-volatile memory and performs an auto-calibration procedure
+ * in order to increase the output current precision.
+ * Such initialization lasts about 6.5 ms.
+ */
+
+ /* Reset the chip during setup */
+ ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_RESET);
+ if (ret < 0)
+ return ret;
+
+ /* Enable phase-shift delay feature */
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
+ if (ret < 0)
+ return ret;
+
+ /* Enable the device */
+ ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_ON);
+ if (ret < 0)
+ return ret;
+
+ /* Duration of initialization */
+ usleep_range(6500, 10000);
+
+ /* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */
+ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL);
+ if (ret < 0)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, ST1202_CHAN_DISABLE_ALL);
+ if (ret < 0)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
+ ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int st1202_probe(struct i2c_client *client)
+{
+ struct st1202_chip *chip;
+ struct st1202_led *led;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return dev_err_probe(&client->dev, -EIO, "SMBUS Byte Data not Supported\n");
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ devm_mutex_init(&client->dev, &chip->lock);
+ chip->client = client;
+
+ ret = st1202_dt_init(chip);
+ if (ret < 0)
+ return ret;
+
+ ret = st1202_setup(chip);
+ if (ret < 0)
+ return ret;
+
+ for (int i = 0; i < ST1202_MAX_LEDS; i++) {
+ led = &chip->leds[i];
+ led->chip = chip;
+ led->led_num = i;
+
+ if (!led->is_active)
+ continue;
+
+ ret = st1202_channel_set(led->chip, led->led_num, true);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to activate LED channel\n");
+
+ ret = st1202_led_pattern_clear(&led->led_cdev);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to clear LED pattern\n");
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id st1202_id[] = {
+ { "st1202-i2c" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, st1202_id);
+
+static const struct of_device_id st1202_dt_ids[] = {
+ { .compatible = "st,led1202" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, st1202_dt_ids);
+
+static struct i2c_driver st1202_driver = {
+ .driver = {
+ .name = "leds-st1202",
+ .of_match_table = of_match_ptr(st1202_dt_ids),
+ },
+ .probe = st1202_probe,
+ .id_table = st1202_id,
+};
+module_i2c_driver(st1202_driver);
+
+MODULE_AUTHOR("Remote Tech LTD");
+MODULE_DESCRIPTION("STMicroelectronics LED1202 : 12-channel constant current LED driver");
+MODULE_LICENSE("GPL");

View file

@ -0,0 +1,70 @@
From e3da313ebcace17f1227566fe1b0d0c3883061f9 Mon Sep 17 00:00:00 2001
From: Manuel Fombuena <fombuena@outlook.com>
Date: Fri, 17 Jan 2025 12:31:49 +0000
Subject: [PATCH 1/5] leds: leds-st1202: fix NULL pointer access on race
condition
st1202_dt_init() calls devm_led_classdev_register_ext() before the
internal data structures are properly setup, so the leds become visible
to user space while being partially initialized, leading to a window
where trying to access them causes a NULL pointer access.
This change moves devm_led_classdev_register_ext() to the last thing to
happen during initialization to eliminate it.
Signed-off-by: Manuel Fombuena <fombuena@outlook.com>
---
drivers/leds/leds-st1202.c | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
--- a/drivers/leds/leds-st1202.c
+++ b/drivers/leds/leds-st1202.c
@@ -261,8 +261,6 @@ static int st1202_dt_init(struct st1202_
int err, reg;
for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
- struct led_init_data init_data = {};
-
err = of_property_read_u32(child, "reg", &reg);
if (err)
return dev_err_probe(dev, err, "Invalid register\n");
@@ -276,15 +274,6 @@ static int st1202_dt_init(struct st1202_
led->led_cdev.pattern_set = st1202_led_pattern_set;
led->led_cdev.pattern_clear = st1202_led_pattern_clear;
led->led_cdev.default_trigger = "pattern";
-
- init_data.fwnode = led->fwnode;
- init_data.devicename = "st1202";
- init_data.default_label = ":";
-
- err = devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data);
- if (err < 0)
- return dev_err_probe(dev, err, "Failed to register LED class device\n");
-
led->led_cdev.brightness_set = st1202_brightness_set;
led->led_cdev.brightness_get = st1202_brightness_get;
}
@@ -368,6 +357,7 @@ static int st1202_probe(struct i2c_clien
return ret;
for (int i = 0; i < ST1202_MAX_LEDS; i++) {
+ struct led_init_data init_data = {};
led = &chip->leds[i];
led->chip = chip;
led->led_num = i;
@@ -384,6 +374,15 @@ static int st1202_probe(struct i2c_clien
if (ret < 0)
return dev_err_probe(&client->dev, ret,
"Failed to clear LED pattern\n");
+
+ init_data.fwnode = led->fwnode;
+ init_data.devicename = "st1202";
+ init_data.default_label = ":";
+
+ ret = devm_led_classdev_register_ext(&client->dev, &led->led_cdev, &init_data);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to register LED class device\n");
}
return 0;