mirror of
https://git.openwrt.org/openwrt/openwrt.git
synced 2026-05-05 01:46:11 +02:00
realtek: drop support for 6.12
Drop support for 6.12 by removing files, patches and kernel configs since 6.18 is the default now. Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com> Link: https://github.com/openwrt/openwrt/pull/22869 Signed-off-by: Nick Hainke <vincent@systemli.org>
This commit is contained in:
parent
36b740d1b5
commit
4a13924fc5
@ -1,51 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/i2c/i2c-gpio-shared.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: mulitple GPIO bitbanged I2C busses with shared SCL
|
||||
|
||||
maintainers:
|
||||
- Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/i2c/i2c-controller.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- const: i2c-gpio-shared
|
||||
|
||||
scl-gpios:
|
||||
description:
|
||||
gpio used for the shared scl signal, this should be flagged as
|
||||
active high using open drain with (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)
|
||||
from <dt-bindings/gpio/gpio.h> since the signal is by definition
|
||||
open drain.
|
||||
maxItems: 1
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
i2c-gpio-shared {
|
||||
compatible = "i2c-gpio-shared";
|
||||
scl-gpios = <&gpio1 31 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
i2c0: i2c@0 {
|
||||
sda-gpios = <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
|
||||
i2c-gpio,delay-us = <2>;
|
||||
};
|
||||
|
||||
i2c1: i2c@1 {
|
||||
sda-gpios = <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
|
||||
i2c-gpio,delay-us = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- scl-gpios
|
||||
@ -1,29 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef RTL838X_IOREMAP_H_
|
||||
#define RTL838X_IOREMAP_H_
|
||||
|
||||
static inline int is_rtl838x_internal_registers(phys_addr_t offset)
|
||||
{
|
||||
/* IO-Block */
|
||||
if (offset >= 0xb8000000 && offset < 0xb9000000)
|
||||
return 1;
|
||||
/* Switch block */
|
||||
if (offset >= 0xbb000000 && offset < 0xbc000000)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void __iomem *plat_ioremap(phys_addr_t offset, unsigned long size,
|
||||
unsigned long flags)
|
||||
{
|
||||
if (is_rtl838x_internal_registers(offset))
|
||||
return (void __iomem *)offset;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int plat_iounmap(const volatile void __iomem *addr)
|
||||
{
|
||||
return is_rtl838x_internal_registers((unsigned long)addr);
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -1,59 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2006-2012 Tony Wu (tonywu@realtek.com)
|
||||
* Copyright (C) 2020 B. Koblitz
|
||||
*/
|
||||
#ifndef _MACH_RTL838X_H_
|
||||
#define _MACH_RTL838X_H_
|
||||
|
||||
#include <asm/types.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/*
|
||||
* Register access macros
|
||||
*/
|
||||
|
||||
#define RTL838X_SW_BASE ((volatile void *) 0xBB000000)
|
||||
|
||||
#define sw_r32(reg) readl(RTL838X_SW_BASE + reg)
|
||||
#define sw_w32(val, reg) writel(val, RTL838X_SW_BASE + reg)
|
||||
#define sw_w32_mask(clear, set, reg) sw_w32((sw_r32(reg) & ~(clear)) | (set), reg)
|
||||
|
||||
#define RTL838X_MODEL_NAME_INFO (0x00D4)
|
||||
#define RTL838X_CHIP_INFO (0x00D8)
|
||||
#define RTL839X_MODEL_NAME_INFO (0x0FF0)
|
||||
#define RTL839X_CHIP_INFO (0x0FF4)
|
||||
#define RTL93XX_MODEL_NAME_INFO (0x0004)
|
||||
#define RTL93XX_CHIP_INFO (0x0008)
|
||||
|
||||
#define RTL838X_INT_RW_CTRL (0x0058)
|
||||
#define RTL838X_EXT_VERSION (0x00D0)
|
||||
#define RTL838X_PLL_CML_CTRL (0x0ff8)
|
||||
|
||||
#define RTL931X_LED_GLB_CTRL (0x0600)
|
||||
#define RTL931X_MAC_L2_GLOBAL_CTRL2 (0x1358)
|
||||
|
||||
/* Definition of family IDs */
|
||||
#define RTL8380_FAMILY_ID (0x8380)
|
||||
#define RTL8390_FAMILY_ID (0x8390)
|
||||
#define RTL9300_FAMILY_ID (0x9300)
|
||||
#define RTL9310_FAMILY_ID (0x9310)
|
||||
|
||||
/* Basic SoC Features */
|
||||
#define RTL838X_CPU_PORT 28
|
||||
#define RTL839X_CPU_PORT 52
|
||||
#define RTL930X_CPU_PORT 28
|
||||
#define RTL931X_CPU_PORT 56
|
||||
|
||||
struct rtl83xx_soc_info {
|
||||
unsigned char *name;
|
||||
unsigned int id;
|
||||
unsigned int family;
|
||||
unsigned int revision;
|
||||
unsigned int cpu;
|
||||
bool testchip;
|
||||
int cpu_port;
|
||||
int memory_size;
|
||||
};
|
||||
|
||||
#endif /* _MACH_RTL838X_H_ */
|
||||
@ -1,5 +0,0 @@
|
||||
#
|
||||
# Makefile for the rtl838x specific parts of the kernel
|
||||
#
|
||||
|
||||
obj-y := setup.o prom.o
|
||||
@ -1,5 +0,0 @@
|
||||
#
|
||||
# Realtek RTL838x SoCs
|
||||
#
|
||||
cflags-$(CONFIG_MACH_REALTEK_RTL) += -I$(srctree)/arch/mips/include/asm/mach-rtl-otto/
|
||||
load-$(CONFIG_MACH_REALTEK_RTL) += 0xffffffff80100000
|
||||
@ -1,386 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* prom.c
|
||||
* Early initialization code for the Realtek RTL838X SoC
|
||||
*
|
||||
* based on the original BSP by
|
||||
* Copyright (C) 2006-2012 Tony Wu (tonywu@realtek.com)
|
||||
* Copyright (C) 2020 B. Koblitz
|
||||
*
|
||||
*/
|
||||
|
||||
#include <asm/fw/fw.h>
|
||||
#include <asm/mips-cps.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/smp-ops.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/smp.h>
|
||||
|
||||
#include <mach-rtl-otto.h>
|
||||
|
||||
#define RTL_SOC_BASE ((volatile void *) 0xB8000000)
|
||||
|
||||
#define RTL83XX_DRAM_CONFIG 0x1004
|
||||
|
||||
#define RTL9300_SRAMSAR0 0x4000
|
||||
#define RTL9300_SRAMSAR1 0x4010
|
||||
#define RTL9300_SRAMSAR2 0x4020
|
||||
#define RTL9300_SRAMSAR3 0x4030
|
||||
#define RTL9300_UMSAR0 0x1300
|
||||
#define RTL9300_UMSAR1 0x1310
|
||||
#define RTL9300_UMSAR2 0x1320
|
||||
#define RTL9300_UMSAR3 0x1330
|
||||
#define RTL9300_O0DOR2 0x4220
|
||||
#define RTL9300_O0DMAR2 0x4224
|
||||
|
||||
#define RTL931X_DRAM_CONFIG 0x14304c
|
||||
|
||||
#define soc_r32(reg) readl(RTL_SOC_BASE + reg)
|
||||
#define soc_w32(val, reg) writel(val, RTL_SOC_BASE + reg)
|
||||
|
||||
struct rtl83xx_soc_info soc_info;
|
||||
EXPORT_SYMBOL(soc_info);
|
||||
|
||||
const void *fdt;
|
||||
|
||||
static char rtl_soc_name[16];
|
||||
static char rtl_system_type[48];
|
||||
|
||||
#ifdef CONFIG_MIPS_MT_SMP
|
||||
|
||||
extern const struct plat_smp_ops vsmp_smp_ops;
|
||||
static struct plat_smp_ops rtl_smp_ops;
|
||||
|
||||
static void rtlsmp_init_secondary(void)
|
||||
{
|
||||
/*
|
||||
* Enable all CPU interrupts, as everything is managed by the external controller.
|
||||
* TODO: Standard vsmp_init_secondary() has special treatment for Malta if external
|
||||
* GIC is available. Maybe we need this too.
|
||||
*/
|
||||
if (mips_gic_present())
|
||||
pr_warn("%s: GIC present. Maybe interrupt enabling required.\n", __func__);
|
||||
else
|
||||
set_c0_status(ST0_IM);
|
||||
}
|
||||
|
||||
static void rtlsmp_finish(void)
|
||||
{
|
||||
/* These devices are low on resources. There might be the chance that CEVT_R4K is
|
||||
* not enabled in kernel build. Nevertheless the timer and interrupt 7 might be
|
||||
* active by default after startup of secondary VPEs. With no registered handler
|
||||
* that leads to continuous unhandeled interrupts. Disable it but keep the counter
|
||||
* running so it can still be used as an entropy source.
|
||||
*/
|
||||
if (!IS_ENABLED(CONFIG_CEVT_R4K)) {
|
||||
write_c0_status(read_c0_status() & ~CAUSEF_IP7);
|
||||
write_c0_compare(read_c0_count() - 1);
|
||||
}
|
||||
|
||||
local_irq_enable();
|
||||
}
|
||||
|
||||
static int rtlsmp_register(void)
|
||||
{
|
||||
if (!cpu_has_mipsmt)
|
||||
return 1;
|
||||
|
||||
rtl_smp_ops = vsmp_smp_ops;
|
||||
rtl_smp_ops.init_secondary = rtlsmp_init_secondary;
|
||||
rtl_smp_ops.smp_finish = rtlsmp_finish;
|
||||
register_smp_ops(&rtl_smp_ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* !CONFIG_MIPS_MT_SMP */
|
||||
|
||||
#define rtlsmp_register() (1)
|
||||
|
||||
#endif
|
||||
|
||||
static void __init apply_early_quirks(void)
|
||||
{
|
||||
if (soc_info.family == RTL8380_FAMILY_ID) {
|
||||
/*
|
||||
* Open up write protected registers. SDK opens/closes this whenever needed. For
|
||||
* simplicity always work with an "open" register set.
|
||||
*/
|
||||
sw_w32(0x3, RTL838X_INT_RW_CTRL);
|
||||
/*
|
||||
* Disable 4 byte address mode of flash controller. If this bit is not cleared
|
||||
* the watchdog cannot reset the SoC. The SDK changes this short before restart.
|
||||
* Until this quirk was implemented all RTL838x devices ran with this disabled
|
||||
* because of a coding error. As no issues were detected keep the behaviour
|
||||
* until more details are known.
|
||||
*/
|
||||
sw_w32_mask(BIT(30), 0, RTL838X_PLL_CML_CTRL);
|
||||
}
|
||||
}
|
||||
|
||||
static void __init apply_dts_quirks(void)
|
||||
{
|
||||
struct device_node *node;
|
||||
|
||||
node = of_find_compatible_node(NULL, NULL, "diodes,pt7a75xx-wdt");
|
||||
if (node) {
|
||||
if (soc_info.family == RTL9310_FAMILY_ID) {
|
||||
pr_info("apply quirk for diodes pt7a75xx watchdog\n");
|
||||
sw_w32_mask(GENMASK(13, 12), BIT(12), RTL931X_LED_GLB_CTRL);
|
||||
sw_w32_mask(0x0, BIT(8), RTL931X_MAC_L2_GLOBAL_CTRL2);
|
||||
};
|
||||
of_node_put(node);
|
||||
}
|
||||
}
|
||||
|
||||
void __init device_tree_init(void)
|
||||
{
|
||||
if (!fdt_check_header(&__appended_dtb)) {
|
||||
fdt = &__appended_dtb;
|
||||
pr_info("Using appended Device Tree.\n");
|
||||
}
|
||||
initial_boot_params = (void *)fdt;
|
||||
unflatten_and_copy_device_tree();
|
||||
apply_dts_quirks();
|
||||
|
||||
/* delay cpc & smp probing to allow devicetree access */
|
||||
mips_cpc_probe();
|
||||
|
||||
if (!register_cps_smp_ops())
|
||||
return;
|
||||
|
||||
if (!rtlsmp_register())
|
||||
return;
|
||||
|
||||
register_up_smp_ops();
|
||||
}
|
||||
|
||||
const char *get_system_type(void)
|
||||
{
|
||||
return rtl_system_type;
|
||||
}
|
||||
|
||||
static void __init rtl838x_read_details(u32 model)
|
||||
{
|
||||
u32 chip_info, ext_version, tmp;
|
||||
|
||||
sw_w32(0xa << 28, RTL838X_CHIP_INFO);
|
||||
|
||||
chip_info = sw_r32(RTL838X_CHIP_INFO);
|
||||
soc_info.cpu = chip_info & 0xffff;
|
||||
|
||||
ext_version = sw_r32(RTL838X_EXT_VERSION);
|
||||
tmp = ext_version & 0x1f;
|
||||
|
||||
if (tmp == 2) {
|
||||
soc_info.revision = 1;
|
||||
} else {
|
||||
tmp = (chip_info >> 16) & 0x1f;
|
||||
if (soc_info.cpu == 0x0477) {
|
||||
soc_info.revision = tmp;
|
||||
soc_info.testchip = true;
|
||||
} else {
|
||||
soc_info.revision = tmp - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __init rtl839x_read_details(u32 model)
|
||||
{
|
||||
u32 chip_info;
|
||||
|
||||
sw_w32(0xa << 28, RTL839X_CHIP_INFO);
|
||||
|
||||
chip_info = sw_r32(RTL839X_CHIP_INFO);
|
||||
soc_info.cpu = chip_info & 0xffff;
|
||||
|
||||
soc_info.revision = (model >> 1) & 0x1f;
|
||||
|
||||
if (!(model & 0x3e))
|
||||
soc_info.testchip = true;
|
||||
}
|
||||
|
||||
static void __init rtl93xx_read_details(u32 model)
|
||||
{
|
||||
u32 chip_info;
|
||||
|
||||
sw_w32(0xa << 16, RTL93XX_CHIP_INFO);
|
||||
|
||||
chip_info = sw_r32(RTL93XX_CHIP_INFO);
|
||||
soc_info.cpu = chip_info & 0xffff;
|
||||
|
||||
soc_info.revision = model & 0xf;
|
||||
|
||||
if (model & 0x30)
|
||||
soc_info.testchip = true;
|
||||
}
|
||||
|
||||
static u32 __init read_model(void)
|
||||
{
|
||||
u32 model, id;
|
||||
|
||||
model = sw_r32(RTL838X_MODEL_NAME_INFO);
|
||||
id = model >> 16 & 0xffff;
|
||||
if ((id >= 0x8380 && id <= 0x8382) || id == 0x8330 || id == 0x8332) {
|
||||
soc_info.id = id;
|
||||
soc_info.family = RTL8380_FAMILY_ID;
|
||||
soc_info.cpu_port = RTL838X_CPU_PORT;
|
||||
apply_early_quirks();
|
||||
rtl838x_read_details(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
model = sw_r32(RTL839X_MODEL_NAME_INFO);
|
||||
id = model >> 16 & 0xffff;
|
||||
if ((id >= 0x8391 && id <= 0x8396) || (id >= 0x8351 && id <= 0x8353)) {
|
||||
soc_info.id = id;
|
||||
soc_info.family = RTL8390_FAMILY_ID;
|
||||
soc_info.cpu_port = RTL839X_CPU_PORT;
|
||||
apply_early_quirks();
|
||||
rtl839x_read_details(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
model = sw_r32(RTL93XX_MODEL_NAME_INFO);
|
||||
id = model >> 16 & 0xffff;
|
||||
if (id >= 0x9301 && id <= 0x9303) {
|
||||
soc_info.id = id;
|
||||
soc_info.family = RTL9300_FAMILY_ID;
|
||||
soc_info.cpu_port = RTL930X_CPU_PORT;
|
||||
apply_early_quirks();
|
||||
rtl93xx_read_details(model);
|
||||
return model;
|
||||
} else if (id >= 0x9311 && id <= 0x9313) {
|
||||
soc_info.id = id;
|
||||
soc_info.family = RTL9310_FAMILY_ID;
|
||||
soc_info.cpu_port = RTL931X_CPU_PORT;
|
||||
apply_early_quirks();
|
||||
rtl93xx_read_details(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __init parse_model(u32 model)
|
||||
{
|
||||
int val;
|
||||
char suffix = 0;
|
||||
|
||||
val = (model >> 11) & 0x1f;
|
||||
if (val > 0 && val <= 26)
|
||||
suffix = 'A' + (val - 1);
|
||||
|
||||
snprintf(rtl_soc_name, sizeof(rtl_soc_name), "RTL%04X%c",
|
||||
soc_info.id, suffix);
|
||||
|
||||
soc_info.name = rtl_soc_name;
|
||||
}
|
||||
|
||||
static void __init set_system_type(void)
|
||||
{
|
||||
char revision = '?';
|
||||
char *es = "";
|
||||
|
||||
if (soc_info.revision >= 0 && soc_info.revision < 26)
|
||||
revision = 'A' + soc_info.revision;
|
||||
|
||||
if (soc_info.testchip)
|
||||
es = " ES";
|
||||
|
||||
snprintf(rtl_system_type, sizeof(rtl_system_type),
|
||||
"Realtek %s%s rev %c (%04X)",
|
||||
soc_info.name, es, revision, soc_info.cpu);
|
||||
}
|
||||
|
||||
static void get_system_memory(void)
|
||||
{
|
||||
unsigned int dcr, bits;
|
||||
|
||||
if (soc_info.family == RTL9310_FAMILY_ID) {
|
||||
dcr = soc_r32(RTL931X_DRAM_CONFIG);
|
||||
bits = (dcr >> 12) + ((dcr >> 6) & 0x3f) + (dcr & 0x3f);
|
||||
} else {
|
||||
dcr = soc_r32(RTL83XX_DRAM_CONFIG);
|
||||
bits = ((dcr >> 28) & 0x3) + ((dcr >> 24) & 0x3) +
|
||||
((dcr >> 20) & 0xf) + ((dcr >> 16) & 0xf) + 20;
|
||||
}
|
||||
|
||||
soc_info.memory_size = 1 << bits;
|
||||
}
|
||||
|
||||
static void prepare_highmem(void)
|
||||
{
|
||||
if ((soc_info.family != RTL9300_FAMILY_ID) ||
|
||||
(soc_info.memory_size <= 256 * 1024 * 1024) ||
|
||||
!IS_ENABLED(CONFIG_HIGHMEM))
|
||||
return;
|
||||
|
||||
/*
|
||||
* The RTL930x provides 3 logical adressing zones that can be configured individually
|
||||
* and offer an additional memory access indirection. Memory is accessed via the OCP
|
||||
* bus that checks these regions and maps logical addresses to physical ones. They are
|
||||
*
|
||||
* zone 1: logical address 0x00000000-0x0fffffff (256 MB) - map register 0xb8004200
|
||||
* zone 2: logical address 0x10000000-0x13ffffff (64 MB) - map register 0xb8004210
|
||||
* zone 3: logical address 0x20000000-0x9fffffff (2 GB) - map register 0xb8004220
|
||||
*
|
||||
* Whenever CPU accesses memory the normal MIPS translation is applied and afterwards
|
||||
* the bus adds the zone mapping. E.g. a read to 0x81230000 is converted to an cached
|
||||
* memory access to logical address 0x01230000. It is issued to the OCP bus and the
|
||||
* mapping from zone 1 register is added. That allows for two memory topologies:
|
||||
*
|
||||
* Linear memory with a maximum of 320 MB:
|
||||
*
|
||||
* Zone | map content | logical | physical
|
||||
* -------------------+-----------------------+-----------------------
|
||||
* 1 | 0x00000000 | 0x00000000-0x0fffffff | 0x00000000-0x0fffffff
|
||||
* 2 | 0x00000000 | 0x10000000-0x13ffffff | 0x10000000-0x13ffffff
|
||||
*
|
||||
* 256MB low memory plus up to 2GB high memory:
|
||||
*
|
||||
* Zone | map content | logical | physical
|
||||
* -------------------+-----------------------+-----------------------
|
||||
* 1 | 0x00000000 | 0x00000000-0x0fffffff | 0x00000000-0x0fffffff
|
||||
* 3 | 0x70000000 | 0x20000000-0x9fffffff | 0x10000000-0x7fffffff
|
||||
*/
|
||||
|
||||
pr_info("highmem kernel on RTL930x with > 256 MB RAM, adapt SoC memory mapping\n");
|
||||
|
||||
soc_w32(0, RTL9300_UMSAR0);
|
||||
soc_w32(0, RTL9300_UMSAR1);
|
||||
soc_w32(0, RTL9300_UMSAR2);
|
||||
soc_w32(0, RTL9300_UMSAR3);
|
||||
soc_w32(0, RTL9300_SRAMSAR0);
|
||||
soc_w32(0, RTL9300_SRAMSAR1);
|
||||
soc_w32(0, RTL9300_SRAMSAR2);
|
||||
soc_w32(0, RTL9300_SRAMSAR3);
|
||||
__sync();
|
||||
|
||||
soc_w32(0x70000000, RTL9300_O0DOR2);
|
||||
soc_w32(0x7fffffff, RTL9300_O0DMAR2);
|
||||
__sync();
|
||||
}
|
||||
|
||||
void __init prom_init(void)
|
||||
{
|
||||
u32 model = read_model();
|
||||
|
||||
parse_model(model);
|
||||
set_system_type();
|
||||
get_system_memory();
|
||||
|
||||
pr_info("%s SoC with %d MB\n", get_system_type(), soc_info.memory_size >> 20);
|
||||
|
||||
prepare_highmem();
|
||||
|
||||
/*
|
||||
* fw_arg2 is be the pointer to the environment. Some devices (e.g. HP JG924A) hand
|
||||
* over other than expected kernel boot arguments. Something like 0xfffdffff looks
|
||||
* suspicous. Do extra cleanup for fw_init_cmdline() to avoid a hang during boot.
|
||||
*/
|
||||
if (fw_arg2 >= CKSEG2)
|
||||
fw_arg2 = 0;
|
||||
|
||||
fw_init_cmdline();
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2020 B. Koblitz
|
||||
* based on the original BSP by
|
||||
* Copyright (C) 2006-2012 Tony Wu (tonywu@realtek.com)
|
||||
*/
|
||||
|
||||
#include <asm/bootinfo.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/time.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/of_clk.h>
|
||||
|
||||
void __init plat_mem_setup(void)
|
||||
{
|
||||
void *dtb;
|
||||
|
||||
set_io_port_base(KSEG1);
|
||||
|
||||
dtb = get_fdt();
|
||||
if (!dtb)
|
||||
panic("no dtb found");
|
||||
|
||||
/* Load the devicetree to let the memory appear. */
|
||||
__dt_setup_arch(dtb);
|
||||
}
|
||||
|
||||
static void plat_time_init_fallback(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
u32 freq = 500000000;
|
||||
|
||||
np = of_find_node_by_name(NULL, "cpus");
|
||||
if (!np) {
|
||||
pr_err("Missing 'cpus' DT node, using default frequency.");
|
||||
} else {
|
||||
if (of_property_read_u32(np, "frequency", &freq) < 0)
|
||||
pr_err("No 'frequency' property in DT, using default.");
|
||||
else
|
||||
pr_info("CPU frequency from device tree: %dMHz", freq / 1000000);
|
||||
of_node_put(np);
|
||||
}
|
||||
mips_hpt_frequency = freq / 2;
|
||||
}
|
||||
|
||||
void __init plat_time_init(void)
|
||||
{
|
||||
/*
|
||||
* Initialization routine resembles generic MIPS plat_time_init() with lazy error
|
||||
* handling. The final fallback is needed until all device trees use new clock syntax.
|
||||
*/
|
||||
struct device_node *np;
|
||||
struct clk *clk;
|
||||
|
||||
of_clk_init(NULL);
|
||||
|
||||
mips_hpt_frequency = 0;
|
||||
np = of_get_cpu_node(0, NULL);
|
||||
if (!np) {
|
||||
pr_err("Failed to get CPU node\n");
|
||||
} else {
|
||||
clk = of_clk_get(np, 0);
|
||||
if (IS_ERR(clk)) {
|
||||
pr_err("Failed to get CPU clock: %ld\n", PTR_ERR(clk));
|
||||
} else {
|
||||
mips_hpt_frequency = clk_get_rate(clk) / 2;
|
||||
clk_put(clk);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mips_hpt_frequency)
|
||||
plat_time_init_fallback();
|
||||
|
||||
timer_probe();
|
||||
}
|
||||
|
||||
void __init arch_init_irq(void)
|
||||
{
|
||||
irqchip_init();
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
menuconfig COMMON_CLK_REALTEK
|
||||
bool "Support for Realtek's clock controllers"
|
||||
depends on MACH_REALTEK_RTL
|
||||
|
||||
if COMMON_CLK_REALTEK
|
||||
|
||||
config COMMON_CLK_RTL83XX
|
||||
bool "Clock driver for Realtek RTL83XX and RTL960X"
|
||||
depends on MACH_REALTEK_RTL
|
||||
select SRAM
|
||||
help
|
||||
This driver adds support for the Realtek RTL83xx series basic clocks.
|
||||
This includes chips in the RTL838x series, such as RTL8380, RTL8381,
|
||||
RTL832, chips from the RTL839x series, such as RTL8390, RT8391,
|
||||
RTL8392, RTL8393 and RTL8396 as well as chips from the RTL960X
|
||||
series, such as RTL9607C, RTL8198D.
|
||||
|
||||
endif
|
||||
@ -1,2 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_COMMON_CLK_RTL83XX) += clk-rtl83xx.o clk-rtl838x-sram.o clk-rtl839x-sram.o
|
||||
@ -1,149 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Realtek RTL838X SRAM clock setters
|
||||
* Copyright (C) 2022 Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
*/
|
||||
|
||||
#include <dt-bindings/clock/rtl83xx-clk.h>
|
||||
|
||||
#include "clk-rtl83xx.h"
|
||||
|
||||
#define rGLB $t0
|
||||
#define rCTR $t1
|
||||
#define rMSK $t2
|
||||
#define rSLP $t3
|
||||
#define rTMP $t4
|
||||
|
||||
.set noreorder
|
||||
|
||||
.globl rtcl_838x_dram_start
|
||||
rtcl_838x_dram_start:
|
||||
|
||||
/*
|
||||
* Functions start here and should avoid access to normal memory. REMARK! Do not forget about
|
||||
* stack pointer and dirty caches that might interfere.
|
||||
*/
|
||||
|
||||
.globl rtcl_838x_dram_set_rate
|
||||
.ent rtcl_838x_dram_set_rate
|
||||
rtcl_838x_dram_set_rate:
|
||||
|
||||
#ifdef CONFIG_RTL838X
|
||||
|
||||
li rCTR, RTL_SW_CORE_BASE
|
||||
addiu rGLB, rCTR, RTL838X_PLL_GLB_CTRL
|
||||
ori rTMP, $0, CLK_CPU
|
||||
beq $a0, rTMP, pre_cpu
|
||||
ori rTMP, $0, CLK_MEM
|
||||
beq $a0, rTMP, pre_mem
|
||||
nop
|
||||
pre_lxb:
|
||||
ori rSLP, $0, RTL838X_GLB_CTRL_LXB_PLL_READY_MASK
|
||||
addiu rCTR, rCTR, RTL838X_PLL_LXB_CTRL0
|
||||
b main_set
|
||||
ori rMSK, $0, RTL838X_GLB_CTRL_EN_LXB_PLL_MASK
|
||||
pre_mem:
|
||||
/* simple 64K data cache flush to avoid unexpected memory access */
|
||||
li rMSK, RTL_SRAM_BASE
|
||||
li rTMP, 2048
|
||||
pre_flush:
|
||||
lw $0, 0(rMSK)
|
||||
addiu rMSK, rMSK, 32
|
||||
addiu rTMP, rTMP, -1
|
||||
bne rTMP, $0, pre_flush
|
||||
lw $0, -4(rMSK)
|
||||
|
||||
ori rSLP, $0, RTL838X_GLB_CTRL_MEM_PLL_READY_MASK
|
||||
addiu rCTR, rCTR, RTL838X_PLL_MEM_CTRL0
|
||||
b main_set
|
||||
ori rMSK, $0, RTL838X_GLB_CTRL_EN_MEM_PLL_MASK
|
||||
pre_cpu:
|
||||
/* switch CPU to LXB clock */
|
||||
ori rMSK, $0, RTL838X_GLB_CTRL_CPU_PLL_SC_MUX_MASK
|
||||
nor rMSK, rMSK, $0
|
||||
sync
|
||||
lw rTMP, 0(rGLB)
|
||||
and rTMP, rTMP, rMSK
|
||||
sw rTMP, 0(rGLB)
|
||||
sync
|
||||
|
||||
ori rSLP, $0, RTL838X_GLB_CTRL_CPU_PLL_READY_MASK
|
||||
addiu rCTR, rCTR, RTL838X_PLL_CPU_CTRL0
|
||||
ori rMSK, $0, RTL838X_GLB_CTRL_EN_CPU_PLL_MASK
|
||||
main_set:
|
||||
/* disable PLL */
|
||||
nor rMSK, rMSK, 0
|
||||
sync
|
||||
lw rTMP, 0(rGLB)
|
||||
sync
|
||||
and rTMP, rTMP, rMSK
|
||||
sync
|
||||
sw rTMP, 0(rGLB)
|
||||
|
||||
/* set new PLL values */
|
||||
sync
|
||||
sw $a1, 0(rCTR)
|
||||
sw $a2, 4(rCTR)
|
||||
sync
|
||||
|
||||
/* enable PLL (will reset it and clear ready status) */
|
||||
nor rMSK, rMSK, 0
|
||||
sync
|
||||
lw rTMP, 0(rGLB)
|
||||
sync
|
||||
or rTMP, rTMP, rMSK
|
||||
sync
|
||||
sw rTMP, 0(rGLB)
|
||||
|
||||
/* wait for PLL to become ready */
|
||||
wait_ready:
|
||||
lw rTMP, 0(rGLB)
|
||||
and rTMP, rTMP, rSLP
|
||||
bne rTMP, $0, wait_ready
|
||||
sync
|
||||
|
||||
/* branch to post processing */
|
||||
ori rTMP, $0, CLK_CPU
|
||||
beq $a0, rTMP, post_cpu
|
||||
ori rTMP, $0, CLK_MEM
|
||||
beq $a0, rTMP, post_mem
|
||||
nop
|
||||
post_lxb:
|
||||
jr $ra
|
||||
nop
|
||||
post_mem:
|
||||
jr $ra
|
||||
nop
|
||||
post_cpu:
|
||||
/* stabilize clock to avoid crash, empirically determined */
|
||||
ori rSLP, $0, 0x3000
|
||||
wait_cpu:
|
||||
bnez rSLP, wait_cpu
|
||||
addiu rSLP, rSLP, -1
|
||||
|
||||
/* switch CPU to PLL clock */
|
||||
ori rMSK, $0, RTL838X_GLB_CTRL_CPU_PLL_SC_MUX_MASK
|
||||
sync
|
||||
lw rTMP, 0(rGLB)
|
||||
or rTMP, rTMP, rMSK
|
||||
sw rTMP, 0(rGLB)
|
||||
sync
|
||||
jr $ra
|
||||
nop
|
||||
|
||||
#else /* !CONFIG_RTL838X */
|
||||
|
||||
jr $ra
|
||||
nop
|
||||
|
||||
#endif
|
||||
|
||||
.end rtcl_838x_dram_set_rate
|
||||
|
||||
/*
|
||||
* End marker. Do not delete.
|
||||
*/
|
||||
.word RTL_SRAM_MARKER
|
||||
.globl rtcl_838x_dram_size
|
||||
rtcl_838x_dram_size:
|
||||
.word .-rtcl_838x_dram_start
|
||||
@ -1,141 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Realtek RTL839X SRAM clock setters
|
||||
* Copyright (C) 2022 Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
*/
|
||||
|
||||
#include <asm/mipsregs.h>
|
||||
#include <dt-bindings/clock/rtl83xx-clk.h>
|
||||
|
||||
#include "clk-rtl83xx.h"
|
||||
|
||||
#define rGLB $t0
|
||||
#define rCTR $t1
|
||||
#define rMSK $t2
|
||||
#define rSLP1 $t3
|
||||
#define rSLP2 $t4
|
||||
#define rSLP3 $t5
|
||||
#define rTMP $t6
|
||||
#define rCP0 $t7
|
||||
|
||||
.set noreorder
|
||||
|
||||
.globl rtcl_839x_dram_start
|
||||
rtcl_839x_dram_start:
|
||||
|
||||
/*
|
||||
* Functions start here and should avoid access to normal memory. REMARK! Do not forget about
|
||||
* stack pointer and dirty caches that might interfere.
|
||||
*/
|
||||
|
||||
.globl rtcl_839x_dram_set_rate
|
||||
.ent rtcl_839x_dram_set_rate
|
||||
rtcl_839x_dram_set_rate:
|
||||
|
||||
#ifdef CONFIG_RTL839X
|
||||
|
||||
/* disable MIPS 34K branch and return prediction */
|
||||
mfc0 rCP0, CP0_CONFIG, 7
|
||||
ori rTMP, rCP0, 0xc
|
||||
mtc0 rTMP, CP0_CONFIG, 7
|
||||
|
||||
li rCTR, RTL_SW_CORE_BASE
|
||||
addiu rGLB, rCTR, RTL839X_PLL_GLB_CTRL
|
||||
ori rTMP, $0, CLK_CPU
|
||||
beq $a0, rTMP, pre_cpu
|
||||
ori rTMP, $0, CLK_MEM
|
||||
beq $a0, rTMP, pre_mem
|
||||
nop
|
||||
pre_lxb:
|
||||
li rSLP1, 0x400000
|
||||
li rSLP2, 0x400000
|
||||
li rSLP3, 0x400000
|
||||
addiu rCTR, rCTR, RTL839X_PLL_LXB_CTRL0
|
||||
b main_set
|
||||
ori rMSK, $0, RTL839X_GLB_CTRL_LXB_CLKSEL_MASK
|
||||
pre_mem:
|
||||
/* try to avoid memory access with simple 64K data cache flush */
|
||||
li rMSK, RTL_SRAM_BASE
|
||||
li rTMP, 2048
|
||||
pre_flush:
|
||||
lw $0, 0(rMSK)
|
||||
addiu rMSK, rMSK, 32
|
||||
addiu rTMP, rTMP, -1
|
||||
bne rTMP, $0, pre_flush
|
||||
lw $0, -4(rMSK)
|
||||
|
||||
li rSLP1, 0x10000
|
||||
li rSLP2, 0x10000
|
||||
li rSLP3, 0x10000
|
||||
addiu rCTR, rCTR, RTL839X_PLL_MEM_CTRL0
|
||||
b main_set
|
||||
ori rMSK, $0, RTL839X_GLB_CTRL_MEM_CLKSEL_MASK
|
||||
pre_cpu:
|
||||
li rSLP1, 0x1000
|
||||
li rSLP2, 0x1000
|
||||
li rSLP3, 0x200
|
||||
addiu rCTR, rCTR, RTL839X_PLL_CPU_CTRL0
|
||||
ori rMSK, $0, RTL839X_GLB_CTRL_CPU_CLKSEL_MASK
|
||||
main_set:
|
||||
/* switch to fixed clock */
|
||||
sync
|
||||
lw rTMP, 0(rGLB)
|
||||
sync
|
||||
or rTMP, rTMP, rMSK
|
||||
sync
|
||||
sw rTMP, 0(rGLB)
|
||||
|
||||
/* wait until fixed clock in use */
|
||||
or rTMP, rSLP1, $0
|
||||
wait_fixclock:
|
||||
bnez rTMP, wait_fixclock
|
||||
addiu rTMP, rTMP, -1
|
||||
|
||||
/* set new PLL values */
|
||||
sync
|
||||
sw $a1, 0(rCTR)
|
||||
sw $a2, 4(rCTR)
|
||||
sync
|
||||
|
||||
/* wait for value takeover */
|
||||
or rTMP, rSLP2, $0
|
||||
wait_pll:
|
||||
bnez rTMP, wait_pll
|
||||
addiu rTMP, rTMP, -1
|
||||
|
||||
/* switch back to PLL clock*/
|
||||
nor rMSK, rMSK, $0
|
||||
sync
|
||||
lw rTMP, 0(rGLB)
|
||||
sync
|
||||
and rTMP, rTMP, rMSK
|
||||
sync
|
||||
sw rTMP, 0(rGLB)
|
||||
|
||||
/* wait until PLL clock in use */
|
||||
or rTMP, rSLP3, $0
|
||||
wait_pllclock:
|
||||
bnez rTMP, wait_pllclock
|
||||
addiu rTMP, rTMP, -1
|
||||
|
||||
/* restore branch prediction */
|
||||
mtc0 rCP0, CP0_CONFIG, 7
|
||||
jr $ra
|
||||
nop
|
||||
|
||||
#else /* !CONFIG_RTL839X */
|
||||
|
||||
jr $ra
|
||||
nop
|
||||
|
||||
#endif
|
||||
|
||||
.end rtcl_839x_dram_set_rate
|
||||
|
||||
/*
|
||||
* End marker. Do not delete.
|
||||
*/
|
||||
.word RTL_SRAM_MARKER
|
||||
.globl rtcl_839x_dram_size
|
||||
rtcl_839x_dram_size:
|
||||
.word .-rtcl_839x_dram_start
|
||||
@ -1,870 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Realtek RTL83XX clock driver
|
||||
* Copyright (C) 2022 Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
*
|
||||
* This driver provides basic clock support for the central core clock unit (CCU) and its PLLs
|
||||
* inside the RTL838X and RTL8389X SOC. Currently CPU, memory and LXB clock information can be
|
||||
* accessed. To make use of the driver add the following devices and configurations at the
|
||||
* appropriate locations to the DT.
|
||||
*
|
||||
* #include <dt-bindings/clock/rtl83xx-clk.h>
|
||||
*
|
||||
* sram0: sram@9f000000 {
|
||||
* compatible = "mmio-sram";
|
||||
* reg = <0x9f000000 0x18000>;
|
||||
* #address-cells = <1>;
|
||||
* #size-cells = <1>;
|
||||
* ranges = <0 0x9f000000 0x18000>;
|
||||
* };
|
||||
*
|
||||
* osc: oscillator {
|
||||
* compatible = "fixed-clock";
|
||||
* #clock-cells = <0>;
|
||||
* clock-frequency = <25000000>;
|
||||
* };
|
||||
*
|
||||
* ccu: clock-controller {
|
||||
* compatible = "realtek,rtl8380-clock";
|
||||
* #clock-cells = <1>;
|
||||
* clocks = <&osc>;
|
||||
* clock-names = "ref_clk";
|
||||
* };
|
||||
*
|
||||
*
|
||||
* The SRAM part is needed to be able to set clocks. When changing clocks the code must not run
|
||||
* from DRAM. Otherwise system might freeze. Take care to adjust CCU compatibility, SRAM address
|
||||
* and size to the target SOC device. Afterwards one can access/identify the clocks in the other
|
||||
* DT devices with <&ccu CLK_CPU>, <&ccu CLK_MEM> or <&ccu CLK_LXB>. Additionally the clocks can
|
||||
* be used inside the kernel with
|
||||
*
|
||||
* cpu_clk = clk_get(NULL, "cpu_clk");
|
||||
* mem_clk = clk_get(NULL, "mem_clk");
|
||||
* lxb_clk = clk_get(NULL, "lxb_clk");
|
||||
*
|
||||
* This driver can be directly used by the DT based cpufreq driver (CONFIG_CPUFREQ_DT) if CPU
|
||||
* references the right clock and sane operating points (OPP) are provided. E.g.
|
||||
*
|
||||
* cpu@0 {
|
||||
* compatible = "mips,mips4KEc";
|
||||
* reg = <0>;
|
||||
* clocks = <&ccu CLK_CPU>;
|
||||
* operating-points-v2 = <&cpu_opp_table>;
|
||||
* };
|
||||
*
|
||||
* cpu_opp_table: opp-table-0 {
|
||||
* compatible = "operating-points-v2";
|
||||
* opp-shared;
|
||||
* opp00 {
|
||||
* opp-hz = /bits/ 64 <425000000>;
|
||||
* };
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/mipsmtregs.h>
|
||||
#include <dt-bindings/clock/rtl83xx-clk.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "clk-rtl83xx.h"
|
||||
|
||||
#define read_sw(reg) ioread32(((void *)RTL_SW_CORE_BASE) + reg)
|
||||
#define read_soc(reg) ioread32(((void *)RTL_SOC_BASE) + reg)
|
||||
|
||||
#define write_sw(val, reg) iowrite32(val, ((void *)RTL_SW_CORE_BASE) + reg)
|
||||
#define write_soc(val, reg) iowrite32(val, ((void *)RTL_SOC_BASE) + reg)
|
||||
|
||||
/*
|
||||
* some hardware specific definitions
|
||||
*/
|
||||
|
||||
#define SOC_RTL838X 0
|
||||
#define SOC_RTL839X 1
|
||||
#define SOC_RTL960X 2
|
||||
#define SOC_COUNT 3
|
||||
|
||||
#define MEM_DDR1 1
|
||||
#define MEM_DDR2 2
|
||||
#define MEM_DDR3 3
|
||||
|
||||
#define REG_CTRL0 0
|
||||
#define REG_CTRL1 1
|
||||
#define REG_COUNT 2
|
||||
|
||||
#define OSC_RATE 25000000
|
||||
|
||||
static const int rtcl_regs[SOC_COUNT][REG_COUNT][CLK_COUNT] = {
|
||||
{
|
||||
{ RTL838X_PLL_CPU_CTRL0, RTL838X_PLL_MEM_CTRL0, RTL838X_PLL_LXB_CTRL0 },
|
||||
{ RTL838X_PLL_CPU_CTRL1, RTL838X_PLL_MEM_CTRL1, RTL838X_PLL_LXB_CTRL1 },
|
||||
}, {
|
||||
{ RTL839X_PLL_CPU_CTRL0, RTL839X_PLL_MEM_CTRL0, RTL839X_PLL_LXB_CTRL0 },
|
||||
{ RTL839X_PLL_CPU_CTRL1, RTL839X_PLL_MEM_CTRL1, RTL839X_PLL_LXB_CTRL1 },
|
||||
}
|
||||
};
|
||||
|
||||
#define RTCL_REG_SET(_rate, _ctrl0, _ctrl1) \
|
||||
{ \
|
||||
.rate = _rate, \
|
||||
.ctrl0 = _ctrl0, \
|
||||
.ctrl1 = _ctrl1, \
|
||||
}
|
||||
|
||||
struct rtcl_reg_set {
|
||||
unsigned int rate;
|
||||
unsigned int ctrl0;
|
||||
unsigned int ctrl1;
|
||||
};
|
||||
|
||||
/*
|
||||
* The following configuration tables are valid operation points for their
|
||||
* corresponding PLLs. The magic numbers are precalculated mulitpliers and
|
||||
* dividers to keep the driver simple. They also provide rates outside the
|
||||
* allowed physical specifications. E.g. DDR3 memory has a lower limit of 303
|
||||
* MHz or the CPU might get unstable if set to anything above its startup
|
||||
* frequency. Additionally the Realtek SOCs tend to expect CPU speed larger
|
||||
* than MEM speed larger than LXB speed. The caller or DT configuration must
|
||||
* take care that only valid operating points are selected.
|
||||
*/
|
||||
|
||||
static const struct rtcl_reg_set rtcl_838x_cpu_reg_set[] = {
|
||||
RTCL_REG_SET(300000000, 0x045c8, 0x1414530e),
|
||||
RTCL_REG_SET(325000000, 0x04648, 0x1414530e),
|
||||
RTCL_REG_SET(350000000, 0x046c8, 0x1414530e),
|
||||
RTCL_REG_SET(375000000, 0x04748, 0x1414530e),
|
||||
RTCL_REG_SET(400000000, 0x045c8, 0x0c14530e),
|
||||
RTCL_REG_SET(425000000, 0x04628, 0x0c14530e),
|
||||
RTCL_REG_SET(450000000, 0x04688, 0x0c14530e),
|
||||
RTCL_REG_SET(475000000, 0x046e8, 0x0c14530e),
|
||||
RTCL_REG_SET(500000000, 0x04748, 0x0c14530e),
|
||||
RTCL_REG_SET(525000000, 0x047a8, 0x0c14530e),
|
||||
RTCL_REG_SET(550000000, 0x04808, 0x0c14530e),
|
||||
RTCL_REG_SET(575000000, 0x04868, 0x0c14530e),
|
||||
RTCL_REG_SET(600000000, 0x048c8, 0x0c14530e),
|
||||
RTCL_REG_SET(625000000, 0x04928, 0x0c14530e)
|
||||
};
|
||||
|
||||
static const struct rtcl_reg_set rtcl_838x_mem_reg_set[] = {
|
||||
RTCL_REG_SET(200000000, 0x041bc, 0x14018C80),
|
||||
RTCL_REG_SET(225000000, 0x0417c, 0x0c018C80),
|
||||
RTCL_REG_SET(250000000, 0x041ac, 0x0c018C80),
|
||||
RTCL_REG_SET(275000000, 0x0412c, 0x04018C80),
|
||||
RTCL_REG_SET(300000000, 0x0414c, 0x04018c80),
|
||||
RTCL_REG_SET(325000000, 0x0416c, 0x04018c80),
|
||||
RTCL_REG_SET(350000000, 0x0418c, 0x04018c80),
|
||||
RTCL_REG_SET(375000000, 0x041ac, 0x04018c80)
|
||||
};
|
||||
|
||||
static const struct rtcl_reg_set rtcl_838x_lxb_reg_set[] = {
|
||||
RTCL_REG_SET(100000000, 0x043c8, 0x001ad30e),
|
||||
RTCL_REG_SET(125000000, 0x043c8, 0x001ad30e),
|
||||
RTCL_REG_SET(150000000, 0x04508, 0x1c1ad30e),
|
||||
RTCL_REG_SET(175000000, 0x04508, 0x1c1ad30e),
|
||||
RTCL_REG_SET(200000000, 0x047c8, 0x001ad30e)
|
||||
};
|
||||
|
||||
static const struct rtcl_reg_set rtcl_839x_cpu_reg_set[] = {
|
||||
RTCL_REG_SET(400000000, 0x0414c, 0x00000005),
|
||||
RTCL_REG_SET(425000000, 0x041ec, 0x00000006),
|
||||
RTCL_REG_SET(450000000, 0x0417c, 0x00000005),
|
||||
RTCL_REG_SET(475000000, 0x0422c, 0x00000006),
|
||||
RTCL_REG_SET(500000000, 0x041ac, 0x00000005),
|
||||
RTCL_REG_SET(525000000, 0x0426c, 0x00000006),
|
||||
RTCL_REG_SET(550000000, 0x0412c, 0x00000004),
|
||||
RTCL_REG_SET(575000000, 0x042ac, 0x00000006),
|
||||
RTCL_REG_SET(600000000, 0x0414c, 0x00000004),
|
||||
RTCL_REG_SET(625000000, 0x042ec, 0x00000006),
|
||||
RTCL_REG_SET(650000000, 0x0416c, 0x00000004),
|
||||
RTCL_REG_SET(675000000, 0x04324, 0x00000006),
|
||||
RTCL_REG_SET(700000000, 0x0418c, 0x00000004),
|
||||
RTCL_REG_SET(725000000, 0x0436c, 0x00000006),
|
||||
RTCL_REG_SET(750000000, 0x0438c, 0x00000006),
|
||||
RTCL_REG_SET(775000000, 0x043ac, 0x00000006),
|
||||
RTCL_REG_SET(800000000, 0x043cc, 0x00000006),
|
||||
RTCL_REG_SET(825000000, 0x043ec, 0x00000006),
|
||||
RTCL_REG_SET(850000000, 0x0440c, 0x00000006)
|
||||
};
|
||||
|
||||
static const struct rtcl_reg_set rtcl_839x_mem_reg_set[] = {
|
||||
RTCL_REG_SET(100000000, 0x041cc, 0x00000000),
|
||||
RTCL_REG_SET(125000000, 0x041ac, 0x00000007),
|
||||
RTCL_REG_SET(150000000, 0x0414c, 0x00000006),
|
||||
RTCL_REG_SET(175000000, 0x0418c, 0x00000006),
|
||||
RTCL_REG_SET(200000000, 0x041cc, 0x00000006),
|
||||
RTCL_REG_SET(225000000, 0x0417c, 0x00000005),
|
||||
RTCL_REG_SET(250000000, 0x041ac, 0x00000005),
|
||||
RTCL_REG_SET(275000000, 0x0412c, 0x00000004),
|
||||
RTCL_REG_SET(300000000, 0x0414c, 0x00000004),
|
||||
RTCL_REG_SET(325000000, 0x0416c, 0x00000004),
|
||||
RTCL_REG_SET(350000000, 0x0418c, 0x00000004),
|
||||
RTCL_REG_SET(375000000, 0x041ac, 0x00000004),
|
||||
RTCL_REG_SET(400000000, 0x041cc, 0x00000004)
|
||||
};
|
||||
|
||||
static const struct rtcl_reg_set rtcl_839x_lxb_reg_set[] = {
|
||||
RTCL_REG_SET(50000000, 0x1414c, 0x00000003),
|
||||
RTCL_REG_SET(100000000, 0x0814c, 0x00000003),
|
||||
RTCL_REG_SET(150000000, 0x0414c, 0x00000003),
|
||||
RTCL_REG_SET(200000000, 0x0414c, 0x00000007)
|
||||
};
|
||||
|
||||
struct rtcl_rtab_set {
|
||||
int count;
|
||||
const struct rtcl_reg_set *rset;
|
||||
};
|
||||
|
||||
#define RTCL_RTAB_SET(_rset) \
|
||||
{ \
|
||||
.count = ARRAY_SIZE(_rset), \
|
||||
.rset = _rset, \
|
||||
}
|
||||
|
||||
static const struct rtcl_rtab_set rtcl_rtab_set[SOC_COUNT][CLK_COUNT] = {
|
||||
{
|
||||
RTCL_RTAB_SET(rtcl_838x_cpu_reg_set),
|
||||
RTCL_RTAB_SET(rtcl_838x_mem_reg_set),
|
||||
RTCL_RTAB_SET(rtcl_838x_lxb_reg_set)
|
||||
}, {
|
||||
RTCL_RTAB_SET(rtcl_839x_cpu_reg_set),
|
||||
RTCL_RTAB_SET(rtcl_839x_mem_reg_set),
|
||||
RTCL_RTAB_SET(rtcl_839x_lxb_reg_set)
|
||||
}
|
||||
};
|
||||
|
||||
#define RTCL_ROUND_SET(_min, _max, _step) \
|
||||
{ \
|
||||
.min = _min, \
|
||||
.max = _max, \
|
||||
.step = _step, \
|
||||
}
|
||||
|
||||
struct rtcl_round_set {
|
||||
unsigned long min;
|
||||
unsigned long max;
|
||||
unsigned long step;
|
||||
};
|
||||
|
||||
static const struct rtcl_round_set rtcl_round_set[SOC_COUNT][CLK_COUNT] = {
|
||||
{
|
||||
RTCL_ROUND_SET(300000000, 625000000, 25000000),
|
||||
RTCL_ROUND_SET(200000000, 375000000, 25000000),
|
||||
RTCL_ROUND_SET(100000000, 200000000, 25000000)
|
||||
}, {
|
||||
RTCL_ROUND_SET(400000000, 850000000, 25000000),
|
||||
RTCL_ROUND_SET(100000000, 400000000, 25000000),
|
||||
RTCL_ROUND_SET(50000000, 200000000, 50000000)
|
||||
}, {
|
||||
RTCL_ROUND_SET(500000000, 1200000000, 25000000)
|
||||
}
|
||||
};
|
||||
|
||||
static const int rtcl_divn3[] = { 2, 3, 4, 6 };
|
||||
static const int rtcl_xdiv[] = { 2, 4, 2 };
|
||||
|
||||
/*
|
||||
* module data structures
|
||||
*/
|
||||
|
||||
#define RTCL_CLK_INFO(_idx, _name, _pname, _dname) \
|
||||
{ \
|
||||
.idx = _idx, \
|
||||
.name = _name, \
|
||||
.parent_name = _pname, \
|
||||
.display_name = _dname, \
|
||||
}
|
||||
|
||||
struct rtcl_clk_info {
|
||||
unsigned int idx;
|
||||
const char *name;
|
||||
const char *parent_name;
|
||||
const char *display_name;
|
||||
};
|
||||
|
||||
struct rtcl_clk {
|
||||
struct clk_hw hw;
|
||||
unsigned int idx;
|
||||
unsigned long min;
|
||||
unsigned long max;
|
||||
unsigned long rate;
|
||||
unsigned long startup;
|
||||
};
|
||||
|
||||
static const struct rtcl_clk_info rtcl_clk_info[CLK_COUNT] = {
|
||||
RTCL_CLK_INFO(CLK_CPU, "cpu_clk", "ref_clk", "CPU"),
|
||||
RTCL_CLK_INFO(CLK_MEM, "mem_clk", "ref_clk", "MEM"),
|
||||
RTCL_CLK_INFO(CLK_LXB, "lxb_clk", "ref_clk", "LXB")
|
||||
};
|
||||
|
||||
struct rtcl_dram {
|
||||
int type;
|
||||
int buswidth;
|
||||
};
|
||||
|
||||
struct rtcl_sram {
|
||||
int *pmark;
|
||||
unsigned long vbase;
|
||||
};
|
||||
|
||||
struct rtcl_ccu {
|
||||
spinlock_t lock;
|
||||
unsigned int soc;
|
||||
struct rtcl_sram sram;
|
||||
struct rtcl_dram dram;
|
||||
struct device_node *np;
|
||||
struct platform_device *pdev;
|
||||
struct rtcl_clk clks[CLK_COUNT];
|
||||
};
|
||||
|
||||
struct rtcl_ccu *rtcl_ccu;
|
||||
|
||||
#define rtcl_hw_to_clk(_hw) container_of(_hw, struct rtcl_clk, hw)
|
||||
|
||||
/*
|
||||
* SRAM relocatable assembler functions. The dram() parts point to normal kernel
|
||||
* memory while the sram() parts are the same functions but relocated to SRAM.
|
||||
*/
|
||||
|
||||
extern void rtcl_838x_dram_start(void);
|
||||
extern int rtcl_838x_dram_size;
|
||||
|
||||
extern void (*rtcl_838x_dram_set_rate)(int clk_idx, int ctrl0, int ctrl1);
|
||||
static void (*rtcl_838x_sram_set_rate)(int clk_idx, int ctrl0, int ctrl1);
|
||||
|
||||
extern void rtcl_839x_dram_start(void);
|
||||
extern int rtcl_839x_dram_size;
|
||||
|
||||
extern void (*rtcl_839x_dram_set_rate)(int clk_idx, int ctrl0, int ctrl1);
|
||||
static void (*rtcl_839x_sram_set_rate)(int clk_idx, int ctrl0, int ctrl1);
|
||||
|
||||
/*
|
||||
* clock setter/getter functions
|
||||
*/
|
||||
|
||||
static unsigned long rtcl_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
{
|
||||
struct rtcl_clk *clk = rtcl_hw_to_clk(hw);
|
||||
unsigned int ctrl0, ctrl1, div1, div2, cmu_ncode_in;
|
||||
unsigned int cmu_sel_prediv, cmu_sel_div4, cmu_divn2, cmu_divn2_selb, cmu_divn3_sel;
|
||||
|
||||
if ((clk->idx >= CLK_COUNT) || (!rtcl_ccu) || (rtcl_ccu->soc >= SOC_COUNT))
|
||||
return 0;
|
||||
|
||||
ctrl0 = read_sw(rtcl_regs[rtcl_ccu->soc][REG_CTRL0][clk->idx]);
|
||||
ctrl1 = read_sw(rtcl_regs[rtcl_ccu->soc][REG_CTRL1][clk->idx]);
|
||||
|
||||
cmu_sel_prediv = 1 << RTL_PLL_CTRL0_CMU_SEL_PREDIV(ctrl0);
|
||||
cmu_sel_div4 = RTL_PLL_CTRL0_CMU_SEL_DIV4(ctrl0) ? 4 : 1;
|
||||
cmu_ncode_in = RTL_PLL_CTRL0_CMU_NCODE_IN(ctrl0) + 4;
|
||||
cmu_divn2 = RTL_PLL_CTRL0_CMU_DIVN2(ctrl0) + 4;
|
||||
|
||||
switch (rtcl_ccu->soc) {
|
||||
case SOC_RTL838X:
|
||||
if ((ctrl0 == 0) && (ctrl1 == 0) && (clk->idx == CLK_LXB))
|
||||
return 200000000;
|
||||
|
||||
cmu_divn2_selb = RTL838X_PLL_CTRL1_CMU_DIVN2_SELB(ctrl1);
|
||||
cmu_divn3_sel = rtcl_divn3[RTL838X_PLL_CTRL1_CMU_DIVN3_SEL(ctrl1)];
|
||||
break;
|
||||
case SOC_RTL839X:
|
||||
cmu_divn2_selb = RTL839X_PLL_CTRL1_CMU_DIVN2_SELB(ctrl1);
|
||||
cmu_divn3_sel = rtcl_divn3[RTL839X_PLL_CTRL1_CMU_DIVN3_SEL(ctrl1)];
|
||||
break;
|
||||
}
|
||||
div1 = cmu_divn2_selb ? cmu_divn3_sel : cmu_divn2;
|
||||
div2 = rtcl_xdiv[clk->idx];
|
||||
|
||||
return (((parent_rate / 16) * cmu_ncode_in) / (div1 * div2)) *
|
||||
cmu_sel_prediv * cmu_sel_div4 * 16;
|
||||
}
|
||||
|
||||
static int rtcl_838x_set_rate(int clk_idx, const struct rtcl_reg_set *reg)
|
||||
{
|
||||
unsigned long irqflags;
|
||||
/*
|
||||
* Runtime of this function (including locking)
|
||||
* CPU: up to 14000 cycles / up to 56 us at 250 MHz (half default speed)
|
||||
*/
|
||||
spin_lock_irqsave(&rtcl_ccu->lock, irqflags);
|
||||
rtcl_838x_sram_set_rate(clk_idx, reg->ctrl0, reg->ctrl1);
|
||||
spin_unlock_irqrestore(&rtcl_ccu->lock, irqflags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtcl_839x_set_rate(int clk_idx, const struct rtcl_reg_set *reg)
|
||||
{
|
||||
unsigned long vpflags;
|
||||
unsigned long irqflags;
|
||||
/*
|
||||
* Runtime of this function (including locking)
|
||||
* CPU: up to 31000 cycles / up to 89 us at 350 MHz (half default speed)
|
||||
*/
|
||||
spin_lock_irqsave(&rtcl_ccu->lock, irqflags);
|
||||
vpflags = dvpe();
|
||||
rtcl_839x_sram_set_rate(clk_idx, reg->ctrl0, reg->ctrl1);
|
||||
evpe(vpflags);
|
||||
spin_unlock_irqrestore(&rtcl_ccu->lock, irqflags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtcl_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
|
||||
{
|
||||
int tab_idx;
|
||||
struct rtcl_clk *clk = rtcl_hw_to_clk(hw);
|
||||
const struct rtcl_rtab_set *rtab = &rtcl_rtab_set[rtcl_ccu->soc][clk->idx];
|
||||
const struct rtcl_round_set *round = &rtcl_round_set[rtcl_ccu->soc][clk->idx];
|
||||
|
||||
if ((parent_rate != OSC_RATE) || (!rtcl_ccu->sram.vbase))
|
||||
return -EINVAL;
|
||||
/*
|
||||
* Currently we do not know if SRAM is stable on these devices. Maybe someone
|
||||
* changes memory in this region and does not care about proper allocation. So
|
||||
* check if something might go wrong.
|
||||
*/
|
||||
if (unlikely(*rtcl_ccu->sram.pmark != RTL_SRAM_MARKER)) {
|
||||
dev_err(&rtcl_ccu->pdev->dev, "SRAM code lost\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tab_idx = (rate - round->min) / round->step;
|
||||
if ((tab_idx < 0) || (tab_idx >= rtab->count) || (rtab->rset[tab_idx].rate != rate))
|
||||
return -EINVAL;
|
||||
|
||||
rtcl_ccu->clks[clk->idx].rate = rate;
|
||||
|
||||
switch (rtcl_ccu->soc) {
|
||||
case SOC_RTL838X:
|
||||
return rtcl_838x_set_rate(clk->idx, &rtab->rset[tab_idx]);
|
||||
case SOC_RTL839X:
|
||||
return rtcl_839x_set_rate(clk->idx, &rtab->rset[tab_idx]);
|
||||
}
|
||||
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static long rtcl_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
struct rtcl_clk *clk = rtcl_hw_to_clk(hw);
|
||||
unsigned long rrate = max(clk->min, min(clk->max, rate));
|
||||
const struct rtcl_round_set *round = &rtcl_round_set[rtcl_ccu->soc][clk->idx];
|
||||
|
||||
rrate = ((rrate + (round->step >> 1)) / round->step) * round->step;
|
||||
rrate -= (rrate > clk->max) ? round->step : 0;
|
||||
rrate += (rrate < clk->min) ? round->step : 0;
|
||||
|
||||
return rrate;
|
||||
}
|
||||
|
||||
static unsigned long rtcl_960x_cpu_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
u32 ocp_pll_ctrl0, ocp_pll_ctrl3, cmu_gcr;
|
||||
u32 cpu_freq_sel0, en_div2_cpu0, cmu_mode, freq_div;
|
||||
unsigned long rate;
|
||||
|
||||
ocp_pll_ctrl0 = read_soc(RTL960X_OCP_PLL_CTRL0);
|
||||
ocp_pll_ctrl3 = read_soc(RTL960X_OCP_PLL_CTRL3);
|
||||
cmu_gcr = read_soc(RTL960X_CMU_GCR);
|
||||
|
||||
cpu_freq_sel0 = RTL960X_OCP_CTRL0_CPU_FREQ_SEL0(ocp_pll_ctrl0);
|
||||
en_div2_cpu0 = RTL960X_OCP_CTRL3_EN_DIV2_CPU0(ocp_pll_ctrl3);
|
||||
cmu_mode = RTL960X_CMU_GCR_CMU_MODE(cmu_gcr);
|
||||
freq_div = RTL960X_CMU_GCR_FREQ_DIV(cmu_gcr);
|
||||
|
||||
rate = ((cpu_freq_sel0 + 2) * 2 * parent_rate) >> en_div2_cpu0;
|
||||
if (cmu_mode != 0)
|
||||
rate >>= freq_div;
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
static unsigned long rtcl_960x_lxb_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
u32 phy_rg5x_pll, lx_freq_sel;
|
||||
unsigned long rate;
|
||||
|
||||
phy_rg5x_pll = read_sw(RTL960X_PHY_RG5X_PLL);
|
||||
lx_freq_sel = RTL960X_LX_FREQ_SEL(phy_rg5x_pll);
|
||||
|
||||
rate = (40 * parent_rate) / (lx_freq_sel + 5);
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
static unsigned long rtcl_960x_mem_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
u32 mem_pll_ctrl2, mem_pll_ctrl3, mem_pll_ctrl5;
|
||||
u32 n_code, pdiv, f_code;
|
||||
unsigned long rate;
|
||||
u64 t;
|
||||
|
||||
mem_pll_ctrl2 = read_soc(RTL960X_MEM_PLL_CTRL2);
|
||||
mem_pll_ctrl3 = read_soc(RTL960X_MEM_PLL_CTRL3);
|
||||
mem_pll_ctrl5 = read_soc(RTL960X_MEM_PLL_CTRL5);
|
||||
|
||||
pdiv = RTL960X_MEM_CTRL2_PDIV(mem_pll_ctrl2);
|
||||
n_code = RTL960X_MEM_CTRL3_N_CODE(mem_pll_ctrl3);
|
||||
f_code = RTL960X_MEM_CTRL5_F_CODE(mem_pll_ctrl5);
|
||||
|
||||
rate = (parent_rate * (n_code + 3)) / (2 * (1 << pdiv));
|
||||
t = parent_rate;
|
||||
t *= f_code;
|
||||
t /= 16384;
|
||||
rate += t;
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
static unsigned long rtcl_960x_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
{
|
||||
struct rtcl_clk *clk = rtcl_hw_to_clk(hw);
|
||||
unsigned long rate;
|
||||
|
||||
if ((clk->idx >= CLK_COUNT) || (!rtcl_ccu) || (rtcl_ccu->soc >= SOC_COUNT))
|
||||
return 0;
|
||||
|
||||
switch (clk->idx) {
|
||||
case CLK_CPU:
|
||||
rate = rtcl_960x_cpu_recalc_rate(hw, parent_rate);
|
||||
break;
|
||||
case CLK_MEM:
|
||||
rate = rtcl_960x_mem_recalc_rate(hw, parent_rate);
|
||||
break;
|
||||
case CLK_LXB:
|
||||
rate = rtcl_960x_lxb_recalc_rate(hw, parent_rate);
|
||||
break;
|
||||
}
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialization functions to register the CCU and its clocks
|
||||
*/
|
||||
|
||||
#define RTCL_SRAM_FUNC(SOC, PBASE, FN) ({ \
|
||||
rtcl_##SOC##_sram_##FN = ((void *)&rtcl_##SOC##_dram_##FN - \
|
||||
(void *)&rtcl_##SOC##_dram_start) + \
|
||||
(void *)PBASE; })
|
||||
|
||||
static const struct clk_ops rtcl_960x_clk_ops = {
|
||||
.recalc_rate = rtcl_960x_recalc_rate,
|
||||
};
|
||||
|
||||
static const struct clk_ops rtcl_clk_ops = {
|
||||
.set_rate = rtcl_set_rate,
|
||||
.round_rate = rtcl_round_rate,
|
||||
.recalc_rate = rtcl_recalc_rate,
|
||||
};
|
||||
|
||||
static int rtcl_ccu_create(struct device_node *np)
|
||||
{
|
||||
int soc;
|
||||
|
||||
if (of_device_is_compatible(np, "realtek,rtl8380-clock"))
|
||||
soc = SOC_RTL838X;
|
||||
else if (of_device_is_compatible(np, "realtek,rtl8390-clock"))
|
||||
soc = SOC_RTL839X;
|
||||
else if (of_device_is_compatible(np, "realtek,rtl9607-clock"))
|
||||
soc = SOC_RTL960X;
|
||||
else
|
||||
return -ENXIO;
|
||||
|
||||
rtcl_ccu = kzalloc(sizeof(*rtcl_ccu), GFP_KERNEL);
|
||||
if (!rtcl_ccu)
|
||||
return -ENOMEM;
|
||||
|
||||
rtcl_ccu->np = np;
|
||||
rtcl_ccu->soc = soc;
|
||||
rtcl_ccu->dram.type = RTL_MC_MCR_DRAMTYPE(read_soc(RTL_MC_MCR));
|
||||
rtcl_ccu->dram.buswidth = RTL_MC_DCR_BUSWIDTH(read_soc(RTL_MC_DCR));
|
||||
spin_lock_init(&rtcl_ccu->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtcl_register_clkhw(int clk_idx)
|
||||
{
|
||||
int ret;
|
||||
struct clk *clk;
|
||||
struct clk_init_data hw_init = { };
|
||||
struct rtcl_clk *rclk = &rtcl_ccu->clks[clk_idx];
|
||||
struct clk_parent_data parent_data = { .fw_name = rtcl_clk_info[clk_idx].parent_name };
|
||||
|
||||
rclk->idx = clk_idx;
|
||||
rclk->hw.init = &hw_init;
|
||||
|
||||
hw_init.num_parents = 1;
|
||||
hw_init.parent_data = &parent_data;
|
||||
hw_init.name = rtcl_clk_info[clk_idx].name;
|
||||
|
||||
if (rtcl_ccu->soc == SOC_RTL960X)
|
||||
hw_init.ops = &rtcl_960x_clk_ops;
|
||||
else
|
||||
hw_init.ops = &rtcl_clk_ops;
|
||||
|
||||
ret = of_clk_hw_register(rtcl_ccu->np, &rclk->hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_hw_register_clkdev(&rclk->hw, rtcl_clk_info[clk_idx].name, NULL);
|
||||
|
||||
clk = clk_get(NULL, rtcl_clk_info[clk_idx].name);
|
||||
rclk->startup = clk_get_rate(clk);
|
||||
clk_put(clk);
|
||||
|
||||
switch (clk_idx) {
|
||||
case CLK_CPU:
|
||||
rclk->min = rtcl_round_set[rtcl_ccu->soc][clk_idx].min;
|
||||
rclk->max = rtcl_round_set[rtcl_ccu->soc][clk_idx].max;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* TODO: This driver supports PLL reclocking and nothing else. Additional
|
||||
* required steps for non CPU PLLs are missing. E.g. if we want to change memory
|
||||
* clocks the right way we must adapt a lot of other settings. This includes
|
||||
* MCR and DTRx timing registers (0xb80001000, 0xb8001008, ...) and a DLL reset
|
||||
* so that hardware operates in the allowed limits. This is far too complex
|
||||
* without official support. Avoid this for now.
|
||||
*/
|
||||
rclk->min = rclk->max = rclk->startup;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct clk_hw *rtcl_get_clkhw(struct of_phandle_args *clkspec, void *prv)
|
||||
{
|
||||
unsigned int idx = clkspec->args[0];
|
||||
|
||||
if (idx >= CLK_COUNT) {
|
||||
pr_err("%s: Invalid index %u\n", __func__, idx);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return &rtcl_ccu->clks[idx].hw;
|
||||
}
|
||||
|
||||
static int rtcl_ccu_register_clocks(void)
|
||||
{
|
||||
int clk_idx, ret;
|
||||
|
||||
for (clk_idx = 0; clk_idx < CLK_COUNT; clk_idx++) {
|
||||
ret = rtcl_register_clkhw(clk_idx);
|
||||
if (ret) {
|
||||
pr_err("%s: Couldn't register %s clock\n",
|
||||
__func__, rtcl_clk_info[clk_idx].display_name);
|
||||
goto err_hw_unregister;
|
||||
}
|
||||
}
|
||||
|
||||
ret = of_clk_add_hw_provider(rtcl_ccu->np, rtcl_get_clkhw, rtcl_ccu);
|
||||
if (ret) {
|
||||
pr_err("%s: Couldn't register clock provider of %s\n",
|
||||
__func__, of_node_full_name(rtcl_ccu->np));
|
||||
goto err_hw_unregister;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_hw_unregister:
|
||||
for (--clk_idx; clk_idx >= 0; --clk_idx)
|
||||
clk_hw_unregister(&rtcl_ccu->clks[clk_idx].hw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rtcl_init_sram(void)
|
||||
{
|
||||
struct gen_pool *sram_pool;
|
||||
phys_addr_t sram_pbase;
|
||||
unsigned long sram_vbase;
|
||||
struct device_node *node;
|
||||
struct platform_device *pdev = NULL;
|
||||
void *dram_start;
|
||||
int dram_size;
|
||||
const char *wrn = ", rate setting disabled.\n";
|
||||
|
||||
switch (rtcl_ccu->soc) {
|
||||
case SOC_RTL838X:
|
||||
dram_start = &rtcl_838x_dram_start;
|
||||
dram_size = rtcl_838x_dram_size;
|
||||
break;
|
||||
case SOC_RTL839X:
|
||||
dram_start = &rtcl_839x_dram_start;
|
||||
dram_size = rtcl_839x_dram_size;
|
||||
break;
|
||||
default:
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
for_each_compatible_node(node, NULL, "mmio-sram") {
|
||||
pdev = of_find_device_by_node(node);
|
||||
if (pdev) {
|
||||
of_node_put(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pdev) {
|
||||
dev_warn(&rtcl_ccu->pdev->dev, "no SRAM device found%s", wrn);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
sram_pool = gen_pool_get(&pdev->dev, NULL);
|
||||
if (!sram_pool) {
|
||||
dev_warn(&rtcl_ccu->pdev->dev, "SRAM pool unavailable%s", wrn);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
sram_vbase = gen_pool_alloc(sram_pool, dram_size);
|
||||
if (!sram_vbase) {
|
||||
dev_warn(&rtcl_ccu->pdev->dev, "can not allocate SRAM%s", wrn);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
sram_pbase = gen_pool_virt_to_phys(sram_pool, sram_vbase);
|
||||
memcpy((void *)sram_pbase, dram_start, dram_size);
|
||||
flush_icache_range((unsigned long)sram_pbase, (unsigned long)(sram_pbase + dram_size));
|
||||
|
||||
switch (rtcl_ccu->soc) {
|
||||
case SOC_RTL838X:
|
||||
RTCL_SRAM_FUNC(838x, sram_pbase, set_rate);
|
||||
break;
|
||||
case SOC_RTL839X:
|
||||
RTCL_SRAM_FUNC(839x, sram_pbase, set_rate);
|
||||
break;
|
||||
}
|
||||
|
||||
rtcl_ccu->sram.pmark = (int *)((void *)sram_pbase + (dram_size - 4));
|
||||
rtcl_ccu->sram.vbase = sram_vbase;
|
||||
|
||||
put_device(&pdev->dev);
|
||||
return 0;
|
||||
|
||||
err_put_device:
|
||||
put_device(&pdev->dev);
|
||||
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static void rtcl_ccu_log_early(void)
|
||||
{
|
||||
char meminfo[80], clkinfo[255], msg[255] = "rtl83xx-clk: initialized";
|
||||
|
||||
sprintf(meminfo, " (%d Bit DDR%d)", rtcl_ccu->dram.buswidth, rtcl_ccu->dram.type);
|
||||
for (int clk_idx = 0; clk_idx < CLK_COUNT; clk_idx++) {
|
||||
sprintf(clkinfo, ", %s %lu MHz", rtcl_clk_info[clk_idx].display_name,
|
||||
rtcl_ccu->clks[clk_idx].startup / 1000000);
|
||||
if (clk_idx == CLK_MEM)
|
||||
strcat(clkinfo, meminfo);
|
||||
strcat(msg, clkinfo);
|
||||
}
|
||||
pr_info("%s\n", msg);
|
||||
}
|
||||
|
||||
static void rtcl_ccu_log_late(void)
|
||||
{
|
||||
struct rtcl_clk *rclk;
|
||||
bool overclock = false;
|
||||
char clkinfo[80], msg[255] = "rate setting enabled";
|
||||
|
||||
for (int clk_idx = 0; clk_idx < CLK_COUNT; clk_idx++) {
|
||||
rclk = &rtcl_ccu->clks[clk_idx];
|
||||
overclock |= rclk->max > rclk->startup;
|
||||
sprintf(clkinfo, ", %s %lu-%lu MHz", rtcl_clk_info[clk_idx].display_name,
|
||||
rclk->min / 1000000, rclk->max / 1000000);
|
||||
strcat(msg, clkinfo);
|
||||
}
|
||||
if (overclock)
|
||||
strcat(msg, ", OVERCLOCK AT OWN RISK");
|
||||
|
||||
dev_info(&rtcl_ccu->pdev->dev, "%s\n", msg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Early registration: This module provides core startup clocks that are needed
|
||||
* for generic SOC init and for further builtin devices (e.g. UART). Register
|
||||
* asap via clock framework.
|
||||
*/
|
||||
|
||||
static void __init rtcl_probe_early(struct device_node *np)
|
||||
{
|
||||
if (rtcl_ccu_create(np))
|
||||
return;
|
||||
|
||||
if (rtcl_ccu_register_clocks())
|
||||
kfree(rtcl_ccu);
|
||||
else
|
||||
rtcl_ccu_log_early();
|
||||
}
|
||||
|
||||
CLK_OF_DECLARE_DRIVER(rtl838x_clk, "realtek,rtl8380-clock", rtcl_probe_early);
|
||||
CLK_OF_DECLARE_DRIVER(rtl839x_clk, "realtek,rtl8390-clock", rtcl_probe_early);
|
||||
CLK_OF_DECLARE_DRIVER(rtl960x_clk, "realtek,rtl9607-clock", rtcl_probe_early);
|
||||
|
||||
/*
|
||||
* Late registration: Finally register as normal platform driver. At this point
|
||||
* we can make use of other modules like SRAM.
|
||||
*/
|
||||
|
||||
static const struct of_device_id rtcl_dt_ids[] = {
|
||||
{ .compatible = "realtek,rtl8380-clock" },
|
||||
{ .compatible = "realtek,rtl8390-clock" },
|
||||
{}
|
||||
};
|
||||
|
||||
static int rtcl_probe_late(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!rtcl_ccu) {
|
||||
dev_err(&pdev->dev, "early initialization not run");
|
||||
return -ENXIO;
|
||||
}
|
||||
rtcl_ccu->pdev = pdev;
|
||||
ret = rtcl_init_sram();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rtcl_ccu_log_late();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rtcl_platform_driver = {
|
||||
.driver = {
|
||||
.name = "rtl83xx-clk",
|
||||
.of_match_table = rtcl_dt_ids,
|
||||
},
|
||||
.probe = rtcl_probe_late,
|
||||
};
|
||||
|
||||
static int __init rtcl_init_subsys(void)
|
||||
{
|
||||
return platform_driver_register(&rtcl_platform_driver);
|
||||
}
|
||||
|
||||
/*
|
||||
* The driver does not know when SRAM module has finally loaded. With an
|
||||
* arch_initcall() we might overtake SRAM initialization. Be polite and give the
|
||||
* system a little more time.
|
||||
*/
|
||||
|
||||
subsys_initcall(rtcl_init_subsys);
|
||||
@ -1,97 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Realtek RTL83XX clock headers
|
||||
* Copyright (C) 2022 Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
*/
|
||||
|
||||
/*
|
||||
* Switch registers (e.g. PLL)
|
||||
*/
|
||||
|
||||
#define RTL_SW_CORE_BASE (0xbb000000)
|
||||
|
||||
#define RTL838X_PLL_GLB_CTRL (0x0fc0)
|
||||
#define RTL838X_PLL_CPU_CTRL0 (0x0fc4)
|
||||
#define RTL838X_PLL_CPU_CTRL1 (0x0fc8)
|
||||
#define RTL838X_PLL_LXB_CTRL0 (0x0fd0)
|
||||
#define RTL838X_PLL_LXB_CTRL1 (0x0fd4)
|
||||
#define RTL838X_PLL_MEM_CTRL0 (0x0fdc)
|
||||
#define RTL838X_PLL_MEM_CTRL1 (0x0fe0)
|
||||
|
||||
#define RTL839X_PLL_GLB_CTRL (0x0024)
|
||||
#define RTL839X_PLL_CPU_CTRL0 (0x0028)
|
||||
#define RTL839X_PLL_CPU_CTRL1 (0x002c)
|
||||
#define RTL839X_PLL_LXB_CTRL0 (0x0038)
|
||||
#define RTL839X_PLL_LXB_CTRL1 (0x003c)
|
||||
#define RTL839X_PLL_MEM_CTRL0 (0x0048)
|
||||
#define RTL839X_PLL_MEM_CTRL1 (0x004c)
|
||||
|
||||
#define RTL960X_PHY_RG5X_PLL (0x1f054)
|
||||
|
||||
#define RTL_PLL_CTRL0_CMU_SEL_PREDIV(v) (((v) >> 0) & 0x3)
|
||||
#define RTL_PLL_CTRL0_CMU_SEL_DIV4(v) (((v) >> 2) & 0x1)
|
||||
#define RTL_PLL_CTRL0_CMU_NCODE_IN(v) (((v) >> 4) & 0xff)
|
||||
#define RTL_PLL_CTRL0_CMU_DIVN2(v) (((v) >> 12) & 0xff)
|
||||
|
||||
#define RTL838X_GLB_CTRL_EN_CPU_PLL_MASK (1 << 0)
|
||||
#define RTL838X_GLB_CTRL_EN_LXB_PLL_MASK (1 << 1)
|
||||
#define RTL838X_GLB_CTRL_EN_MEM_PLL_MASK (1 << 2)
|
||||
#define RTL838X_GLB_CTRL_CPU_PLL_READY_MASK (1 << 8)
|
||||
#define RTL838X_GLB_CTRL_LXB_PLL_READY_MASK (1 << 9)
|
||||
#define RTL838X_GLB_CTRL_MEM_PLL_READY_MASK (1 << 10)
|
||||
#define RTL838X_GLB_CTRL_CPU_PLL_SC_MUX_MASK (1 << 12)
|
||||
|
||||
#define RTL838X_PLL_CTRL1_CMU_DIVN2_SELB(v) (((v) >> 26) & 0x1)
|
||||
#define RTL838X_PLL_CTRL1_CMU_DIVN3_SEL(v) (((v) >> 27) & 0x3)
|
||||
|
||||
#define RTL839X_GLB_CTRL_CPU_CLKSEL_MASK (1 << 11)
|
||||
#define RTL839X_GLB_CTRL_MEM_CLKSEL_MASK (1 << 12)
|
||||
#define RTL839X_GLB_CTRL_LXB_CLKSEL_MASK (1 << 13)
|
||||
|
||||
#define RTL839X_PLL_CTRL1_CMU_DIVN2_SELB(v) (((v) >> 2) & 0x1)
|
||||
#define RTL839X_PLL_CTRL1_CMU_DIVN3_SEL(v) (((v) >> 0) & 0x3)
|
||||
|
||||
#define RTL960X_LX_FREQ_SEL(v) ((v) & 0xf)
|
||||
|
||||
/*
|
||||
* Core registers (e.g. memory controller)
|
||||
*/
|
||||
|
||||
#define RTL_SOC_BASE (0xB8000000)
|
||||
|
||||
#define RTL_MC_MCR (0x1000)
|
||||
#define RTL_MC_DCR (0x1004)
|
||||
#define RTL_MC_DTR0 (0x1008)
|
||||
#define RTL_MC_DTR1 (0x100c)
|
||||
#define RTL_MC_DTR2 (0x1010)
|
||||
#define RTL_MC_DMCR (0x101c)
|
||||
#define RTL_MC_DACCR (0x1500)
|
||||
#define RTL_MC_DCDR (0x1060)
|
||||
|
||||
#define RTL_MC_MCR_DRAMTYPE(v) ((((v) >> 28) & 0xf) + 1)
|
||||
#define RTL_MC_DCR_BUSWIDTH(v) (8 << (((v) >> 24) & 0xf))
|
||||
|
||||
#define RTL960X_OCP_PLL_CTRL0 (0x0200)
|
||||
#define RTL960X_OCP_PLL_CTRL3 (0x020c)
|
||||
#define RTL960X_CMU_GCR (0x0380)
|
||||
#define RTL960X_MEM_PLL_CTRL2 (0x023c)
|
||||
#define RTL960X_MEM_PLL_CTRL3 (0x0240)
|
||||
#define RTL960X_MEM_PLL_CTRL5 (0x0248)
|
||||
|
||||
#define RTL960X_OCP_CTRL0_CPU_FREQ_SEL0(v) (((v) >> 16) & 0x3f)
|
||||
|
||||
#define RTL960X_OCP_CTRL3_EN_DIV2_CPU0(v) (((v) >> 18) & 0x1)
|
||||
|
||||
#define RTL960X_CMU_GCR_CMU_MODE(v) ((v) & 0x3)
|
||||
#define RTL960X_CMU_GCR_FREQ_DIV(v) (((v) >> 4) & 0x7)
|
||||
|
||||
#define RTL960X_MEM_CTRL2_PDIV(v) (((v) >> 14) & 0x3)
|
||||
#define RTL960X_MEM_CTRL3_N_CODE(v) (((v) >> 24) & 0xff)
|
||||
#define RTL960X_MEM_CTRL5_F_CODE(v) ((v) & 0x1fff)
|
||||
|
||||
/*
|
||||
* Other stuff
|
||||
*/
|
||||
|
||||
#define RTL_SRAM_MARKER (0x5eaf00d5)
|
||||
#define RTL_SRAM_BASE (0x9f000000)
|
||||
@ -1,179 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Bitbanging driver for multiple I2C busses with shared SCL pin using the GPIO API
|
||||
* Copyright (c) 2025 Markus Stockhausen <markus.stockhausen at gmx.de>
|
||||
*/
|
||||
|
||||
#include <linux/i2c-algo-bit.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define GPIO_SHARED_MAX_BUS 4
|
||||
|
||||
struct gpio_shared_ctx;
|
||||
|
||||
struct gpio_shared_bus {
|
||||
int num;
|
||||
struct gpio_desc *sda;
|
||||
struct i2c_adapter adap;
|
||||
struct i2c_algo_bit_data bit_data;
|
||||
struct gpio_shared_ctx *ctx;
|
||||
};
|
||||
|
||||
struct gpio_shared_ctx {
|
||||
struct device *dev;
|
||||
struct gpio_desc *scl;
|
||||
struct mutex lock;
|
||||
struct gpio_shared_bus bus[GPIO_SHARED_MAX_BUS];
|
||||
};
|
||||
|
||||
static void gpio_shared_setsda(void *data, int state)
|
||||
{
|
||||
struct gpio_shared_bus *bus = data;
|
||||
|
||||
gpiod_set_value_cansleep(bus->sda, state);
|
||||
}
|
||||
|
||||
static void gpio_shared_setscl(void *data, int state)
|
||||
{
|
||||
struct gpio_shared_bus *bus = data;
|
||||
struct gpio_shared_ctx *ctx = bus->ctx;
|
||||
|
||||
gpiod_set_value_cansleep(ctx->scl, state);
|
||||
}
|
||||
|
||||
static int gpio_shared_getsda(void *data)
|
||||
{
|
||||
struct gpio_shared_bus *bus = data;
|
||||
|
||||
return gpiod_get_value_cansleep(bus->sda);
|
||||
}
|
||||
|
||||
static int gpio_shared_getscl(void *data)
|
||||
{
|
||||
struct gpio_shared_bus *bus = data;
|
||||
struct gpio_shared_ctx *ctx = bus->ctx;
|
||||
|
||||
return gpiod_get_value_cansleep(ctx->scl);
|
||||
}
|
||||
|
||||
static int gpio_shared_pre_xfer(struct i2c_adapter *adap)
|
||||
{
|
||||
struct gpio_shared_bus *bus = container_of(adap, typeof(*bus), adap);
|
||||
struct gpio_shared_ctx *ctx = bus->ctx;
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
dev_dbg(ctx->dev, "lock before transfer to bus %d\n", bus->num);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_shared_post_xfer(struct i2c_adapter *adap)
|
||||
{
|
||||
struct gpio_shared_bus *bus = container_of(adap, typeof(*bus), adap);
|
||||
struct gpio_shared_ctx *ctx = bus->ctx;
|
||||
|
||||
dev_dbg(ctx->dev, "unlock after transfer to bus %d\n", bus->num);
|
||||
mutex_unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
static int gpio_shared_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct fwnode_handle *child;
|
||||
struct gpio_shared_ctx *ctx;
|
||||
int msecs, ret, bus_num = -1;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctx->dev = dev;
|
||||
mutex_init(&ctx->lock);
|
||||
|
||||
ctx->scl = devm_gpiod_get(dev, "scl", GPIOD_OUT_HIGH_OPEN_DRAIN);
|
||||
if (IS_ERR(ctx->scl))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->scl), "shared SCL node not found\n");
|
||||
|
||||
if (device_get_child_node_count(dev) > GPIO_SHARED_MAX_BUS)
|
||||
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
|
||||
device_for_each_child_node(dev, child) {
|
||||
struct gpio_shared_bus *bus = &ctx->bus[++bus_num];
|
||||
struct i2c_adapter *adap = &bus->adap;
|
||||
struct i2c_algo_bit_data *bit_data = &bus->bit_data;
|
||||
|
||||
bus->sda = devm_fwnode_gpiod_get(dev, child, "sda", GPIOD_OUT_HIGH_OPEN_DRAIN,
|
||||
fwnode_get_name(child));
|
||||
if (IS_ERR(bus->sda)) {
|
||||
fwnode_handle_put(child);
|
||||
dev_err(dev, "SDA node for bus %d not found\n", bus_num);
|
||||
continue;
|
||||
}
|
||||
|
||||
bus->num = bus_num;
|
||||
bus->ctx = ctx;
|
||||
|
||||
bit_data->data = bus;
|
||||
bit_data->setsda = gpio_shared_setsda;
|
||||
bit_data->setscl = gpio_shared_setscl;
|
||||
bit_data->pre_xfer = gpio_shared_pre_xfer;
|
||||
bit_data->post_xfer = gpio_shared_post_xfer;
|
||||
|
||||
if (fwnode_property_read_u32(child, "i2c-gpio,delay-us", &bit_data->udelay))
|
||||
bit_data->udelay = 5;
|
||||
if (!fwnode_property_read_bool(child, "i2c-gpio,sda-output-only"))
|
||||
bit_data->getsda = gpio_shared_getsda;
|
||||
if (!device_property_read_bool(dev, "i2c-gpio,scl-output-only"))
|
||||
bit_data->getscl = gpio_shared_getscl;
|
||||
|
||||
if (!device_property_read_u32(dev, "i2c-gpio,timeout-ms", &msecs))
|
||||
bit_data->timeout = msecs_to_jiffies(msecs);
|
||||
else
|
||||
bit_data->timeout = HZ / 10; /* 100ms */
|
||||
|
||||
if (gpiod_cansleep(bus->sda) || gpiod_cansleep(ctx->scl))
|
||||
dev_warn(dev, "Slow GPIO pins might wreak havoc into I2C/SMBus bus timing");
|
||||
else
|
||||
bit_data->can_do_atomic = true;
|
||||
|
||||
adap->owner = THIS_MODULE;
|
||||
strscpy(adap->name, KBUILD_MODNAME, sizeof(adap->name));
|
||||
adap->dev.parent = dev;
|
||||
device_set_node(&adap->dev, child);
|
||||
adap->algo_data = &bus->bit_data;
|
||||
adap->class = I2C_CLASS_HWMON;
|
||||
|
||||
ret = i2c_bit_add_bus(adap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_info(dev, "shared I2C bus %u using lines %u (SDA) and %u (SCL) delay=%d\n",
|
||||
bus_num, desc_to_gpio(bus->sda), desc_to_gpio(ctx->scl),
|
||||
bit_data->udelay);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id gpio_shared_of_match[] = {
|
||||
{ .compatible = "i2c-gpio-shared" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, gpio_shared_of_match);
|
||||
|
||||
static struct platform_driver gpio_shared_driver = {
|
||||
.probe = gpio_shared_probe,
|
||||
.driver = {
|
||||
.name = "i2c-gpio-shared",
|
||||
.of_match_table = gpio_shared_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(gpio_shared_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen at gmx.de>");
|
||||
MODULE_DESCRIPTION("bitbanging multi I2C driver for shared SCL");
|
||||
@ -1,11 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config NET_DSA_RTL83XX
|
||||
tristate "Realtek RTL838x/RTL839x switch support"
|
||||
depends on MACH_REALTEK_RTL
|
||||
select NET_DSA_TAG_RTL_OTTO
|
||||
help
|
||||
This driver adds support for Realtek RTL83xx series switching.
|
||||
|
||||
config NET_DSA_RTL83XX_RTL930X_L3_OFFLOAD
|
||||
bool "Realtek RTL930x layer 3 offload (experimental)"
|
||||
depends on NET_DSA_RTL83XX
|
||||
@ -1,3 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_NET_DSA_RTL83XX) += rtl_otto_dsa.o
|
||||
rtl_otto_dsa-objs := common.o dsa.o rtl838x.o rtl839x.o rtl930x.o rtl931x.o debugfs.o qos.o tc.o
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,977 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <asm/mach-rtl-otto/mach-rtl-otto.h>
|
||||
|
||||
#include "rtl83xx.h"
|
||||
|
||||
#define RTL838X_DRIVER_NAME "rtl838x"
|
||||
|
||||
#define RTL8390_LED_GLB_CTRL (0x00E4)
|
||||
#define RTL8390_LED_SET_2_3_CTRL (0x00E8)
|
||||
#define RTL8390_LED_SET_0_1_CTRL (0x00EC)
|
||||
#define RTL8390_LED_COPR_SET_SEL_CTRL(p) (0x00F0 + (((p >> 4) << 2)))
|
||||
#define RTL8390_LED_FIB_SET_SEL_CTRL(p) (0x0100 + (((p >> 4) << 2)))
|
||||
#define RTL8390_LED_COPR_PMASK_CTRL(p) (0x0110 + (((p >> 5) << 2)))
|
||||
#define RTL8390_LED_FIB_PMASK_CTRL(p) (0x00118 + (((p >> 5) << 2)))
|
||||
#define RTL8390_LED_COMBO_CTRL(p) (0x0120 + (((p >> 5) << 2)))
|
||||
#define RTL8390_LED_SW_CTRL (0x0128)
|
||||
#define RTL8390_LED_SW_P_EN_CTRL(p) (0x012C + (((p / 10) << 2)))
|
||||
#define RTL8390_LED_SW_P_CTRL(p) (0x0144 + (((p) << 2)))
|
||||
|
||||
#define RTL838X_MIR_QID_CTRL(grp) (0xAD44 + (((grp) << 2)))
|
||||
#define RTL838X_MIR_RSPAN_VLAN_CTRL(grp) (0xA340 + (((grp) << 2)))
|
||||
#define RTL838X_MIR_RSPAN_VLAN_CTRL_MAC(grp) (0xAA70 + (((grp) << 2)))
|
||||
#define RTL838X_MIR_RSPAN_TX_CTRL (0xA350)
|
||||
#define RTL838X_MIR_RSPAN_TX_TAG_RM_CTRL (0xAA80)
|
||||
#define RTL838X_MIR_RSPAN_TX_TAG_EN_CTRL (0xAA84)
|
||||
#define RTL839X_MIR_RSPAN_VLAN_CTRL(grp) (0xA340 + (((grp) << 2)))
|
||||
#define RTL839X_MIR_RSPAN_TX_CTRL (0x69b0)
|
||||
#define RTL839X_MIR_RSPAN_TX_TAG_RM_CTRL (0x2550)
|
||||
#define RTL839X_MIR_RSPAN_TX_TAG_EN_CTRL (0x2554)
|
||||
#define RTL839X_MIR_SAMPLE_RATE_CTRL (0x2558)
|
||||
|
||||
#define RTL838X_STAT_PRVTE_DROP_COUNTERS (0x6A00)
|
||||
#define RTL839X_STAT_PRVTE_DROP_COUNTERS (0x3E00)
|
||||
#define RTL930X_STAT_PRVTE_DROP_COUNTERS (0xB5B8)
|
||||
#define RTL931X_STAT_PRVTE_DROP_COUNTERS (0xd800)
|
||||
|
||||
const char *rtl838x_drop_cntr[] = {
|
||||
"ALE_TX_GOOD_PKTS", "MAC_RX_DROP", "ACL_FWD_DROP", "HW_ATTACK_PREVENTION_DROP",
|
||||
"RMA_DROP", "VLAN_IGR_FLTR_DROP", "INNER_OUTER_CFI_EQUAL_1_DROP", "PORT_MOVE_DROP",
|
||||
"NEW_SA_DROP", "MAC_LIMIT_SYS_DROP", "MAC_LIMIT_VLAN_DROP", "MAC_LIMIT_PORT_DROP",
|
||||
"SWITCH_MAC_DROP", "ROUTING_EXCEPTION_DROP", "DA_LKMISS_DROP", "RSPAN_DROP",
|
||||
"ACL_LKMISS_DROP", "ACL_DROP", "INBW_DROP", "IGR_METER_DROP",
|
||||
"ACCEPT_FRAME_TYPE_DROP", "STP_IGR_DROP", "INVALID_SA_DROP", "SA_BLOCKING_DROP",
|
||||
"DA_BLOCKING_DROP", "L2_INVALID_DPM_DROP", "MCST_INVALID_DPM_DROP", "RX_FLOW_CONTROL_DROP",
|
||||
"STORM_SPPRS_DROP", "LALS_DROP", "VLAN_EGR_FILTER_DROP", "STP_EGR_DROP",
|
||||
"SRC_PORT_FILTER_DROP", "PORT_ISOLATION_DROP", "ACL_FLTR_DROP", "MIRROR_FLTR_DROP",
|
||||
"TX_MAX_DROP", "LINK_DOWN_DROP", "FLOW_CONTROL_DROP", "BRIDGE .1d discards"
|
||||
};
|
||||
|
||||
const char *rtl839x_drop_cntr[] = {
|
||||
"ALE_TX_GOOD_PKTS", "ERROR_PKTS", "EGR_ACL_DROP", "EGR_METER_DROP",
|
||||
"OAM", "CFM", "VLAN_IGR_FLTR", "VLAN_ERR",
|
||||
"INNER_OUTER_CFI_EQUAL_1", "VLAN_TAG_FORMAT", "SRC_PORT_SPENDING_TREE", "INBW",
|
||||
"RMA", "HW_ATTACK_PREVENTION", "PROTO_STORM", "MCAST_SA",
|
||||
"IGR_ACL_DROP", "IGR_METER_DROP", "DFLT_ACTION_FOR_MISS_ACL_AND_C2SC", "NEW_SA",
|
||||
"PORT_MOVE", "SA_BLOCKING", "ROUTING_EXCEPTION", "SRC_PORT_SPENDING_TREE_NON_FWDING",
|
||||
"MAC_LIMIT", "UNKNOW_STORM", "MISS_DROP", "CPU_MAC_DROP",
|
||||
"DA_BLOCKING", "SRC_PORT_FILTER_BEFORE_EGR_ACL", "VLAN_EGR_FILTER", "SPANNING_TRE",
|
||||
"PORT_ISOLATION", "OAM_EGRESS_DROP", "MIRROR_ISOLATION", "MAX_LEN_BEFORE_EGR_ACL",
|
||||
"SRC_PORT_FILTER_BEFORE_MIRROR", "MAX_LEN_BEFORE_MIRROR", "SPECIAL_CONGEST_BEFORE_MIRROR",
|
||||
"LINK_STATUS_BEFORE_MIRROR",
|
||||
"WRED_BEFORE_MIRROR", "MAX_LEN_AFTER_MIRROR", "SPECIAL_CONGEST_AFTER_MIRROR",
|
||||
"LINK_STATUS_AFTER_MIRROR",
|
||||
"WRED_AFTER_MIRROR"
|
||||
};
|
||||
|
||||
const char *rtl930x_drop_cntr[] = {
|
||||
"OAM_PARSER", "UC_RPF", "DEI_CFI", "MAC_IP_SUBNET_BASED_VLAN", "VLAN_IGR_FILTER",
|
||||
"L2_UC_MC", "IPV_IP6_MC_BRIDGE", "PTP", "USER_DEF_0_3", "RESERVED",
|
||||
"RESERVED1", "RESERVED2", "BPDU_RMA", "LACP", "LLDP",
|
||||
"EAPOL", "XX_RMA", "L3_IPUC_NON_IP", "IP4_IP6_HEADER_ERROR", "L3_BAD_IP",
|
||||
"L3_DIP_DMAC_MISMATCH", "IP4_IP_OPTION", "IP_UC_MC_ROUTING_LOOK_UP_MISS", "L3_DST_NULL_INTF",
|
||||
"L3_PBR_NULL_INTF",
|
||||
"HOST_NULL_INTF", "ROUTE_NULL_INTF", "BRIDGING_ACTION", "ROUTING_ACTION", "IPMC_RPF",
|
||||
"L2_NEXTHOP_AGE_OUT", "L3_UC_TTL_FAIL", "L3_MC_TTL_FAIL", "L3_UC_MTU_FAIL", "L3_MC_MTU_FAIL",
|
||||
"L3_UC_ICMP_REDIR", "IP6_MLD_OTHER_ACT", "ND", "IP_MC_RESERVED", "IP6_HBH",
|
||||
"INVALID_SA", "L2_HASH_FULL", "NEW_SA", "PORT_MOVE_FORBID", "STATIC_PORT_MOVING",
|
||||
"DYNMIC_PORT_MOVING", "L3_CRC", "MAC_LIMIT", "ATTACK_PREVENT", "ACL_FWD_ACTION",
|
||||
"OAMPDU", "OAM_MUX", "TRUNK_FILTER", "ACL_DROP", "IGR_BW",
|
||||
"ACL_METER", "VLAN_ACCEPT_FRAME_TYPE", "MSTP_SRC_DROP_DISABLED_BLOCKING", "SA_BLOCK", "DA_BLOCK",
|
||||
"STORM_CONTROL", "VLAN_EGR_FILTER", "MSTP_DESTINATION_DROP", "SRC_PORT_FILTER", "PORT_ISOLATION",
|
||||
"TX_MAX_FRAME_SIZE", "EGR_LINK_STATUS", "MAC_TX_DISABLE", "MAC_PAUSE_FRAME", "MAC_RX_DROP",
|
||||
"MIRROR_ISOLATE", "RX_FC", "EGR_QUEUE", "HSM_RUNOUT", "ROUTING_DISABLE", "INVALID_L2_NEXTHOP_ENTRY",
|
||||
"L3_MC_SRC_FLT", "CPUTAG_FLT", "FWD_PMSK_NULL", "IPUC_ROUTING_LOOKUP_MISS", "MY_DEV_DROP",
|
||||
"STACK_NONUC_BLOCKING_PMSK", "STACK_PORT_NOT_FOUND", "ACL_LOOPBACK_DROP", "IP6_ROUTING_EXT_HEADER"
|
||||
};
|
||||
|
||||
const char *rtl931x_drop_cntr[] = {
|
||||
"ALE_RX_GOOD_PKTS", "RX_MAX_FRAME_SIZE", "MAC_RX_DROP", "OPENFLOW_IP_MPLS_TTL", "OPENFLOW_TBL_MISS",
|
||||
"IGR_BW", "SPECIAL_CONGEST", "EGR_QUEUE", "RESERVED", "EGR_LINK_STATUS", "STACK_UCAST_NONUCAST_TTL", /* 10 */
|
||||
"STACK_NONUC_BLOCKING_PMSK", "L2_CRC", "SRC_PORT_FILTER", "PARSER_PACKET_TOO_LONG", "PARSER_MALFORM_PACKET",
|
||||
"MPLS_OVER_2_LBL", "EACL_METER", "IACL_METER", "PROTO_STORM", "INVALID_CAPWAP_HEADER", /* 20 */
|
||||
"MAC_IP_SUBNET_BASED_VLAN", "OAM_PARSER", "UC_MC_RPF", "IP_MAC_BINDING_MATCH_MISMATCH", "SA_BLOCK",
|
||||
"TUNNEL_IP_ADDRESS_CHECK", "EACL_DROP", "IACL_DROP", "ATTACK_PREVENT", "SYSTEM_PORT_LIMIT_LEARN", /* 30 */
|
||||
"OAMPDU", "CCM_RX", "CFM_UNKNOWN_TYPE", "LBM_LBR_LTM_LTR", "Y_1731", "VLAN_LIMIT_LEARN",
|
||||
"VLAN_ACCEPT_FRAME_TYPE", "CFI_1", "STATIC_DYNAMIC_PORT_MOVING", "PORT_MOVE_FORBID", /* 40 */
|
||||
"L3_CRC", "BPDU_PTP_LLDP_EAPOL_RMA", "MSTP_SRC_DROP_DISABLED_BLOCKING", "INVALID_SA", "NEW_SA",
|
||||
"VLAN_IGR_FILTER", "IGR_VLAN_CONVERT", "GRATUITOUS_ARP", "MSTP_SRC_DROP", "L2_HASH_FULL", /* 50 */
|
||||
"MPLS_UNKNOWN_LBL", "L3_IPUC_NON_IP", "TTL", "MTU", "ICMP_REDIRECT", "STORM_CONTROL", "L3_DIP_DMAC_MISMATCH",
|
||||
"IP4_IP_OPTION", "IP6_HBH_EXT_HEADER", "IP4_IP6_HEADER_ERROR", /* 60 */
|
||||
"ROUTING_IP_ADDR_CHECK", "ROUTING_EXCEPTION", "DA_BLOCK", "OAM_MUX", "PORT_ISOLATION", "VLAN_EGR_FILTER",
|
||||
"MIRROR_ISOLATE", "MSTP_DESTINATION_DROP", "L2_MC_BRIDGE", "IP_UC_MC_ROUTING_LOOK_UP_MISS", /* 70 */
|
||||
"L2_UC", "L2_MC", "IP4_MC", "IP6_MC", "L3_UC_MC_ROUTE", "UNKNOWN_L2_UC_FLPM", "BC_FLPM",
|
||||
"VLAN_PRO_UNKNOWN_L2_MC_FLPM", "VLAN_PRO_UNKNOWN_IP4_MC_FLPM", "VLAN_PROFILE_UNKNOWN_IP6_MC_FLPM", /* 80 */
|
||||
};
|
||||
|
||||
static ssize_t rtl838x_common_read(char __user *buffer, size_t count,
|
||||
loff_t *ppos, unsigned int value)
|
||||
{
|
||||
char *buf;
|
||||
ssize_t len;
|
||||
|
||||
if (*ppos != 0)
|
||||
return 0;
|
||||
|
||||
buf = kasprintf(GFP_KERNEL, "0x%08x\n", value);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (count < strlen(buf)) {
|
||||
kfree(buf);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
len = simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
|
||||
kfree(buf);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t rtl838x_common_write(const char __user *buffer, size_t count,
|
||||
loff_t *ppos, unsigned int *value)
|
||||
{
|
||||
char b[32];
|
||||
ssize_t len;
|
||||
int ret;
|
||||
|
||||
if (*ppos != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (count >= sizeof(b))
|
||||
return -ENOSPC;
|
||||
|
||||
len = simple_write_to_buffer(b, sizeof(b) - 1, ppos,
|
||||
buffer, count);
|
||||
if (len < 0)
|
||||
return len;
|
||||
|
||||
b[len] = '\0';
|
||||
ret = kstrtouint(b, 16, value);
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t stp_state_read(struct file *filp, char __user *buffer, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct rtldsa_port *p = filp->private_data;
|
||||
struct dsa_switch *ds = p->dp->ds;
|
||||
int state = rtldsa_port_get_stp_state(ds->priv, p->dp->index);
|
||||
|
||||
if (state < 0)
|
||||
return state;
|
||||
|
||||
return rtl838x_common_read(buffer, count, ppos, (u32)state);
|
||||
}
|
||||
|
||||
static ssize_t stp_state_write(struct file *filp, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rtldsa_port *p = filp->private_data;
|
||||
u32 value;
|
||||
size_t res = rtl838x_common_write(buffer, count, ppos, &value);
|
||||
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
rtldsa_port_stp_state_set(p->dp->ds, p->dp->index, (u8)value);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static const struct file_operations stp_state_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = simple_open,
|
||||
.read = stp_state_read,
|
||||
.write = stp_state_write,
|
||||
};
|
||||
|
||||
static ssize_t drop_counter_read(struct file *filp, char __user *buffer, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = filp->private_data;
|
||||
const char **d;
|
||||
u32 v;
|
||||
char *buf;
|
||||
int n = 0, len, offset;
|
||||
int num;
|
||||
|
||||
switch (priv->family_id) {
|
||||
case RTL8380_FAMILY_ID:
|
||||
d = rtl838x_drop_cntr;
|
||||
offset = RTL838X_STAT_PRVTE_DROP_COUNTERS;
|
||||
num = ARRAY_SIZE(rtl838x_drop_cntr);
|
||||
BUILD_BUG_ON(num != 40);
|
||||
break;
|
||||
case RTL8390_FAMILY_ID:
|
||||
d = rtl839x_drop_cntr;
|
||||
offset = RTL839X_STAT_PRVTE_DROP_COUNTERS;
|
||||
num = ARRAY_SIZE(rtl839x_drop_cntr);
|
||||
BUILD_BUG_ON(num != 45);
|
||||
break;
|
||||
case RTL9300_FAMILY_ID:
|
||||
d = rtl930x_drop_cntr;
|
||||
offset = RTL930X_STAT_PRVTE_DROP_COUNTERS;
|
||||
num = ARRAY_SIZE(rtl930x_drop_cntr);
|
||||
BUILD_BUG_ON(num != 85);
|
||||
break;
|
||||
case RTL9310_FAMILY_ID:
|
||||
d = rtl931x_drop_cntr;
|
||||
offset = RTL931X_STAT_PRVTE_DROP_COUNTERS;
|
||||
num = ARRAY_SIZE(rtl931x_drop_cntr);
|
||||
BUILD_BUG_ON(num != 81);
|
||||
break;
|
||||
}
|
||||
|
||||
buf = kmalloc(30 * num, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
v = sw_r32(offset + (i << 2)) & 0xffff;
|
||||
n += sprintf(buf + n, "%s: %d\n", d[i], v);
|
||||
}
|
||||
|
||||
if (count < strlen(buf)) {
|
||||
kfree(buf);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
len = simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
|
||||
kfree(buf);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static const struct file_operations drop_counter_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = simple_open,
|
||||
.read = drop_counter_read,
|
||||
};
|
||||
|
||||
static void l2_table_print_entry(struct seq_file *m, struct rtl838x_switch_priv *priv,
|
||||
struct rtl838x_l2_entry *e)
|
||||
{
|
||||
u64 portmask;
|
||||
|
||||
if (e->type == L2_UNICAST) {
|
||||
seq_puts(m, "L2_UNICAST\n");
|
||||
|
||||
seq_printf(m, " mac %02x:%02x:%02x:%02x:%02x:%02x vid %u rvid %u\n",
|
||||
e->mac[0], e->mac[1], e->mac[2], e->mac[3], e->mac[4], e->mac[5],
|
||||
e->vid, e->rvid);
|
||||
|
||||
seq_printf(m, " port %d age %d", e->port, e->age);
|
||||
if (e->is_trunk) {
|
||||
seq_printf(m, " trunk %d trunk_members: 0x%08llx non-primary: 0x%08llx primary-port: %d",
|
||||
e->trunk,
|
||||
priv->lags_port_members[e->trunk],
|
||||
priv->lag_non_primary,
|
||||
priv->lag_primary[e->trunk]);
|
||||
}
|
||||
if (e->is_static)
|
||||
seq_puts(m, " static");
|
||||
if (e->block_da)
|
||||
seq_puts(m, " block_da");
|
||||
if (e->block_sa)
|
||||
seq_puts(m, " block_sa");
|
||||
if (e->suspended)
|
||||
seq_puts(m, " suspended");
|
||||
if (e->next_hop)
|
||||
seq_printf(m, " next_hop route_id %u", e->nh_route_id);
|
||||
seq_puts(m, "\n");
|
||||
|
||||
} else {
|
||||
if (e->type == L2_MULTICAST) {
|
||||
seq_puts(m, "L2_MULTICAST\n");
|
||||
|
||||
seq_printf(m, " mac %02x:%02x:%02x:%02x:%02x:%02x vid %u rvid %u\n",
|
||||
e->mac[0], e->mac[1], e->mac[2], e->mac[3], e->mac[4], e->mac[5],
|
||||
e->vid, e->rvid);
|
||||
}
|
||||
|
||||
if (e->type == IP4_MULTICAST || e->type == IP6_MULTICAST) {
|
||||
seq_puts(m, (e->type == IP4_MULTICAST) ?
|
||||
"IP4_MULTICAST\n" : "IP6_MULTICAST\n");
|
||||
|
||||
seq_printf(m, " gip %08x sip %08x vid %u rvid %u\n",
|
||||
e->mc_gip, e->mc_sip, e->vid, e->rvid);
|
||||
}
|
||||
|
||||
portmask = priv->r->read_mcast_pmask(e->mc_portmask_index);
|
||||
seq_printf(m, " index %u ports", e->mc_portmask_index);
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (portmask & BIT_ULL(i))
|
||||
seq_printf(m, " %d", i);
|
||||
}
|
||||
seq_puts(m, "\n");
|
||||
}
|
||||
|
||||
seq_puts(m, "\n");
|
||||
}
|
||||
|
||||
static int l2_table_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = m->private;
|
||||
struct rtl838x_l2_entry e;
|
||||
int bucket, index;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
for (int i = 0; i < priv->fib_entries; i++) {
|
||||
bucket = i >> 2;
|
||||
index = i & 0x3;
|
||||
priv->r->read_l2_entry_using_hash(bucket, index, &e);
|
||||
|
||||
if (!e.valid)
|
||||
continue;
|
||||
|
||||
seq_printf(m, "Hash table bucket %d index %d ", bucket, index);
|
||||
l2_table_print_entry(m, priv, &e);
|
||||
|
||||
if (!((i + 1) % 64))
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
priv->r->read_cam(i, &e);
|
||||
|
||||
if (!e.valid)
|
||||
continue;
|
||||
|
||||
seq_printf(m, "CAM index %d ", i);
|
||||
l2_table_print_entry(m, priv, &e);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l2_table_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return single_open(filp, l2_table_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations l2_table_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = l2_table_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int rtldsa_pmsks_table_raw_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = m->private;
|
||||
u64 all_ports;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
for (int i = 0; i < MAX_MC_PMASKS; i += 4) {
|
||||
seq_printf(m, "%04i: ", i);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
bool is_set = test_bit(i + j, priv->mc_group_bm);
|
||||
seq_printf(m, " %c0x%016llx%c", is_set ? ' ' : '(',
|
||||
priv->r->read_mcast_pmask(i + j),
|
||||
is_set ? ' ' : ')');
|
||||
}
|
||||
seq_printf(m, "\n");
|
||||
}
|
||||
|
||||
all_ports = priv->r->read_mcast_pmask(MC_PMASK_ALL_PORTS_IDX);
|
||||
seq_printf(m, "MC_PMASK_ALL_PORTS (%i): 0x%016llx\n",
|
||||
MC_PMASK_ALL_PORTS_IDX, all_ports);
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtldsa_pmsks_table_raw_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return single_open(filp, rtldsa_pmsks_table_raw_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations rtldsa_pmsks_table_raw_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rtldsa_pmsks_table_raw_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int rtldsa_pmsks_table_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = m->private;
|
||||
u64 ports;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
for (int i = 0; i < MC_PMASK_ALL_PORTS_IDX; i++) {
|
||||
if (!test_bit(i, priv->mc_group_bm))
|
||||
continue;
|
||||
|
||||
ports = priv->r->read_mcast_pmask(i);
|
||||
seq_printf(m, "%04i:", i);
|
||||
for (int j = 0; j < sizeof(ports)*8; j++)
|
||||
if (ports & BIT_ULL(j))
|
||||
seq_printf(m, " %i", j);
|
||||
seq_printf(m, "\n");
|
||||
}
|
||||
|
||||
ports = priv->r->read_mcast_pmask(MC_PMASK_ALL_PORTS_IDX);
|
||||
seq_printf(m, "MC_PMASK_ALL_PORTS (%i):", MC_PMASK_ALL_PORTS_IDX);
|
||||
for (int i = 0; i < sizeof(ports)*8; i++)
|
||||
if (ports & BIT_ULL(i))
|
||||
seq_printf(m, " %i", i);
|
||||
seq_printf(m, "\n");
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtldsa_pmsks_table_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return single_open(filp, rtldsa_pmsks_table_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations rtldsa_pmsks_table_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rtldsa_pmsks_table_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int rtldsa_vlan_profiles_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = m->private;
|
||||
struct rtldsa_vlan_profile profile;
|
||||
int ret, profiles_max;
|
||||
|
||||
if (!priv->r->vlan_profile_get)
|
||||
return -ENOTSUPP;
|
||||
|
||||
profiles_max = max(RTL838X_VLAN_PROFILE_MAX, RTL839X_VLAN_PROFILE_MAX);
|
||||
profiles_max = max(profiles_max, RTL930X_VLAN_PROFILE_MAX);
|
||||
profiles_max = max(profiles_max, RTL931X_VLAN_PROFILE_MAX);
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
seq_printf(m,
|
||||
"prof-idx: L2 learn | UNKN L2MC FLD PMSK (IDX) | UNKN IPMC FLD PMSK (IDX) | UNKN IPv6MC FLD PMSK (IDX)\n");
|
||||
for (int i = 0; i <= profiles_max; i++) {
|
||||
ret = priv->r->vlan_profile_get(i, &profile);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
if (profile.pmsk_is_idx)
|
||||
seq_printf(m, "%i: %i %03i %03i %03i\n", i,
|
||||
profile.l2_learn,
|
||||
profile.unkn_mc_fld.pmsks_idx.l2,
|
||||
profile.unkn_mc_fld.pmsks_idx.ip,
|
||||
profile.unkn_mc_fld.pmsks_idx.ip6);
|
||||
else
|
||||
seq_printf(m, "%i: %i 0x%016llx 0x%016llx 0x%016llx\n", i,
|
||||
profile.l2_learn,
|
||||
profile.unkn_mc_fld.pmsks.l2,
|
||||
profile.unkn_mc_fld.pmsks.ip,
|
||||
profile.unkn_mc_fld.pmsks.ip6);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtldsa_vlan_profiles_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return single_open(filp, rtldsa_vlan_profiles_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations rtldsa_vlan_profiles_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rtldsa_vlan_profiles_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int rtldsa_vlan_table_raw_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = m->private;
|
||||
struct rtl838x_vlan_info info;
|
||||
|
||||
if (!priv->r->vlan_tables_read)
|
||||
return -ENOTSUPP;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
seq_printf(m, "VID: profile-index untagged-ports member-ports\n");
|
||||
|
||||
for (int i = 0; i < MAX_VLANS; i++) {
|
||||
priv->r->vlan_tables_read(i, &info);
|
||||
|
||||
if (!info.member_ports)
|
||||
continue;
|
||||
|
||||
seq_printf(m, "%i: %2i 0x%016llx 0x%016llx\n", i,
|
||||
info.profile_id,
|
||||
info.untagged_ports,
|
||||
info.member_ports);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtldsa_vlan_table_raw_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return single_open(filp, rtldsa_vlan_table_raw_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations rtldsa_vlan_table_raw_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rtldsa_vlan_table_raw_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
|
||||
static int rtldsa_vlan_table_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = m->private;
|
||||
struct rtl838x_vlan_info info;
|
||||
|
||||
if (!priv->r->vlan_tables_read)
|
||||
return -ENOTSUPP;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
for (int i = 0; i < MAX_VLANS; i++) {
|
||||
priv->r->vlan_tables_read(i, &info);
|
||||
|
||||
if (!info.member_ports)
|
||||
continue;
|
||||
|
||||
seq_printf(m, "%i: %i |", i, info.profile_id);
|
||||
for (int j = 0; j < sizeof(info.member_ports)*8; j++)
|
||||
if (info.member_ports & BIT_ULL(j))
|
||||
seq_printf(m, " %i%s", j,
|
||||
(info.untagged_ports & BIT_ULL(j)) ? "" : "t");
|
||||
seq_printf(m, "\n");
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtldsa_vlan_table_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return single_open(filp, rtldsa_vlan_table_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations rtldsa_vlan_table_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rtldsa_vlan_table_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static ssize_t age_out_read(struct file *filp, char __user *buffer, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct rtldsa_port *p = filp->private_data;
|
||||
struct dsa_switch *ds = p->dp->ds;
|
||||
struct rtl838x_switch_priv *priv = ds->priv;
|
||||
int value = sw_r32(priv->r->l2_port_aging_out);
|
||||
|
||||
if (value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
return rtl838x_common_read(buffer, count, ppos, (u32)value);
|
||||
}
|
||||
|
||||
static ssize_t age_out_write(struct file *filp, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rtldsa_port *p = filp->private_data;
|
||||
u32 value;
|
||||
size_t res = rtl838x_common_write(buffer, count, ppos, &value);
|
||||
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
rtldsa_port_fast_age(p->dp->ds, p->dp->index);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static const struct file_operations age_out_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = simple_open,
|
||||
.read = age_out_read,
|
||||
.write = age_out_write,
|
||||
};
|
||||
|
||||
static ssize_t port_egress_rate_read(struct file *filp, char __user *buffer, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct rtldsa_port *p = filp->private_data;
|
||||
struct dsa_switch *ds = p->dp->ds;
|
||||
struct rtl838x_switch_priv *priv = ds->priv;
|
||||
int value;
|
||||
|
||||
if (priv->family_id == RTL8380_FAMILY_ID)
|
||||
value = rtl838x_get_egress_rate(priv, p->dp->index);
|
||||
else
|
||||
value = rtl839x_get_egress_rate(priv, p->dp->index);
|
||||
|
||||
if (value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
return rtl838x_common_read(buffer, count, ppos, (u32)value);
|
||||
}
|
||||
|
||||
static ssize_t port_egress_rate_write(struct file *filp, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rtldsa_port *p = filp->private_data;
|
||||
struct dsa_switch *ds = p->dp->ds;
|
||||
struct rtl838x_switch_priv *priv = ds->priv;
|
||||
u32 value;
|
||||
size_t res = rtl838x_common_write(buffer, count, ppos, &value);
|
||||
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
if (priv->family_id == RTL8380_FAMILY_ID)
|
||||
rtl838x_set_egress_rate(priv, p->dp->index, value);
|
||||
else
|
||||
rtl839x_set_egress_rate(priv, p->dp->index, value);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static const struct file_operations port_egress_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = simple_open,
|
||||
.read = port_egress_rate_read,
|
||||
.write = port_egress_rate_write,
|
||||
};
|
||||
|
||||
static const struct debugfs_reg32 port_ctrl_regs[] = {
|
||||
{ .name = "port_isolation", .offset = RTL838X_PORT_ISO_CTRL(0), },
|
||||
{ .name = "mac_force_mode", .offset = RTL838X_MAC_FORCE_MODE_CTRL, },
|
||||
};
|
||||
|
||||
static void rtl838x_dbgfs_cleanup(struct rtl838x_switch_priv *priv)
|
||||
{
|
||||
debugfs_remove_recursive(priv->dbgfs_dir);
|
||||
|
||||
/* kfree(priv->dbgfs_entries); */
|
||||
}
|
||||
|
||||
static int rtl838x_dbgfs_port_init(struct dentry *parent, struct rtl838x_switch_priv *priv,
|
||||
int port)
|
||||
{
|
||||
struct dentry *port_dir;
|
||||
struct debugfs_regset32 *port_ctrl_regset;
|
||||
|
||||
port_dir = debugfs_create_dir(priv->ports[port].dp->name, parent);
|
||||
|
||||
if (priv->family_id == RTL8380_FAMILY_ID) {
|
||||
debugfs_create_x32("storm_rate_uc", 0644, port_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_STORM_CTRL_PORT_UC(port)));
|
||||
|
||||
debugfs_create_x32("storm_rate_mc", 0644, port_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_STORM_CTRL_PORT_MC(port)));
|
||||
|
||||
debugfs_create_x32("storm_rate_bc", 0644, port_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_STORM_CTRL_PORT_BC(port)));
|
||||
} else {
|
||||
debugfs_create_x32("storm_rate_uc", 0644, port_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_STORM_CTRL_PORT_UC_0(port)));
|
||||
|
||||
debugfs_create_x32("storm_rate_mc", 0644, port_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_STORM_CTRL_PORT_MC_0(port)));
|
||||
|
||||
debugfs_create_x32("storm_rate_bc", 0644, port_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_STORM_CTRL_PORT_BC_0(port)));
|
||||
}
|
||||
|
||||
debugfs_create_u32("id", 0444, port_dir, (u32 *)&priv->ports[port].dp->index);
|
||||
|
||||
port_ctrl_regset = devm_kzalloc(priv->dev, sizeof(*port_ctrl_regset), GFP_KERNEL);
|
||||
if (!port_ctrl_regset)
|
||||
return -ENOMEM;
|
||||
|
||||
port_ctrl_regset->regs = port_ctrl_regs;
|
||||
port_ctrl_regset->nregs = ARRAY_SIZE(port_ctrl_regs);
|
||||
port_ctrl_regset->base = (void *)(RTL838X_SW_BASE + (port << 2));
|
||||
debugfs_create_regset32("port_ctrl", 0400, port_dir, port_ctrl_regset);
|
||||
|
||||
debugfs_create_file("stp_state", 0600, port_dir, &priv->ports[port], &stp_state_fops);
|
||||
debugfs_create_file("age_out", 0600, port_dir, &priv->ports[port], &age_out_fops);
|
||||
debugfs_create_file("port_egress_rate", 0600, port_dir, &priv->ports[port],
|
||||
&port_egress_fops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl838x_dbgfs_leds(struct dentry *parent, struct rtl838x_switch_priv *priv)
|
||||
{
|
||||
struct dentry *led_dir;
|
||||
|
||||
led_dir = debugfs_create_dir("led", parent);
|
||||
|
||||
if (priv->family_id == RTL8380_FAMILY_ID) {
|
||||
debugfs_create_x32("led_glb_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED_GLB_CTRL));
|
||||
debugfs_create_x32("led_mode_sel", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED_MODE_SEL));
|
||||
debugfs_create_x32("led_mode_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED_MODE_CTRL));
|
||||
debugfs_create_x32("led_p_en_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED_P_EN_CTRL));
|
||||
debugfs_create_x32("led_sw_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED_SW_CTRL));
|
||||
debugfs_create_x32("led0_sw_p_en_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED0_SW_P_EN_CTRL));
|
||||
debugfs_create_x32("led1_sw_p_en_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED1_SW_P_EN_CTRL));
|
||||
debugfs_create_x32("led2_sw_p_en_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED2_SW_P_EN_CTRL));
|
||||
for (int p = 0; p < 28; p++) {
|
||||
char led_sw_p_ctrl_name[20];
|
||||
|
||||
snprintf(led_sw_p_ctrl_name, sizeof(led_sw_p_ctrl_name),
|
||||
"led_sw_p_ctrl.%02d", p);
|
||||
debugfs_create_x32(led_sw_p_ctrl_name, 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_LED_SW_P_CTRL_PORT(p)));
|
||||
}
|
||||
} else if (priv->family_id == RTL8390_FAMILY_ID) {
|
||||
char port_led_name[20];
|
||||
|
||||
debugfs_create_x32("led_glb_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_GLB_CTRL));
|
||||
debugfs_create_x32("led_set_2_3", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_SET_2_3_CTRL));
|
||||
debugfs_create_x32("led_set_0_1", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_SET_0_1_CTRL));
|
||||
for (int p = 0; p < 4; p++) {
|
||||
snprintf(port_led_name, sizeof(port_led_name), "led_copr_set_sel.%1d", p);
|
||||
debugfs_create_x32(port_led_name, 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_COPR_SET_SEL_CTRL(p << 4)));
|
||||
snprintf(port_led_name, sizeof(port_led_name), "led_fib_set_sel.%1d", p);
|
||||
debugfs_create_x32(port_led_name, 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_FIB_SET_SEL_CTRL(p << 4)));
|
||||
}
|
||||
debugfs_create_x32("led_copr_pmask_ctrl_0", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_COPR_PMASK_CTRL(0)));
|
||||
debugfs_create_x32("led_copr_pmask_ctrl_1", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_COPR_PMASK_CTRL(32)));
|
||||
debugfs_create_x32("led_fib_pmask_ctrl_0", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_FIB_PMASK_CTRL(0)));
|
||||
debugfs_create_x32("led_fib_pmask_ctrl_1", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_FIB_PMASK_CTRL(32)));
|
||||
debugfs_create_x32("led_combo_ctrl_0", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_COMBO_CTRL(0)));
|
||||
debugfs_create_x32("led_combo_ctrl_1", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_COMBO_CTRL(32)));
|
||||
debugfs_create_x32("led_sw_ctrl", 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_SW_CTRL));
|
||||
for (int p = 0; p < 5; p++) {
|
||||
snprintf(port_led_name, sizeof(port_led_name), "led_sw_p_en_ctrl.%1d", p);
|
||||
debugfs_create_x32(port_led_name, 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_SW_P_EN_CTRL(p * 10)));
|
||||
}
|
||||
for (int p = 0; p < 28; p++) {
|
||||
snprintf(port_led_name, sizeof(port_led_name), "led_sw_p_ctrl.%02d", p);
|
||||
debugfs_create_x32(port_led_name, 0644, led_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL8390_LED_SW_P_CTRL(p)));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rtl838x_dbgfs_init(struct rtl838x_switch_priv *priv)
|
||||
{
|
||||
struct dentry *rtl838x_dir;
|
||||
struct dentry *port_dir;
|
||||
struct dentry *mirror_dir;
|
||||
struct debugfs_regset32 *port_ctrl_regset;
|
||||
int ret;
|
||||
char lag_name[10];
|
||||
char mirror_name[10];
|
||||
|
||||
pr_info("%s called\n", __func__);
|
||||
rtl838x_dir = debugfs_lookup(RTL838X_DRIVER_NAME, NULL);
|
||||
if (!rtl838x_dir)
|
||||
rtl838x_dir = debugfs_create_dir(RTL838X_DRIVER_NAME, NULL);
|
||||
|
||||
priv->dbgfs_dir = rtl838x_dir;
|
||||
|
||||
debugfs_create_x32("soc", 0444, rtl838x_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MODEL_NAME_INFO));
|
||||
|
||||
/* Create one directory per port */
|
||||
for (int i = 0; i < priv->cpu_port; i++) {
|
||||
if (priv->ports[i].phy) {
|
||||
ret = rtl838x_dbgfs_port_init(rtl838x_dir, priv, i);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create directory for CPU-port */
|
||||
port_dir = debugfs_create_dir("cpu_port", rtl838x_dir);
|
||||
port_ctrl_regset = devm_kzalloc(priv->dev, sizeof(*port_ctrl_regset), GFP_KERNEL);
|
||||
if (!port_ctrl_regset) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
port_ctrl_regset->regs = port_ctrl_regs;
|
||||
port_ctrl_regset->nregs = ARRAY_SIZE(port_ctrl_regs);
|
||||
port_ctrl_regset->base = (void *)(RTL838X_SW_BASE + (priv->cpu_port << 2));
|
||||
debugfs_create_regset32("port_ctrl", 0400, port_dir, port_ctrl_regset);
|
||||
debugfs_create_u8("id", 0444, port_dir, &priv->cpu_port);
|
||||
|
||||
/* Create entries for LAGs */
|
||||
for (int i = 0; i < priv->ds->num_lag_ids; i++) {
|
||||
snprintf(lag_name, sizeof(lag_name), "lag.%02d", i);
|
||||
if (priv->family_id == RTL8380_FAMILY_ID)
|
||||
debugfs_create_x32(lag_name, 0644, rtl838x_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + priv->r->trk_mbr_ctr(i)));
|
||||
else
|
||||
debugfs_create_x64(lag_name, 0644, rtl838x_dir,
|
||||
(u64 *)(RTL838X_SW_BASE + priv->r->trk_mbr_ctr(i)));
|
||||
}
|
||||
|
||||
/* Create directories for mirror groups */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
snprintf(mirror_name, sizeof(mirror_name), "mirror.%1d", i);
|
||||
mirror_dir = debugfs_create_dir(mirror_name, rtl838x_dir);
|
||||
if (priv->family_id == RTL8380_FAMILY_ID) {
|
||||
debugfs_create_x32("ctrl", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_CTRL + i * 4));
|
||||
debugfs_create_x32("ingress_pm", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_SPM_CTRL + i * 4));
|
||||
debugfs_create_x32("egress_pm", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_DPM_CTRL + i * 4));
|
||||
debugfs_create_x32("qid", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_QID_CTRL(i)));
|
||||
debugfs_create_x32("rspan_vlan", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_RSPAN_VLAN_CTRL(i)));
|
||||
debugfs_create_x32("rspan_vlan_mac", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_RSPAN_VLAN_CTRL_MAC(i)));
|
||||
debugfs_create_x32("rspan_tx", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_RSPAN_TX_CTRL));
|
||||
debugfs_create_x32("rspan_tx_tag_rm", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_RSPAN_TX_TAG_RM_CTRL));
|
||||
debugfs_create_x32("rspan_tx_tag_en", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_MIR_RSPAN_TX_TAG_EN_CTRL));
|
||||
} else {
|
||||
debugfs_create_x32("ctrl", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_MIR_CTRL + i * 4));
|
||||
debugfs_create_x64("ingress_pm", 0644, mirror_dir,
|
||||
(u64 *)(RTL838X_SW_BASE + RTL839X_MIR_SPM_CTRL + i * 8));
|
||||
debugfs_create_x64("egress_pm", 0644, mirror_dir,
|
||||
(u64 *)(RTL838X_SW_BASE + RTL839X_MIR_DPM_CTRL + i * 8));
|
||||
debugfs_create_x32("rspan_vlan", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_MIR_RSPAN_VLAN_CTRL(i)));
|
||||
debugfs_create_x32("rspan_tx", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_MIR_RSPAN_TX_CTRL));
|
||||
debugfs_create_x32("rspan_tx_tag_rm", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_MIR_RSPAN_TX_TAG_RM_CTRL));
|
||||
debugfs_create_x32("rspan_tx_tag_en", 0644, mirror_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_MIR_RSPAN_TX_TAG_EN_CTRL));
|
||||
debugfs_create_x64("sample_rate", 0644, mirror_dir,
|
||||
(u64 *)(RTL838X_SW_BASE + RTL839X_MIR_SAMPLE_RATE_CTRL));
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->family_id == RTL8380_FAMILY_ID)
|
||||
debugfs_create_x32("bpdu_flood_mask", 0644, rtl838x_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + priv->r->rma_bpdu_fld_pmask));
|
||||
else
|
||||
debugfs_create_x64("bpdu_flood_mask", 0644, rtl838x_dir,
|
||||
(u64 *)(RTL838X_SW_BASE + priv->r->rma_bpdu_fld_pmask));
|
||||
|
||||
if (priv->family_id == RTL8380_FAMILY_ID)
|
||||
debugfs_create_x32("vlan_ctrl", 0644, rtl838x_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL838X_VLAN_CTRL));
|
||||
else
|
||||
debugfs_create_x32("vlan_ctrl", 0644, rtl838x_dir,
|
||||
(u32 *)(RTL838X_SW_BASE + RTL839X_VLAN_CTRL));
|
||||
|
||||
ret = rtl838x_dbgfs_leds(rtl838x_dir, priv);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
debugfs_create_file("drop_counters", 0400, rtl838x_dir, priv, &drop_counter_fops);
|
||||
|
||||
debugfs_create_file("l2_table", 0400, rtl838x_dir, priv, &l2_table_fops);
|
||||
|
||||
debugfs_create_file("port_masks_table_raw", 0400, rtl838x_dir, priv,
|
||||
&rtldsa_pmsks_table_raw_fops);
|
||||
|
||||
debugfs_create_file("port_masks_table", 0400, rtl838x_dir, priv,
|
||||
&rtldsa_pmsks_table_fops);
|
||||
|
||||
debugfs_create_file("vlan_profiles", 0400, rtl838x_dir, priv,
|
||||
&rtldsa_vlan_profiles_fops);
|
||||
|
||||
debugfs_create_file("vlan_table_raw", 0400, rtl838x_dir, priv,
|
||||
&rtldsa_vlan_table_raw_fops);
|
||||
|
||||
debugfs_create_file("vlan_table", 0400, rtl838x_dir, priv,
|
||||
&rtldsa_vlan_table_fops);
|
||||
|
||||
return;
|
||||
err:
|
||||
rtl838x_dbgfs_cleanup(priv);
|
||||
}
|
||||
|
||||
void rtl930x_dbgfs_init(struct rtl838x_switch_priv *priv)
|
||||
{
|
||||
struct dentry *dbg_dir;
|
||||
|
||||
pr_info("%s called\n", __func__);
|
||||
dbg_dir = debugfs_lookup(RTL838X_DRIVER_NAME, NULL);
|
||||
if (!dbg_dir)
|
||||
dbg_dir = debugfs_create_dir(RTL838X_DRIVER_NAME, NULL);
|
||||
|
||||
priv->dbgfs_dir = dbg_dir;
|
||||
|
||||
debugfs_create_file("drop_counters", 0400, dbg_dir, priv, &drop_counter_fops);
|
||||
|
||||
debugfs_create_file("l2_table", 0400, dbg_dir, priv, &l2_table_fops);
|
||||
|
||||
debugfs_create_file("port_masks_table_raw", 0400, dbg_dir, priv,
|
||||
&rtldsa_pmsks_table_raw_fops);
|
||||
|
||||
debugfs_create_file("port_masks_table", 0400, dbg_dir, priv,
|
||||
&rtldsa_pmsks_table_fops);
|
||||
|
||||
debugfs_create_file("vlan_profiles", 0400, dbg_dir, priv,
|
||||
&rtldsa_vlan_profiles_fops);
|
||||
|
||||
debugfs_create_file("vlan_table_raw", 0400, dbg_dir, priv,
|
||||
&rtldsa_vlan_table_raw_fops);
|
||||
|
||||
debugfs_create_file("vlan_table", 0400, dbg_dir, priv,
|
||||
&rtldsa_vlan_table_fops);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,371 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <net/dsa.h>
|
||||
#include <linux/delay.h>
|
||||
#include <asm/mach-rtl-otto/mach-rtl-otto.h>
|
||||
|
||||
#include "rtl83xx.h"
|
||||
|
||||
enum scheduler_type {
|
||||
WEIGHTED_FAIR_QUEUE = 0,
|
||||
WEIGHTED_ROUND_ROBIN,
|
||||
};
|
||||
|
||||
int rtldsa_max_available_queue[] = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
int rtldsa_default_queue_weights[] = {1, 1, 1, 1, 1, 1, 1, 1};
|
||||
int dot1p_priority_remapping[] = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
|
||||
static void rtl839x_read_scheduling_table(int port)
|
||||
{
|
||||
u32 cmd = 1 << 9 | /* Execute cmd */
|
||||
0 << 8 | /* Read */
|
||||
0 << 6 | /* Table type 0b00 */
|
||||
(port & 0x3f);
|
||||
rtl839x_exec_tbl2_cmd(cmd);
|
||||
}
|
||||
|
||||
static void rtl839x_write_scheduling_table(int port)
|
||||
{
|
||||
u32 cmd = 1 << 9 | /* Execute cmd */
|
||||
1 << 8 | /* Write */
|
||||
0 << 6 | /* Table type 0b00 */
|
||||
(port & 0x3f);
|
||||
rtl839x_exec_tbl2_cmd(cmd);
|
||||
}
|
||||
|
||||
static void rtl839x_read_out_q_table(int port)
|
||||
{
|
||||
u32 cmd = 1 << 9 | /* Execute cmd */
|
||||
0 << 8 | /* Read */
|
||||
2 << 6 | /* Table type 0b10 */
|
||||
(port & 0x3f);
|
||||
rtl839x_exec_tbl2_cmd(cmd);
|
||||
}
|
||||
|
||||
u32 rtl838x_get_egress_rate(struct rtl838x_switch_priv *priv, int port)
|
||||
{
|
||||
if (port > priv->cpu_port)
|
||||
return 0;
|
||||
|
||||
return sw_r32(RTL838X_SCHED_P_EGR_RATE_CTRL(port)) & 0x3fff;
|
||||
}
|
||||
|
||||
/* Sets the rate limit, 10MBit/s is equal to a rate value of 625 */
|
||||
int rtl838x_set_egress_rate(struct rtl838x_switch_priv *priv, int port, u32 rate)
|
||||
{
|
||||
u32 old_rate;
|
||||
|
||||
if (port > priv->cpu_port)
|
||||
return -1;
|
||||
|
||||
old_rate = sw_r32(RTL838X_SCHED_P_EGR_RATE_CTRL(port));
|
||||
sw_w32(rate, RTL838X_SCHED_P_EGR_RATE_CTRL(port));
|
||||
|
||||
return old_rate;
|
||||
}
|
||||
|
||||
/* Sets the rate limit, 10MBit/s is equal to a rate value of 625 */
|
||||
u32 rtl839x_get_egress_rate(struct rtl838x_switch_priv *priv, int port)
|
||||
{
|
||||
u32 rate;
|
||||
|
||||
pr_debug("%s: Getting egress rate on port %d to %d\n", __func__, port, rate);
|
||||
if (port >= priv->cpu_port)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
rtl839x_read_scheduling_table(port);
|
||||
|
||||
rate = sw_r32(RTL839X_TBL_ACCESS_DATA_2(7));
|
||||
rate <<= 12;
|
||||
rate |= sw_r32(RTL839X_TBL_ACCESS_DATA_2(8)) >> 20;
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
/* Sets the rate limit, 10MBit/s is equal to a rate value of 625, returns previous rate */
|
||||
int rtl839x_set_egress_rate(struct rtl838x_switch_priv *priv, int port, u32 rate)
|
||||
{
|
||||
u32 old_rate;
|
||||
|
||||
pr_debug("%s: Setting egress rate on port %d to %d\n", __func__, port, rate);
|
||||
if (port >= priv->cpu_port)
|
||||
return -1;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
rtl839x_read_scheduling_table(port);
|
||||
|
||||
old_rate = sw_r32(RTL839X_TBL_ACCESS_DATA_2(7)) & 0xff;
|
||||
old_rate <<= 12;
|
||||
old_rate |= sw_r32(RTL839X_TBL_ACCESS_DATA_2(8)) >> 20;
|
||||
sw_w32_mask(0xff, (rate >> 12) & 0xff, RTL839X_TBL_ACCESS_DATA_2(7));
|
||||
sw_w32_mask(0xfff << 20, rate << 20, RTL839X_TBL_ACCESS_DATA_2(8));
|
||||
|
||||
rtl839x_write_scheduling_table(port);
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
return old_rate;
|
||||
}
|
||||
|
||||
static void rtl838x_setup_prio2queue_matrix(int *min_queues)
|
||||
{
|
||||
u32 v = 0;
|
||||
|
||||
pr_info("Current Intprio2queue setting: %08x\n", sw_r32(RTL838X_QM_INTPRI2QID_CTRL));
|
||||
for (int i = 0; i < MAX_PRIOS; i++)
|
||||
v |= i << (min_queues[i] * 3);
|
||||
sw_w32(v, RTL838X_QM_INTPRI2QID_CTRL);
|
||||
}
|
||||
|
||||
static void rtl839x_setup_prio2queue_matrix(int *min_queues)
|
||||
{
|
||||
pr_info("Current Intprio2queue setting: %08x\n", sw_r32(RTL839X_QM_INTPRI2QID_CTRL(0)));
|
||||
for (int i = 0; i < MAX_PRIOS; i++) {
|
||||
int q = min_queues[i];
|
||||
|
||||
sw_w32(i << (q * 3), RTL839X_QM_INTPRI2QID_CTRL(q));
|
||||
}
|
||||
}
|
||||
|
||||
/* Sets the CPU queue depending on the internal priority of a packet */
|
||||
static void rtl83xx_setup_prio2queue_cpu_matrix(int *max_queues)
|
||||
{
|
||||
int reg = soc_info.family == RTL8380_FAMILY_ID ? RTL838X_QM_PKT2CPU_INTPRI_MAP
|
||||
: RTL839X_QM_PKT2CPU_INTPRI_MAP;
|
||||
u32 v = 0;
|
||||
|
||||
pr_info("QM_PKT2CPU_INTPRI_MAP: %08x\n", sw_r32(reg));
|
||||
for (int i = 0; i < MAX_PRIOS; i++)
|
||||
v |= max_queues[i] << (i * 3);
|
||||
sw_w32(v, reg);
|
||||
}
|
||||
|
||||
static void rtl83xx_setup_default_prio2queue(void)
|
||||
{
|
||||
if (soc_info.family == RTL8380_FAMILY_ID)
|
||||
rtl838x_setup_prio2queue_matrix(rtldsa_max_available_queue);
|
||||
else
|
||||
rtl839x_setup_prio2queue_matrix(rtldsa_max_available_queue);
|
||||
|
||||
rtl83xx_setup_prio2queue_cpu_matrix(rtldsa_max_available_queue);
|
||||
}
|
||||
|
||||
/* Sets the output queue assigned to a port, the port can be the CPU-port */
|
||||
void rtl839x_set_egress_queue(int port, int queue)
|
||||
{
|
||||
sw_w32(queue << ((port % 10) * 3), RTL839X_QM_PORT_QNUM(port));
|
||||
}
|
||||
|
||||
/* Sets the priority assigned of an ingress port, the port can be the CPU-port */
|
||||
static void rtl83xx_set_ingress_priority(int port, int priority)
|
||||
{
|
||||
if (soc_info.family == RTL8380_FAMILY_ID)
|
||||
sw_w32(priority << ((port % 10) * 3), RTL838X_PRI_SEL_PORT_PRI(port));
|
||||
else
|
||||
sw_w32(priority << ((port % 10) * 3), RTL839X_PRI_SEL_PORT_PRI(port));
|
||||
}
|
||||
|
||||
static int rtl839x_get_scheduling_algorithm(struct rtl838x_switch_priv *priv, int port)
|
||||
{
|
||||
u32 v;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
rtl839x_read_scheduling_table(port);
|
||||
v = sw_r32(RTL839X_TBL_ACCESS_DATA_2(8));
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
|
||||
if (v & BIT(19))
|
||||
return WEIGHTED_ROUND_ROBIN;
|
||||
|
||||
return WEIGHTED_FAIR_QUEUE;
|
||||
}
|
||||
|
||||
static void rtl839x_set_scheduling_algorithm(struct rtl838x_switch_priv *priv, int port,
|
||||
enum scheduler_type sched)
|
||||
{
|
||||
enum scheduler_type t = rtl839x_get_scheduling_algorithm(priv, port);
|
||||
u32 v, oam_state, oam_port_state;
|
||||
u32 count;
|
||||
int i, egress_rate;
|
||||
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
/* Check whether we need to empty the egress queue of that port due to Errata E0014503 */
|
||||
if (sched == WEIGHTED_FAIR_QUEUE && t == WEIGHTED_ROUND_ROBIN && port != priv->cpu_port) {
|
||||
/* Read Operations, Adminstatrion and Management control register */
|
||||
oam_state = sw_r32(RTL839X_OAM_CTRL);
|
||||
|
||||
/* Get current OAM state */
|
||||
oam_port_state = sw_r32(RTL839X_OAM_PORT_ACT_CTRL(port));
|
||||
|
||||
/* Disable OAM to block traffice */
|
||||
v = sw_r32(RTL839X_OAM_CTRL);
|
||||
sw_w32_mask(0, 1, RTL839X_OAM_CTRL);
|
||||
v = sw_r32(RTL839X_OAM_CTRL);
|
||||
|
||||
/* Set to trap action OAM forward (bits 1, 2) and OAM Mux Action Drop (bit 0) */
|
||||
sw_w32(0x2, RTL839X_OAM_PORT_ACT_CTRL(port));
|
||||
|
||||
/* Set port egress rate to unlimited */
|
||||
egress_rate = rtl839x_set_egress_rate(priv, port, 0xFFFFF);
|
||||
|
||||
/* Wait until the egress used page count of that port is 0 */
|
||||
i = 0;
|
||||
do {
|
||||
usleep_range(100, 200);
|
||||
rtl839x_read_out_q_table(port);
|
||||
count = sw_r32(RTL839X_TBL_ACCESS_DATA_2(6));
|
||||
count >>= 20;
|
||||
i++;
|
||||
} while (i < 3500 && count > 0);
|
||||
}
|
||||
|
||||
/* Actually set the scheduling algorithm */
|
||||
rtl839x_read_scheduling_table(port);
|
||||
sw_w32_mask(BIT(19), sched ? BIT(19) : 0, RTL839X_TBL_ACCESS_DATA_2(8));
|
||||
rtl839x_write_scheduling_table(port);
|
||||
|
||||
if (sched == WEIGHTED_FAIR_QUEUE && t == WEIGHTED_ROUND_ROBIN && port != priv->cpu_port) {
|
||||
/* Restore OAM state to control register */
|
||||
sw_w32(oam_state, RTL839X_OAM_CTRL);
|
||||
|
||||
/* Restore trap action state */
|
||||
sw_w32(oam_port_state, RTL839X_OAM_PORT_ACT_CTRL(port));
|
||||
|
||||
/* Restore port egress rate */
|
||||
rtl839x_set_egress_rate(priv, port, egress_rate);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
}
|
||||
|
||||
static void rtl839x_set_scheduling_queue_weights(struct rtl838x_switch_priv *priv, int port,
|
||||
int *queue_weights)
|
||||
{
|
||||
mutex_lock(&priv->reg_mutex);
|
||||
|
||||
rtl839x_read_scheduling_table(port);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int lsb = 48 + i * 8;
|
||||
int low_byte = 8 - (lsb >> 5);
|
||||
int start_bit = lsb - (low_byte << 5);
|
||||
int high_mask = 0x3ff >> (32 - start_bit);
|
||||
|
||||
sw_w32_mask(0x3ff << start_bit, (queue_weights[i] & 0x3ff) << start_bit,
|
||||
RTL839X_TBL_ACCESS_DATA_2(low_byte));
|
||||
if (high_mask)
|
||||
sw_w32_mask(high_mask, (queue_weights[i] & 0x3ff) >> (32 - start_bit),
|
||||
RTL839X_TBL_ACCESS_DATA_2(low_byte - 1));
|
||||
}
|
||||
|
||||
rtl839x_write_scheduling_table(port);
|
||||
mutex_unlock(&priv->reg_mutex);
|
||||
}
|
||||
|
||||
void rtldsa_838x_qos_init(struct rtl838x_switch_priv *priv)
|
||||
{
|
||||
u32 v;
|
||||
|
||||
pr_info("Setting up RTL838X QoS\n");
|
||||
pr_info("RTL838X_PRI_SEL_TBL_CTRL(i): %08x\n", sw_r32(RTL838X_PRI_SEL_TBL_CTRL(0)));
|
||||
rtl83xx_setup_default_prio2queue();
|
||||
|
||||
/* Enable inner (bit 12) and outer (bit 13) priority remapping from DSCP */
|
||||
sw_w32_mask(0, BIT(12) | BIT(13), RTL838X_PRI_DSCP_INVLD_CTRL0);
|
||||
|
||||
/* Set default weight for calculating internal priority, in prio selection group 0
|
||||
* Port based (prio 3), Port outer-tag (4), DSCP (5), Inner Tag (6), Outer Tag (7)
|
||||
*/
|
||||
v = 3 | (4 << 3) | (5 << 6) | (6 << 9) | (7 << 12);
|
||||
sw_w32(v, RTL838X_PRI_SEL_TBL_CTRL(0));
|
||||
|
||||
/* Set the inner and outer priority one-to-one to re-marked outer dot1p priority */
|
||||
v = 0;
|
||||
for (int p = 0; p < 8; p++)
|
||||
v |= p << (3 * p);
|
||||
sw_w32(v, RTL838X_RMK_OPRI_CTRL);
|
||||
sw_w32(v, RTL838X_RMK_IPRI_CTRL);
|
||||
|
||||
v = 0;
|
||||
for (int p = 0; p < 8; p++)
|
||||
v |= (dot1p_priority_remapping[p] & 0x7) << (p * 3);
|
||||
sw_w32(v, RTL838X_PRI_SEL_IPRI_REMAP);
|
||||
|
||||
/* On all ports set scheduler type to WFQ */
|
||||
for (int i = 0; i <= soc_info.cpu_port; i++)
|
||||
sw_w32(0, RTL838X_SCHED_P_TYPE_CTRL(i));
|
||||
|
||||
/* Enable egress scheduler for CPU-Port */
|
||||
sw_w32_mask(0, BIT(8), RTL838X_SCHED_LB_CTRL(soc_info.cpu_port));
|
||||
|
||||
/* Enable egress drop allways on */
|
||||
sw_w32_mask(0, BIT(11), RTL838X_FC_P_EGR_DROP_CTRL(soc_info.cpu_port));
|
||||
|
||||
/* Give special trap frames priority 7 (BPDUs) and routing exceptions: */
|
||||
sw_w32_mask(0, 7 << 3 | 7, RTL838X_QM_PKT2CPU_INTPRI_2);
|
||||
/* Give RMA frames priority 7: */
|
||||
sw_w32_mask(0, 7, RTL838X_QM_PKT2CPU_INTPRI_1);
|
||||
}
|
||||
|
||||
void rtldsa_839x_qos_init(struct rtl838x_switch_priv *priv)
|
||||
{
|
||||
u32 v;
|
||||
|
||||
pr_info("Setting up RTL839X QoS\n");
|
||||
pr_info("RTL839X_PRI_SEL_TBL_CTRL(i): %08x\n", sw_r32(RTL839X_PRI_SEL_TBL_CTRL(0)));
|
||||
rtl83xx_setup_default_prio2queue();
|
||||
|
||||
for (int port = 0; port < soc_info.cpu_port; port++)
|
||||
sw_w32(7, RTL839X_QM_PORT_QNUM(port));
|
||||
|
||||
/* CPU-port gets queue number 7 */
|
||||
sw_w32(7, RTL839X_QM_PORT_QNUM(soc_info.cpu_port));
|
||||
|
||||
for (int port = 0; port <= soc_info.cpu_port; port++) {
|
||||
rtl83xx_set_ingress_priority(port, 0);
|
||||
rtl839x_set_scheduling_algorithm(priv, port, WEIGHTED_FAIR_QUEUE);
|
||||
rtl839x_set_scheduling_queue_weights(priv, port, rtldsa_default_queue_weights);
|
||||
/* Do re-marking based on outer tag */
|
||||
sw_w32_mask(0, BIT(port % 32), RTL839X_RMK_PORT_DEI_TAG_CTRL(port));
|
||||
}
|
||||
|
||||
/* Remap dot1p priorities to internal priority, for this the outer tag needs be re-marked */
|
||||
v = 0;
|
||||
for (int p = 0; p < 8; p++)
|
||||
v |= (dot1p_priority_remapping[p] & 0x7) << (p * 3);
|
||||
sw_w32(v, RTL839X_PRI_SEL_IPRI_REMAP);
|
||||
|
||||
/* Configure Drop Precedence for Drop Eligible Indicator (DEI)
|
||||
* Index 0: 0
|
||||
* Index 1: 2
|
||||
* Each indicator is 2 bits long
|
||||
*/
|
||||
sw_w32(2 << 2, RTL839X_PRI_SEL_DEI2DP_REMAP);
|
||||
|
||||
/* Re-mark DEI: 4 bit-fields of 2 bits each, field 0 is bits 0-1, ... */
|
||||
sw_w32((0x1 << 2) | (0x1 << 4), RTL839X_RMK_DEI_CTRL);
|
||||
|
||||
/* Set Congestion avoidance drop probability to 0 for drop precedences 0-2 (bits 24-31)
|
||||
* low threshold (bits 0-11) to 4095 and high threshold (bits 12-23) to 4095
|
||||
* Weighted Random Early Detection (WRED) is used
|
||||
*/
|
||||
sw_w32(4095 << 12 | 4095, RTL839X_WRED_PORT_THR_CTRL(0));
|
||||
sw_w32(4095 << 12 | 4095, RTL839X_WRED_PORT_THR_CTRL(1));
|
||||
sw_w32(4095 << 12 | 4095, RTL839X_WRED_PORT_THR_CTRL(2));
|
||||
|
||||
/* Set queue-based congestion avoidance properties, register fields are as
|
||||
* for forward RTL839X_WRED_PORT_THR_CTRL
|
||||
*/
|
||||
for (int q = 0; q < 8; q++) {
|
||||
sw_w32(255 << 24 | 78 << 12 | 68, RTL839X_WRED_QUEUE_THR_CTRL(q, 0));
|
||||
sw_w32(255 << 24 | 74 << 12 | 64, RTL839X_WRED_QUEUE_THR_CTRL(q, 0));
|
||||
sw_w32(255 << 24 | 70 << 12 | 60, RTL839X_WRED_QUEUE_THR_CTRL(q, 0));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,202 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef _NET_DSA_RTL83XX_H
|
||||
#define _NET_DSA_RTL83XX_H
|
||||
|
||||
#include <net/dsa.h>
|
||||
#include "rtl838x.h"
|
||||
|
||||
struct fdb_update_work {
|
||||
struct work_struct work;
|
||||
struct net_device *ndev;
|
||||
u64 macs[];
|
||||
};
|
||||
|
||||
enum mib_reg {
|
||||
MIB_REG_INVALID = 0,
|
||||
MIB_REG_STD,
|
||||
MIB_REG_PRV,
|
||||
MIB_TBL_STD,
|
||||
MIB_TBL_PRV,
|
||||
};
|
||||
|
||||
#define MIB_ITEM(_reg, _offset, _size) \
|
||||
{.reg = _reg, .offset = _offset, .size = _size}
|
||||
|
||||
#define MIB_LIST_ITEM(_name, _item) \
|
||||
{.name = _name, .item = _item}
|
||||
|
||||
struct rtldsa_mib_item {
|
||||
enum mib_reg reg;
|
||||
unsigned int offset;
|
||||
unsigned int size;
|
||||
};
|
||||
|
||||
struct rtldsa_mib_list_item {
|
||||
const char *name;
|
||||
struct rtldsa_mib_item item;
|
||||
};
|
||||
|
||||
struct rtldsa_mib_desc {
|
||||
struct rtldsa_mib_item symbol_errors;
|
||||
|
||||
struct rtldsa_mib_item if_in_octets;
|
||||
struct rtldsa_mib_item if_out_octets;
|
||||
struct rtldsa_mib_item if_in_ucast_pkts;
|
||||
struct rtldsa_mib_item if_in_mcast_pkts;
|
||||
struct rtldsa_mib_item if_in_bcast_pkts;
|
||||
struct rtldsa_mib_item if_out_ucast_pkts;
|
||||
struct rtldsa_mib_item if_out_mcast_pkts;
|
||||
struct rtldsa_mib_item if_out_bcast_pkts;
|
||||
struct rtldsa_mib_item if_out_discards;
|
||||
struct rtldsa_mib_item single_collisions;
|
||||
struct rtldsa_mib_item multiple_collisions;
|
||||
struct rtldsa_mib_item deferred_transmissions;
|
||||
struct rtldsa_mib_item late_collisions;
|
||||
struct rtldsa_mib_item excessive_collisions;
|
||||
struct rtldsa_mib_item crc_align_errors;
|
||||
struct rtldsa_mib_item rx_pkts_over_max_octets;
|
||||
|
||||
struct rtldsa_mib_item unsupported_opcodes;
|
||||
|
||||
struct rtldsa_mib_item rx_undersize_pkts;
|
||||
struct rtldsa_mib_item rx_oversize_pkts;
|
||||
struct rtldsa_mib_item rx_fragments;
|
||||
struct rtldsa_mib_item rx_jabbers;
|
||||
|
||||
struct rtldsa_mib_item tx_pkts[ETHTOOL_RMON_HIST_MAX];
|
||||
struct rtldsa_mib_item rx_pkts[ETHTOOL_RMON_HIST_MAX];
|
||||
struct ethtool_rmon_hist_range rmon_ranges[ETHTOOL_RMON_HIST_MAX];
|
||||
|
||||
struct rtldsa_mib_item drop_events;
|
||||
struct rtldsa_mib_item collisions;
|
||||
|
||||
struct rtldsa_mib_item rx_pause_frames;
|
||||
struct rtldsa_mib_item tx_pause_frames;
|
||||
|
||||
size_t list_count;
|
||||
const struct rtldsa_mib_list_item *list;
|
||||
};
|
||||
|
||||
/* API for switch table access */
|
||||
struct table_reg {
|
||||
u16 addr;
|
||||
u16 data;
|
||||
u8 max_data;
|
||||
u8 c_bit;
|
||||
u8 t_bit;
|
||||
u8 rmode;
|
||||
u8 tbl;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
#define TBL_DESC(_addr, _data, _max_data, _c_bit, _t_bit, _rmode) \
|
||||
{ .addr = _addr, .data = _data, .max_data = _max_data, .c_bit = _c_bit, \
|
||||
.t_bit = _t_bit, .rmode = _rmode \
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
RTL8380_TBL_L2 = 0,
|
||||
RTL8380_TBL_0,
|
||||
RTL8380_TBL_1,
|
||||
RTL8390_TBL_L2,
|
||||
RTL8390_TBL_0,
|
||||
RTL8390_TBL_1,
|
||||
RTL8390_TBL_2,
|
||||
RTL9300_TBL_L2,
|
||||
RTL9300_TBL_0,
|
||||
RTL9300_TBL_1,
|
||||
RTL9300_TBL_2,
|
||||
RTL9300_TBL_HSB,
|
||||
RTL9300_TBL_HSA,
|
||||
RTL9310_TBL_0,
|
||||
RTL9310_TBL_1,
|
||||
RTL9310_TBL_2,
|
||||
RTL9310_TBL_3,
|
||||
RTL9310_TBL_4,
|
||||
RTL9310_TBL_5,
|
||||
RTL_TBL_END
|
||||
} rtl838x_tbl_reg_t;
|
||||
|
||||
void rtl_table_init(void);
|
||||
struct table_reg *rtl_table_get(rtl838x_tbl_reg_t r, int t);
|
||||
void rtl_table_release(struct table_reg *r);
|
||||
int rtl_table_read(struct table_reg *r, int idx);
|
||||
int rtl_table_write(struct table_reg *r, int idx);
|
||||
inline u16 rtl_table_data(struct table_reg *r, int i);
|
||||
inline u32 rtl_table_data_r(struct table_reg *r, int i);
|
||||
inline void rtl_table_data_w(struct table_reg *r, u32 v, int i);
|
||||
|
||||
int rtldsa_83xx_lag_setup_algomask(struct rtl838x_switch_priv *priv, int group,
|
||||
struct netdev_lag_upper_info *info);
|
||||
|
||||
void rtldsa_838x_qos_init(struct rtl838x_switch_priv *priv);
|
||||
void rtldsa_839x_qos_init(struct rtl838x_switch_priv *priv);
|
||||
|
||||
void rtldsa_port_fast_age(struct dsa_switch *ds, int port);
|
||||
int rtl83xx_packet_cntr_alloc(struct rtl838x_switch_priv *priv);
|
||||
int rtldsa_port_get_stp_state(struct rtl838x_switch_priv *priv, int port);
|
||||
int rtl83xx_port_is_under(const struct net_device *dev, struct rtl838x_switch_priv *priv);
|
||||
void rtldsa_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
|
||||
int rtl83xx_setup_tc(struct net_device *dev, enum tc_setup_type type, void *type_data);
|
||||
|
||||
/* Port register accessor functions for the RTL839x and RTL931X SoCs */
|
||||
void rtl839x_mask_port_reg_be(u64 clear, u64 set, int reg);
|
||||
u32 rtl839x_get_egress_rate(struct rtl838x_switch_priv *priv, int port);
|
||||
u64 rtl839x_get_port_reg_be(int reg);
|
||||
void rtl839x_set_port_reg_be(u64 set, int reg);
|
||||
void rtl839x_mask_port_reg_le(u64 clear, u64 set, int reg);
|
||||
int rtl839x_set_egress_rate(struct rtl838x_switch_priv *priv, int port, u32 rate);
|
||||
void rtl839x_set_port_reg_le(u64 set, int reg);
|
||||
u64 rtl839x_get_port_reg_le(int reg);
|
||||
|
||||
/* Port register accessor functions for the RTL838x and RTL930X SoCs */
|
||||
void rtl838x_mask_port_reg(u64 clear, u64 set, int reg);
|
||||
void rtl838x_set_port_reg(u64 set, int reg);
|
||||
u32 rtl838x_get_egress_rate(struct rtl838x_switch_priv *priv, int port);
|
||||
u64 rtl838x_get_port_reg(int reg);
|
||||
int rtl838x_set_egress_rate(struct rtl838x_switch_priv *priv, int port, u32 rate);
|
||||
|
||||
/* RTL838x-specific */
|
||||
u32 rtl838x_hash(struct rtl838x_switch_priv *priv, u64 seed);
|
||||
void rtldsa_838x_print_matrix(void);
|
||||
|
||||
/* RTL839x-specific */
|
||||
u32 rtl839x_hash(struct rtl838x_switch_priv *priv, u64 seed);
|
||||
void rtl839x_exec_tbl2_cmd(u32 cmd);
|
||||
void rtldsa_839x_print_matrix(void);
|
||||
|
||||
/* RTL930x-specific */
|
||||
u32 rtl930x_hash(struct rtl838x_switch_priv *priv, u64 seed);
|
||||
void rtldsa_930x_print_matrix(void);
|
||||
|
||||
/* RTL931x-specific */
|
||||
void rtldsa_931x_print_matrix(void);
|
||||
|
||||
int rtl83xx_lag_add(struct dsa_switch *ds, int group, int port, struct netdev_lag_upper_info *info);
|
||||
int rtl83xx_lag_del(struct dsa_switch *ds, int group, int port);
|
||||
|
||||
/*
|
||||
* TODO: The following functions are currently not in use. So compiler will complain if
|
||||
* they are static and not made available externally. To preserve them for future use
|
||||
* collect them in this section.
|
||||
*/
|
||||
|
||||
void rtl839x_pie_rule_dump(struct pie_rule *pr);
|
||||
void rtl839x_set_egress_queue(int port, int queue);
|
||||
|
||||
void rtl9300_dump_debug(void);
|
||||
void rtl930x_pie_rule_dump_raw(u32 r[]);
|
||||
|
||||
extern const struct dsa_switch_ops rtldsa_83xx_switch_ops;
|
||||
extern const struct dsa_switch_ops rtldsa_93xx_switch_ops;
|
||||
|
||||
extern const struct rtldsa_config rtldsa_838x_cfg;
|
||||
extern const struct rtldsa_config rtldsa_839x_cfg;
|
||||
extern const struct rtldsa_config rtldsa_930x_cfg;
|
||||
extern const struct rtldsa_config rtldsa_931x_cfg;
|
||||
|
||||
/* TODO actually from arch/mips/rtl838x/prom.c */
|
||||
extern struct rtl83xx_soc_info soc_info;
|
||||
|
||||
#endif /* _NET_DSA_RTL83XX_H */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,409 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <net/dsa.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <net/flow_offload.h>
|
||||
#include <linux/rhashtable.h>
|
||||
#include <asm/mach-rtl-otto/mach-rtl-otto.h>
|
||||
|
||||
#include "rtl83xx.h"
|
||||
#include "rtl838x.h"
|
||||
|
||||
/* Parse the flow rule for the matching conditions */
|
||||
static int rtl83xx_parse_flow_rule(struct rtl838x_switch_priv *priv,
|
||||
struct flow_rule *rule, struct rtl83xx_flow *flow)
|
||||
{
|
||||
struct flow_dissector *dissector = rule->match.dissector;
|
||||
|
||||
pr_debug("In %s\n", __func__);
|
||||
/* KEY_CONTROL and KEY_BASIC are needed for forming a meaningful key */
|
||||
if ((dissector->used_keys & BIT(FLOW_DISSECTOR_KEY_CONTROL)) == 0 ||
|
||||
(dissector->used_keys & BIT(FLOW_DISSECTOR_KEY_BASIC)) == 0) {
|
||||
pr_err("Cannot form TC key: used_keys = 0x%llx\n", dissector->used_keys);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
|
||||
struct flow_match_basic match;
|
||||
|
||||
pr_debug("%s: BASIC\n", __func__);
|
||||
flow_rule_match_basic(rule, &match);
|
||||
if (match.key->n_proto == htons(ETH_P_ARP))
|
||||
flow->rule.frame_type = 0;
|
||||
if (match.key->n_proto == htons(ETH_P_IP))
|
||||
flow->rule.frame_type = 2;
|
||||
if (match.key->n_proto == htons(ETH_P_IPV6))
|
||||
flow->rule.frame_type = 3;
|
||||
if ((match.key->n_proto == htons(ETH_P_ARP)) || flow->rule.frame_type)
|
||||
flow->rule.frame_type_m = 3;
|
||||
if (flow->rule.frame_type >= 2) {
|
||||
if (match.key->ip_proto == IPPROTO_UDP)
|
||||
flow->rule.frame_type_l4 = 0;
|
||||
if (match.key->ip_proto == IPPROTO_TCP)
|
||||
flow->rule.frame_type_l4 = 1;
|
||||
if (match.key->ip_proto == IPPROTO_ICMP || match.key->ip_proto == IPPROTO_ICMPV6)
|
||||
flow->rule.frame_type_l4 = 2;
|
||||
if (match.key->ip_proto == IPPROTO_TCP)
|
||||
flow->rule.frame_type_l4 = 3;
|
||||
if ((match.key->ip_proto == IPPROTO_UDP) || flow->rule.frame_type_l4)
|
||||
flow->rule.frame_type_l4_m = 7;
|
||||
}
|
||||
}
|
||||
|
||||
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
|
||||
struct flow_match_eth_addrs match;
|
||||
|
||||
pr_debug("%s: ETH_ADDR\n", __func__);
|
||||
flow_rule_match_eth_addrs(rule, &match);
|
||||
ether_addr_copy(flow->rule.dmac, match.key->dst);
|
||||
ether_addr_copy(flow->rule.dmac_m, match.mask->dst);
|
||||
ether_addr_copy(flow->rule.smac, match.key->src);
|
||||
ether_addr_copy(flow->rule.smac_m, match.mask->src);
|
||||
}
|
||||
|
||||
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) {
|
||||
struct flow_match_vlan match;
|
||||
|
||||
pr_debug("%s: VLAN\n", __func__);
|
||||
flow_rule_match_vlan(rule, &match);
|
||||
flow->rule.itag = match.key->vlan_id;
|
||||
flow->rule.itag_m = match.mask->vlan_id;
|
||||
/* TODO: What about match.key->vlan_priority? */
|
||||
}
|
||||
|
||||
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
|
||||
struct flow_match_ipv4_addrs match;
|
||||
|
||||
pr_debug("%s: IPV4\n", __func__);
|
||||
flow_rule_match_ipv4_addrs(rule, &match);
|
||||
flow->rule.is_ipv6 = false;
|
||||
flow->rule.dip = match.key->dst;
|
||||
flow->rule.dip_m = match.mask->dst;
|
||||
flow->rule.sip = match.key->src;
|
||||
flow->rule.sip_m = match.mask->src;
|
||||
} else if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) {
|
||||
struct flow_match_ipv6_addrs match;
|
||||
|
||||
pr_debug("%s: IPV6\n", __func__);
|
||||
flow->rule.is_ipv6 = true;
|
||||
flow_rule_match_ipv6_addrs(rule, &match);
|
||||
flow->rule.dip6 = match.key->dst;
|
||||
flow->rule.dip6_m = match.mask->dst;
|
||||
flow->rule.sip6 = match.key->src;
|
||||
flow->rule.sip6_m = match.mask->src;
|
||||
}
|
||||
|
||||
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
|
||||
struct flow_match_ports match;
|
||||
|
||||
pr_debug("%s: PORTS\n", __func__);
|
||||
flow_rule_match_ports(rule, &match);
|
||||
flow->rule.dport = match.key->dst;
|
||||
flow->rule.dport_m = match.mask->dst;
|
||||
flow->rule.sport = match.key->src;
|
||||
flow->rule.sport_m = match.mask->src;
|
||||
}
|
||||
|
||||
/* TODO: ICMP */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtl83xx_flow_bypass_all(struct rtl83xx_flow *flow)
|
||||
{
|
||||
flow->rule.bypass_sel = true;
|
||||
flow->rule.bypass_all = true;
|
||||
flow->rule.bypass_igr_stp = true;
|
||||
flow->rule.bypass_ibc_sc = true;
|
||||
}
|
||||
|
||||
static int rtl83xx_parse_fwd(struct rtl838x_switch_priv *priv,
|
||||
const struct flow_action_entry *act, struct rtl83xx_flow *flow)
|
||||
{
|
||||
struct net_device *dev = act->dev;
|
||||
int port;
|
||||
|
||||
port = rtl83xx_port_is_under(dev, priv);
|
||||
if (port < 0) {
|
||||
netdev_info(dev, "%s: not a DSA device.\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
flow->rule.fwd_sel = true;
|
||||
flow->rule.fwd_data = port;
|
||||
pr_debug("Using port index: %d\n", port);
|
||||
rtl83xx_flow_bypass_all(flow);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl83xx_add_flow(struct rtl838x_switch_priv *priv, struct flow_cls_offload *f,
|
||||
struct rtl83xx_flow *flow)
|
||||
{
|
||||
struct flow_rule *rule = flow_cls_offload_flow_rule(f);
|
||||
const struct flow_action_entry *act;
|
||||
int i, err;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
rtl83xx_parse_flow_rule(priv, rule, flow);
|
||||
|
||||
flow_action_for_each(i, act, &rule->action) {
|
||||
switch (act->id) {
|
||||
case FLOW_ACTION_DROP:
|
||||
pr_debug("%s: DROP\n", __func__);
|
||||
flow->rule.drop = true;
|
||||
rtl83xx_flow_bypass_all(flow);
|
||||
return 0;
|
||||
|
||||
case FLOW_ACTION_TRAP:
|
||||
pr_debug("%s: TRAP\n", __func__);
|
||||
flow->rule.fwd_data = priv->cpu_port;
|
||||
flow->rule.fwd_act = PIE_ACT_REDIRECT_TO_PORT;
|
||||
rtl83xx_flow_bypass_all(flow);
|
||||
break;
|
||||
|
||||
case FLOW_ACTION_MANGLE:
|
||||
pr_err("%s: FLOW_ACTION_MANGLE not supported\n", __func__);
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
case FLOW_ACTION_ADD:
|
||||
pr_err("%s: FLOW_ACTION_ADD not supported\n", __func__);
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
case FLOW_ACTION_VLAN_PUSH:
|
||||
pr_debug("%s: VLAN_PUSH\n", __func__);
|
||||
/* TODO: act->vlan.proto */
|
||||
flow->rule.ivid_act = PIE_ACT_VID_ASSIGN;
|
||||
flow->rule.ivid_sel = true;
|
||||
flow->rule.ivid_data = htons(act->vlan.vid);
|
||||
flow->rule.ovid_act = PIE_ACT_VID_ASSIGN;
|
||||
flow->rule.ovid_sel = true;
|
||||
flow->rule.ovid_data = htons(act->vlan.vid);
|
||||
flow->rule.fwd_mod_to_cpu = true;
|
||||
break;
|
||||
|
||||
case FLOW_ACTION_VLAN_POP:
|
||||
pr_debug("%s: VLAN_POP\n", __func__);
|
||||
flow->rule.ivid_act = PIE_ACT_VID_ASSIGN;
|
||||
flow->rule.ivid_data = 0;
|
||||
flow->rule.ivid_sel = true;
|
||||
flow->rule.ovid_act = PIE_ACT_VID_ASSIGN;
|
||||
flow->rule.ovid_data = 0;
|
||||
flow->rule.ovid_sel = true;
|
||||
flow->rule.fwd_mod_to_cpu = true;
|
||||
break;
|
||||
|
||||
case FLOW_ACTION_CSUM:
|
||||
pr_err("%s: FLOW_ACTION_CSUM not supported\n", __func__);
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
case FLOW_ACTION_REDIRECT:
|
||||
pr_debug("%s: REDIRECT\n", __func__);
|
||||
err = rtl83xx_parse_fwd(priv, act, flow);
|
||||
if (err)
|
||||
return err;
|
||||
flow->rule.fwd_act = PIE_ACT_REDIRECT_TO_PORT;
|
||||
break;
|
||||
|
||||
case FLOW_ACTION_MIRRED:
|
||||
pr_debug("%s: MIRRED\n", __func__);
|
||||
err = rtl83xx_parse_fwd(priv, act, flow);
|
||||
if (err)
|
||||
return err;
|
||||
flow->rule.fwd_act = PIE_ACT_COPY_TO_PORT;
|
||||
break;
|
||||
|
||||
default:
|
||||
pr_err("%s: Flow action not supported: %d\n", __func__, act->id);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rhashtable_params tc_ht_params = {
|
||||
.head_offset = offsetof(struct rtl83xx_flow, node),
|
||||
.key_offset = offsetof(struct rtl83xx_flow, cookie),
|
||||
.key_len = sizeof(((struct rtl83xx_flow *)0)->cookie),
|
||||
.automatic_shrinking = true,
|
||||
};
|
||||
|
||||
static int rtl83xx_configure_flower(struct rtl838x_switch_priv *priv,
|
||||
struct flow_cls_offload *f)
|
||||
{
|
||||
struct rtl83xx_flow *flow;
|
||||
int err = 0;
|
||||
|
||||
pr_debug("In %s\n", __func__);
|
||||
|
||||
rcu_read_lock();
|
||||
pr_debug("Cookie %08lx\n", f->cookie);
|
||||
flow = rhashtable_lookup(&priv->tc_ht, &f->cookie, tc_ht_params);
|
||||
if (flow) {
|
||||
pr_info("%s: Got flow\n", __func__);
|
||||
err = -EEXIST;
|
||||
goto rcu_unlock;
|
||||
}
|
||||
|
||||
rcu_unlock:
|
||||
rcu_read_unlock();
|
||||
if (flow)
|
||||
goto out;
|
||||
pr_debug("%s: New flow\n", __func__);
|
||||
|
||||
flow = kzalloc(sizeof(*flow), GFP_KERNEL);
|
||||
if (!flow) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
flow->cookie = f->cookie;
|
||||
flow->priv = priv;
|
||||
|
||||
err = rhashtable_insert_fast(&priv->tc_ht, &flow->node, tc_ht_params);
|
||||
if (err) {
|
||||
pr_err("Could not insert add new rule\n");
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
rtl83xx_add_flow(priv, f, flow); /* TODO: check error */
|
||||
|
||||
/* Add log action to flow */
|
||||
flow->rule.packet_cntr = rtl83xx_packet_cntr_alloc(priv);
|
||||
if (flow->rule.packet_cntr >= 0) {
|
||||
pr_debug("Using packet counter %d\n", flow->rule.packet_cntr);
|
||||
flow->rule.log_sel = true;
|
||||
flow->rule.log_data = flow->rule.packet_cntr;
|
||||
}
|
||||
|
||||
err = priv->r->pie_rule_add(priv, &flow->rule);
|
||||
return err;
|
||||
|
||||
out_free:
|
||||
kfree(flow);
|
||||
out:
|
||||
pr_err("%s: error %d\n", __func__, err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rtl83xx_delete_flower(struct rtl838x_switch_priv *priv,
|
||||
struct flow_cls_offload *cls_flower)
|
||||
{
|
||||
struct rtl83xx_flow *flow;
|
||||
|
||||
pr_debug("In %s\n", __func__);
|
||||
rcu_read_lock();
|
||||
flow = rhashtable_lookup_fast(&priv->tc_ht, &cls_flower->cookie, tc_ht_params);
|
||||
if (!flow) {
|
||||
rcu_read_unlock();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv->r->pie_rule_rm(priv, &flow->rule);
|
||||
|
||||
rhashtable_remove_fast(&priv->tc_ht, &flow->node, tc_ht_params);
|
||||
|
||||
kfree_rcu(flow, rcu_head);
|
||||
|
||||
rcu_read_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl83xx_stats_flower(struct rtl838x_switch_priv *priv,
|
||||
struct flow_cls_offload *cls_flower)
|
||||
{
|
||||
struct rtl83xx_flow *flow;
|
||||
unsigned long lastused = 0;
|
||||
int total_packets, new_packets;
|
||||
|
||||
pr_debug("%s:\n", __func__);
|
||||
flow = rhashtable_lookup_fast(&priv->tc_ht, &cls_flower->cookie, tc_ht_params);
|
||||
if (!flow)
|
||||
return -1;
|
||||
|
||||
if (flow->rule.packet_cntr >= 0) {
|
||||
total_packets = priv->r->packet_cntr_read(flow->rule.packet_cntr);
|
||||
pr_debug("Total packets: %d\n", total_packets);
|
||||
new_packets = total_packets - flow->rule.last_packet_cnt;
|
||||
flow->rule.last_packet_cnt = total_packets;
|
||||
}
|
||||
|
||||
/* TODO: We need a second PIE rule to count the bytes */
|
||||
flow_stats_update(&cls_flower->stats, 100 * new_packets, new_packets, 0, lastused,
|
||||
FLOW_ACTION_HW_STATS_IMMEDIATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl83xx_setup_tc_cls_flower(struct rtl838x_switch_priv *priv,
|
||||
struct flow_cls_offload *cls_flower)
|
||||
{
|
||||
pr_debug("%s: %d\n", __func__, cls_flower->command);
|
||||
switch (cls_flower->command) {
|
||||
case FLOW_CLS_REPLACE:
|
||||
return rtl83xx_configure_flower(priv, cls_flower);
|
||||
case FLOW_CLS_DESTROY:
|
||||
return rtl83xx_delete_flower(priv, cls_flower);
|
||||
case FLOW_CLS_STATS:
|
||||
return rtl83xx_stats_flower(priv, cls_flower);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int rtl83xx_setup_tc_block_cb(enum tc_setup_type type, void *type_data,
|
||||
void *cb_priv)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = cb_priv;
|
||||
|
||||
switch (type) {
|
||||
case TC_SETUP_CLSFLOWER:
|
||||
pr_debug("%s: TC_SETUP_CLSFLOWER\n", __func__);
|
||||
return rtl83xx_setup_tc_cls_flower(priv, type_data);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static LIST_HEAD(rtl83xx_block_cb_list);
|
||||
|
||||
int rtl83xx_setup_tc(struct net_device *dev, enum tc_setup_type type, void *type_data)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv;
|
||||
struct flow_block_offload *f = type_data;
|
||||
static bool first_time = true;
|
||||
int err;
|
||||
|
||||
pr_debug("%s: %d\n", __func__, type);
|
||||
|
||||
if (!netdev_uses_dsa(dev)) {
|
||||
pr_err("%s: no DSA\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
priv = dev->dsa_ptr->ds->priv;
|
||||
|
||||
switch (type) {
|
||||
case TC_SETUP_BLOCK:
|
||||
if (first_time) {
|
||||
first_time = false;
|
||||
err = rhashtable_init(&priv->tc_ht, &tc_ht_params);
|
||||
if (err)
|
||||
pr_err("%s: Could not initialize hash table\n", __func__);
|
||||
}
|
||||
|
||||
f->unlocked_driver_cb = true;
|
||||
return flow_block_cb_setup_simple(type_data,
|
||||
&rtl83xx_block_cb_list,
|
||||
rtl83xx_setup_tc_block_cb,
|
||||
priv, priv, true);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,275 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef _RTL838X_ETH_H
|
||||
#define _RTL838X_ETH_H
|
||||
|
||||
#define RTETH_MAX_MAC_REGS 3
|
||||
|
||||
/* Register definition */
|
||||
|
||||
#define RTETH_838X_CPU_PORT 28
|
||||
#define RTETH_838X_DMA_IF_INTR_MSK (0x9f50)
|
||||
#define RTETH_838X_DMA_IF_INTR_STS (0x9f54)
|
||||
#define RTETH_838X_MAC_ADDR_CTRL (0xa9ec)
|
||||
#define RTETH_838X_MAC_ADDR_CTRL_ALE (0x6b04)
|
||||
#define RTETH_838X_MAC_ADDR_CTRL_MAC (0xa320)
|
||||
#define RTETH_838X_MAC_FORCE_MODE_CTRL (0xa104 + RTETH_838X_CPU_PORT * 4)
|
||||
#define RTETH_838X_MAC_L2_PORT_CTRL (0xd560 + RTETH_838X_CPU_PORT * 128)
|
||||
#define RTETH_838X_QM_PKT2CPU_INTPRI_MAP (0x5f10)
|
||||
#define RTETH_838X_QM_PKT2CPU_INTPRI_0 (0x5f04)
|
||||
#define RTETH_838X_QM_PKT2CPU_INTPRI_CNT 3
|
||||
|
||||
#define RTETH_839X_CPU_PORT 52
|
||||
#define RTETH_839X_DMA_IF_INTR_MSK (0x7864)
|
||||
#define RTETH_839X_DMA_IF_INTR_STS (0x7868)
|
||||
#define RTETH_839X_MAC_ADDR_CTRL (0x02b4)
|
||||
#define RTETH_839X_MAC_FORCE_MODE_CTRL (0x02bc + RTETH_839X_CPU_PORT * 4)
|
||||
#define RTETH_839X_MAC_L2_PORT_CTRL (0x8004 + RTETH_839X_CPU_PORT * 128)
|
||||
#define RTETH_839X_QM_PKT2CPU_INTPRI_MAP (0x1154)
|
||||
#define RTETH_839X_QM_PKT2CPU_INTPRI_0 (0x1148)
|
||||
#define RTETH_839X_QM_PKT2CPU_INTPRI_CNT 3
|
||||
|
||||
#define RTETH_930X_CPU_PORT 28
|
||||
#define RTETH_930X_DMA_IF_INTR_MSK (0xe010)
|
||||
#define RTETH_930X_DMA_IF_INTR_STS (0xe01c)
|
||||
#define RTETH_930X_MAC_FORCE_MODE_CTRL (0xca1c + RTETH_930X_CPU_PORT * 4)
|
||||
#define RTETH_930X_MAC_L2_ADDR_CTRL (0xc714)
|
||||
#define RTETH_930X_MAC_L2_PORT_CTRL (0x3268 + RTETH_930X_CPU_PORT * 64)
|
||||
#define RTETH_930X_QM_RSN2CPUQID_CTRL_0 (0xa344)
|
||||
#define RTETH_930X_QM_RSN2CPUQID_CTRL_CNT 11
|
||||
|
||||
#define RTETH_931X_CPU_PORT 56
|
||||
#define RTETH_931X_DMA_IF_INTR_MSK (0x0910)
|
||||
#define RTETH_931X_DMA_IF_INTR_STS (0x091c)
|
||||
#define RTETH_931X_MAC_FORCE_MODE_CTRL (0x0dcc + RTETH_931X_CPU_PORT * 4)
|
||||
#define RTETH_931X_MAC_L2_ADDR_CTRL (0x135c)
|
||||
#define RTETH_931X_MAC_L2_PORT_CTRL (0x6000 + RTETH_931X_CPU_PORT * 128)
|
||||
#define RTETH_931X_QM_RSN2CPUQID_CTRL_0 (0xa9f4)
|
||||
#define RTETH_931X_QM_RSN2CPUQID_CTRL_CNT 14
|
||||
|
||||
/*
|
||||
* Reset
|
||||
*/
|
||||
#define RTL838X_RST_GLB_CTRL_0 (0x003c)
|
||||
#define RTL839X_RST_GLB_CTRL (0x0014)
|
||||
#define RTL930X_RST_GLB_CTRL_0 (0x000c)
|
||||
#define RTL931X_RST_GLB_CTRL (0x0400)
|
||||
|
||||
/* Switch interrupts */
|
||||
#define RTL839X_IMR_PORT_LINK_STS_CHG (0x0068)
|
||||
#define RTL839X_ISR_PORT_LINK_STS_CHG (0x00a0)
|
||||
|
||||
/*
|
||||
* CPU port MAC control. On RTL93XX the functionality of the MAC port control register is
|
||||
* split into MAC_L2_PORT_CTRL and MAC_PORT_CTRL and the L2 register holds the important
|
||||
* bits for the driver. To avoid confusion on splitted models use the L2 naming convention
|
||||
* for all targets.
|
||||
*/
|
||||
|
||||
/* DMA interrupt control and status registers */
|
||||
#define RTL838X_DMA_IF_CTRL (0x9f58)
|
||||
|
||||
#define RTL839X_DMA_IF_CTRL (0x786c)
|
||||
|
||||
#define RTL930X_DMA_IF_CTRL (0xe028)
|
||||
#define RTL930X_L2_NTFY_IF_INTR_MSK (0xe04C)
|
||||
#define RTL930X_L2_NTFY_IF_INTR_STS (0xe050)
|
||||
|
||||
/* TODO: RTL931X_DMA_IF_CTRL has different bits meanings */
|
||||
#define RTL931X_DMA_IF_CTRL (0x0928)
|
||||
#define RTL931X_L2_NTFY_IF_INTR_MSK (0x09E4)
|
||||
#define RTL931X_L2_NTFY_IF_INTR_STS (0x09E8)
|
||||
|
||||
#define RTL839X_DMA_IF_INTR_NOTIFY_MASK GENMASK(22, 20)
|
||||
|
||||
/* Ringbuffer setup */
|
||||
#define RTL838X_DMA_RX_BASE (0x9f00)
|
||||
#define RTL839X_DMA_RX_BASE (0x780c)
|
||||
#define RTL930X_DMA_RX_BASE (0xdf00)
|
||||
#define RTL931X_DMA_RX_BASE (0x0800)
|
||||
|
||||
#define RTL838X_DMA_TX_BASE (0x9f40)
|
||||
#define RTL839X_DMA_TX_BASE (0x784c)
|
||||
#define RTL930X_DMA_TX_BASE (0xe000)
|
||||
#define RTL931X_DMA_TX_BASE (0x0900)
|
||||
|
||||
#define RTL838X_DMA_IF_RX_RING_SIZE (0xB7E4)
|
||||
#define RTL839X_DMA_IF_RX_RING_SIZE (0x6038)
|
||||
#define RTL930X_DMA_IF_RX_RING_SIZE (0x7C60)
|
||||
#define RTL931X_DMA_IF_RX_RING_SIZE (0x2080)
|
||||
|
||||
#define RTL838X_DMA_IF_RX_RING_CNTR (0xB7E8)
|
||||
#define RTL839X_DMA_IF_RX_RING_CNTR (0x603c)
|
||||
#define RTL930X_DMA_IF_RX_RING_CNTR (0x7C8C)
|
||||
#define RTL931X_DMA_IF_RX_RING_CNTR (0x20AC)
|
||||
|
||||
#define RTL838X_DMA_IF_TX_CUR_DESC_ADDR_CTRL (0x9F48)
|
||||
#define RTL930X_DMA_IF_TX_CUR_DESC_ADDR_CTRL (0xE008)
|
||||
|
||||
/* L2 features */
|
||||
#define RTL839X_TBL_ACCESS_L2_CTRL (0x1180)
|
||||
#define RTL839X_TBL_ACCESS_L2_DATA(idx) (0x1184 + ((idx) << 2))
|
||||
#define RTL838X_TBL_ACCESS_CTRL_0 (0x6914)
|
||||
#define RTL838X_TBL_ACCESS_DATA_0(idx) (0x6918 + ((idx) << 2))
|
||||
|
||||
#define RTL838X_EEE_TX_TIMER_GIGA_CTRL (0xaa04)
|
||||
#define RTL838X_EEE_TX_TIMER_GELITE_CTRL (0xaa08)
|
||||
|
||||
#define RTL930X_L2_UNKN_UC_FLD_PMSK (0x9064)
|
||||
#define RTL931X_L2_UNKN_UC_FLD_PMSK (0xC8F4)
|
||||
|
||||
#define RTL838X_L2_TBL_FLUSH_CTRL (0x3370)
|
||||
#define RTL839X_L2_TBL_FLUSH_CTRL (0x3ba0)
|
||||
#define RTL930X_L2_TBL_FLUSH_CTRL (0x9404)
|
||||
#define RTL931X_L2_TBL_FLUSH_CTRL (0xCD9C)
|
||||
|
||||
/* MAC link state bits */
|
||||
#define FORCE_EN BIT(0)
|
||||
#define FORCE_LINK_EN BIT(1)
|
||||
#define NWAY_EN BIT(2)
|
||||
#define DUPLX_MODE BIT(3)
|
||||
#define TX_PAUSE_EN BIT(6)
|
||||
#define RX_PAUSE_EN BIT(7)
|
||||
|
||||
/* L2 Notification DMA interface */
|
||||
#define RTL839X_DMA_IF_NBUF_BASE_DESC_ADDR_CTRL (0x785C)
|
||||
#define RTL839X_L2_NOTIFICATION_CTRL (0x7808)
|
||||
#define RTL931X_L2_NTFY_RING_BASE_ADDR (0x09DC)
|
||||
#define RTL931X_L2_NTFY_RING_CUR_ADDR (0x09E0)
|
||||
#define RTL839X_L2_NOTIFICATION_CTRL (0x7808)
|
||||
#define RTL931X_L2_NTFY_CTRL (0xCDC8)
|
||||
#define RTL838X_L2_CTRL_0 (0x3200)
|
||||
#define RTL838X_L2_CTRL_1 (0x3204)
|
||||
#define RTL839X_L2_CTRL_0 (0x3800)
|
||||
#define RTL930X_L2_CTRL (0x8FD8)
|
||||
#define RTL931X_L2_CTRL (0xC800)
|
||||
|
||||
/* TRAPPING to CPU-PORT */
|
||||
#define RTL838X_SPCL_TRAP_IGMP_CTRL (0x6984)
|
||||
#define RTL838X_RMA_CTRL_0 (0x4300)
|
||||
#define RTL838X_RMA_CTRL_1 (0x4304)
|
||||
#define RTL839X_RMA_CTRL_0 (0x1200)
|
||||
|
||||
#define RTL839X_SPCL_TRAP_IGMP_CTRL (0x1058)
|
||||
#define RTL839X_RMA_CTRL_1 (0x1204)
|
||||
#define RTL839X_RMA_CTRL_2 (0x1208)
|
||||
#define RTL839X_RMA_CTRL_3 (0x120C)
|
||||
|
||||
#define RTL930X_VLAN_APP_PKT_CTRL (0xA23C)
|
||||
#define RTL930X_RMA_CTRL_0 (0x9E60)
|
||||
#define RTL930X_RMA_CTRL_1 (0x9E64)
|
||||
#define RTL930X_RMA_CTRL_2 (0x9E68)
|
||||
|
||||
#define RTL931X_VLAN_APP_PKT_CTRL (0x96b0)
|
||||
#define RTL931X_RMA_CTRL_0 (0x8800)
|
||||
#define RTL931X_RMA_CTRL_1 (0x8804)
|
||||
#define RTL931X_RMA_CTRL_2 (0x8808)
|
||||
|
||||
/* Chip configuration registers of the RTL9310 */
|
||||
#define RTL931X_MEM_ENCAP_INIT (0x4854)
|
||||
#define RTL931X_MEM_MIB_INIT (0x7E18)
|
||||
#define RTL931X_MEM_ACL_INIT (0x40BC)
|
||||
#define RTL931X_MEM_ALE_INIT_0 (0x83F0)
|
||||
#define RTL931X_MEM_ALE_INIT_1 (0x83F4)
|
||||
#define RTL931X_MEM_ALE_INIT_2 (0x82E4)
|
||||
#define RTL931X_MDX_CTRL_RSVD (0x0fcc)
|
||||
#define RTL931X_PS_SOC_CTRL (0x13f8)
|
||||
|
||||
/* shared CPU tag definitions for RTL930X/RTL931X */
|
||||
#define RTL93XX_CPU_TAG1_FWD_MASK GENMASK(11, 8)
|
||||
|
||||
#define RTL93XX_CPU_TAG1_FWD_ALE 0
|
||||
#define RTL93XX_CPU_TAG1_FWD_PHYSICAL 1
|
||||
#define RTL93XX_CPU_TAG1_FWD_LOGICAL 2
|
||||
#define RTL93XX_CPU_TAG1_FWD_TRUNK 3
|
||||
#define RTL93XX_CPU_TAG1_FWD_ONE_HOP 4
|
||||
#define RTL93XX_CPU_TAG1_FWD_LOGICAL_ONE_HOP 5
|
||||
#define RTL93XX_CPU_TAG1_FWD_UCST_CPU_MIN_PORT 6
|
||||
#define RTL93XX_CPU_TAG1_FWD_UCST_CPU 7
|
||||
#define RTL93XX_CPU_TAG1_FWD_BCST_CPU 8
|
||||
|
||||
#define RTL93XX_CPU_TAG1_IGNORE_STP_MASK GENMASK(2, 2)
|
||||
|
||||
/* Default MTU with jumbo frames support */
|
||||
#define DEFAULT_MTU 9000
|
||||
|
||||
inline int rtl838x_dma_if_rx_ring_size(int i)
|
||||
{
|
||||
return RTL838X_DMA_IF_RX_RING_SIZE + ((i >> 3) << 2);
|
||||
}
|
||||
|
||||
inline int rtl839x_dma_if_rx_ring_size(int i)
|
||||
{
|
||||
return RTL839X_DMA_IF_RX_RING_SIZE + ((i >> 3) << 2);
|
||||
}
|
||||
|
||||
inline int rtl930x_dma_if_rx_ring_size(int i)
|
||||
{
|
||||
return RTL930X_DMA_IF_RX_RING_SIZE + ((i / 3) << 2);
|
||||
}
|
||||
|
||||
inline int rtl931x_dma_if_rx_ring_size(int i)
|
||||
{
|
||||
return RTL931X_DMA_IF_RX_RING_SIZE + ((i / 3) << 2);
|
||||
}
|
||||
|
||||
inline int rtl838x_dma_if_rx_ring_cntr(int i)
|
||||
{
|
||||
return RTL838X_DMA_IF_RX_RING_CNTR + ((i >> 3) << 2);
|
||||
}
|
||||
|
||||
inline int rtl839x_dma_if_rx_ring_cntr(int i)
|
||||
{
|
||||
return RTL839X_DMA_IF_RX_RING_CNTR + ((i >> 3) << 2);
|
||||
}
|
||||
|
||||
inline int rtl930x_dma_if_rx_ring_cntr(int i)
|
||||
{
|
||||
return RTL930X_DMA_IF_RX_RING_CNTR + ((i / 3) << 2);
|
||||
}
|
||||
|
||||
inline int rtl931x_dma_if_rx_ring_cntr(int i)
|
||||
{
|
||||
return RTL931X_DMA_IF_RX_RING_CNTR + ((i / 3) << 2);
|
||||
}
|
||||
|
||||
struct p_hdr;
|
||||
struct dsa_tag;
|
||||
struct rteth_ctrl;
|
||||
struct rteth_packet;
|
||||
|
||||
struct rteth_config {
|
||||
int cpu_port;
|
||||
int rx_rings;
|
||||
int tx_rx_enable;
|
||||
int tx_trigger_mask;
|
||||
int mac_l2_port_ctrl;
|
||||
int qm_pkt2cpu_intpri_map;
|
||||
int qm_rsn2cpuqid_ctrl;
|
||||
int qm_rsn2cpuqid_cnt;
|
||||
int dma_if_intr_sts;
|
||||
int dma_if_intr_msk;
|
||||
int l2_ntfy_if_intr_sts;
|
||||
int l2_ntfy_if_intr_msk;
|
||||
int dma_if_ctrl;
|
||||
int mac_force_mode_ctrl;
|
||||
int dma_rx_base;
|
||||
int dma_tx_base;
|
||||
int (*dma_if_rx_ring_size)(int ring);
|
||||
int (*dma_if_rx_ring_cntr)(int ring);
|
||||
int rst_glb_ctrl;
|
||||
u32 mac_reg[RTETH_MAX_MAC_REGS];
|
||||
int l2_tbl_flush_ctrl;
|
||||
void (*create_tx_header)(struct rteth_packet *h, unsigned int dest_port, int prio);
|
||||
bool (*decode_tag)(struct rteth_packet *h, struct dsa_tag *tag);
|
||||
void (*hw_en_rxtx)(struct rteth_ctrl *ctrl);
|
||||
void (*hw_init)(struct rteth_ctrl *ctrl);
|
||||
void (*hw_stop)(struct rteth_ctrl *ctrl);
|
||||
void (*hw_reset)(struct rteth_ctrl *ctrl);
|
||||
int (*init_mac)(struct rteth_ctrl *ctrl);
|
||||
void (*setup_notify_ring_buffer)(struct rteth_ctrl *ctrl);
|
||||
void (*update_counter)(struct rteth_ctrl *ctrl, int ring, int released);
|
||||
const struct net_device_ops *netdev_ops;
|
||||
};
|
||||
|
||||
#endif /* _RTL838X_ETH_H */
|
||||
@ -1,541 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define RTSDS_REG_CNT 32
|
||||
#define RTSDS_VAL_MASK GENMASK(15, 0)
|
||||
#define RTSDS_SUBPAGE(page) (page % 64)
|
||||
|
||||
#define RTSDS_MMD_PAGE_MASK GENMASK(15, 8)
|
||||
#define RTSDS_MMD_REG_MASK GENMASK(7, 0)
|
||||
|
||||
#define RTSDS_838X_SDS_CNT 6
|
||||
#define RTSDS_838X_PAGE_CNT 4
|
||||
#define RTSDS_838X_BASE 0xe780
|
||||
|
||||
#define RTSDS_839X_SDS_CNT 14
|
||||
#define RTSDS_839X_PAGE_CNT 12
|
||||
#define RTSDS_839X_BASE 0xa000
|
||||
|
||||
#define RTSDS_930X_SDS_CNT 12
|
||||
#define RTSDS_930X_PAGE_CNT 64
|
||||
#define RTSDS_930X_BASE 0x03b0
|
||||
|
||||
#define RTSDS_931X_SDS_CNT 14
|
||||
#define RTSDS_931X_PAGE_CNT 192
|
||||
#define RTSDS_931X_BASE 0x5638
|
||||
|
||||
#define RTSDS_93XX_CMD_READ 0
|
||||
#define RTSDS_93XX_CMD_WRITE BIT(1)
|
||||
#define RTSDS_93XX_CMD_BUSY BIT(0)
|
||||
#define RTSDS_93XX_CMD_SDS_MASK GENMASK(6, 2)
|
||||
#define RTSDS_93XX_CMD_PAGE_MASK GENMASK(12, 7)
|
||||
#define RTSDS_93XX_CMD_REG_MASK GENMASK(17, 13)
|
||||
|
||||
struct rtsds_ctrl {
|
||||
struct device *dev;
|
||||
struct regmap *map;
|
||||
struct mii_bus *bus;
|
||||
const struct rtsds_config *cfg;
|
||||
};
|
||||
|
||||
struct rtsds_config {
|
||||
int sds_cnt;
|
||||
int page_cnt;
|
||||
int base;
|
||||
int (*get_backing_sds)(int sds, int page);
|
||||
int (*read)(struct rtsds_ctrl *ctrl, int sds, int page, int regnum);
|
||||
int (*write)(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, u16 value);
|
||||
};
|
||||
|
||||
static bool rtsds_mmd_to_sds(struct rtsds_ctrl *ctrl, int addr, int devad, int mmd_regnum,
|
||||
int *sds_page, int *sds_regnum)
|
||||
{
|
||||
*sds_page = FIELD_GET(RTSDS_MMD_PAGE_MASK, mmd_regnum);
|
||||
*sds_regnum = FIELD_GET(RTSDS_MMD_REG_MASK, mmd_regnum);
|
||||
|
||||
return !(addr < 0 || addr >= ctrl->cfg->sds_cnt ||
|
||||
*sds_page < 0 || *sds_page >= ctrl->cfg->page_cnt ||
|
||||
*sds_regnum < 0 || *sds_regnum >= RTSDS_REG_CNT ||
|
||||
devad != MDIO_MMD_VEND1);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
/*
|
||||
* The SerDes offer a lot of magic that sill needs to be uncovered. Getting this info manually
|
||||
* with mdio command line tools can be time consuming. Provide a convenient register dump.
|
||||
*/
|
||||
|
||||
#define RTSDS_DBG_PAGE_NAMES 48
|
||||
#define RTSDS_DBG_ROOT_DIR "realtek_otto_serdes"
|
||||
|
||||
struct rtsds_debug_info {
|
||||
struct rtsds_ctrl *ctrl;
|
||||
int sds;
|
||||
};
|
||||
|
||||
static const char * const rtsds_page_name[RTSDS_DBG_PAGE_NAMES] = {
|
||||
[0] = "SDS", [1] = "SDS_EXT",
|
||||
[2] = "FIB", [3] = "FIB_EXT",
|
||||
[4] = "DTE", [5] = "DTE_EXT",
|
||||
[6] = "TGX", [7] = "TGX_EXT",
|
||||
[8] = "ANA_RG", [9] = "ANA_RG_EXT",
|
||||
[10] = "ANA_TG", [11] = "ANA_TG_EXT",
|
||||
[31] = "ANA_WDIG",
|
||||
[32] = "ANA_MISC", [33] = "ANA_COM",
|
||||
[34] = "ANA_SP", [35] = "ANA_SP_EXT",
|
||||
[36] = "ANA_1G", [37] = "ANA_1G_EXT",
|
||||
[38] = "ANA_2G", [39] = "ANA_2G_EXT",
|
||||
[40] = "ANA_3G", [41] = "ANA_3G_EXT",
|
||||
[42] = "ANA_5G", [43] = "ANA_5G_EXT",
|
||||
[44] = "ANA_6G", [45] = "ANA_6G_EXT",
|
||||
[46] = "ANA_10G", [47] = "ANA_10G_EXT",
|
||||
};
|
||||
|
||||
static int rtsds_sds_to_mmd(int sds_page, int sds_regnum)
|
||||
{
|
||||
return FIELD_PREP(RTSDS_MMD_PAGE_MASK, sds_page) |
|
||||
FIELD_PREP(RTSDS_MMD_REG_MASK, sds_regnum);
|
||||
}
|
||||
|
||||
static int rtsds_dbg_registers_show(struct seq_file *seqf, void *unused)
|
||||
{
|
||||
struct rtsds_debug_info *dbg_info = seqf->private;
|
||||
struct rtsds_ctrl *ctrl = dbg_info->ctrl;
|
||||
struct mii_bus *bus = ctrl->bus;
|
||||
int sds = dbg_info->sds;
|
||||
int regnum, page = 0;
|
||||
int subpage;
|
||||
|
||||
do {
|
||||
subpage = RTSDS_SUBPAGE(page);
|
||||
if (!subpage) {
|
||||
seq_printf(seqf, "Back SDS %02d:", ctrl->cfg->get_backing_sds(sds, page));
|
||||
for (regnum = 0; regnum < RTSDS_REG_CNT; regnum++)
|
||||
seq_printf(seqf, " %02X", regnum);
|
||||
seq_puts(seqf, "\n");
|
||||
}
|
||||
|
||||
if (subpage < RTSDS_DBG_PAGE_NAMES && rtsds_page_name[subpage])
|
||||
seq_printf(seqf, "%*s: ", -11, rtsds_page_name[subpage]);
|
||||
else
|
||||
seq_printf(seqf, "PAGE %02X : ", page);
|
||||
|
||||
for (regnum = 0; regnum < RTSDS_REG_CNT; regnum++)
|
||||
seq_printf(seqf, "%04X ",
|
||||
mdiobus_c45_read(bus, sds, MDIO_MMD_VEND1,
|
||||
rtsds_sds_to_mmd(page, regnum)));
|
||||
seq_puts(seqf, "\n");
|
||||
} while (++page < ctrl->cfg->page_cnt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_registers);
|
||||
|
||||
static int rtsds_debug_init(struct rtsds_ctrl *ctrl)
|
||||
{
|
||||
struct rtsds_debug_info *dbg_info;
|
||||
struct dentry *dir, *root;
|
||||
char dirname[32];
|
||||
|
||||
root = debugfs_create_dir(RTSDS_DBG_ROOT_DIR, NULL);
|
||||
if (IS_ERR(root))
|
||||
return PTR_ERR(root);
|
||||
|
||||
for (int sds = 0; sds < ctrl->cfg->sds_cnt; sds++) {
|
||||
dbg_info = devm_kzalloc(ctrl->dev, sizeof(*dbg_info), GFP_KERNEL);
|
||||
if (!dbg_info)
|
||||
return -ENOMEM;
|
||||
|
||||
dbg_info->ctrl = ctrl;
|
||||
dbg_info->sds = sds;
|
||||
|
||||
snprintf(dirname, sizeof(dirname), "serdes.%d", sds);
|
||||
dir = debugfs_create_dir(dirname, root);
|
||||
|
||||
debugfs_create_file("registers", 0600, dir, dbg_info,
|
||||
&rtsds_dbg_registers_fops);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
||||
/*
|
||||
* The RTL838x has 6 SerDes. The 16 bit registers start at 0xbb00e780 and are mapped directly into
|
||||
* 32 bit memory addresses. High 16 bits are always empty. A "lower" memory block serves pages 0/3
|
||||
* a "higher" memory block pages 1/2.
|
||||
*/
|
||||
|
||||
static int rtsds_838x_reg_offset(int sds, int page, int regnum)
|
||||
{
|
||||
if (page == 0 || page == 3)
|
||||
return (sds << 9) + (page << 7) + (regnum << 2);
|
||||
|
||||
/* (page == 1 || page == 2) */
|
||||
return 0xb80 + (sds << 8) + (page << 7) + (regnum << 2);
|
||||
}
|
||||
|
||||
static int rtsds_838x_read(struct rtsds_ctrl *ctrl, int sds, int page, int regnum)
|
||||
{
|
||||
int offset = rtsds_838x_reg_offset(sds, page, regnum);
|
||||
int ret, value;
|
||||
|
||||
ret = regmap_read(ctrl->map, ctrl->cfg->base + offset, &value);
|
||||
|
||||
return ret ? ret : value & RTSDS_VAL_MASK;
|
||||
}
|
||||
|
||||
static int rtsds_838x_write(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, u16 value)
|
||||
{
|
||||
int offset = rtsds_838x_reg_offset(sds, page, regnum);
|
||||
|
||||
return regmap_write(ctrl->map, ctrl->cfg->base + offset, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* The RTL839x has 14 SerDes starting at 0xbb00a000. 0-7, 10, 11 are 5GBit, 8, 9, 12, 13 are
|
||||
* 10 GBit. Two adjacent SerDes are tightly coupled and share a 1024 bytes register area. Per 32
|
||||
* bit address two registers are stored. The first register is stored in the lower 2 bytes ("on
|
||||
* the right" due to big endian) and the second register in the upper 2 bytes. The following
|
||||
* register areas are known:
|
||||
*
|
||||
* - XSG0 (4 pages @ offset 0x000): for even SerDes
|
||||
* - XSG1 (4 pages @ offset 0x100): for odd SerDes
|
||||
* - TGRX (4 pages @ offset 0x200): for even 10G SerDes
|
||||
* - ANA_RG (2 pages @ offset 0x300): for even 5G SerDes
|
||||
* - ANA_RG (2 pages @ offset 0x380): for odd 5G SerDes
|
||||
* - ANA_TG (2 pages @ offset 0x300): for even 10G SerDes
|
||||
* - ANA_TG (2 pages @ offset 0x380): for odd 10G SerDes
|
||||
*
|
||||
* The most consistent mapping that aligns to the RTL93xx devices is:
|
||||
*
|
||||
* even 5G SerDes odd 5G SerDes even 10G SerDes odd 10G SerDes
|
||||
* Page 0: XSG0/0 XSG1/0 XSG0/0 XSG1/0
|
||||
* Page 1: XSG0/1 XSG1/1 XSG0/1 XSG1/1
|
||||
* Page 2: XSG0/2 XSG1/2 XSG0/2 XSG1/2
|
||||
* Page 3: XSG0/3 XSG1/3 XSG0/3 XSG1/3
|
||||
* Page 4: <zero> <zero> TGRX/0 <zero>
|
||||
* Page 5: <zero> <zero> TGRX/1 <zero>
|
||||
* Page 6: <zero> <zero> TGRX/2 <zero>
|
||||
* Page 7: <zero> <zero> TGRX/3 <zero>
|
||||
* Page 8: ANA_RG ANA_RG <zero> <zero>
|
||||
* Page 9: ANA_RG_EXT ANA_RG_EXT <zero> <zero>
|
||||
* Page 10: <zero> <zero> ANA_TG ANA_TG
|
||||
* Page 11: <zero> <zero> ANA_TG_EXT ANA_TG_EXT
|
||||
*/
|
||||
|
||||
static int rtsds_839x_reg_offset(int sds, int page, int regnum)
|
||||
{
|
||||
int offset = ((sds & 0xfe) << 9) + ((regnum & 0xfe) << 1) + (page << 6);
|
||||
int sds5g = (GENMASK(11, 10) | GENMASK(7, 0)) & BIT(sds);
|
||||
|
||||
if (page < 4)
|
||||
return offset + ((sds & 1) << 8);
|
||||
else if ((page & 4) && (sds == 8 || sds == 12))
|
||||
return offset + 0x100;
|
||||
else if (page >= 8 && page <= 9 && sds5g)
|
||||
return offset + 0x100 + ((sds & 1) << 7);
|
||||
else if (page >= 10 && !sds5g)
|
||||
return offset + 0x80 + ((sds & 1) << 7);
|
||||
|
||||
return -EINVAL; /* hole */
|
||||
}
|
||||
|
||||
static int rtsds_839x_read(struct rtsds_ctrl *ctrl, int sds, int page, int regnum)
|
||||
{
|
||||
int offset = rtsds_839x_reg_offset(sds, page, regnum);
|
||||
int shift = regnum & 1 ? 16 : 0;
|
||||
int ret, value;
|
||||
|
||||
if (offset < 0)
|
||||
return 0;
|
||||
|
||||
ret = regmap_read(ctrl->map, ctrl->cfg->base + offset, &value);
|
||||
|
||||
return ret ? ret : (value >> shift) & RTSDS_VAL_MASK;
|
||||
}
|
||||
|
||||
static int rtsds_839x_write(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, u16 value)
|
||||
{
|
||||
int offset = rtsds_839x_reg_offset(sds, page, regnum);
|
||||
int write_value = value;
|
||||
int neigh_value;
|
||||
|
||||
if (offset < 0)
|
||||
return 0;
|
||||
|
||||
neigh_value = rtsds_839x_read(ctrl, sds, page, regnum ^ 1);
|
||||
if (neigh_value < 0)
|
||||
return neigh_value;
|
||||
|
||||
if (regnum & 1)
|
||||
write_value = (write_value << 16) + neigh_value;
|
||||
else
|
||||
write_value = (neigh_value << 16) + write_value;
|
||||
|
||||
return regmap_write(ctrl->map, ctrl->cfg->base + offset, write_value);
|
||||
}
|
||||
|
||||
static int rtsds_83xx_get_backing_sds(int sds, int page)
|
||||
{
|
||||
return sds;
|
||||
}
|
||||
|
||||
static int rtsds_rt93xx_io(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, int cmd)
|
||||
{
|
||||
int ret, op, value;
|
||||
|
||||
op = FIELD_PREP(RTSDS_93XX_CMD_SDS_MASK, sds) |
|
||||
FIELD_PREP(RTSDS_93XX_CMD_PAGE_MASK, page) |
|
||||
FIELD_PREP(RTSDS_93XX_CMD_REG_MASK, regnum) |
|
||||
RTSDS_93XX_CMD_BUSY | cmd;
|
||||
|
||||
regmap_write(ctrl->map, ctrl->cfg->base, op);
|
||||
ret = regmap_read_poll_timeout(ctrl->map, ctrl->cfg->base, value,
|
||||
!(value & RTSDS_93XX_CMD_BUSY), 30, 1000000);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(ctrl->dev, "SerDes I/O timed out\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* RTL93xx targets use a shared implementation. Their SerDes data is accessed through two IO
|
||||
* registers which simulate commands to an internal MDIO bus.
|
||||
*
|
||||
* The RTL930x family has 12 SerDes of three types.
|
||||
*
|
||||
* - SerDes 0-1 exist on the RTL9301 and 9302B and are QSGMII capable
|
||||
* - SerDes 2-9 are USXGMII capabable with either quad or single configuration
|
||||
* - SerDes 10-11 are 10GBase-R capable
|
||||
*/
|
||||
static int rtsds_930x_get_backing_sds(int sds, int page)
|
||||
{
|
||||
return sds;
|
||||
}
|
||||
|
||||
/*
|
||||
* The RTL931x family has 14 "frontend" SerDes that are cascaded. All operations (e.g. reset) work
|
||||
* on this frontend view while their registers are distributed over a total of least 26 background
|
||||
* SerDes with 64 pages and 32 registers. Three types of SerDes exist:
|
||||
*
|
||||
* - Serdes 0,1 are "simple" and work on one background serdes.
|
||||
* - "Even" SerDes with numbers 2, 4, 6, 8, 10, 12 work on two background SerDes. One analog and
|
||||
* one digital.
|
||||
* - "Odd" SerDes with numbers 3, 5, 7, 9, 11, 13 work on a total of 3 background SerDes (one
|
||||
* analog and two digital)
|
||||
*
|
||||
* This maps to:
|
||||
*
|
||||
* Frontend SerDes | 0 1 2 3 4 5 6 7 8 9 10 11 12 13
|
||||
* -----------------+------------------------------------------
|
||||
* Backend SerDes 1 | 0 1 2 3 6 7 10 11 14 15 18 19 22 23
|
||||
* Backend SerDes 2 | 0 1 2 4 6 8 10 12 14 16 18 20 22 24
|
||||
* Backend SerDes 3 | 0 1 3 5 7 9 11 13 15 17 19 21 23 25
|
||||
*
|
||||
* Note: In Realtek proprietary XSGMII mode (10G pumped SGMII) the frontend SerDes works on the
|
||||
* two digital SerDes while in all other modes it works on the analog and the first digital
|
||||
* SerDes. Overlapping (e.g. backend SerDes 7 can be analog or digital 2) is avoided by the
|
||||
* existing hardware designs.
|
||||
*
|
||||
* Align this for readability by simulating a total of 192 pages and mix them as follows.
|
||||
*
|
||||
* frontend page "even" frontend SerDes "odd" frontend SerDes
|
||||
* page 0x00-0x3f (analog): page 0x00-0x3f back SDS page 0x00-0x3f back SDS
|
||||
* page 0x40-0x7f (digi 1): page 0x00-0x3f back SDS page 0x00-0x3f back SDS+1
|
||||
* page 0x80-0xbf (digi 2): page 0x00-0x3f back SDS+1 page 0x00-0x3f back SDS+2
|
||||
*/
|
||||
static int rtsds_931x_get_backing_sds(int sds, int page)
|
||||
{
|
||||
int map[] = { 0, 1, 2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23 };
|
||||
int backsds;
|
||||
|
||||
/* First two RTL931x SerDes have 1:1 frontend/backend mapping */
|
||||
if (sds < 2)
|
||||
return sds;
|
||||
|
||||
backsds = map[sds];
|
||||
if (sds & 1)
|
||||
backsds += (page >> 6); /* distribute "odd" to 3 background SerDes */
|
||||
else
|
||||
backsds += (page >> 7); /* distribute "even" to 2 background SerDes */
|
||||
|
||||
return backsds;
|
||||
}
|
||||
|
||||
static int rtsds_93xx_read(struct rtsds_ctrl *ctrl, int sds, int page, int regnum)
|
||||
{
|
||||
int subpage = RTSDS_SUBPAGE(page);
|
||||
int ret, backsds, value;
|
||||
|
||||
backsds = ctrl->cfg->get_backing_sds(sds, page);
|
||||
ret = rtsds_rt93xx_io(ctrl, backsds, subpage, regnum, RTSDS_93XX_CMD_READ);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(ctrl->map, ctrl->cfg->base + 4, &value);
|
||||
|
||||
return ret ? ret : value & RTSDS_VAL_MASK;
|
||||
}
|
||||
|
||||
static int rtsds_93xx_write(struct rtsds_ctrl *ctrl, int sds, int page, int regnum, u16 value)
|
||||
{
|
||||
int subpage = RTSDS_SUBPAGE(page);
|
||||
int ret, backsds;
|
||||
|
||||
backsds = ctrl->cfg->get_backing_sds(sds, page);
|
||||
ret = regmap_write(ctrl->map, ctrl->cfg->base + 4, value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return rtsds_rt93xx_io(ctrl, backsds, subpage, regnum, RTSDS_93XX_CMD_WRITE);
|
||||
}
|
||||
|
||||
static int rtsds_read(struct mii_bus *bus, int addr, int devad, int regnum)
|
||||
{
|
||||
struct rtsds_ctrl *ctrl = bus->priv;
|
||||
int sds_page, sds_regnum;
|
||||
|
||||
if (!rtsds_mmd_to_sds(ctrl, addr, devad, regnum, &sds_page, &sds_regnum))
|
||||
return -EINVAL;
|
||||
|
||||
return ctrl->cfg->read(ctrl, addr, sds_page, sds_regnum);
|
||||
}
|
||||
|
||||
static int rtsds_write(struct mii_bus *bus, int addr, int devad, int regnum, u16 value)
|
||||
{
|
||||
struct rtsds_ctrl *ctrl = bus->priv;
|
||||
int sds_page, sds_regnum;
|
||||
|
||||
if (!rtsds_mmd_to_sds(ctrl, addr, devad, regnum, &sds_page, &sds_regnum))
|
||||
return -EINVAL;
|
||||
|
||||
return ctrl->cfg->write(ctrl, addr, sds_page, sds_regnum, value);
|
||||
}
|
||||
|
||||
static int rtsds_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rtsds_ctrl *ctrl;
|
||||
struct mii_bus *bus;
|
||||
int ret;
|
||||
|
||||
bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*ctrl));
|
||||
if (!bus)
|
||||
return -ENOMEM;
|
||||
|
||||
ctrl = bus->priv;
|
||||
ctrl->map = syscon_node_to_regmap(np->parent);
|
||||
if (IS_ERR(ctrl->map))
|
||||
return PTR_ERR(ctrl->map);
|
||||
|
||||
ctrl->dev = dev;
|
||||
ctrl->cfg = (const struct rtsds_config *)device_get_match_data(ctrl->dev);
|
||||
ctrl->bus = bus;
|
||||
|
||||
snprintf(bus->id, MII_BUS_ID_SIZE, "realtek-serdes-mdio");
|
||||
bus->name = "Realtek SerDes MDIO bus";
|
||||
bus->parent = dev;
|
||||
bus->read_c45 = rtsds_read;
|
||||
bus->write_c45 = rtsds_write;
|
||||
bus->phy_mask = ~0ULL;
|
||||
|
||||
ret = devm_of_mdiobus_register(dev, bus, dev->of_node);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
rtsds_debug_init(ctrl);
|
||||
#endif
|
||||
|
||||
dev_info(dev, "Realtek SerDes mdio bus initialized, %d SerDes, %d pages, %d registers\n",
|
||||
ctrl->cfg->sds_cnt, ctrl->cfg->page_cnt, RTSDS_REG_CNT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtsds_config rtsds_838x_cfg = {
|
||||
.sds_cnt = RTSDS_838X_SDS_CNT,
|
||||
.page_cnt = RTSDS_838X_PAGE_CNT,
|
||||
.base = RTSDS_838X_BASE,
|
||||
.get_backing_sds = rtsds_83xx_get_backing_sds,
|
||||
.read = rtsds_838x_read,
|
||||
.write = rtsds_838x_write,
|
||||
};
|
||||
|
||||
static const struct rtsds_config rtsds_839x_cfg = {
|
||||
.sds_cnt = RTSDS_839X_SDS_CNT,
|
||||
.page_cnt = RTSDS_839X_PAGE_CNT,
|
||||
.base = RTSDS_839X_BASE,
|
||||
.get_backing_sds = rtsds_83xx_get_backing_sds,
|
||||
.read = rtsds_839x_read,
|
||||
.write = rtsds_839x_write,
|
||||
};
|
||||
|
||||
static const struct rtsds_config rtsds_930x_cfg = {
|
||||
.sds_cnt = RTSDS_930X_SDS_CNT,
|
||||
.page_cnt = RTSDS_930X_PAGE_CNT,
|
||||
.base = RTSDS_930X_BASE,
|
||||
.get_backing_sds = rtsds_930x_get_backing_sds,
|
||||
.read = rtsds_93xx_read,
|
||||
.write = rtsds_93xx_write,
|
||||
};
|
||||
|
||||
static const struct rtsds_config rtsds_931x_cfg = {
|
||||
.sds_cnt = RTSDS_931X_SDS_CNT,
|
||||
.page_cnt = RTSDS_931X_PAGE_CNT,
|
||||
.base = RTSDS_931X_BASE,
|
||||
.get_backing_sds = rtsds_931x_get_backing_sds,
|
||||
.read = rtsds_93xx_read,
|
||||
.write = rtsds_93xx_write,
|
||||
};
|
||||
|
||||
static const struct of_device_id rtsds_of_match[] = {
|
||||
{
|
||||
.compatible = "realtek,rtl8380-serdes-mdio",
|
||||
.data = &rtsds_838x_cfg,
|
||||
},
|
||||
{
|
||||
.compatible = "realtek,rtl8392-serdes-mdio",
|
||||
.data = &rtsds_839x_cfg,
|
||||
},
|
||||
{
|
||||
.compatible = "realtek,rtl9301-serdes-mdio",
|
||||
.data = &rtsds_930x_cfg,
|
||||
},
|
||||
{
|
||||
.compatible = "realtek,rtl9311-serdes-mdio",
|
||||
.data = &rtsds_931x_cfg,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rtsds_of_match);
|
||||
|
||||
static struct platform_driver rtsds_mdio_driver = {
|
||||
.driver = {
|
||||
.name = "realtek-otto-serdes-mdio",
|
||||
.of_match_table = rtsds_of_match
|
||||
},
|
||||
.probe = rtsds_probe,
|
||||
};
|
||||
module_platform_driver(rtsds_mdio_driver);
|
||||
|
||||
MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
|
||||
MODULE_DESCRIPTION("Realtek Otto SerDes MDIO bus");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,707 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Realtek RTL838X Ethernet MDIO interface driver
|
||||
*
|
||||
* Copyright (C) 2020 B. Koblitz
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/sfp.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/mdio.h>
|
||||
|
||||
/*
|
||||
* Realtek PHYs have three special page registers. Register 31 (page select) switches the
|
||||
* register pages and gives access to special registers that are mapped into register
|
||||
* range 16-24. Register 30 (extended page select) does basically the same. It especially
|
||||
* grants access to special internal data like fibre, copper or serdes setup. Register
|
||||
* 29 is a write only companion of register 30. As it flips back to 0 and register 30
|
||||
* shows the original write content it should be avoided at all cost.
|
||||
*/
|
||||
#define RTL821x_PAGE_SELECT 0x1f
|
||||
#define RTL821x_EXT_PAGE_SELECT 0x1e
|
||||
|
||||
#define RTL8XXX_PAGE_MAIN 0x0000
|
||||
#define RTL821X_PAGE_PORT 0x0266
|
||||
#define RTL821X_PAGE_POWER 0x0a40
|
||||
#define RTL821X_PAGE_GPHY 0x0a42
|
||||
#define RTL821X_PAGE_MAC 0x0a43
|
||||
#define RTL821X_PAGE_STATE 0x0b80
|
||||
#define RTL821X_PAGE_PATCH 0x0b82
|
||||
#define RTL821X_MAC_SDS_PAGE(sds, page) (0x404 + (sds) * 0x20 + (page))
|
||||
|
||||
#define RTL821X_PHYCR2 0x19
|
||||
#define RTL821X_PHYCR2_PHY_EEE_ENABLE BIT(5)
|
||||
|
||||
#define RTL821X_MEDIA_PAGE_AUTO 0
|
||||
#define RTL821X_MEDIA_PAGE_COPPER 1
|
||||
#define RTL821X_MEDIA_PAGE_FIBRE 3
|
||||
#define RTL821X_MEDIA_PAGE_INTERNAL 8
|
||||
|
||||
#define RTL821X_JOIN_FIRST 0
|
||||
#define RTL821X_JOIN_LAST 1
|
||||
#define RTL821X_JOIN_OTHER 2
|
||||
|
||||
#define RTL8214FC_MEDIA_COPPER BIT(11)
|
||||
|
||||
#define PHY_ID_RTL8214C 0x001cc942
|
||||
#define PHY_ID_RTL8218B_E 0x001cc980
|
||||
#define PHY_ID_RTL8214_OR_8218 0x001cc981
|
||||
#define PHY_ID_RTL8218D 0x001cc983
|
||||
#define PHY_ID_RTL8218E 0x001cc984
|
||||
#define PHY_ID_RTL8218B_I 0x001cca40
|
||||
|
||||
/* These PHYs share the same id (0x001cc981) */
|
||||
#define PHY_IS_NOT_RTL821X 0
|
||||
#define PHY_IS_RTL8214FC 1
|
||||
#define PHY_IS_RTL8214FB 2
|
||||
#define PHY_IS_RTL8218B_E 3
|
||||
|
||||
struct rtl821x_shared_priv {
|
||||
int base_addr;
|
||||
int ports;
|
||||
};
|
||||
|
||||
/* TODO: for kernel 6.18 drop this function and use it from phy_package library instead */
|
||||
static void *phy_package_get_priv(struct phy_device *phydev)
|
||||
{
|
||||
return phydev->shared->priv;
|
||||
}
|
||||
|
||||
static int rtl821x_package_join(struct phy_device *phydev, int ports)
|
||||
{
|
||||
struct rtl821x_shared_priv *shared_priv;
|
||||
int base_addr = phydev->mdio.addr & ~(ports - 1);
|
||||
|
||||
devm_phy_package_join(&phydev->mdio.dev, phydev, base_addr,
|
||||
sizeof(struct rtl821x_shared_priv));
|
||||
|
||||
shared_priv = phy_package_get_priv(phydev);
|
||||
shared_priv->base_addr = base_addr;
|
||||
shared_priv->ports++;
|
||||
|
||||
if (shared_priv->ports == 1)
|
||||
return RTL821X_JOIN_FIRST;
|
||||
|
||||
if (shared_priv->ports == ports)
|
||||
return RTL821X_JOIN_LAST;
|
||||
|
||||
return RTL821X_JOIN_OTHER;
|
||||
}
|
||||
|
||||
static inline struct phy_device *get_package_phy(struct phy_device *phydev, int port)
|
||||
{
|
||||
struct rtl821x_shared_priv *shared_priv = phy_package_get_priv(phydev);
|
||||
|
||||
return mdiobus_get_phy(phydev->mdio.bus, shared_priv->base_addr + port);
|
||||
}
|
||||
|
||||
static inline struct phy_device *get_base_phy(struct phy_device *phydev)
|
||||
{
|
||||
return get_package_phy(phydev, 0);
|
||||
}
|
||||
|
||||
static int rtl821x_match_phy_device(struct phy_device *phydev)
|
||||
{
|
||||
int oldpage, oldxpage, chip_mode, chip_cfg_mode;
|
||||
struct mii_bus *bus = phydev->mdio.bus;
|
||||
int addr = phydev->mdio.addr & ~3;
|
||||
|
||||
if (phydev->phy_id == PHY_ID_RTL8218B_E)
|
||||
return PHY_IS_RTL8218B_E;
|
||||
|
||||
if (phydev->phy_id != PHY_ID_RTL8214_OR_8218)
|
||||
return PHY_IS_NOT_RTL821X;
|
||||
|
||||
/*
|
||||
* RTL8214FC and RTL8218B are the same PHYs with different configurations. That info is
|
||||
* stored in the first PHY of the package. In all known configurations packages start at
|
||||
* bus addresses that are multiples of four. Avoid paged access as this is not available
|
||||
* during detection.
|
||||
*/
|
||||
|
||||
oldpage = mdiobus_read(bus, addr, RTL821x_PAGE_SELECT);
|
||||
oldxpage = mdiobus_read(bus, addr, RTL821x_EXT_PAGE_SELECT);
|
||||
|
||||
mdiobus_write(bus, addr, RTL821x_EXT_PAGE_SELECT, 0x8);
|
||||
mdiobus_write(bus, addr, RTL821x_PAGE_SELECT, 0x278);
|
||||
mdiobus_write(bus, addr, 0x12, 0x455);
|
||||
mdiobus_write(bus, addr, RTL821x_PAGE_SELECT, 0x260);
|
||||
chip_mode = mdiobus_read(bus, addr, 0x12);
|
||||
dev_dbg(&phydev->mdio.dev, "got RTL8218B/RTL8214Fx chip mode %04x\n", chip_mode);
|
||||
|
||||
mdiobus_write(bus, addr, RTL821x_EXT_PAGE_SELECT, oldxpage);
|
||||
mdiobus_write(bus, addr, RTL821x_PAGE_SELECT, oldpage);
|
||||
|
||||
/* no values while reading the 5th port during 5-8th port detection of RTL8218B */
|
||||
if (!chip_mode)
|
||||
return PHY_IS_RTL8218B_E;
|
||||
|
||||
chip_cfg_mode = (chip_mode >> 4) & 0xf;
|
||||
chip_mode &= 0xf;
|
||||
|
||||
if (chip_mode == 0xd || chip_mode == 0xf)
|
||||
return PHY_IS_RTL8218B_E;
|
||||
|
||||
if (chip_mode == 0x4 || chip_mode == 0x6)
|
||||
return PHY_IS_RTL8214FC;
|
||||
|
||||
if (chip_mode != 0xc && chip_mode != 0xe)
|
||||
return PHY_IS_NOT_RTL821X;
|
||||
|
||||
if (chip_cfg_mode == 0x4 || chip_cfg_mode == 0x6)
|
||||
return PHY_IS_RTL8214FC;
|
||||
|
||||
return PHY_IS_RTL8214FB;
|
||||
}
|
||||
|
||||
static int rtl8218b_ext_match_phy_device(struct phy_device *phydev,
|
||||
const struct phy_driver *phydrv)
|
||||
{
|
||||
return rtl821x_match_phy_device(phydev) == PHY_IS_RTL8218B_E;
|
||||
}
|
||||
|
||||
static int rtl8214fc_match_phy_device(struct phy_device *phydev,
|
||||
const struct phy_driver *phydrv)
|
||||
{
|
||||
return rtl821x_match_phy_device(phydev) == PHY_IS_RTL8214FC;
|
||||
}
|
||||
|
||||
static int rtl821x_read_page(struct phy_device *phydev)
|
||||
{
|
||||
return __phy_read(phydev, RTL821x_PAGE_SELECT);
|
||||
}
|
||||
|
||||
static int rtl821x_write_page(struct phy_device *phydev, int page)
|
||||
{
|
||||
return __phy_write(phydev, RTL821x_PAGE_SELECT, page);
|
||||
}
|
||||
|
||||
static bool __rtl8214fc_media_is_fibre(struct phy_device *phydev)
|
||||
{
|
||||
struct phy_device *basephy = get_base_phy(phydev);
|
||||
static int regs[] = {16, 19, 20, 21};
|
||||
int reg = regs[phydev->mdio.addr & 3];
|
||||
int oldpage, oldxpage, val;
|
||||
|
||||
oldpage = __phy_read(basephy, RTL821x_PAGE_SELECT);
|
||||
oldxpage = __phy_read(basephy, RTL821x_EXT_PAGE_SELECT);
|
||||
|
||||
__phy_write(basephy, RTL821x_EXT_PAGE_SELECT, RTL821X_MEDIA_PAGE_INTERNAL);
|
||||
__phy_write(basephy, RTL821x_PAGE_SELECT, RTL821X_PAGE_PORT);
|
||||
val = __phy_read(basephy, reg);
|
||||
|
||||
__phy_write(basephy, RTL821x_EXT_PAGE_SELECT, oldxpage);
|
||||
__phy_write(basephy, RTL821x_PAGE_SELECT, oldpage);
|
||||
|
||||
return !(val & RTL8214FC_MEDIA_COPPER);
|
||||
}
|
||||
|
||||
static bool rtl8214fc_media_is_fibre(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
phy_lock_mdio_bus(phydev);
|
||||
ret = __rtl8214fc_media_is_fibre(phydev);
|
||||
phy_unlock_mdio_bus(phydev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rtl8214fc_power_set(struct phy_device *phydev, int port, bool on)
|
||||
{
|
||||
int page = port == PORT_FIBRE ? RTL821X_MEDIA_PAGE_FIBRE : RTL821X_MEDIA_PAGE_COPPER;
|
||||
int oldxpage = __phy_read(phydev, RTL821x_EXT_PAGE_SELECT);
|
||||
int pdown = on ? 0 : BMCR_PDOWN;
|
||||
|
||||
phydev_info(phydev, "power %s %s\n", on ? "on" : "off",
|
||||
port == PORT_FIBRE ? "fibre" : "copper");
|
||||
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, page);
|
||||
phy_modify_paged(phydev, RTL821X_PAGE_POWER, 0x10, BMCR_PDOWN, pdown);
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, oldxpage);
|
||||
}
|
||||
|
||||
static int rtl8214fc_suspend(struct phy_device *phydev)
|
||||
{
|
||||
rtl8214fc_power_set(phydev, PORT_MII, false);
|
||||
rtl8214fc_power_set(phydev, PORT_FIBRE, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl8214fc_resume(struct phy_device *phydev)
|
||||
{
|
||||
bool set_fibre = rtl8214fc_media_is_fibre(phydev);
|
||||
|
||||
rtl8214fc_power_set(phydev, PORT_MII, !set_fibre);
|
||||
rtl8214fc_power_set(phydev, PORT_FIBRE, set_fibre);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtl8214fc_media_set(struct phy_device *phydev, bool set_fibre)
|
||||
{
|
||||
struct phy_device *basephy = get_base_phy(phydev);
|
||||
int oldxpage = phy_read(basephy, RTL821x_EXT_PAGE_SELECT);
|
||||
int copper = set_fibre ? 0 : RTL8214FC_MEDIA_COPPER;
|
||||
static int regs[] = {16, 19, 20, 21};
|
||||
int reg = regs[phydev->mdio.addr & 3];
|
||||
|
||||
phydev_info(phydev, "switch to %s\n", set_fibre ? "fibre" : "copper");
|
||||
phy_write(basephy, RTL821x_EXT_PAGE_SELECT, RTL821X_MEDIA_PAGE_INTERNAL);
|
||||
phy_modify_paged(basephy, RTL821X_PAGE_PORT, reg, RTL8214FC_MEDIA_COPPER, copper);
|
||||
phy_write(basephy, RTL821x_EXT_PAGE_SELECT, oldxpage);
|
||||
|
||||
if (!phydev->suspended) {
|
||||
rtl8214fc_power_set(phydev, PORT_MII, !set_fibre);
|
||||
rtl8214fc_power_set(phydev, PORT_FIBRE, set_fibre);
|
||||
}
|
||||
}
|
||||
|
||||
static int rtl8214fc_set_tunable(struct phy_device *phydev,
|
||||
struct ethtool_tunable *tuna, const void *data)
|
||||
{
|
||||
/*
|
||||
* The RTL8214FC driver usually detects insertion of SFP modules and automatically toggles
|
||||
* between copper and fiber. There may be cases where the user wants to switch the port on
|
||||
* demand. Usually ethtool offers to change the port of a multiport network card with
|
||||
* "ethtool -s lan25 port fibre/tp" if the driver supports it. This does not work for
|
||||
* attached phys. For more details see phy_ethtool_ksettings_set(). To avoid patching the
|
||||
* kernel misuse the phy downshift tunable to offer that feature. For this use
|
||||
* "ethtool --set-phy-tunable lan25 downshift on/off".
|
||||
*/
|
||||
switch (tuna->id) {
|
||||
case ETHTOOL_PHY_DOWNSHIFT:
|
||||
rtl8214fc_media_set(phydev, !rtl8214fc_media_is_fibre(phydev));
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int rtl8214fc_get_tunable(struct phy_device *phydev,
|
||||
struct ethtool_tunable *tuna, void *data)
|
||||
{
|
||||
/* Needed to make rtl8214fc_set_tunable() work */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl8214fc_get_features(struct phy_device *phydev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = genphy_read_abilities(phydev);
|
||||
if (ret)
|
||||
return ret;
|
||||
/*
|
||||
* The RTL8214FC only advertises TP capabilities in the standard registers. This is
|
||||
* independent from what fibre/copper combination is currently activated. For now just
|
||||
* announce the superset of all possible features.
|
||||
*/
|
||||
linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, phydev->supported);
|
||||
linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl8214fc_read_status(struct phy_device *phydev)
|
||||
{
|
||||
bool changed;
|
||||
int ret;
|
||||
|
||||
if (rtl8214fc_media_is_fibre(phydev)) {
|
||||
phydev->port = PORT_FIBRE;
|
||||
ret = genphy_c37_read_status(phydev, &changed);
|
||||
} else {
|
||||
phydev->port = PORT_MII; /* for now aligend with rest of code */
|
||||
ret = genphy_read_status(phydev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rtl8214fc_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (rtl8214fc_media_is_fibre(phydev))
|
||||
ret = genphy_c37_config_aneg(phydev);
|
||||
else
|
||||
ret = genphy_config_aneg(phydev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rtl821x_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
|
||||
{
|
||||
struct mii_bus *bus = phydev->mdio.bus;
|
||||
int addr = phydev->mdio.addr;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* The RTL821x PHYs are usually only C22 capable and are defined accordingly in DTS.
|
||||
* Nevertheless GPL source drops clearly indicate that EEE features can be accessed
|
||||
* directly via C45. Testing shows that C45 over C22 (as used in kernel EEE framework)
|
||||
* works as well but only as long as PHY polling is disabled in the SOC. To avoid ugly
|
||||
* hacks pass through C45 accesses for important EEE registers. Maybe some day the mdio
|
||||
* bus can intercept these patterns and switch off/on polling on demand. That way this
|
||||
* phy device driver can avoid handling special cases on its own.
|
||||
*/
|
||||
|
||||
if ((devnum == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) ||
|
||||
(devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) ||
|
||||
(devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE))
|
||||
ret = __mdiobus_c45_read(bus, addr, devnum, regnum);
|
||||
else
|
||||
ret = -EOPNOTSUPP;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rtl821x_write_mmd(struct phy_device *phydev, int devnum, u16 regnum, u16 val)
|
||||
{
|
||||
struct mii_bus *bus = phydev->mdio.bus;
|
||||
int addr = phydev->mdio.addr;
|
||||
int ret;
|
||||
|
||||
/* see rtl821x_read_mmd() */
|
||||
if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV)
|
||||
ret = __mdiobus_c45_write(bus, addr, devnum, regnum, val);
|
||||
else
|
||||
ret = -EOPNOTSUPP;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rtl8214fc_read_mmd(struct phy_device *phydev, int devnum, u16 regnum)
|
||||
{
|
||||
if (__rtl8214fc_media_is_fibre(phydev))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return rtl821x_read_mmd(phydev, devnum, regnum);
|
||||
}
|
||||
|
||||
static int rtl8214fc_write_mmd(struct phy_device *phydev, int devnum, u16 regnum, u16 val)
|
||||
{
|
||||
if (__rtl8214fc_media_is_fibre(phydev))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return rtl821x_write_mmd(phydev, devnum, regnum, val);
|
||||
}
|
||||
|
||||
static int rtl8214fc_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
|
||||
{
|
||||
__ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
|
||||
DECLARE_PHY_INTERFACE_MASK(interfaces);
|
||||
struct phy_device *phydev = upstream;
|
||||
phy_interface_t iface;
|
||||
|
||||
sfp_parse_support(phydev->sfp_bus, id, support, interfaces);
|
||||
iface = sfp_select_interface(phydev->sfp_bus, support);
|
||||
|
||||
phydev_info(phydev, "%s SFP module inserted\n", phy_modes(iface));
|
||||
|
||||
rtl8214fc_media_set(phydev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtl8214fc_sfp_remove(void *upstream)
|
||||
{
|
||||
struct phy_device *phydev = upstream;
|
||||
|
||||
rtl8214fc_media_set(phydev, false);
|
||||
}
|
||||
|
||||
static const struct sfp_upstream_ops rtl8214fc_sfp_ops = {
|
||||
.attach = phy_sfp_attach,
|
||||
.detach = phy_sfp_detach,
|
||||
.module_insert = rtl8214fc_sfp_insert,
|
||||
.module_remove = rtl8214fc_sfp_remove,
|
||||
};
|
||||
|
||||
static int rtl8214c_phy_probe(struct phy_device *phydev)
|
||||
{
|
||||
rtl821x_package_join(phydev, 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl8218x_phy_probe(struct phy_device *phydev)
|
||||
{
|
||||
rtl821x_package_join(phydev, 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl821x_config_init(struct phy_device *phydev)
|
||||
{
|
||||
/* Disable PHY-mode EEE so LPI is passed to the MAC */
|
||||
phy_modify_paged(phydev, RTL821X_PAGE_MAC, RTL821X_PHYCR2,
|
||||
RTL821X_PHYCR2_PHY_EEE_ENABLE, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl8218d_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int oldpage, oldxpage;
|
||||
bool is_qsgmii;
|
||||
int chip_info;
|
||||
|
||||
rtl821x_config_init(phydev);
|
||||
|
||||
if (phydev->mdio.addr % 8)
|
||||
return 0;
|
||||
/*
|
||||
* The RTl8218D has two MAC (aka SoC side) operation modes. Either dual QSGMII
|
||||
* or single XSGMII (Realtek proprietary) link. The mode is usually configured via
|
||||
* strapping pins CHIP_MODE1/2. For the moment offer a configuration that at least
|
||||
* works for RTL93xx devices. This sequence even "revives" a PHY that has been hard
|
||||
* reset with
|
||||
*
|
||||
* phy_write(phydev, 0x1e, 0x8);
|
||||
* phy_write_paged(phydev, 0x262, 0x10, 0x1);
|
||||
*/
|
||||
oldpage = phy_read(phydev, RTL821x_PAGE_SELECT);
|
||||
oldxpage = phy_read(phydev, RTL821x_EXT_PAGE_SELECT);
|
||||
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, 0x8);
|
||||
chip_info = phy_read_paged(phydev, 0x327, 0x15);
|
||||
is_qsgmii = (phy_read_paged(phydev, 0x260, 0x12) & 0xf0) == 0xd0;
|
||||
|
||||
pr_info("RTL8218D (chip_id=%d, rev_id=%d) on port %d running in %s mode\n",
|
||||
(chip_info >> 7) & 0x7, chip_info & 0x7f, phydev->mdio.addr,
|
||||
is_qsgmii ? "QSGMII" : "XSGMII");
|
||||
|
||||
if (is_qsgmii) {
|
||||
for (int sds = 0; sds < 2; sds++) {
|
||||
/* unknown amplification value */
|
||||
phy_modify_paged(phydev, 0x4a8 + sds * 0x100, 0x12, BIT(3), 0);
|
||||
/* main aplification */
|
||||
phy_modify_paged(phydev, 0x4ab + sds * 0x100, 0x16, 0x3e0, 0x1e0);
|
||||
/* unknown LCCMU value */
|
||||
phy_write_paged(phydev, 0x4ac + sds * 0x100, 0x15, 0x4380);
|
||||
}
|
||||
} else {
|
||||
/* serdes 0/1 disable auto negotiation */
|
||||
phy_modify_paged(phydev, 0x400, 0x12, 0, BIT(8));
|
||||
phy_modify_paged(phydev, 0x500, 0x12, 0, BIT(8));
|
||||
/* unknown eye configuration */
|
||||
phy_modify_paged(phydev, 0x4b8, 0x12, BIT(3), 0);
|
||||
}
|
||||
|
||||
/* reset serdes 0 */
|
||||
phy_write_paged(phydev, 0x400, 0x10, 0x1700);
|
||||
phy_write_paged(phydev, 0x400, 0x10, 0x1703);
|
||||
|
||||
/* reset serdes 1 */
|
||||
phy_write_paged(phydev, 0x500, 0x10, 0x1400);
|
||||
phy_write_paged(phydev, 0x500, 0x10, 0x1403);
|
||||
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, oldxpage);
|
||||
phy_write(phydev, RTL821x_PAGE_SELECT, oldpage);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtl8218b_cmu_reset(struct phy_device *phydev, int reset_id)
|
||||
{
|
||||
int bitpos = reset_id * 2;
|
||||
|
||||
/* CMU seems to have 8 pairs of reset bits that always work the same way */
|
||||
phy_modify_paged(phydev, 0x467, 0x14, 0, BIT(bitpos));
|
||||
phy_modify_paged(phydev, 0x467, 0x14, 0, BIT(bitpos + 1));
|
||||
phy_write_paged(phydev, 0x467, 0x14, 0x0);
|
||||
}
|
||||
|
||||
static int rtl8218b_config_init(struct phy_device *phydev)
|
||||
{
|
||||
int oldpage, oldxpage;
|
||||
|
||||
rtl821x_config_init(phydev);
|
||||
|
||||
if (phydev->mdio.addr % 8)
|
||||
return 0;
|
||||
/*
|
||||
* Realtek provides two ways of initializing the PHY package. Either by U-Boot or via
|
||||
* vendor software and SDK. In case U-Boot setup is missing, run basic configuration
|
||||
* so that ports at least get link up and pass traffic.
|
||||
*/
|
||||
|
||||
oldpage = phy_read(phydev, RTL821x_PAGE_SELECT);
|
||||
oldxpage = phy_read(phydev, RTL821x_EXT_PAGE_SELECT);
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, 0x8);
|
||||
|
||||
/* activate 32/40 bit redundancy algorithm for first MAC serdes */
|
||||
phy_modify_paged(phydev, RTL821X_MAC_SDS_PAGE(0, 1), 0x14, 0, BIT(3));
|
||||
/* magic CMU setting for stable connectivity of first MAC serdes */
|
||||
phy_write_paged(phydev, 0x462, 0x15, 0x6e58);
|
||||
/* magic setting for rate select 10G full */
|
||||
phy_write_paged(phydev, 0x464, 0x15, 0x202a);
|
||||
/* magic setting for variable gain amplifier */
|
||||
phy_modify_paged(phydev, 0x464, 0x12, 0, 0x1f80);
|
||||
/* magic setting for equalizer of second MAC serdes */
|
||||
phy_write_paged(phydev, RTL821X_MAC_SDS_PAGE(1, 8), 0x12, 0x2020);
|
||||
/* unknown magic for second MAC serdes */
|
||||
phy_write_paged(phydev, RTL821X_MAC_SDS_PAGE(1, 9), 0x11, 0xc014);
|
||||
rtl8218b_cmu_reset(phydev, 0);
|
||||
|
||||
for (int sds = 0; sds < 2; sds++) {
|
||||
/* disable ring PLL for serdes 2+3 */
|
||||
phy_modify_paged(phydev, RTL821X_MAC_SDS_PAGE(sds + 1, 8), 0x11, BIT(15), 0);
|
||||
/* force negative clock edge */
|
||||
phy_modify_paged(phydev, RTL821X_MAC_SDS_PAGE(sds, 0), 0x17, 0, BIT(14));
|
||||
rtl8218b_cmu_reset(phydev, 5 + sds);
|
||||
/* soft reset */
|
||||
phy_modify_paged(phydev, RTL821X_MAC_SDS_PAGE(sds, 0), 0x13, 0, BIT(6));
|
||||
phy_modify_paged(phydev, RTL821X_MAC_SDS_PAGE(sds, 0), 0x13, BIT(6), 0);
|
||||
}
|
||||
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, oldxpage);
|
||||
phy_write(phydev, RTL821x_PAGE_SELECT, oldpage);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl8214fc_phy_probe(struct phy_device *phydev)
|
||||
{
|
||||
static int regs[] = {16, 19, 20, 21};
|
||||
int ret;
|
||||
|
||||
rtl821x_package_join(phydev, 4);
|
||||
|
||||
/*
|
||||
* Normally phy_probe() only initializes PHY structures and setup is run in
|
||||
* config_init(). The RTL8214FC needs configuration before SFP probing while
|
||||
* the preferred media is still copper. This way all SFP events (even before
|
||||
* the first config_init()) will find a consistent port state.
|
||||
*/
|
||||
|
||||
/* Step 1 - package setup: Due to similar design reuse RTL8218B coding */
|
||||
ret = rtl8218b_config_init(phydev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (phydev->mdio.addr % 8 == 0) {
|
||||
/* Force all ports to copper */
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, RTL821X_MEDIA_PAGE_INTERNAL);
|
||||
for (int port = 0; port < 4; port++)
|
||||
phy_modify_paged(phydev, 0x266, regs[port], 0, GENMASK(11, 10));
|
||||
}
|
||||
|
||||
/* Step 2 - port setup */
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, RTL821X_MEDIA_PAGE_FIBRE);
|
||||
/* set fiber SerDes RX to negative edge */
|
||||
phy_modify_paged(phydev, 0x8, 0x17, 0, BIT(14));
|
||||
/* auto negotiation disable link on */
|
||||
phy_modify_paged(phydev, 0x8, 0x14, 0, BIT(2));
|
||||
/* disable fiber 100MBit */
|
||||
phy_modify_paged(phydev, 0x8, 0x11, BIT(5), 0);
|
||||
phy_write(phydev, RTL821x_EXT_PAGE_SELECT, RTL821X_MEDIA_PAGE_AUTO);
|
||||
|
||||
/* Disable EEE. 0xa5d/0x10 is the same as MDIO_MMD_AN / MDIO_AN_EEE_ADV */
|
||||
phy_write_paged(phydev, 0xa5d, 0x10, 0x0000);
|
||||
|
||||
return phy_sfp_probe(phydev, &rtl8214fc_sfp_ops);
|
||||
}
|
||||
|
||||
static struct phy_driver rtl83xx_phy_driver[] = {
|
||||
{
|
||||
PHY_ID_MATCH_EXACT(PHY_ID_RTL8214C),
|
||||
.name = "Realtek RTL8214C",
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.probe = rtl8214c_phy_probe,
|
||||
.read_page = rtl821x_read_page,
|
||||
.write_page = rtl821x_write_page,
|
||||
.suspend = genphy_suspend,
|
||||
.resume = genphy_resume,
|
||||
},
|
||||
{
|
||||
.match_phy_device = rtl8214fc_match_phy_device,
|
||||
.name = "Realtek RTL8214FC",
|
||||
.config_aneg = rtl8214fc_config_aneg,
|
||||
.get_features = rtl8214fc_get_features,
|
||||
.get_tunable = rtl8214fc_get_tunable,
|
||||
.probe = rtl8214fc_phy_probe,
|
||||
.read_mmd = rtl8214fc_read_mmd,
|
||||
.read_page = rtl821x_read_page,
|
||||
.read_status = rtl8214fc_read_status,
|
||||
.resume = rtl8214fc_resume,
|
||||
.set_tunable = rtl8214fc_set_tunable,
|
||||
.suspend = rtl8214fc_suspend,
|
||||
.write_mmd = rtl8214fc_write_mmd,
|
||||
.write_page = rtl821x_write_page,
|
||||
},
|
||||
{
|
||||
.match_phy_device = rtl8218b_ext_match_phy_device,
|
||||
.name = "Realtek RTL8218B (external)",
|
||||
.config_init = rtl8218b_config_init,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.probe = rtl8218x_phy_probe,
|
||||
.read_mmd = rtl821x_read_mmd,
|
||||
.read_page = rtl821x_read_page,
|
||||
.resume = genphy_resume,
|
||||
.suspend = genphy_suspend,
|
||||
.write_mmd = rtl821x_write_mmd,
|
||||
.write_page = rtl821x_write_page,
|
||||
},
|
||||
{
|
||||
PHY_ID_MATCH_MODEL(PHY_ID_RTL8218B_I),
|
||||
.name = "Realtek RTL8218B (internal)",
|
||||
.config_init = rtl821x_config_init,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.probe = rtl8218x_phy_probe,
|
||||
.read_mmd = rtl821x_read_mmd,
|
||||
.read_page = rtl821x_read_page,
|
||||
.resume = genphy_resume,
|
||||
.suspend = genphy_suspend,
|
||||
.write_mmd = rtl821x_write_mmd,
|
||||
.write_page = rtl821x_write_page,
|
||||
},
|
||||
{
|
||||
PHY_ID_MATCH_EXACT(PHY_ID_RTL8218D),
|
||||
.name = "REALTEK RTL8218D",
|
||||
.config_init = rtl8218d_config_init,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.probe = rtl8218x_phy_probe,
|
||||
.read_mmd = rtl821x_read_mmd,
|
||||
.read_page = rtl821x_read_page,
|
||||
.resume = genphy_resume,
|
||||
.suspend = genphy_suspend,
|
||||
.write_mmd = rtl821x_write_mmd,
|
||||
.write_page = rtl821x_write_page,
|
||||
},
|
||||
{
|
||||
PHY_ID_MATCH_EXACT(PHY_ID_RTL8218E),
|
||||
.name = "REALTEK RTL8218E",
|
||||
.config_init = rtl821x_config_init,
|
||||
.features = PHY_GBIT_FEATURES,
|
||||
.probe = rtl8218x_phy_probe,
|
||||
.read_mmd = rtl821x_read_mmd,
|
||||
.read_page = rtl821x_read_page,
|
||||
.resume = genphy_resume,
|
||||
.suspend = genphy_suspend,
|
||||
.write_mmd = rtl821x_write_mmd,
|
||||
.write_page = rtl821x_write_page,
|
||||
},
|
||||
};
|
||||
|
||||
module_phy_driver(rtl83xx_phy_driver);
|
||||
|
||||
static const struct mdio_device_id __maybe_unused rtl83xx_tbl[] = {
|
||||
{ PHY_ID_MATCH_MODEL(PHY_ID_RTL8214_OR_8218) },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, rtl83xx_tbl);
|
||||
|
||||
MODULE_AUTHOR("B. Koblitz");
|
||||
MODULE_DESCRIPTION("RTL83xx PHY driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@ -1,235 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Realtek thermal sensor driver
|
||||
*
|
||||
* Copyright (C) 2025 Bjørn Mork <bjorn@mork.no>>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include "thermal_hwmon.h"
|
||||
|
||||
#define RTL8380_THERMAL_METER_CTRL0 0x98
|
||||
#define RTL8380_THERMAL_METER_CTRL1 0x9c
|
||||
#define RTL8380_THERMAL_METER_CTRL2 0xa0
|
||||
#define RTL8380_THERMAL_METER_RESULT 0xa4
|
||||
#define RTL8380_TM_ENABLE BIT(0)
|
||||
#define RTL8380_TEMP_VALID BIT(8)
|
||||
#define RTL8380_TEMP_OUT_MASK GENMASK(6, 0)
|
||||
|
||||
#define RTL8390_THERMAL_METER0_CTRL0 0x274
|
||||
#define RTL8390_THERMAL_METER0_CTRL1 0x278
|
||||
#define RTL8390_THERMAL_METER0_CTRL2 0x27c
|
||||
#define RTL8390_THERMAL_METER0_RESULT 0x280
|
||||
#define RTL8390_THERMAL_METER1_CTRL0 0x284
|
||||
#define RTL8390_THERMAL_METER1_CTRL1 0x288
|
||||
#define RTL8390_THERMAL_METER1_CTRL2 0x28c
|
||||
#define RTL8390_THERMAL_METER1_RESULT 0x290
|
||||
#define RTL8390_TM_ENABLE BIT(0)
|
||||
#define RTL8390_TEMP_VALID BIT(8)
|
||||
#define RTL8390_TEMP_OUT_MASK GENMASK(6, 0)
|
||||
|
||||
#define RTL9300_THERMAL_METER_CTRL0 0x60
|
||||
#define RTL9300_THERMAL_METER_CTRL1 0x64
|
||||
#define RTL9300_THERMAL_METER_CTRL2 0x68
|
||||
#define RTL9300_THERMAL_METER_RESULT0 0x6c
|
||||
#define RTL9300_THERMAL_METER_RESULT1 0x70
|
||||
#define RTL9300_TM_ENABLE BIT(16)
|
||||
#define RTL9300_TEMP_VALID BIT(24)
|
||||
#define RTL9300_TEMP_OUT_MASK GENMASK(23, 16)
|
||||
#define RTL9300_SAMPLE_DLY_SHIFT (16)
|
||||
#define RTL9300_SAMPLE_DLY_MASK GENMASK(RTL9300_SAMPLE_DLY_SHIFT + 15, RTL9300_SAMPLE_DLY_SHIFT)
|
||||
#define RTL9300_COMPARE_DLY_SHIFT (0)
|
||||
#define RTL9300_COMPARE_DLY_MASK GENMASK(RTL9300_COMPARE_DLY_SHIFT + 15, RTL9300_COMPARE_DLY_SHIFT)
|
||||
|
||||
#define RTL9607_THERMAL_CTRL_0 0x150
|
||||
#define RTL9607_REG_PPOW BIT(29)
|
||||
#define RTL9607_THERMAL_CTRL_2 0x158
|
||||
#define RTL9607_THERMAL_CTRL_5 0x164
|
||||
#define RTL9607_THERMAL_STS_0 0x178
|
||||
#define RTL9607_TEMP_OUT_MASK GENMASK(18, 0)
|
||||
#define RTL9607_TEMP_OUT_SIGN BIT(18)
|
||||
#define RTL9607_TEMP_OUT_INT GENMASK(17, 10)
|
||||
#define RTL9607_TEMP_OUT_FLOAT GENMASK(9, 0)
|
||||
|
||||
struct realtek_thermal_priv {
|
||||
struct regmap *regmap;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
static void rtl8380_thermal_init(struct realtek_thermal_priv *priv)
|
||||
{
|
||||
priv->enabled = !regmap_update_bits(priv->regmap, RTL8380_THERMAL_METER_CTRL0, RTL8380_TM_ENABLE, RTL8380_TM_ENABLE);
|
||||
}
|
||||
|
||||
static int rtl8380_get_temp(struct thermal_zone_device *tz, int *res)
|
||||
{
|
||||
struct realtek_thermal_priv *priv = thermal_zone_device_priv(tz);
|
||||
int offset = thermal_zone_get_offset(tz);
|
||||
int slope = thermal_zone_get_slope(tz);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!priv->enabled)
|
||||
rtl8380_thermal_init(priv);
|
||||
|
||||
ret = regmap_read(priv->regmap, RTL8380_THERMAL_METER_RESULT, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!(val & RTL8380_TEMP_VALID))
|
||||
return -EAGAIN;
|
||||
|
||||
*res = FIELD_GET(RTL8380_TEMP_OUT_MASK, val) * slope + offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_device_ops rtl8380_ops = {
|
||||
.get_temp = rtl8380_get_temp,
|
||||
};
|
||||
|
||||
static void rtl8390_thermal_init(struct realtek_thermal_priv *priv)
|
||||
{
|
||||
priv->enabled = !regmap_update_bits(priv->regmap, RTL8390_THERMAL_METER0_CTRL0, RTL8390_TM_ENABLE, RTL8390_TM_ENABLE);
|
||||
}
|
||||
|
||||
static int rtl8390_get_temp(struct thermal_zone_device *tz, int *res)
|
||||
{
|
||||
struct realtek_thermal_priv *priv = thermal_zone_device_priv(tz);
|
||||
int offset = thermal_zone_get_offset(tz);
|
||||
int slope = thermal_zone_get_slope(tz);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!priv->enabled)
|
||||
rtl8390_thermal_init(priv);
|
||||
/* assume sensor0 is the CPU, both sensor0 & sensor1 report same values +/- 1 degree C */
|
||||
ret = regmap_read(priv->regmap, RTL8390_THERMAL_METER0_RESULT, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!(val & RTL8390_TEMP_VALID))
|
||||
return -EAGAIN;
|
||||
|
||||
*res = FIELD_GET(RTL8390_TEMP_OUT_MASK, val) * slope + offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_device_ops rtl8390_ops = {
|
||||
.get_temp = rtl8390_get_temp,
|
||||
};
|
||||
|
||||
static void rtl9300_thermal_init(struct realtek_thermal_priv *priv)
|
||||
{
|
||||
/* increasing sample delay makes get_temp() succeed more often */
|
||||
regmap_update_bits(priv->regmap, RTL9300_THERMAL_METER_CTRL1, RTL9300_SAMPLE_DLY_MASK, 0x0800 << RTL9300_SAMPLE_DLY_SHIFT);
|
||||
priv->enabled = !regmap_update_bits(priv->regmap, RTL9300_THERMAL_METER_CTRL2, RTL9300_TM_ENABLE, RTL9300_TM_ENABLE);
|
||||
}
|
||||
|
||||
static int rtl9300_get_temp(struct thermal_zone_device *tz, int *res)
|
||||
{
|
||||
struct realtek_thermal_priv *priv = thermal_zone_device_priv(tz);
|
||||
int offset = thermal_zone_get_offset(tz);
|
||||
int slope = thermal_zone_get_slope(tz);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!priv->enabled)
|
||||
rtl9300_thermal_init(priv);
|
||||
|
||||
ret = regmap_read(priv->regmap, RTL9300_THERMAL_METER_RESULT0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!(val & RTL9300_TEMP_VALID))
|
||||
return -EAGAIN;
|
||||
|
||||
*res = FIELD_GET(RTL9300_TEMP_OUT_MASK, val) * slope + offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_device_ops rtl9300_ops = {
|
||||
.get_temp = rtl9300_get_temp,
|
||||
};
|
||||
|
||||
static void rtl9607_thermal_init(struct realtek_thermal_priv *priv)
|
||||
{
|
||||
priv->enabled = !regmap_update_bits(priv->regmap, RTL9607_THERMAL_CTRL_0, RTL9607_REG_PPOW, RTL9607_REG_PPOW);
|
||||
}
|
||||
|
||||
static int rtl9607_get_temp(struct thermal_zone_device *tz, int *res)
|
||||
{
|
||||
struct realtek_thermal_priv *priv = thermal_zone_device_priv(tz);
|
||||
int offset = thermal_zone_get_offset(tz);
|
||||
int slope = thermal_zone_get_slope(tz);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!priv->enabled)
|
||||
rtl9607_thermal_init(priv);
|
||||
|
||||
ret = regmap_read(priv->regmap, RTL9607_THERMAL_STS_0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The temperature sensor output consist of sign at bit 18, integer part at bits 17~10
|
||||
* and fractional point with 0.5 scaling at bits 9~0.
|
||||
* For simplicity, we only care about the integer part.
|
||||
*/
|
||||
*res = FIELD_GET(RTL9607_TEMP_OUT_INT, val) * slope + offset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_device_ops rtl9607_ops = {
|
||||
.get_temp = rtl9607_get_temp,
|
||||
};
|
||||
|
||||
static int realtek_thermal_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct realtek_thermal_priv *priv;
|
||||
struct thermal_zone_device *tzdev;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct regmap *regmap;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
regmap = syscon_node_to_regmap(dev->of_node->parent);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
|
||||
priv->regmap = regmap;
|
||||
tzdev = devm_thermal_of_zone_register(dev, 0, priv, device_get_match_data(dev));
|
||||
if (IS_ERR(tzdev))
|
||||
return PTR_ERR(tzdev);
|
||||
|
||||
return devm_thermal_add_hwmon_sysfs(dev, tzdev);
|
||||
}
|
||||
|
||||
static const struct of_device_id realtek_sensor_ids[] = {
|
||||
{ .compatible = "realtek,rtl8380-thermal", .data = &rtl8380_ops, },
|
||||
{ .compatible = "realtek,rtl8390-thermal", .data = &rtl8390_ops, },
|
||||
{ .compatible = "realtek,rtl9300-thermal", .data = &rtl9300_ops, },
|
||||
{ .compatible = "realtek,rtl9607-thermal", .data = &rtl9607_ops, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, realtek_sensor_ids);
|
||||
|
||||
static struct platform_driver realtek_thermal_driver = {
|
||||
.probe = realtek_thermal_probe,
|
||||
.driver = {
|
||||
.name = "realtek-thermal",
|
||||
.of_match_table = realtek_sensor_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(realtek_thermal_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>");
|
||||
MODULE_DESCRIPTION("Realtek temperature sensor");
|
||||
@ -1,15 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2022 Markus Stockhausen
|
||||
*
|
||||
* RTL83XX clock indices
|
||||
*/
|
||||
#ifndef __DT_BINDINGS_CLOCK_RTL83XX_H
|
||||
#define __DT_BINDINGS_CLOCK_RTL83XX_H
|
||||
|
||||
#define CLK_CPU 0
|
||||
#define CLK_MEM 1
|
||||
#define CLK_LXB 2
|
||||
#define CLK_COUNT 3
|
||||
|
||||
#endif /* __DT_BINDINGS_CLOCK_RTL83XX_H */
|
||||
@ -1,76 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* net/dsa/tag_trailer.c - Trailer tag format handling
|
||||
* Copyright (c) 2008-2009 Marvell Semiconductor
|
||||
*/
|
||||
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "tag.h"
|
||||
|
||||
#define RTL_OTTO_NAME "rtl_otto"
|
||||
|
||||
/*
|
||||
* TODO: This driver was copied over from trailer tagging. It will be developed
|
||||
* downstream in OpenWrt in conjunction with the Realtek Otto ethernet driver.
|
||||
* For now rely on the old trailer handling and keep everything as is.
|
||||
*/
|
||||
|
||||
static struct sk_buff *rtl_otto_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct dsa_port *dp = dsa_user_to_port(dev);
|
||||
u8 *trailer;
|
||||
|
||||
trailer = skb_put(skb, 4);
|
||||
trailer[0] = 0x80;
|
||||
trailer[1] = dp->index;
|
||||
trailer[2] = 0x10;
|
||||
trailer[3] = 0x00;
|
||||
|
||||
return skb;
|
||||
}
|
||||
|
||||
static struct sk_buff *rtl_otto_rcv(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
u8 *trailer;
|
||||
int source_port;
|
||||
|
||||
if (skb_linearize(skb))
|
||||
return NULL;
|
||||
|
||||
trailer = skb_tail_pointer(skb) - 4;
|
||||
|
||||
if (trailer[0] != 0x80 || (trailer[1] & 0x80) != 0x00 ||
|
||||
(trailer[2] & 0xef) != 0x00 || trailer[3] != 0x00)
|
||||
return NULL;
|
||||
|
||||
if (trailer[1] & 0x40)
|
||||
skb->offload_fwd_mark = 1;
|
||||
|
||||
source_port = trailer[1] & 0x3f;
|
||||
|
||||
skb->dev = dsa_conduit_find_user(dev, 0, source_port);
|
||||
if (!skb->dev)
|
||||
return NULL;
|
||||
|
||||
if (pskb_trim_rcsum(skb, skb->len - 4))
|
||||
return NULL;
|
||||
|
||||
return skb;
|
||||
}
|
||||
|
||||
static const struct dsa_device_ops rtl_otto_netdev_ops = {
|
||||
.name = RTL_OTTO_NAME,
|
||||
.proto = DSA_TAG_PROTO_RTL_OTTO,
|
||||
.xmit = rtl_otto_xmit,
|
||||
.rcv = rtl_otto_rcv,
|
||||
.needed_tailroom = 4,
|
||||
};
|
||||
|
||||
MODULE_DESCRIPTION("DSA tag driver for Realtek Otto switches (RTL83xx/RTL93xx)");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_RTL_OTTO, RTL_OTTO_NAME);
|
||||
|
||||
module_dsa_tag_driver(rtl_otto_netdev_ops);
|
||||
@ -1,481 +0,0 @@
|
||||
From 42d20a6a61b8fccbb57d80df1ccde7dd82d5bbd6 Mon Sep 17 00:00:00 2001
|
||||
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Date: Wed, 16 Oct 2024 11:54:34 +1300
|
||||
Subject: [PATCH] spi: spi-mem: Add Realtek SPI-NAND controller
|
||||
|
||||
Add a driver for the SPI-NAND controller on the RTL9300 family of
|
||||
devices.
|
||||
|
||||
The controller supports
|
||||
* Serial/Dual/Quad data with
|
||||
* PIO and DMA data read/write operation
|
||||
* Configurable flash access timing
|
||||
|
||||
There is a separate ECC controller on the RTL9300 which isn't currently
|
||||
supported (instead we rely on the on-die ECC supported by most SPI-NAND
|
||||
chips).
|
||||
|
||||
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Link: https://patch.msgid.link/20241015225434.3970360-4-chris.packham@alliedtelesis.co.nz
|
||||
Signed-off-by: Mark Brown <broonie@kernel.org>
|
||||
---
|
||||
MAINTAINERS | 6 +
|
||||
drivers/spi/Kconfig | 11 +
|
||||
drivers/spi/Makefile | 1 +
|
||||
drivers/spi/spi-realtek-rtl-snand.c | 405 ++++++++++++++++++++++++++++
|
||||
4 files changed, 423 insertions(+)
|
||||
create mode 100644 drivers/spi/spi-realtek-rtl-snand.c
|
||||
|
||||
--- a/MAINTAINERS
|
||||
+++ b/MAINTAINERS
|
||||
@@ -19507,6 +19507,12 @@ S: Maintained
|
||||
F: Documentation/devicetree/bindings/net/dsa/realtek.yaml
|
||||
F: drivers/net/dsa/realtek/*
|
||||
|
||||
+REALTEK SPI-NAND
|
||||
+M: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
+S: Maintained
|
||||
+F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml
|
||||
+F: drivers/spi/spi-realtek-rtl-snand.c
|
||||
+
|
||||
REALTEK WIRELESS DRIVER (rtlwifi family)
|
||||
M: Ping-Ke Shih <pkshih@realtek.com>
|
||||
L: linux-wireless@vger.kernel.org
|
||||
--- a/drivers/spi/Kconfig
|
||||
+++ b/drivers/spi/Kconfig
|
||||
@@ -843,6 +843,17 @@ config SPI_PXA2XX
|
||||
config SPI_PXA2XX_PCI
|
||||
def_tristate SPI_PXA2XX && PCI && COMMON_CLK
|
||||
|
||||
+config SPI_REALTEK_SNAND
|
||||
+ tristate "Realtek SPI-NAND Flash Controller"
|
||||
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
||||
+ select REGMAP
|
||||
+ help
|
||||
+ This enables support for the SPI-NAND Flash controller on
|
||||
+ Realtek SoCs.
|
||||
+
|
||||
+ This driver does not support generic SPI. The implementation
|
||||
+ only supports the spi-mem interface.
|
||||
+
|
||||
config SPI_ROCKCHIP
|
||||
tristate "Rockchip SPI controller driver"
|
||||
depends on ARCH_ROCKCHIP || COMPILE_TEST
|
||||
--- a/drivers/spi/Makefile
|
||||
+++ b/drivers/spi/Makefile
|
||||
@@ -120,6 +120,7 @@ obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockc
|
||||
obj-$(CONFIG_SPI_ROCKCHIP_SFC) += spi-rockchip-sfc.o
|
||||
obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o
|
||||
obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o
|
||||
+obj-$(CONFIG_SPI_REALTEK_SNAND) += spi-realtek-rtl-snand.o
|
||||
obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o
|
||||
obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
|
||||
obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/spi/spi-realtek-rtl-snand.c
|
||||
@@ -0,0 +1,405 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+
|
||||
+#include <linux/completion.h>
|
||||
+#include <linux/dma-mapping.h>
|
||||
+#include <linux/interrupt.h>
|
||||
+#include <linux/mod_devicetable.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/spi/spi.h>
|
||||
+#include <linux/spi/spi-mem.h>
|
||||
+
|
||||
+#define SNAFCFR 0x00
|
||||
+#define SNAFCFR_DMA_IE BIT(20)
|
||||
+#define SNAFCCR 0x04
|
||||
+#define SNAFWCMR 0x08
|
||||
+#define SNAFRCMR 0x0c
|
||||
+#define SNAFRDR 0x10
|
||||
+#define SNAFWDR 0x14
|
||||
+#define SNAFDTR 0x18
|
||||
+#define SNAFDRSAR 0x1c
|
||||
+#define SNAFDIR 0x20
|
||||
+#define SNAFDIR_DMA_IP BIT(0)
|
||||
+#define SNAFDLR 0x24
|
||||
+#define SNAFSR 0x40
|
||||
+#define SNAFSR_NFCOS BIT(3)
|
||||
+#define SNAFSR_NFDRS BIT(2)
|
||||
+#define SNAFSR_NFDWS BIT(1)
|
||||
+
|
||||
+#define CMR_LEN(len) ((len) - 1)
|
||||
+#define CMR_WID(width) (((width) >> 1) << 28)
|
||||
+
|
||||
+struct rtl_snand {
|
||||
+ struct device *dev;
|
||||
+ struct regmap *regmap;
|
||||
+ struct completion comp;
|
||||
+};
|
||||
+
|
||||
+static irqreturn_t rtl_snand_irq(int irq, void *data)
|
||||
+{
|
||||
+ struct rtl_snand *snand = data;
|
||||
+ u32 val = 0;
|
||||
+
|
||||
+ regmap_read(snand->regmap, SNAFSR, &val);
|
||||
+ if (val & (SNAFSR_NFCOS | SNAFSR_NFDRS | SNAFSR_NFDWS))
|
||||
+ return IRQ_NONE;
|
||||
+
|
||||
+ regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP);
|
||||
+ complete(&snand->comp);
|
||||
+
|
||||
+ return IRQ_HANDLED;
|
||||
+}
|
||||
+
|
||||
+static bool rtl_snand_supports_op(struct spi_mem *mem,
|
||||
+ const struct spi_mem_op *op)
|
||||
+{
|
||||
+ if (!spi_mem_default_supports_op(mem, op))
|
||||
+ return false;
|
||||
+ if (op->cmd.nbytes != 1 || op->cmd.buswidth != 1)
|
||||
+ return false;
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+static void rtl_snand_set_cs(struct rtl_snand *snand, int cs, bool active)
|
||||
+{
|
||||
+ u32 val;
|
||||
+
|
||||
+ if (active)
|
||||
+ val = ~(1 << (4 * cs));
|
||||
+ else
|
||||
+ val = ~0;
|
||||
+
|
||||
+ regmap_write(snand->regmap, SNAFCCR, val);
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_wait_ready(struct rtl_snand *snand)
|
||||
+{
|
||||
+ u32 val;
|
||||
+
|
||||
+ return regmap_read_poll_timeout(snand->regmap, SNAFSR, val, !(val & SNAFSR_NFCOS),
|
||||
+ 0, 2 * USEC_PER_MSEC);
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_xfer_head(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 val, len = 0;
|
||||
+
|
||||
+ rtl_snand_set_cs(snand, cs, true);
|
||||
+
|
||||
+ val = op->cmd.opcode << 24;
|
||||
+ len = 1;
|
||||
+ if (op->addr.nbytes && op->addr.buswidth == 1) {
|
||||
+ val |= op->addr.val << ((3 - op->addr.nbytes) * 8);
|
||||
+ len += op->addr.nbytes;
|
||||
+ }
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(len));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (op->addr.buswidth > 1) {
|
||||
+ val = op->addr.val << ((3 - op->addr.nbytes) * 8);
|
||||
+ len = op->addr.nbytes;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWCMR,
|
||||
+ CMR_WID(op->addr.buswidth) | CMR_LEN(len));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (op->dummy.nbytes) {
|
||||
+ val = 0;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWCMR,
|
||||
+ CMR_WID(op->dummy.buswidth) | CMR_LEN(op->dummy.nbytes));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void rtl_snand_xfer_tail(struct rtl_snand *snand, int cs)
|
||||
+{
|
||||
+ rtl_snand_set_cs(snand, cs, false);
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
|
||||
+{
|
||||
+ unsigned int pos, nbytes;
|
||||
+ int ret;
|
||||
+ u32 val, len = 0;
|
||||
+
|
||||
+ ret = rtl_snand_xfer_head(snand, cs, op);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
+ pos = 0;
|
||||
+ len = op->data.nbytes;
|
||||
+
|
||||
+ while (pos < len) {
|
||||
+ nbytes = len - pos;
|
||||
+ if (nbytes > 4)
|
||||
+ nbytes = 4;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFRCMR,
|
||||
+ CMR_WID(op->data.buswidth) | CMR_LEN(nbytes));
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = regmap_read(snand->regmap, SNAFRDR, &val);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ memcpy(op->data.buf.in + pos, &val, nbytes);
|
||||
+
|
||||
+ pos += nbytes;
|
||||
+ }
|
||||
+ } else if (op->data.dir == SPI_MEM_DATA_OUT) {
|
||||
+ pos = 0;
|
||||
+ len = op->data.nbytes;
|
||||
+
|
||||
+ while (pos < len) {
|
||||
+ nbytes = len - pos;
|
||||
+ if (nbytes > 4)
|
||||
+ nbytes = 4;
|
||||
+
|
||||
+ memcpy(&val, op->data.buf.out + pos, nbytes);
|
||||
+
|
||||
+ pos += nbytes;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(nbytes));
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = rtl_snand_wait_ready(snand);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+out_deselect:
|
||||
+ rtl_snand_xfer_tail(snand, cs);
|
||||
+
|
||||
+ if (ret)
|
||||
+ dev_err(snand->dev, "transfer failed %d\n", ret);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_dma_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
|
||||
+{
|
||||
+ int ret;
|
||||
+ dma_addr_t buf_dma;
|
||||
+ enum dma_data_direction dir;
|
||||
+ u32 trig;
|
||||
+
|
||||
+ ret = rtl_snand_xfer_head(snand, cs, op);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
+ dir = DMA_FROM_DEVICE;
|
||||
+ trig = 0;
|
||||
+ } else if (op->data.dir == SPI_MEM_DATA_OUT) {
|
||||
+ dir = DMA_TO_DEVICE;
|
||||
+ trig = 1;
|
||||
+ } else {
|
||||
+ ret = -EOPNOTSUPP;
|
||||
+ goto out_deselect;
|
||||
+ }
|
||||
+
|
||||
+ buf_dma = dma_map_single(snand->dev, op->data.buf.in, op->data.nbytes, dir);
|
||||
+ ret = dma_mapping_error(snand->dev, buf_dma);
|
||||
+ if (ret)
|
||||
+ goto out_deselect;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP);
|
||||
+ if (ret)
|
||||
+ goto out_unmap;
|
||||
+
|
||||
+ ret = regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, SNAFCFR_DMA_IE);
|
||||
+ if (ret)
|
||||
+ goto out_unmap;
|
||||
+
|
||||
+ reinit_completion(&snand->comp);
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDLR,
|
||||
+ CMR_WID(op->data.buswidth) | (op->data.nbytes & 0xffff));
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDTR, trig);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
|
||||
+ ret = -ETIMEDOUT;
|
||||
+
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+out_disable_int:
|
||||
+ regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, 0);
|
||||
+out_unmap:
|
||||
+ dma_unmap_single(snand->dev, buf_dma, op->data.nbytes, dir);
|
||||
+out_deselect:
|
||||
+ rtl_snand_xfer_tail(snand, cs);
|
||||
+
|
||||
+ if (ret)
|
||||
+ dev_err(snand->dev, "transfer failed %d\n", ret);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static bool rtl_snand_dma_op(const struct spi_mem_op *op)
|
||||
+{
|
||||
+ switch (op->data.dir) {
|
||||
+ case SPI_MEM_DATA_IN:
|
||||
+ case SPI_MEM_DATA_OUT:
|
||||
+ return op->data.nbytes > 32;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int rtl_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
+{
|
||||
+ struct rtl_snand *snand = spi_controller_get_devdata(mem->spi->controller);
|
||||
+ int cs = spi_get_chipselect(mem->spi, 0);
|
||||
+
|
||||
+ dev_dbg(snand->dev, "cs %d op cmd %02x %d:%d, dummy %d:%d, addr %08llx@%d:%d, data %d:%d\n",
|
||||
+ cs, op->cmd.opcode,
|
||||
+ op->cmd.buswidth, op->cmd.nbytes, op->dummy.buswidth,
|
||||
+ op->dummy.nbytes, op->addr.val, op->addr.buswidth,
|
||||
+ op->addr.nbytes, op->data.buswidth, op->data.nbytes);
|
||||
+
|
||||
+ if (rtl_snand_dma_op(op))
|
||||
+ return rtl_snand_dma_xfer(snand, cs, op);
|
||||
+ else
|
||||
+ return rtl_snand_xfer(snand, cs, op);
|
||||
+}
|
||||
+
|
||||
+static const struct spi_controller_mem_ops rtl_snand_mem_ops = {
|
||||
+ .supports_op = rtl_snand_supports_op,
|
||||
+ .exec_op = rtl_snand_exec_op,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id rtl_snand_match[] = {
|
||||
+ { .compatible = "realtek,rtl9301-snand" },
|
||||
+ { .compatible = "realtek,rtl9302b-snand" },
|
||||
+ { .compatible = "realtek,rtl9302c-snand" },
|
||||
+ { .compatible = "realtek,rtl9303-snand" },
|
||||
+ {},
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, rtl_snand_match);
|
||||
+
|
||||
+static int rtl_snand_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct rtl_snand *snand;
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct spi_controller *ctrl;
|
||||
+ void __iomem *base;
|
||||
+ const struct regmap_config rc = {
|
||||
+ .reg_bits = 32,
|
||||
+ .val_bits = 32,
|
||||
+ .reg_stride = 4,
|
||||
+ .cache_type = REGCACHE_NONE,
|
||||
+ };
|
||||
+ int irq, ret;
|
||||
+
|
||||
+ ctrl = devm_spi_alloc_host(dev, sizeof(*snand));
|
||||
+ if (!ctrl)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ snand = spi_controller_get_devdata(ctrl);
|
||||
+ snand->dev = dev;
|
||||
+
|
||||
+ base = devm_platform_ioremap_resource(pdev, 0);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ snand->regmap = devm_regmap_init_mmio(dev, base, &rc);
|
||||
+ if (IS_ERR(snand->regmap))
|
||||
+ return PTR_ERR(snand->regmap);
|
||||
+
|
||||
+ init_completion(&snand->comp);
|
||||
+
|
||||
+ irq = platform_get_irq(pdev, 0);
|
||||
+ if (irq < 0)
|
||||
+ return irq;
|
||||
+
|
||||
+ ret = dma_set_mask(snand->dev, DMA_BIT_MASK(32));
|
||||
+ if (ret)
|
||||
+ return dev_err_probe(dev, ret, "failed to set DMA mask\n");
|
||||
+
|
||||
+ ret = devm_request_irq(dev, irq, rtl_snand_irq, 0, "rtl-snand", snand);
|
||||
+ if (ret)
|
||||
+ return dev_err_probe(dev, ret, "failed to request irq\n");
|
||||
+
|
||||
+ ctrl->num_chipselect = 2;
|
||||
+ ctrl->mem_ops = &rtl_snand_mem_ops;
|
||||
+ ctrl->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||
+ ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
|
||||
+ device_set_node(&ctrl->dev, dev_fwnode(dev));
|
||||
+
|
||||
+ return devm_spi_register_controller(dev, ctrl);
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver rtl_snand_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "realtek-rtl-snand",
|
||||
+ .of_match_table = rtl_snand_match,
|
||||
+ },
|
||||
+ .probe = rtl_snand_probe,
|
||||
+};
|
||||
+module_platform_driver(rtl_snand_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Realtek SPI-NAND Flash Controller Driver");
|
||||
+MODULE_LICENSE("GPL");
|
||||
@ -1,96 +0,0 @@
|
||||
From 25d284715845a465a1a3693a09cf8b6ab8bd9caf Mon Sep 17 00:00:00 2001
|
||||
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Date: Thu, 31 Oct 2024 08:49:20 +1300
|
||||
Subject: [PATCH] spi: spi-mem: rtl-snand: Correctly handle DMA transfers
|
||||
|
||||
The RTL9300 has some limitations on the maximum DMA transfers possible.
|
||||
For reads this is 2080 bytes (520*4) for writes this is 520 bytes. Deal
|
||||
with this by splitting transfers into appropriately sized parts.
|
||||
|
||||
Fixes: 42d20a6a61b8 ("spi: spi-mem: Add Realtek SPI-NAND controller")
|
||||
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Link: https://patch.msgid.link/20241030194920.3202282-1-chris.packham@alliedtelesis.co.nz
|
||||
Signed-off-by: Mark Brown <broonie@kernel.org>
|
||||
---
|
||||
drivers/spi/spi-realtek-rtl-snand.c | 46 +++++++++++++++++++----------
|
||||
1 file changed, 30 insertions(+), 16 deletions(-)
|
||||
|
||||
--- a/drivers/spi/spi-realtek-rtl-snand.c
|
||||
+++ b/drivers/spi/spi-realtek-rtl-snand.c
|
||||
@@ -231,19 +231,22 @@ out_deselect:
|
||||
|
||||
static int rtl_snand_dma_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
|
||||
{
|
||||
+ unsigned int pos, nbytes;
|
||||
int ret;
|
||||
dma_addr_t buf_dma;
|
||||
enum dma_data_direction dir;
|
||||
- u32 trig;
|
||||
+ u32 trig, len, maxlen;
|
||||
|
||||
ret = rtl_snand_xfer_head(snand, cs, op);
|
||||
if (ret)
|
||||
goto out_deselect;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
+ maxlen = 2080;
|
||||
dir = DMA_FROM_DEVICE;
|
||||
trig = 0;
|
||||
} else if (op->data.dir == SPI_MEM_DATA_OUT) {
|
||||
+ maxlen = 520;
|
||||
dir = DMA_TO_DEVICE;
|
||||
trig = 1;
|
||||
} else {
|
||||
@@ -264,26 +267,37 @@ static int rtl_snand_dma_xfer(struct rtl
|
||||
if (ret)
|
||||
goto out_unmap;
|
||||
|
||||
- reinit_completion(&snand->comp);
|
||||
+ pos = 0;
|
||||
+ len = op->data.nbytes;
|
||||
|
||||
- ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma);
|
||||
- if (ret)
|
||||
- goto out_disable_int;
|
||||
+ while (pos < len) {
|
||||
+ nbytes = len - pos;
|
||||
+ if (nbytes > maxlen)
|
||||
+ nbytes = maxlen;
|
||||
|
||||
- ret = regmap_write(snand->regmap, SNAFDLR,
|
||||
- CMR_WID(op->data.buswidth) | (op->data.nbytes & 0xffff));
|
||||
- if (ret)
|
||||
- goto out_disable_int;
|
||||
+ reinit_completion(&snand->comp);
|
||||
|
||||
- ret = regmap_write(snand->regmap, SNAFDTR, trig);
|
||||
- if (ret)
|
||||
- goto out_disable_int;
|
||||
+ ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma + pos);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
|
||||
- if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
|
||||
- ret = -ETIMEDOUT;
|
||||
+ pos += nbytes;
|
||||
|
||||
- if (ret)
|
||||
- goto out_disable_int;
|
||||
+ ret = regmap_write(snand->regmap, SNAFDLR,
|
||||
+ CMR_WID(op->data.buswidth) | nbytes);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ ret = regmap_write(snand->regmap, SNAFDTR, trig);
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+
|
||||
+ if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
|
||||
+ ret = -ETIMEDOUT;
|
||||
+
|
||||
+ if (ret)
|
||||
+ goto out_disable_int;
|
||||
+ }
|
||||
|
||||
out_disable_int:
|
||||
regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, 0);
|
||||
@ -1,84 +0,0 @@
|
||||
From c5eda0333076e031197816454998a918f1de0831 Mon Sep 17 00:00:00 2001
|
||||
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Date: Fri, 1 Nov 2024 09:03:46 +1300
|
||||
Subject: [PATCH] dt-bindings: i2c: Add Realtek RTL I2C Controller
|
||||
|
||||
Add dt-schema for the I2C controller on the RTL9300 Ethernet switch
|
||||
with integrated SoC.
|
||||
|
||||
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -0,0 +1,69 @@
|
||||
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
+%YAML 1.2
|
||||
+---
|
||||
+$id: http://devicetree.org/schemas/i2c/realtek,rtl9301-i2c.yaml#
|
||||
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
+
|
||||
+title: Realtek RTL I2C Controller
|
||||
+
|
||||
+maintainers:
|
||||
+ - Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
+
|
||||
+description:
|
||||
+ The RTL9300 SoC has two I2C controllers. Each of these has an SCL line (which
|
||||
+ if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
|
||||
+ assigned to either I2C controller.
|
||||
+
|
||||
+properties:
|
||||
+ compatible:
|
||||
+ oneOf:
|
||||
+ - items:
|
||||
+ - enum:
|
||||
+ - realtek,rtl9302b-i2c
|
||||
+ - realtek,rtl9302c-i2c
|
||||
+ - realtek,rtl9303-i2c
|
||||
+ - const: realtek,rtl9301-i2c
|
||||
+ - const: realtek,rtl9301-i2c
|
||||
+
|
||||
+ reg:
|
||||
+ description: Register offset and size this I2C controller.
|
||||
+
|
||||
+ "#address-cells":
|
||||
+ const: 1
|
||||
+
|
||||
+ "#size-cells":
|
||||
+ const: 0
|
||||
+
|
||||
+patternProperties:
|
||||
+ '^i2c@[0-7]$':
|
||||
+ $ref: /schemas/i2c/i2c-controller.yaml
|
||||
+ unevaluatedProperties: false
|
||||
+
|
||||
+ properties:
|
||||
+ reg:
|
||||
+ description: The SDA pin associated with the I2C bus.
|
||||
+ maxItems: 1
|
||||
+
|
||||
+ required:
|
||||
+ - reg
|
||||
+
|
||||
+required:
|
||||
+ - compatible
|
||||
+ - reg
|
||||
+
|
||||
+additionalProperties: false
|
||||
+
|
||||
+examples:
|
||||
+ - |
|
||||
+ i2c@36c {
|
||||
+ compatible = "realtek,rtl9301-i2c";
|
||||
+ reg = <0x36c 0x14>;
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ i2c@2 {
|
||||
+ reg = <2>;
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+ };
|
||||
+ };
|
||||
@ -1,493 +0,0 @@
|
||||
From c366be720235301fdadf67e6f1ea6ff32669c074 Mon Sep 17 00:00:00 2001
|
||||
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Date: Wed, 6 Nov 2024 13:18:35 +1300
|
||||
Subject: [PATCH] i2c: Add driver for the RTL9300 I2C controller
|
||||
|
||||
Add support for the I2C controller on the RTL9300 SoC. There are two I2C
|
||||
controllers in the RTL9300 that are part of the Ethernet switch register
|
||||
block. Each of these controllers owns a SCL pin (GPIO8 for the fiorst
|
||||
I2C controller, GPIO17 for the second). There are 8 possible SDA pins
|
||||
(GPIO9-16) that can be assigned to either I2C controller. This
|
||||
relationship is represented in the device tree with a child node for
|
||||
each SDA line in use.
|
||||
|
||||
This is based on the openwrt implementation[1] but has been
|
||||
significantly modified
|
||||
|
||||
[1] - https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=target/linux/realtek/files-5.15/drivers/i2c/busses/i2c-rtl9300.c
|
||||
|
||||
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
|
||||
--- a/MAINTAINERS
|
||||
+++ b/MAINTAINERS
|
||||
@@ -20164,6 +20164,13 @@ S: Maintained
|
||||
T: git https://github.com/pkshih/rtw.git
|
||||
F: drivers/net/wireless/realtek/rtl8xxxu/
|
||||
|
||||
+RTL9300 I2C DRIVER (rtl9300-i2c)
|
||||
+M: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
+L: linux-i2c@vger.kernel.org
|
||||
+S: Maintained
|
||||
+F: Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+F: drivers/i2c/busses/i2c-rtl9300.c
|
||||
+
|
||||
RTRS TRANSPORT DRIVERS
|
||||
M: Md. Haris Iqbal <haris.iqbal@ionos.com>
|
||||
M: Jack Wang <jinpu.wang@ionos.com>
|
||||
--- a/drivers/i2c/busses/Kconfig
|
||||
+++ b/drivers/i2c/busses/Kconfig
|
||||
@@ -1062,6 +1062,16 @@ config I2C_RK3X
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called i2c-rk3x.
|
||||
|
||||
+config I2C_RTL9300
|
||||
+ tristate "Realtek RTL9300 I2C controller"
|
||||
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
||||
+ help
|
||||
+ Say Y here to include support for the I2C controller in Realtek
|
||||
+ RTL9300 SoCs.
|
||||
+
|
||||
+ This driver can also be built as a module. If so, the module will
|
||||
+ be called i2c-rtl9300.
|
||||
+
|
||||
config I2C_RZV2M
|
||||
tristate "Renesas RZ/V2M adapter"
|
||||
depends on ARCH_RENESAS || COMPILE_TEST
|
||||
--- a/drivers/i2c/busses/Makefile
|
||||
+++ b/drivers/i2c/busses/Makefile
|
||||
@@ -103,6 +103,7 @@ obj-$(CONFIG_I2C_QCOM_GENI) += i2c-qcom-
|
||||
obj-$(CONFIG_I2C_QUP) += i2c-qup.o
|
||||
obj-$(CONFIG_I2C_RIIC) += i2c-riic.o
|
||||
obj-$(CONFIG_I2C_RK3X) += i2c-rk3x.o
|
||||
+obj-$(CONFIG_I2C_RTL9300) += i2c-rtl9300.o
|
||||
obj-$(CONFIG_I2C_RZV2M) += i2c-rzv2m.o
|
||||
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
|
||||
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -0,0 +1,423 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+
|
||||
+#include <linux/bits.h>
|
||||
+#include <linux/i2c.h>
|
||||
+#include <linux/i2c-mux.h>
|
||||
+#include <linux/mod_devicetable.h>
|
||||
+#include <linux/mfd/syscon.h>
|
||||
+#include <linux/mutex.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+enum rtl9300_bus_freq {
|
||||
+ RTL9300_I2C_STD_FREQ,
|
||||
+ RTL9300_I2C_FAST_FREQ,
|
||||
+};
|
||||
+
|
||||
+struct rtl9300_i2c;
|
||||
+
|
||||
+struct rtl9300_i2c_chan {
|
||||
+ struct i2c_adapter adap;
|
||||
+ struct rtl9300_i2c *i2c;
|
||||
+ enum rtl9300_bus_freq bus_freq;
|
||||
+ u8 sda_pin;
|
||||
+};
|
||||
+
|
||||
+#define RTL9300_I2C_MUX_NCHAN 8
|
||||
+
|
||||
+struct rtl9300_i2c {
|
||||
+ struct regmap *regmap;
|
||||
+ struct device *dev;
|
||||
+ struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
|
||||
+ u32 reg_base;
|
||||
+ u8 sda_pin;
|
||||
+ struct mutex lock;
|
||||
+};
|
||||
+
|
||||
+#define RTL9300_I2C_MST_CTRL1 0x0
|
||||
+#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS 8
|
||||
+#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK GENMASK(31, 8)
|
||||
+#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS 4
|
||||
+#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK GENMASK(6, 4)
|
||||
+#define RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL BIT(3)
|
||||
+#define RTL9300_I2C_MST_CTRL1_RWOP BIT(2)
|
||||
+#define RTL9300_I2C_MST_CTRL1_I2C_FAIL BIT(1)
|
||||
+#define RTL9300_I2C_MST_CTRL1_I2C_TRIG BIT(0)
|
||||
+#define RTL9300_I2C_MST_CTRL2 0x4
|
||||
+#define RTL9300_I2C_MST_CTRL2_RD_MODE BIT(15)
|
||||
+#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS 8
|
||||
+#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK GENMASK(14, 8)
|
||||
+#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS 4
|
||||
+#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK GENMASK(7, 4)
|
||||
+#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS 2
|
||||
+#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK GENMASK(3, 2)
|
||||
+#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS 0
|
||||
+#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK GENMASK(1, 0)
|
||||
+#define RTL9300_I2C_MST_DATA_WORD0 0x8
|
||||
+#define RTL9300_I2C_MST_DATA_WORD1 0xc
|
||||
+#define RTL9300_I2C_MST_DATA_WORD2 0x10
|
||||
+#define RTL9300_I2C_MST_DATA_WORD3 0x14
|
||||
+
|
||||
+#define RTL9300_I2C_MST_GLB_CTRL 0x384
|
||||
+
|
||||
+static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
|
||||
+{
|
||||
+ u32 val, mask;
|
||||
+ int ret;
|
||||
+
|
||||
+ val = len << RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS;
|
||||
+ mask = RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK;
|
||||
+
|
||||
+ ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ val = reg << RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS;
|
||||
+ mask = RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK;
|
||||
+
|
||||
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 val, mask;
|
||||
+
|
||||
+ ret = regmap_update_bits(i2c->regmap, RTL9300_I2C_MST_GLB_CTRL, BIT(sda_pin), BIT(sda_pin));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ val = (sda_pin << RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS) |
|
||||
+ RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
|
||||
+ mask = RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK | RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
|
||||
+
|
||||
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
|
||||
+ u16 addr, u16 len)
|
||||
+{
|
||||
+ u32 val, mask;
|
||||
+
|
||||
+ val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
|
||||
+ mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
|
||||
+
|
||||
+ val |= addr << RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS;
|
||||
+ mask |= RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK;
|
||||
+
|
||||
+ val |= ((len - 1) & 0xf) << RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS;
|
||||
+ mask |= RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK;
|
||||
+
|
||||
+ mask |= RTL9300_I2C_MST_CTRL2_RD_MODE;
|
||||
+
|
||||
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
+{
|
||||
+ u32 vals[4] = {};
|
||||
+ int i, ret;
|
||||
+
|
||||
+ if (len > 16)
|
||||
+ return -EIO;
|
||||
+
|
||||
+ ret = regmap_bulk_read(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
+ vals, ARRAY_SIZE(vals));
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ for (i = 0; i < len; i++) {
|
||||
+ buf[i] = vals[i/4] & 0xff;
|
||||
+ vals[i/4] >>= 8;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
+{
|
||||
+ u32 vals[4] = {};
|
||||
+ int i;
|
||||
+
|
||||
+ if (len > 16)
|
||||
+ return -EIO;
|
||||
+
|
||||
+ for (i = 0; i < len; i++) {
|
||||
+ if (i % 4 == 0)
|
||||
+ vals[i/4] = 0;
|
||||
+ vals[i/4] <<= 8;
|
||||
+ vals[i/4] |= buf[i];
|
||||
+ }
|
||||
+
|
||||
+ return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
+ vals, ARRAY_SIZE(vals));
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
|
||||
+{
|
||||
+ return regmap_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, data);
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
|
||||
+ int size, union i2c_smbus_data *data, int len)
|
||||
+{
|
||||
+ u32 val, mask;
|
||||
+ int ret;
|
||||
+
|
||||
+ val = read_write == I2C_SMBUS_WRITE ? RTL9300_I2C_MST_CTRL1_RWOP : 0;
|
||||
+ mask = RTL9300_I2C_MST_CTRL1_RWOP;
|
||||
+
|
||||
+ val |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
|
||||
+ mask |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
|
||||
+
|
||||
+ ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
|
||||
+ val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 2000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (val & RTL9300_I2C_MST_CTRL1_I2C_FAIL)
|
||||
+ return -EIO;
|
||||
+
|
||||
+ if (read_write == I2C_SMBUS_READ) {
|
||||
+ if (size == I2C_SMBUS_BYTE || size == I2C_SMBUS_BYTE_DATA) {
|
||||
+ ret = regmap_read(i2c->regmap,
|
||||
+ i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ data->byte = val & 0xff;
|
||||
+ } else if (size == I2C_SMBUS_WORD_DATA) {
|
||||
+ ret = regmap_read(i2c->regmap,
|
||||
+ i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ data->word = val & 0xffff;
|
||||
+ } else {
|
||||
+ ret = rtl9300_i2c_read(i2c, &data->block[0], len);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags,
|
||||
+ char read_write, u8 command, int size,
|
||||
+ union i2c_smbus_data *data)
|
||||
+{
|
||||
+ struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap);
|
||||
+ struct rtl9300_i2c *i2c = chan->i2c;
|
||||
+ int len = 0, ret;
|
||||
+
|
||||
+ mutex_lock(&i2c->lock);
|
||||
+ if (chan->sda_pin != i2c->sda_pin) {
|
||||
+ ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ i2c->sda_pin = chan->sda_pin;
|
||||
+ }
|
||||
+
|
||||
+ switch (size) {
|
||||
+ case I2C_SMBUS_QUICK:
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_SMBUS_BYTE:
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ } else {
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_SMBUS_BYTE_DATA:
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_writel(i2c, data->byte);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_SMBUS_WORD_DATA:
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 2);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_writel(i2c, data->word);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_SMBUS_BLOCK_DATA:
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ len = data->block[0];
|
||||
+ break;
|
||||
+
|
||||
+ default:
|
||||
+ dev_err(&adap->dev, "Unsupported transaction %d\n", size);
|
||||
+ ret = -EOPNOTSUPP;
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+
|
||||
+ ret = rtl9300_i2c_execute_xfer(i2c, read_write, size, data, len);
|
||||
+
|
||||
+out_unlock:
|
||||
+ mutex_unlock(&i2c->lock);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
+{
|
||||
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
|
||||
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
|
||||
+ I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||
+}
|
||||
+
|
||||
+static const struct i2c_algorithm rtl9300_i2c_algo = {
|
||||
+ .smbus_xfer = rtl9300_i2c_smbus_xfer,
|
||||
+ .functionality = rtl9300_i2c_func,
|
||||
+};
|
||||
+
|
||||
+static struct i2c_adapter_quirks rtl9300_i2c_quirks = {
|
||||
+ .flags = I2C_AQ_NO_CLK_STRETCH,
|
||||
+ .max_read_len = 16,
|
||||
+ .max_write_len = 16,
|
||||
+};
|
||||
+
|
||||
+static int rtl9300_i2c_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct rtl9300_i2c *i2c;
|
||||
+ u32 clock_freq, sda_pin;
|
||||
+ int ret, i = 0;
|
||||
+ struct fwnode_handle *child;
|
||||
+
|
||||
+ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
|
||||
+ if (!i2c)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ i2c->regmap = syscon_node_to_regmap(dev->parent->of_node);
|
||||
+ if (IS_ERR(i2c->regmap))
|
||||
+ return PTR_ERR(i2c->regmap);
|
||||
+ i2c->dev = dev;
|
||||
+
|
||||
+ mutex_init(&i2c->lock);
|
||||
+
|
||||
+ ret = device_property_read_u32(dev, "reg", &i2c->reg_base);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ platform_set_drvdata(pdev, i2c);
|
||||
+
|
||||
+ if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
|
||||
+ return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
+
|
||||
+ device_for_each_child_node(dev, child) {
|
||||
+ struct rtl9300_i2c_chan *chan = &i2c->chans[i];
|
||||
+ struct i2c_adapter *adap = &chan->adap;
|
||||
+
|
||||
+ ret = fwnode_property_read_u32(child, "reg", &sda_pin);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = fwnode_property_read_u32(child, "clock-frequency", &clock_freq);
|
||||
+ if (ret)
|
||||
+ clock_freq = I2C_MAX_STANDARD_MODE_FREQ;
|
||||
+
|
||||
+ switch (clock_freq) {
|
||||
+ case I2C_MAX_STANDARD_MODE_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_STD_FREQ;
|
||||
+ break;
|
||||
+
|
||||
+ case I2C_MAX_FAST_MODE_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_FAST_FREQ;
|
||||
+ break;
|
||||
+ default:
|
||||
+ dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
|
||||
+ sda_pin, clock_freq);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ chan->sda_pin = sda_pin;
|
||||
+ chan->i2c = i2c;
|
||||
+ adap = &i2c->chans[i].adap;
|
||||
+ adap->owner = THIS_MODULE;
|
||||
+ adap->algo = &rtl9300_i2c_algo;
|
||||
+ adap->quirks = &rtl9300_i2c_quirks;
|
||||
+ adap->retries = 3;
|
||||
+ adap->dev.parent = dev;
|
||||
+ i2c_set_adapdata(adap, chan);
|
||||
+ adap->dev.of_node = to_of_node(child);
|
||||
+ snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_pin);
|
||||
+ i++;
|
||||
+
|
||||
+ ret = devm_i2c_add_adapter(dev, adap);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+ i2c->sda_pin = 0xff;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
+ { .compatible = "realtek,rtl9301-i2c" },
|
||||
+ { .compatible = "realtek,rtl9302b-i2c" },
|
||||
+ { .compatible = "realtek,rtl9302c-i2c" },
|
||||
+ { .compatible = "realtek,rtl9303-i2c" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
|
||||
+
|
||||
+static struct platform_driver rtl9300_i2c_driver = {
|
||||
+ .probe = rtl9300_i2c_probe,
|
||||
+ .driver = {
|
||||
+ .name = "i2c-rtl9300",
|
||||
+ .of_match_table = i2c_rtl9300_dt_ids,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+module_platform_driver(rtl9300_i2c_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("RTL9300 I2C controller driver");
|
||||
+MODULE_LICENSE("GPL");
|
||||
@ -1,27 +0,0 @@
|
||||
From 5f05fc6e2218db7ecc52c60eb34b707fe69262c2 Mon Sep 17 00:00:00 2001
|
||||
From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
|
||||
Date: Wed, 2 Jul 2025 08:15:31 +0200
|
||||
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301: Fix missing 'reg'
|
||||
constraint
|
||||
|
||||
Lists should have fixed amount if items, so add missing constraint to
|
||||
the 'reg' property (only one address space entry).
|
||||
|
||||
Fixes: c5eda0333076 ("dt-bindings: i2c: Add Realtek RTL I2C Controller")
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250702061530.6940-2-krzysztof.kozlowski@linaro.org
|
||||
|
||||
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -26,7 +26,8 @@ properties:
|
||||
- const: realtek,rtl9301-i2c
|
||||
|
||||
reg:
|
||||
- description: Register offset and size this I2C controller.
|
||||
+ items:
|
||||
+ - description: Register offset and size this I2C controller.
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
@ -1,38 +0,0 @@
|
||||
From 57f312b955938fc4663f430cb57a71f2414f601b Mon Sep 17 00:00:00 2001
|
||||
From: Alex Guo <alexguo1023@gmail.com>
|
||||
Date: Sun, 10 Aug 2025 20:05:13 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Fix out-of-bounds bug in rtl9300_i2c_smbus_xfer
|
||||
|
||||
The data->block[0] variable comes from user. Without proper check,
|
||||
the variable may be very large to cause an out-of-bounds bug.
|
||||
|
||||
Fix this bug by checking the value of data->block[0] first.
|
||||
|
||||
1. commit 39244cc75482 ("i2c: ismt: Fix an out-of-bounds bug in
|
||||
ismt_access()")
|
||||
2. commit 92fbb6d1296f ("i2c: xgene-slimpro: Fix out-of-bounds bug in
|
||||
xgene_slimpro_i2c_xfer()")
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Signed-off-by: Alex Guo <alexguo1023@gmail.com>
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-1-cd9dca0db722@narfation.org
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -281,6 +281,10 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
+ if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
|
||||
+ ret = -EINVAL;
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
@ -1,75 +0,0 @@
|
||||
From d67b740b9edfa46310355e2b68050f79ebf05a4c Mon Sep 17 00:00:00 2001
|
||||
From: Harshal Gohel <hg@simonwunderlich.de>
|
||||
Date: Sun, 10 Aug 2025 20:05:14 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Fix multi-byte I2C write
|
||||
|
||||
The RTL93xx I2C controller has 4 32 bit registers to store the bytes for
|
||||
the upcoming I2C transmission. The first byte is stored in the
|
||||
least-significant byte of the first register. And the last byte in the most
|
||||
significant byte of the last register. A map of the transferred bytes to
|
||||
their order in the registers is:
|
||||
|
||||
reg 0: 0x04_03_02_01
|
||||
reg 1: 0x08_07_06_05
|
||||
reg 2: 0x0c_0b_0a_09
|
||||
reg 3: 0x10_0f_0e_0d
|
||||
|
||||
The i2c_read() function basically demonstrates how the hardware would pick
|
||||
up bytes from this register set. But the i2c_write() function was just
|
||||
pushing bytes one after another to the least significant byte of a register
|
||||
AFTER shifting the last one to the next more significant byte position.
|
||||
|
||||
If you would then have tried to send a buffer with numbers 1-11 using
|
||||
i2c_write(), you would have ended up with following register content:
|
||||
|
||||
reg 0: 0x01_02_03_04
|
||||
reg 1: 0x05_06_07_08
|
||||
reg 2: 0x00_09_0a_0b
|
||||
reg 3: 0x00_00_00_00
|
||||
|
||||
On the wire, you would then have seen:
|
||||
|
||||
Sr Addr Wr [A] 04 A 03 A 02 A 01 A 08 A 07 A 06 A 05 A 0b A 0a A 09 A P
|
||||
|
||||
But the correct data transmission was expected to be
|
||||
|
||||
Sr Addr Wr [A] 01 A 02 A 03 A 04 A 05 A 06 A 07 A 08 A 09 A 0a A 0b A P
|
||||
|
||||
Because of this multi-byte ordering problem, only single byte i2c_write()
|
||||
operations were executed correctly (on the wire).
|
||||
|
||||
By shifting the byte directly to the correct end position in the register,
|
||||
it is possible to avoid this incorrect byte ordering and fix multi-byte
|
||||
transmissions.
|
||||
|
||||
The second initialization (to 0) of vals was also be dropped because this
|
||||
array is initialized to 0 on the stack by using `= {};`. This makes the
|
||||
fix a lot more readable.
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Signed-off-by: Harshal Gohel <hg@simonwunderlich.de>
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Co-developed-by: Sven Eckelmann <sven@narfation.org>
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-2-cd9dca0db722@narfation.org
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -143,10 +143,10 @@ static int rtl9300_i2c_write(struct rtl9
|
||||
return -EIO;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
- if (i % 4 == 0)
|
||||
- vals[i/4] = 0;
|
||||
- vals[i/4] <<= 8;
|
||||
- vals[i/4] |= buf[i];
|
||||
+ unsigned int shift = (i % 4) * 8;
|
||||
+ unsigned int reg = i / 4;
|
||||
+
|
||||
+ vals[reg] |= buf[i] << shift;
|
||||
}
|
||||
|
||||
return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
@ -1,37 +0,0 @@
|
||||
From ceee7776c010c5f09d30985c9e5223b363a6172a Mon Sep 17 00:00:00 2001
|
||||
From: Sven Eckelmann <sven@narfation.org>
|
||||
Date: Sun, 10 Aug 2025 20:05:15 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Increase timeout for transfer polling
|
||||
|
||||
The timeout for transfers was only set to 2ms. Because of this relatively
|
||||
low limit, 12-byte read operations to the frontend MCU of a RTL8239 POE PSE
|
||||
chip cluster was consistently resulting in a timeout.
|
||||
|
||||
The original OpenWrt downstream driver [1] was not using any timeout limit
|
||||
at all. This is also possible by setting the timeout_us parameter of
|
||||
regmap_read_poll_timeout() to 0. But since the driver currently implements
|
||||
the ETIMEDOUT error, it is more sensible to increase the timeout in such a
|
||||
way that communication with the (quite common) Realtek I2C-connected POE
|
||||
management solution is possible.
|
||||
|
||||
[1] https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=target/linux/realtek/files-6.12/drivers/i2c/busses/i2c-rtl9300.c;h=c4d973195ef39dc56d6207e665d279745525fcac#l202
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-3-cd9dca0db722@narfation.org
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -175,7 +175,7 @@ static int rtl9300_i2c_execute_xfer(stru
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
|
||||
- val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 2000);
|
||||
+ val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 100000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
From 82b350dd8185ce790e61555c436f90b6501af23c Mon Sep 17 00:00:00 2001
|
||||
From: Sven Eckelmann <sven@narfation.org>
|
||||
Date: Sun, 10 Aug 2025 20:05:16 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Add missing count byte for SMBus Block Ops
|
||||
|
||||
The expected on-wire format of an SMBus Block Write is
|
||||
|
||||
S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P
|
||||
|
||||
Everything starting from the Count byte is provided by the I2C subsystem in
|
||||
the array data->block. But the driver was skipping the Count byte
|
||||
(data->block[0]) when sending it to the RTL93xx I2C controller.
|
||||
|
||||
Only the actual data could be seen on the wire:
|
||||
|
||||
S Addr Wr [A] Comm [A] Data [A] Data [A] ... [A] Data [A] P
|
||||
|
||||
This wire format is not SMBus Block Write compatible but matches the format
|
||||
of an I2C Block Write. Simply adding the count byte to the buffer for the
|
||||
I2C controller is enough to fix the transmission.
|
||||
|
||||
This also affects read because the I2C controller must receive the count
|
||||
byte + $count * data bytes.
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Cc: <stable@vger.kernel.org> # v6.13+
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-4-cd9dca0db722@narfation.org
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -285,15 +285,15 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0] + 1);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
|
||||
+ ret = rtl9300_i2c_write(i2c, &data->block[0], data->block[0] + 1);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
}
|
||||
- len = data->block[0];
|
||||
+ len = data->block[0] + 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1,40 +0,0 @@
|
||||
From cd6c956fbc13156bcbcca084b46a8380caebc2a8 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sun, 31 Aug 2025 10:04:46 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: fix channel number bound check
|
||||
|
||||
Fix the current check for number of channels (child nodes in the device
|
||||
tree). Before, this was:
|
||||
|
||||
if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
|
||||
|
||||
RTL9300_I2C_MUX_NCHAN gives the maximum number of channels so checking
|
||||
with '>=' isn't correct because it doesn't allow the last channel
|
||||
number. Thus, fix it to:
|
||||
|
||||
if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
|
||||
|
||||
Issue occured on a TP-Link TL-ST1008F v2.0 device (8 SFP+ ports) and fix
|
||||
is tested there.
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Cc: stable@vger.kernel.org # v6.13+
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250831100457.3114-2-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -353,7 +353,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
|
||||
platform_set_drvdata(pdev, i2c);
|
||||
|
||||
- if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
|
||||
+ if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
|
||||
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
|
||||
device_for_each_child_node(dev, child) {
|
||||
@ -1,61 +0,0 @@
|
||||
From 06418cb5a1a542a003fdb4ad8e76ea542d57cfba Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sun, 31 Aug 2025 10:04:47 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: ensure data length is within supported range
|
||||
|
||||
Add an explicit check for the xfer length to 'rtl9300_i2c_config_xfer'
|
||||
to ensure the data length isn't within the supported range. In
|
||||
particular a data length of 0 is not supported by the hardware and
|
||||
causes unintended or destructive behaviour.
|
||||
|
||||
This limitation becomes obvious when looking at the register
|
||||
documentation [1]. 4 bits are reserved for DATA_WIDTH and the value
|
||||
of these 4 bits is used as N + 1, allowing a data length range of
|
||||
1 <= len <= 16.
|
||||
|
||||
Affected by this is the SMBus Quick Operation which works with a data
|
||||
length of 0. Passing 0 as the length causes an underflow of the value
|
||||
due to:
|
||||
|
||||
(len - 1) & 0xf
|
||||
|
||||
and effectively specifying a transfer length of 16 via the registers.
|
||||
This causes a 16-byte write operation instead of a Quick Write. For
|
||||
example, on SFP modules without write-protected EEPROM this soft-bricks
|
||||
them by overwriting some initial bytes.
|
||||
|
||||
For completeness, also add a quirk for the zero length.
|
||||
|
||||
[1] https://svanheule.net/realtek/longan/register/i2c_mst1_ctrl2
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Cc: stable@vger.kernel.org # v6.13+
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250831100457.3114-3-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -99,6 +99,9 @@ static int rtl9300_i2c_config_xfer(struc
|
||||
{
|
||||
u32 val, mask;
|
||||
|
||||
+ if (len < 1 || len > 16)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
|
||||
mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
|
||||
|
||||
@@ -323,7 +326,7 @@ static const struct i2c_algorithm rtl930
|
||||
};
|
||||
|
||||
static struct i2c_adapter_quirks rtl9300_i2c_quirks = {
|
||||
- .flags = I2C_AQ_NO_CLK_STRETCH,
|
||||
+ .flags = I2C_AQ_NO_CLK_STRETCH | I2C_AQ_NO_ZERO_LEN,
|
||||
.max_read_len = 16,
|
||||
.max_write_len = 16,
|
||||
};
|
||||
@ -1,77 +0,0 @@
|
||||
From ede965fd555ac2536cf651893a998dbfd8e57b86 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sun, 31 Aug 2025 10:04:48 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: remove broken SMBus Quick operation support
|
||||
|
||||
Remove the SMBus Quick operation from this driver because it is not
|
||||
natively supported by the hardware and is wrongly implemented in the
|
||||
driver.
|
||||
|
||||
The I2C controllers in Realtek RTL9300 and RTL9310 are SMBus-compliant
|
||||
but there doesn't seem to be native support for the SMBus Quick
|
||||
operation. It is not explicitly mentioned in the documentation but
|
||||
looking at the registers which configure an SMBus transaction, one can
|
||||
see that the data length cannot be set to 0. This suggests that the
|
||||
hardware doesn't allow any SMBus message without data bytes (except for
|
||||
those it does on it's own, see SMBus Block Read).
|
||||
|
||||
The current implementation of SMBus Quick operation passes a length of
|
||||
0 (which is actually invalid). Before the fix of a bug in a previous
|
||||
commit, this led to a read operation of 16 bytes from any register (the
|
||||
one of a former transaction or any other value.
|
||||
|
||||
This caused issues like soft-bricked SFP modules after a simple probe
|
||||
with i2cdetect which uses Quick by default. Running this with SFP
|
||||
modules whose EEPROM isn't write-protected, some of the initial bytes
|
||||
are overwritten because a 16-byte write operation is executed instead of
|
||||
a Quick Write. (This temporarily soft-bricked one of my DAC cables.)
|
||||
|
||||
Because SMBus Quick operation is obviously not supported on these
|
||||
controllers (because a length of 0 cannot be set, even when no register
|
||||
address is set), remove that instead of claiming there is support. There
|
||||
also shouldn't be any kind of emulated 'Quick' which just does another
|
||||
kind of operation in the background. Otherwise, specific issues occur
|
||||
in case of a 'Quick' Write which actually writes unknown data to an
|
||||
unknown register.
|
||||
|
||||
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
|
||||
Cc: stable@vger.kernel.org # v6.13+
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250831100457.3114-4-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -225,15 +225,6 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
- case I2C_SMBUS_QUICK:
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- break;
|
||||
-
|
||||
case I2C_SMBUS_BYTE:
|
||||
if (read_write == I2C_SMBUS_WRITE) {
|
||||
ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
@@ -315,9 +306,9 @@ out_unlock:
|
||||
|
||||
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
{
|
||||
- return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
|
||||
- I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
|
||||
- I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||
+ return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
|
||||
+ I2C_FUNC_SMBUS_I2C_BLOCK;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm rtl9300_i2c_algo = {
|
||||
@ -1,26 +0,0 @@
|
||||
From 095530512152e6811278de9c30f170f0ac9705eb Mon Sep 17 00:00:00 2001
|
||||
From: Sven Eckelmann <sven@narfation.org>
|
||||
Date: Sat, 27 Sep 2025 11:52:16 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Drop unsupported I2C_FUNC_SMBUS_I2C_BLOCK
|
||||
|
||||
While applying the patch for commit ede965fd555a ("i2c: rtl9300: remove
|
||||
broken SMBus Quick operation support"), a conflict was incorrectly solved
|
||||
by adding the I2C_FUNC_SMBUS_I2C_BLOCK feature flag. But the code to handle
|
||||
I2C_SMBUS_I2C_BLOCK_DATA requests will be added by a separate commit.
|
||||
|
||||
Fixes: ede965fd555a ("i2c: rtl9300: remove broken SMBus Quick operation support")
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -307,8 +307,7 @@ out_unlock:
|
||||
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
{
|
||||
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
- I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
|
||||
- I2C_FUNC_SMBUS_I2C_BLOCK;
|
||||
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm rtl9300_i2c_algo = {
|
||||
@ -1,119 +0,0 @@
|
||||
From 415216ae3196e67bdb9515519f219d553bd38d3a Mon Sep 17 00:00:00 2001
|
||||
From: Harshal Gohel <hg@simonwunderlich.de>
|
||||
Date: Sat, 27 Sep 2025 11:52:17 +0200
|
||||
Subject: [PATCH] i2c: rtl9300: Implement I2C block read and write
|
||||
|
||||
It was noticed that the original implementation of SMBus Block Write in the
|
||||
driver was actually an I2C Block Write. Both differ only in the Count byte
|
||||
before the actual data:
|
||||
|
||||
S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P
|
||||
|
||||
The I2C Block Write is just skipping this Count byte and starts directly
|
||||
with the data:
|
||||
|
||||
S Addr Wr [A] Comm [A] Data [A] Data [A] ... [A] Data [A] P
|
||||
|
||||
The I2C controller of RTL93xx doesn't handle this Count byte special and it
|
||||
is simply another one of (16 possible) data bytes. Adding support for the
|
||||
I2C Block Write therefore only requires skipping the count byte (0) in
|
||||
data->block.
|
||||
|
||||
It is similar for reads. The SMBUS Block read is having a Count byte before
|
||||
the data:
|
||||
|
||||
S Addr Wr [A] Comm [A]
|
||||
Sr Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P
|
||||
|
||||
And the I2C Block Read is directly starting with the actual data:
|
||||
|
||||
S Addr Wr [A] Comm [A]
|
||||
Sr Addr Rd [A] [Data] A [Data] A ... A [Data] NA P
|
||||
|
||||
The I2C controller is also not handling this byte in a special way. It
|
||||
simply provides every byte after the Rd marker + Ack as part of the 16 byte
|
||||
receive buffer (registers). The content of this buffer just has to be
|
||||
copied to the right position in the receive data->block.
|
||||
|
||||
Signed-off-by: Harshal Gohel <hg@simonwunderlich.de>
|
||||
Co-developed-by: Sven Eckelmann <sven@narfation.org>
|
||||
Signed-off-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Reviewed-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -186,22 +186,32 @@ static int rtl9300_i2c_execute_xfer(stru
|
||||
return -EIO;
|
||||
|
||||
if (read_write == I2C_SMBUS_READ) {
|
||||
- if (size == I2C_SMBUS_BYTE || size == I2C_SMBUS_BYTE_DATA) {
|
||||
+ switch (size) {
|
||||
+ case I2C_SMBUS_BYTE:
|
||||
+ case I2C_SMBUS_BYTE_DATA:
|
||||
ret = regmap_read(i2c->regmap,
|
||||
i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->byte = val & 0xff;
|
||||
- } else if (size == I2C_SMBUS_WORD_DATA) {
|
||||
+ break;
|
||||
+ case I2C_SMBUS_WORD_DATA:
|
||||
ret = regmap_read(i2c->regmap,
|
||||
i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->word = val & 0xffff;
|
||||
- } else {
|
||||
+ break;
|
||||
+ case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
+ ret = rtl9300_i2c_read(i2c, &data->block[1], len);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ break;
|
||||
+ default:
|
||||
ret = rtl9300_i2c_read(i2c, &data->block[0], len);
|
||||
if (ret)
|
||||
return ret;
|
||||
+ break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +300,25 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
len = data->block[0] + 1;
|
||||
break;
|
||||
|
||||
+ case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
|
||||
+ ret = -EINVAL;
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ if (read_write == I2C_SMBUS_WRITE) {
|
||||
+ ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+ }
|
||||
+ len = data->block[0];
|
||||
+ break;
|
||||
+
|
||||
default:
|
||||
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
|
||||
ret = -EOPNOTSUPP;
|
||||
@@ -307,7 +336,8 @@ out_unlock:
|
||||
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
{
|
||||
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
- I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
|
||||
+ I2C_FUNC_SMBUS_I2C_BLOCK;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm rtl9300_i2c_algo = {
|
||||
@ -1,354 +0,0 @@
|
||||
From 5a6ecb27435ef7a67d7bec4543f0c6303f34e8a6 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:23 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: use regmap fields and API for registers
|
||||
|
||||
Adapt the RTL9300 I2C controller driver to use more of the regmap
|
||||
API, especially make use of reg_field and regmap_field instead of macros
|
||||
to represent registers. Most register operations are performed through
|
||||
regmap_field_* API then.
|
||||
|
||||
Handle SCL selection using separate chip-specific functions since this
|
||||
is already known to differ between the Realtek SoC families in such a
|
||||
way that this cannot be properly handled using just a different
|
||||
reg_field.
|
||||
|
||||
This makes it easier to add support for newer generations or to handle
|
||||
differences between specific revisions within a series. Just by
|
||||
defining a separate driver data structure with the corresponding
|
||||
register field definitions and linking it to a new compatible.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-2-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -23,97 +23,117 @@ struct rtl9300_i2c_chan {
|
||||
u8 sda_pin;
|
||||
};
|
||||
|
||||
+enum rtl9300_i2c_reg_scope {
|
||||
+ REG_SCOPE_GLOBAL,
|
||||
+ REG_SCOPE_MASTER,
|
||||
+};
|
||||
+
|
||||
+struct rtl9300_i2c_reg_field {
|
||||
+ struct reg_field field;
|
||||
+ enum rtl9300_i2c_reg_scope scope;
|
||||
+};
|
||||
+
|
||||
+enum rtl9300_i2c_reg_fields {
|
||||
+ F_DATA_WIDTH = 0,
|
||||
+ F_DEV_ADDR,
|
||||
+ F_I2C_FAIL,
|
||||
+ F_I2C_TRIG,
|
||||
+ F_MEM_ADDR,
|
||||
+ F_MEM_ADDR_WIDTH,
|
||||
+ F_RD_MODE,
|
||||
+ F_RWOP,
|
||||
+ F_SCL_FREQ,
|
||||
+ F_SCL_SEL,
|
||||
+ F_SDA_OUT_SEL,
|
||||
+ F_SDA_SEL,
|
||||
+
|
||||
+ /* keep last */
|
||||
+ F_NUM_FIELDS
|
||||
+};
|
||||
+
|
||||
+struct rtl9300_i2c_drv_data {
|
||||
+ struct rtl9300_i2c_reg_field field_desc[F_NUM_FIELDS];
|
||||
+ int (*select_scl)(struct rtl9300_i2c *i2c, u8 scl);
|
||||
+ u32 data_reg;
|
||||
+ u8 max_nchan;
|
||||
+};
|
||||
+
|
||||
#define RTL9300_I2C_MUX_NCHAN 8
|
||||
|
||||
struct rtl9300_i2c {
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
|
||||
+ struct regmap_field *fields[F_NUM_FIELDS];
|
||||
u32 reg_base;
|
||||
+ u32 data_reg;
|
||||
u8 sda_pin;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
#define RTL9300_I2C_MST_CTRL1 0x0
|
||||
-#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS 8
|
||||
-#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK GENMASK(31, 8)
|
||||
-#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS 4
|
||||
-#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK GENMASK(6, 4)
|
||||
-#define RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL BIT(3)
|
||||
-#define RTL9300_I2C_MST_CTRL1_RWOP BIT(2)
|
||||
-#define RTL9300_I2C_MST_CTRL1_I2C_FAIL BIT(1)
|
||||
-#define RTL9300_I2C_MST_CTRL1_I2C_TRIG BIT(0)
|
||||
#define RTL9300_I2C_MST_CTRL2 0x4
|
||||
-#define RTL9300_I2C_MST_CTRL2_RD_MODE BIT(15)
|
||||
-#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS 8
|
||||
-#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK GENMASK(14, 8)
|
||||
-#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS 4
|
||||
-#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK GENMASK(7, 4)
|
||||
-#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS 2
|
||||
-#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK GENMASK(3, 2)
|
||||
-#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS 0
|
||||
-#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK GENMASK(1, 0)
|
||||
#define RTL9300_I2C_MST_DATA_WORD0 0x8
|
||||
#define RTL9300_I2C_MST_DATA_WORD1 0xc
|
||||
#define RTL9300_I2C_MST_DATA_WORD2 0x10
|
||||
#define RTL9300_I2C_MST_DATA_WORD3 0x14
|
||||
-
|
||||
#define RTL9300_I2C_MST_GLB_CTRL 0x384
|
||||
|
||||
static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
|
||||
{
|
||||
- u32 val, mask;
|
||||
int ret;
|
||||
|
||||
- val = len << RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS;
|
||||
- mask = RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK;
|
||||
-
|
||||
- ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
|
||||
+ ret = regmap_field_write(i2c->fields[F_MEM_ADDR_WIDTH], len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- val = reg << RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS;
|
||||
- mask = RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK;
|
||||
+ return regmap_field_write(i2c->fields[F_MEM_ADDR], reg);
|
||||
+}
|
||||
|
||||
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+static int rtl9300_i2c_select_scl(struct rtl9300_i2c *i2c, u8 scl)
|
||||
+{
|
||||
+ return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
|
||||
{
|
||||
+ struct rtl9300_i2c_drv_data *drv_data;
|
||||
int ret;
|
||||
- u32 val, mask;
|
||||
|
||||
- ret = regmap_update_bits(i2c->regmap, RTL9300_I2C_MST_GLB_CTRL, BIT(sda_pin), BIT(sda_pin));
|
||||
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+
|
||||
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_pin), BIT(sda_pin));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- val = (sda_pin << RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS) |
|
||||
- RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
|
||||
- mask = RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK | RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
|
||||
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_pin);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+ return drv_data->select_scl(i2c, 0);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
|
||||
u16 addr, u16 len)
|
||||
{
|
||||
- u32 val, mask;
|
||||
+ int ret;
|
||||
|
||||
if (len < 1 || len > 16)
|
||||
return -EINVAL;
|
||||
|
||||
- val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
|
||||
- mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
|
||||
-
|
||||
- val |= addr << RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS;
|
||||
- mask |= RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK;
|
||||
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- val |= ((len - 1) & 0xf) << RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS;
|
||||
- mask |= RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK;
|
||||
+ ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- mask |= RTL9300_I2C_MST_CTRL2_RD_MODE;
|
||||
+ ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
|
||||
+ return regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
@@ -124,8 +144,7 @@ static int rtl9300_i2c_read(struct rtl93
|
||||
if (len > 16)
|
||||
return -EIO;
|
||||
|
||||
- ret = regmap_bulk_read(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
- vals, ARRAY_SIZE(vals));
|
||||
+ ret = regmap_bulk_read(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -152,52 +171,49 @@ static int rtl9300_i2c_write(struct rtl9
|
||||
vals[reg] |= buf[i] << shift;
|
||||
}
|
||||
|
||||
- return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
|
||||
- vals, ARRAY_SIZE(vals));
|
||||
+ return regmap_bulk_write(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals));
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
|
||||
{
|
||||
- return regmap_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, data);
|
||||
+ return regmap_write(i2c->regmap, i2c->data_reg, data);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
|
||||
int size, union i2c_smbus_data *data, int len)
|
||||
{
|
||||
- u32 val, mask;
|
||||
+ u32 val;
|
||||
int ret;
|
||||
|
||||
- val = read_write == I2C_SMBUS_WRITE ? RTL9300_I2C_MST_CTRL1_RWOP : 0;
|
||||
- mask = RTL9300_I2C_MST_CTRL1_RWOP;
|
||||
-
|
||||
- val |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
|
||||
- mask |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
|
||||
+ ret = regmap_field_write(i2c->fields[F_RWOP], read_write == I2C_SMBUS_WRITE);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
|
||||
+ ret = regmap_field_write(i2c->fields[F_I2C_TRIG], 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
|
||||
- val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 100000);
|
||||
+ ret = regmap_field_read_poll_timeout(i2c->fields[F_I2C_TRIG], val, !val, 100, 100000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- if (val & RTL9300_I2C_MST_CTRL1_I2C_FAIL)
|
||||
+ ret = regmap_field_read(i2c->fields[F_I2C_FAIL], &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ if (val)
|
||||
return -EIO;
|
||||
|
||||
if (read_write == I2C_SMBUS_READ) {
|
||||
switch (size) {
|
||||
case I2C_SMBUS_BYTE:
|
||||
case I2C_SMBUS_BYTE_DATA:
|
||||
- ret = regmap_read(i2c->regmap,
|
||||
- i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
+ ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->byte = val & 0xff;
|
||||
break;
|
||||
case I2C_SMBUS_WORD_DATA:
|
||||
- ret = regmap_read(i2c->regmap,
|
||||
- i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
|
||||
+ ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->word = val & 0xffff;
|
||||
@@ -355,9 +371,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rtl9300_i2c *i2c;
|
||||
+ struct fwnode_handle *child;
|
||||
+ struct rtl9300_i2c_drv_data *drv_data;
|
||||
+ struct reg_field fields[F_NUM_FIELDS];
|
||||
u32 clock_freq, sda_pin;
|
||||
int ret, i = 0;
|
||||
- struct fwnode_handle *child;
|
||||
|
||||
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
|
||||
if (!i2c)
|
||||
@@ -376,9 +394,22 @@ static int rtl9300_i2c_probe(struct plat
|
||||
|
||||
platform_set_drvdata(pdev, i2c);
|
||||
|
||||
- if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
|
||||
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+ if (device_get_child_node_count(dev) > drv_data->max_nchan)
|
||||
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
|
||||
+ i2c->data_reg = i2c->reg_base + drv_data->data_reg;
|
||||
+ for (i = 0; i < F_NUM_FIELDS; i++) {
|
||||
+ fields[i] = drv_data->field_desc[i].field;
|
||||
+ if (drv_data->field_desc[i].scope == REG_SCOPE_MASTER)
|
||||
+ fields[i].reg += i2c->reg_base;
|
||||
+ }
|
||||
+ ret = devm_regmap_field_bulk_alloc(dev, i2c->regmap, i2c->fields,
|
||||
+ fields, F_NUM_FIELDS);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ i = 0;
|
||||
device_for_each_child_node(dev, child) {
|
||||
struct rtl9300_i2c_chan *chan = &i2c->chans[i];
|
||||
struct i2c_adapter *adap = &chan->adap;
|
||||
@@ -395,7 +426,6 @@ static int rtl9300_i2c_probe(struct plat
|
||||
case I2C_MAX_STANDARD_MODE_FREQ:
|
||||
chan->bus_freq = RTL9300_I2C_STD_FREQ;
|
||||
break;
|
||||
-
|
||||
case I2C_MAX_FAST_MODE_FREQ:
|
||||
chan->bus_freq = RTL9300_I2C_FAST_FREQ;
|
||||
break;
|
||||
@@ -427,11 +457,37 @@ static int rtl9300_i2c_probe(struct plat
|
||||
return 0;
|
||||
}
|
||||
|
||||
+#define GLB_REG_FIELD(reg, msb, lsb) \
|
||||
+ { .field = REG_FIELD(reg, msb, lsb), .scope = REG_SCOPE_GLOBAL }
|
||||
+#define MST_REG_FIELD(reg, msb, lsb) \
|
||||
+ { .field = REG_FIELD(reg, msb, lsb), .scope = REG_SCOPE_MASTER }
|
||||
+
|
||||
+static const struct rtl9300_i2c_drv_data rtl9300_i2c_drv_data = {
|
||||
+ .field_desc = {
|
||||
+ [F_MEM_ADDR] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 8, 31),
|
||||
+ [F_SDA_OUT_SEL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 4, 6),
|
||||
+ [F_SCL_SEL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 3, 3),
|
||||
+ [F_RWOP] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 2, 2),
|
||||
+ [F_I2C_FAIL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 1, 1),
|
||||
+ [F_I2C_TRIG] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 0, 0),
|
||||
+ [F_RD_MODE] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 15, 15),
|
||||
+ [F_DEV_ADDR] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 8, 14),
|
||||
+ [F_DATA_WIDTH] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 4, 7),
|
||||
+ [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 2, 3),
|
||||
+ [F_SCL_FREQ] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 0, 1),
|
||||
+ [F_SDA_SEL] = GLB_REG_FIELD(RTL9300_I2C_MST_GLB_CTRL, 0, 7),
|
||||
+ },
|
||||
+ .select_scl = rtl9300_i2c_select_scl,
|
||||
+ .data_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
+ .max_nchan = RTL9300_I2C_MUX_NCHAN,
|
||||
+};
|
||||
+
|
||||
+
|
||||
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
- { .compatible = "realtek,rtl9301-i2c" },
|
||||
- { .compatible = "realtek,rtl9302b-i2c" },
|
||||
- { .compatible = "realtek,rtl9302c-i2c" },
|
||||
- { .compatible = "realtek,rtl9303-i2c" },
|
||||
+ { .compatible = "realtek,rtl9301-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9302b-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9302c-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9303-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
|
||||
@ -1,51 +0,0 @@
|
||||
From 80f3e37d5e734bbfe891592bb669ceb5e8b314dc Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:24 +0000
|
||||
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301-i2c: fix wording and typos
|
||||
|
||||
Fix wording of binding description to use plural because there is not
|
||||
only a single RTL9300 SoC. RTL9300 describes a whole family of Realtek
|
||||
SoCs.
|
||||
|
||||
Add missing word 'of' in description of reg property.
|
||||
|
||||
Change 'SDA pin' to 'SDA line number' because the property must contain
|
||||
the SDA (channel) number ranging from 0-7 instead of a real pin number.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-3-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -10,7 +10,7 @@ maintainers:
|
||||
- Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
|
||||
description:
|
||||
- The RTL9300 SoC has two I2C controllers. Each of these has an SCL line (which
|
||||
+ RTL9300 SoCs have two I2C controllers. Each of these has an SCL line (which
|
||||
if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
|
||||
assigned to either I2C controller.
|
||||
|
||||
@@ -27,7 +27,7 @@ properties:
|
||||
|
||||
reg:
|
||||
items:
|
||||
- - description: Register offset and size this I2C controller.
|
||||
+ - description: Register offset and size of this I2C controller.
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
@@ -42,7 +42,7 @@ patternProperties:
|
||||
|
||||
properties:
|
||||
reg:
|
||||
- description: The SDA pin associated with the I2C bus.
|
||||
+ description: The SDA line number associated with the I2C bus.
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
@ -1,118 +0,0 @@
|
||||
From 8ff3819d7edcd56e4c533b9391a156cd607048fa Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:25 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: rename internal sda_pin to sda_num
|
||||
|
||||
Rename the internally used 'sda_pin' to 'sda_num' to make it clear that
|
||||
this is NOT the actual pin number of the GPIO pin but rather the logical
|
||||
SDA channel number. Although the alternate function SDA_Y is sometimes
|
||||
given with the GPIO number, this is not always the case. Thus, avoid any
|
||||
confusion or misconfiguration by giving the variable the correct name.
|
||||
|
||||
This follows the description change in the devicetree bindings.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-4-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -20,7 +20,7 @@ struct rtl9300_i2c_chan {
|
||||
struct i2c_adapter adap;
|
||||
struct rtl9300_i2c *i2c;
|
||||
enum rtl9300_bus_freq bus_freq;
|
||||
- u8 sda_pin;
|
||||
+ u8 sda_num;
|
||||
};
|
||||
|
||||
enum rtl9300_i2c_reg_scope {
|
||||
@@ -67,7 +67,7 @@ struct rtl9300_i2c {
|
||||
struct regmap_field *fields[F_NUM_FIELDS];
|
||||
u32 reg_base;
|
||||
u32 data_reg;
|
||||
- u8 sda_pin;
|
||||
+ u8 sda_num;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
@@ -102,11 +102,11 @@ static int rtl9300_i2c_config_io(struct
|
||||
|
||||
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
|
||||
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_pin), BIT(sda_pin));
|
||||
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_num), BIT(sda_num));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_pin);
|
||||
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -243,11 +243,11 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
int len = 0, ret;
|
||||
|
||||
mutex_lock(&i2c->lock);
|
||||
- if (chan->sda_pin != i2c->sda_pin) {
|
||||
+ if (chan->sda_num != i2c->sda_num) {
|
||||
ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
- i2c->sda_pin = chan->sda_pin;
|
||||
+ i2c->sda_num = chan->sda_num;
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
@@ -374,7 +374,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
struct fwnode_handle *child;
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
struct reg_field fields[F_NUM_FIELDS];
|
||||
- u32 clock_freq, sda_pin;
|
||||
+ u32 clock_freq, sda_num;
|
||||
int ret, i = 0;
|
||||
|
||||
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
|
||||
@@ -414,7 +414,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
struct rtl9300_i2c_chan *chan = &i2c->chans[i];
|
||||
struct i2c_adapter *adap = &chan->adap;
|
||||
|
||||
- ret = fwnode_property_read_u32(child, "reg", &sda_pin);
|
||||
+ ret = fwnode_property_read_u32(child, "reg", &sda_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -431,11 +431,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
break;
|
||||
default:
|
||||
dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
|
||||
- sda_pin, clock_freq);
|
||||
+ sda_num, clock_freq);
|
||||
break;
|
||||
}
|
||||
|
||||
- chan->sda_pin = sda_pin;
|
||||
+ chan->sda_num = sda_num;
|
||||
chan->i2c = i2c;
|
||||
adap = &i2c->chans[i].adap;
|
||||
adap->owner = THIS_MODULE;
|
||||
@@ -445,14 +445,14 @@ static int rtl9300_i2c_probe(struct plat
|
||||
adap->dev.parent = dev;
|
||||
i2c_set_adapdata(adap, chan);
|
||||
adap->dev.of_node = to_of_node(child);
|
||||
- snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_pin);
|
||||
+ snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_num);
|
||||
i++;
|
||||
|
||||
ret = devm_i2c_add_adapter(dev, adap);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
- i2c->sda_pin = 0xff;
|
||||
+ i2c->sda_num = 0xff;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
From 6b0549abc8582a81425f89a436def8e28d8d7dce Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:26 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: move setting SCL frequency to config_io
|
||||
|
||||
Move the register operation to set the SCL frequency to the
|
||||
rtl9300_i2c_config_io function instead of the rtl9300_i2c_config_xfer
|
||||
function. This rather belongs there next to selecting the current SDA
|
||||
output line.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-5-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -95,18 +95,23 @@ static int rtl9300_i2c_select_scl(struct
|
||||
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
|
||||
+static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
{
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
int ret;
|
||||
|
||||
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
|
||||
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_num), BIT(sda_num));
|
||||
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
|
||||
+ BIT(chan->sda_num));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_num);
|
||||
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -121,10 +126,6 @@ static int rtl9300_i2c_config_xfer(struc
|
||||
if (len < 1 || len > 16)
|
||||
return -EINVAL;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -244,7 +245,7 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
|
||||
mutex_lock(&i2c->lock);
|
||||
if (chan->sda_num != i2c->sda_num) {
|
||||
- ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
|
||||
+ ret = rtl9300_i2c_config_io(i2c, chan);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
i2c->sda_num = chan->sda_num;
|
||||
@ -1,49 +0,0 @@
|
||||
From 0cb24186d0ebd7dd12c070fed9d782bf7c6dfb1e Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:27 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: do not set read mode on every transfer
|
||||
|
||||
Move the operation to set the read mode from config_xfer to probe.
|
||||
|
||||
The I2C controller of RTL9300 and RTL9310 support a legacy message mode
|
||||
for READs with 'Read Address Data' instead of the standard format 'Write
|
||||
Address ; Read Data'. There is no way to pass that via smbus_xfer, thus
|
||||
there is no point in supported this in the driver and moreover no point
|
||||
in setting this on every transaction. Setting this once in the probe
|
||||
call is sufficient.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-6-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -130,11 +130,7 @@ static int rtl9300_i2c_config_xfer(struc
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
- return regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
+ return regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
@@ -455,6 +451,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
}
|
||||
i2c->sda_num = 0xff;
|
||||
|
||||
+ /* only use standard read format */
|
||||
+ ret = regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,382 +0,0 @@
|
||||
From 447dd46f95014eb4ea94f6164963bf23ce05b927 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:28 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: separate xfer configuration and execution
|
||||
|
||||
So far, the rtl9300_i2c_smbus_xfer code is quite a mess with function
|
||||
calls distributed over the whole function setting different values in
|
||||
different cases. Calls to rtl9300_i2c_config_xfer and
|
||||
rtl9300_i2c_reg_addr_set are used in every case-block with varying
|
||||
values whose meaning is not instantly obvious. In some cases, there are
|
||||
additional calls within these case-blocks doing more things.
|
||||
|
||||
This is in general a bad design and especially really bad for
|
||||
readability and maintainability because it distributes changes or
|
||||
issues to multiple locations due to the same function being called with
|
||||
different hardcoded values in different places.
|
||||
|
||||
To have a good structure, setting different parameters based on the
|
||||
desired operation should not be interleaved with applying these
|
||||
parameters to the hardware registers. Or in different words, the
|
||||
parameter site should be mixed with the call site.
|
||||
|
||||
Thus, separate configuration and execution of an SMBus xfer within
|
||||
rtl9300_i2c_smbus_xfer to improve readability and maintainability. Add a
|
||||
new 'struct rtl9300_i2c_xfer' to carry the required parameters for an
|
||||
xfer which are configured based on the input parameters within a single
|
||||
switch-case block, without having any function calls within this block.
|
||||
|
||||
The function calls to actually apply these values to the hardware
|
||||
registers then appear below in a single place and just operate on the
|
||||
passed instance of 'struct rtl9300_i2c_xfer'. These are
|
||||
'rtl9300_i2c_prepare_xfer' which combines applying all parameters of the
|
||||
xfer to the corresponding register, and 'rtl9300_i2c_do_xfer' which
|
||||
actually executes the xfer and does post-processing if needed.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-7-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
+#include <linux/unaligned.h>
|
||||
|
||||
enum rtl9300_bus_freq {
|
||||
RTL9300_I2C_STD_FREQ,
|
||||
@@ -71,6 +72,22 @@ struct rtl9300_i2c {
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
+enum rtl9300_i2c_xfer_type {
|
||||
+ RTL9300_I2C_XFER_BYTE,
|
||||
+ RTL9300_I2C_XFER_WORD,
|
||||
+ RTL9300_I2C_XFER_BLOCK,
|
||||
+};
|
||||
+
|
||||
+struct rtl9300_i2c_xfer {
|
||||
+ enum rtl9300_i2c_xfer_type type;
|
||||
+ u16 dev_addr;
|
||||
+ u8 reg_addr;
|
||||
+ u8 reg_addr_len;
|
||||
+ u8 *data;
|
||||
+ u8 data_len;
|
||||
+ bool write;
|
||||
+};
|
||||
+
|
||||
#define RTL9300_I2C_MST_CTRL1 0x0
|
||||
#define RTL9300_I2C_MST_CTRL2 0x4
|
||||
#define RTL9300_I2C_MST_DATA_WORD0 0x8
|
||||
@@ -95,45 +112,37 @@ static int rtl9300_i2c_select_scl(struct
|
||||
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
+static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
{
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
int ret;
|
||||
|
||||
- drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+ if (i2c->sda_num == chan->sda_num)
|
||||
+ return 0;
|
||||
|
||||
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
|
||||
- BIT(chan->sda_num));
|
||||
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
|
||||
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+ ret = drv_data->select_scl(i2c, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
|
||||
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
|
||||
+ BIT(chan->sda_num));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- return drv_data->select_scl(i2c, 0);
|
||||
-}
|
||||
-
|
||||
-static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
|
||||
- u16 addr, u16 len)
|
||||
-{
|
||||
- int ret;
|
||||
-
|
||||
- if (len < 1 || len > 16)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
|
||||
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- return regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
|
||||
+ i2c->sda_num = chan->sda_num;
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
+static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
|
||||
{
|
||||
u32 vals[4] = {};
|
||||
int i, ret;
|
||||
@@ -153,7 +162,7 @@ static int rtl9300_i2c_read(struct rtl93
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
|
||||
+static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
|
||||
{
|
||||
u32 vals[4] = {};
|
||||
int i;
|
||||
@@ -176,16 +185,51 @@ static int rtl9300_i2c_writel(struct rtl
|
||||
return regmap_write(i2c->regmap, i2c->data_reg, data);
|
||||
}
|
||||
|
||||
-static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
|
||||
- int size, union i2c_smbus_data *data, int len)
|
||||
+static int rtl9300_i2c_prepare_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
|
||||
{
|
||||
- u32 val;
|
||||
int ret;
|
||||
|
||||
- ret = regmap_field_write(i2c->fields[F_RWOP], read_write == I2C_SMBUS_WRITE);
|
||||
+ if (xfer->data_len < 1 || xfer->data_len > 16)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_DEV_ADDR], xfer->dev_addr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ ret = rtl9300_i2c_reg_addr_set(i2c, xfer->reg_addr, xfer->reg_addr_len);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_RWOP], xfer->write);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (xfer->data_len - 1) & 0xf);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (xfer->write) {
|
||||
+ switch (xfer->type) {
|
||||
+ case RTL9300_I2C_XFER_BYTE:
|
||||
+ ret = rtl9300_i2c_writel(i2c, *xfer->data);
|
||||
+ break;
|
||||
+ case RTL9300_I2C_XFER_WORD:
|
||||
+ ret = rtl9300_i2c_writel(i2c, get_unaligned((const u16 *)xfer->data));
|
||||
+ break;
|
||||
+ default:
|
||||
+ ret = rtl9300_i2c_write(i2c, xfer->data, xfer->data_len);
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int rtl9300_i2c_do_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
|
||||
+{
|
||||
+ u32 val;
|
||||
+ int ret;
|
||||
+
|
||||
ret = regmap_field_write(i2c->fields[F_I2C_TRIG], 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -200,28 +244,24 @@ static int rtl9300_i2c_execute_xfer(stru
|
||||
if (val)
|
||||
return -EIO;
|
||||
|
||||
- if (read_write == I2C_SMBUS_READ) {
|
||||
- switch (size) {
|
||||
- case I2C_SMBUS_BYTE:
|
||||
- case I2C_SMBUS_BYTE_DATA:
|
||||
+ if (!xfer->write) {
|
||||
+ switch (xfer->type) {
|
||||
+ case RTL9300_I2C_XFER_BYTE:
|
||||
ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
- data->byte = val & 0xff;
|
||||
+
|
||||
+ *xfer->data = val & 0xff;
|
||||
break;
|
||||
- case I2C_SMBUS_WORD_DATA:
|
||||
+ case RTL9300_I2C_XFER_WORD:
|
||||
ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
- data->word = val & 0xffff;
|
||||
- break;
|
||||
- case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
- ret = rtl9300_i2c_read(i2c, &data->block[1], len);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
+
|
||||
+ put_unaligned(val & 0xffff, (u16*)xfer->data);
|
||||
break;
|
||||
default:
|
||||
- ret = rtl9300_i2c_read(i2c, &data->block[0], len);
|
||||
+ ret = rtl9300_i2c_read(i2c, xfer->data, xfer->data_len);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
@@ -237,108 +277,62 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
{
|
||||
struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap);
|
||||
struct rtl9300_i2c *i2c = chan->i2c;
|
||||
- int len = 0, ret;
|
||||
+ struct rtl9300_i2c_xfer xfer = {0};
|
||||
+ int ret;
|
||||
+
|
||||
+ if (addr > 0x7f)
|
||||
+ return -EINVAL;
|
||||
|
||||
mutex_lock(&i2c->lock);
|
||||
- if (chan->sda_num != i2c->sda_num) {
|
||||
- ret = rtl9300_i2c_config_io(i2c, chan);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- i2c->sda_num = chan->sda_num;
|
||||
- }
|
||||
+
|
||||
+ ret = rtl9300_i2c_config_chan(i2c, chan);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+
|
||||
+ xfer.dev_addr = addr & 0x7f;
|
||||
+ xfer.write = (read_write == I2C_SMBUS_WRITE);
|
||||
+ xfer.reg_addr = command;
|
||||
+ xfer.reg_addr_len = 1;
|
||||
|
||||
switch (size) {
|
||||
case I2C_SMBUS_BYTE:
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- } else {
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
+ xfer.data = (read_write == I2C_SMBUS_READ) ? &data->byte : &command;
|
||||
+ xfer.data_len = 1;
|
||||
+ xfer.reg_addr = 0;
|
||||
+ xfer.reg_addr_len = 0;
|
||||
+ xfer.type = RTL9300_I2C_XFER_BYTE;
|
||||
break;
|
||||
-
|
||||
case I2C_SMBUS_BYTE_DATA:
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_writel(i2c, data->byte);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
+ xfer.data = &data->byte;
|
||||
+ xfer.data_len = 1;
|
||||
+ xfer.type = RTL9300_I2C_XFER_BYTE;
|
||||
break;
|
||||
-
|
||||
case I2C_SMBUS_WORD_DATA:
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 2);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_writel(i2c, data->word);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
+ xfer.data = (u8 *)&data->word;
|
||||
+ xfer.data_len = 2;
|
||||
+ xfer.type = RTL9300_I2C_XFER_WORD;
|
||||
break;
|
||||
-
|
||||
case I2C_SMBUS_BLOCK_DATA:
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
|
||||
- ret = -EINVAL;
|
||||
- goto out_unlock;
|
||||
- }
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0] + 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_write(i2c, &data->block[0], data->block[0] + 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
- len = data->block[0] + 1;
|
||||
+ xfer.data = &data->block[0];
|
||||
+ xfer.data_len = data->block[0] + 1;
|
||||
+ xfer.type = RTL9300_I2C_XFER_BLOCK;
|
||||
break;
|
||||
-
|
||||
case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
|
||||
- ret = -EINVAL;
|
||||
- goto out_unlock;
|
||||
- }
|
||||
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- if (read_write == I2C_SMBUS_WRITE) {
|
||||
- ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
|
||||
- if (ret)
|
||||
- goto out_unlock;
|
||||
- }
|
||||
- len = data->block[0];
|
||||
+ xfer.data = &data->block[1];
|
||||
+ xfer.data_len = data->block[0];
|
||||
+ xfer.type = RTL9300_I2C_XFER_BLOCK;
|
||||
break;
|
||||
-
|
||||
default:
|
||||
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
- ret = rtl9300_i2c_execute_xfer(i2c, read_write, size, data, len);
|
||||
+ ret = rtl9300_i2c_prepare_xfer(i2c, &xfer);
|
||||
+ if (ret)
|
||||
+ goto out_unlock;
|
||||
+
|
||||
+ ret = rtl9300_i2c_do_xfer(i2c, &xfer);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&i2c->lock);
|
||||
@ -1,68 +0,0 @@
|
||||
From bcd5f0da57e6c47a884dcad94ad6b0e32cce8705 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:29 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: use scoped guard instead of explicit
|
||||
lock/unlock
|
||||
|
||||
Use the scoped guard infrastructure which unlocks a mutex automatically
|
||||
when the guard goes out of scope, instead of explicit lock and unlock.
|
||||
This simplifies the code and control flow in rtl9300_i2c_smbus_xfer and
|
||||
removes the need of using goto in error cases to unlock before
|
||||
returning.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-8-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -72,6 +72,8 @@ struct rtl9300_i2c {
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
+DEFINE_GUARD(rtl9300_i2c, struct rtl9300_i2c *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
|
||||
+
|
||||
enum rtl9300_i2c_xfer_type {
|
||||
RTL9300_I2C_XFER_BYTE,
|
||||
RTL9300_I2C_XFER_WORD,
|
||||
@@ -283,11 +285,11 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
if (addr > 0x7f)
|
||||
return -EINVAL;
|
||||
|
||||
- mutex_lock(&i2c->lock);
|
||||
+ guard(rtl9300_i2c)(i2c);
|
||||
|
||||
ret = rtl9300_i2c_config_chan(i2c, chan);
|
||||
if (ret)
|
||||
- goto out_unlock;
|
||||
+ return ret;
|
||||
|
||||
xfer.dev_addr = addr & 0x7f;
|
||||
xfer.write = (read_write == I2C_SMBUS_WRITE);
|
||||
@@ -324,20 +326,14 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
break;
|
||||
default:
|
||||
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
|
||||
- ret = -EOPNOTSUPP;
|
||||
- goto out_unlock;
|
||||
+ return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = rtl9300_i2c_prepare_xfer(i2c, &xfer);
|
||||
if (ret)
|
||||
- goto out_unlock;
|
||||
-
|
||||
- ret = rtl9300_i2c_do_xfer(i2c, &xfer);
|
||||
-
|
||||
-out_unlock:
|
||||
- mutex_unlock(&i2c->lock);
|
||||
+ return ret;
|
||||
|
||||
- return ret;
|
||||
+ return rtl9300_i2c_do_xfer(i2c, &xfer);
|
||||
}
|
||||
|
||||
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
|
||||
@ -1,94 +0,0 @@
|
||||
From 17689aafb793599a862617a127429dd3d6f675c9 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:30 +0000
|
||||
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301-i2c: extend for RTL9310
|
||||
support
|
||||
|
||||
Adjust the regex for child-node address to account for the fact that
|
||||
RTL9310 supports 12 instead of only 8 SDA lines. Also, narrow this per
|
||||
variant.
|
||||
|
||||
Add a vendor-specific property to explicitly specify the SCL line number
|
||||
of the defined I2C controller/master. This is required, in particular
|
||||
for RTL9310, to operate on the correct SCL for each controller. Require
|
||||
this property to be specified for RTL9310.
|
||||
|
||||
Add compatibles for known SoC variants RTL9311, RTL9312 and RTL9313.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-9-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -13,6 +13,8 @@ description:
|
||||
RTL9300 SoCs have two I2C controllers. Each of these has an SCL line (which
|
||||
if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
|
||||
assigned to either I2C controller.
|
||||
+ RTL9310 SoCs have equal capabilities but support 12 common SDA lines which
|
||||
+ can be assigned to either I2C controller.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
@@ -23,7 +25,15 @@ properties:
|
||||
- realtek,rtl9302c-i2c
|
||||
- realtek,rtl9303-i2c
|
||||
- const: realtek,rtl9301-i2c
|
||||
- - const: realtek,rtl9301-i2c
|
||||
+ - items:
|
||||
+ - enum:
|
||||
+ - realtek,rtl9311-i2c
|
||||
+ - realtek,rtl9312-i2c
|
||||
+ - realtek,rtl9313-i2c
|
||||
+ - const: realtek,rtl9310-i2c
|
||||
+ - enum:
|
||||
+ - realtek,rtl9301-i2c
|
||||
+ - realtek,rtl9310-i2c
|
||||
|
||||
reg:
|
||||
items:
|
||||
@@ -35,8 +45,14 @@ properties:
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
+ realtek,scl:
|
||||
+ $ref: /schemas/types.yaml#/definitions/uint32
|
||||
+ description:
|
||||
+ The SCL line number of this I2C controller.
|
||||
+ enum: [ 0, 1 ]
|
||||
+
|
||||
patternProperties:
|
||||
- '^i2c@[0-7]$':
|
||||
+ '^i2c@[0-9ab]$':
|
||||
$ref: /schemas/i2c/i2c-controller.yaml
|
||||
unevaluatedProperties: false
|
||||
|
||||
@@ -48,6 +64,25 @@ patternProperties:
|
||||
required:
|
||||
- reg
|
||||
|
||||
+
|
||||
+allOf:
|
||||
+ - if:
|
||||
+ properties:
|
||||
+ compatible:
|
||||
+ contains:
|
||||
+ const: realtek,rtl9310-i2c
|
||||
+ then:
|
||||
+ required:
|
||||
+ - realtek,scl
|
||||
+ - if:
|
||||
+ properties:
|
||||
+ compatible:
|
||||
+ contains:
|
||||
+ const: realtek,rtl9301-i2c
|
||||
+ then:
|
||||
+ patternProperties:
|
||||
+ '^i2c@[89ab]$': false
|
||||
+
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
@ -1,134 +0,0 @@
|
||||
From 8d43287120ce6437e7a77e735d99137f3fdb3ae9 Mon Sep 17 00:00:00 2001
|
||||
From: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Date: Sat, 27 Sep 2025 10:19:31 +0000
|
||||
Subject: [PATCH] i2c: rtl9300: add support for RTL9310 I2C controller
|
||||
|
||||
Add support for the internal I2C controllers of RTL9310 series based
|
||||
SoCs to the driver for RTL9300. Add register definitions, chip-specific
|
||||
functions and compatible strings for known RTL9310-based SoCs RTL9311,
|
||||
RTL9312 and RTL9313.
|
||||
|
||||
Make use of a new device tree property 'realtek,scl' which needs to be
|
||||
specified in case both or only the second master is used. This is
|
||||
required due how the register layout changed in contrast to RTL9300,
|
||||
which has SCL selection in a global register instead of a
|
||||
master-specific one.
|
||||
|
||||
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
Tested-by: Sven Eckelmann <sven@narfation.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20250927101931.71575-10-jelonek.jonas@gmail.com
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -60,14 +60,16 @@ struct rtl9300_i2c_drv_data {
|
||||
};
|
||||
|
||||
#define RTL9300_I2C_MUX_NCHAN 8
|
||||
+#define RTL9310_I2C_MUX_NCHAN 12
|
||||
|
||||
struct rtl9300_i2c {
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
- struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
|
||||
+ struct rtl9300_i2c_chan chans[RTL9310_I2C_MUX_NCHAN];
|
||||
struct regmap_field *fields[F_NUM_FIELDS];
|
||||
u32 reg_base;
|
||||
u32 data_reg;
|
||||
+ u8 scl_num;
|
||||
u8 sda_num;
|
||||
struct mutex lock;
|
||||
};
|
||||
@@ -98,6 +100,12 @@ struct rtl9300_i2c_xfer {
|
||||
#define RTL9300_I2C_MST_DATA_WORD3 0x14
|
||||
#define RTL9300_I2C_MST_GLB_CTRL 0x384
|
||||
|
||||
+#define RTL9310_I2C_MST_IF_CTRL 0x1004
|
||||
+#define RTL9310_I2C_MST_IF_SEL 0x1008
|
||||
+#define RTL9310_I2C_MST_CTRL 0x0
|
||||
+#define RTL9310_I2C_MST_MEMADDR_CTRL 0x4
|
||||
+#define RTL9310_I2C_MST_DATA_CTRL 0x8
|
||||
+
|
||||
static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
|
||||
{
|
||||
int ret;
|
||||
@@ -114,6 +122,11 @@ static int rtl9300_i2c_select_scl(struct
|
||||
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
|
||||
}
|
||||
|
||||
+static int rtl9310_i2c_select_scl(struct rtl9300_i2c *i2c, u8 scl)
|
||||
+{
|
||||
+ return regmap_field_update_bits(i2c->fields[F_SCL_SEL], BIT(scl), BIT(scl));
|
||||
+}
|
||||
+
|
||||
static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
{
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
@@ -127,7 +140,7 @@ static int rtl9300_i2c_config_chan(struc
|
||||
return ret;
|
||||
|
||||
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
- ret = drv_data->select_scl(i2c, 0);
|
||||
+ ret = drv_data->select_scl(i2c, i2c->scl_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -361,7 +374,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
struct fwnode_handle *child;
|
||||
struct rtl9300_i2c_drv_data *drv_data;
|
||||
struct reg_field fields[F_NUM_FIELDS];
|
||||
- u32 clock_freq, sda_num;
|
||||
+ u32 clock_freq, scl_num, sda_num;
|
||||
int ret, i = 0;
|
||||
|
||||
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
|
||||
@@ -379,6 +392,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ ret = device_property_read_u32(dev, "realtek,scl", &scl_num);
|
||||
+ if (ret || scl_num != 1)
|
||||
+ scl_num = 0;
|
||||
+ i2c->scl_num = (u8)scl_num;
|
||||
+
|
||||
platform_set_drvdata(pdev, i2c);
|
||||
|
||||
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
@@ -474,12 +492,35 @@ static const struct rtl9300_i2c_drv_data
|
||||
.max_nchan = RTL9300_I2C_MUX_NCHAN,
|
||||
};
|
||||
|
||||
+static const struct rtl9300_i2c_drv_data rtl9310_i2c_drv_data = {
|
||||
+ .field_desc = {
|
||||
+ [F_SCL_SEL] = GLB_REG_FIELD(RTL9310_I2C_MST_IF_SEL, 12, 13),
|
||||
+ [F_SDA_SEL] = GLB_REG_FIELD(RTL9310_I2C_MST_IF_SEL, 0, 11),
|
||||
+ [F_SCL_FREQ] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 30, 31),
|
||||
+ [F_DEV_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 11, 17),
|
||||
+ [F_SDA_OUT_SEL] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 18, 21),
|
||||
+ [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 9, 10),
|
||||
+ [F_DATA_WIDTH] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 5, 8),
|
||||
+ [F_RD_MODE] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 4, 4),
|
||||
+ [F_RWOP] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 2, 2),
|
||||
+ [F_I2C_FAIL] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 1, 1),
|
||||
+ [F_I2C_TRIG] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 0, 0),
|
||||
+ [F_MEM_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_MEMADDR_CTRL, 0, 23),
|
||||
+ },
|
||||
+ .select_scl = rtl9310_i2c_select_scl,
|
||||
+ .data_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
+ .max_nchan = RTL9310_I2C_MUX_NCHAN,
|
||||
+};
|
||||
|
||||
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
{ .compatible = "realtek,rtl9301-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9302b-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9302c-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9303-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9310-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9311-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9312-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9313-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
|
||||
@ -1,51 +0,0 @@
|
||||
From 85e27f218121bdaa8e8afd68674262aa154d2cb4 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Mon, 4 Aug 2025 04:03:26 -0400
|
||||
Subject: clocksource/drivers/timer-rtl-otto: Drop set_counter function
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The current counter value is a read only register. It will be
|
||||
reset when writing a new target timer value with rttm_set_period().
|
||||
rttm_set_counter() is essentially a noop. Drop it.
|
||||
|
||||
While this makes rttm_start_timer() and rttm_enable_timer() the
|
||||
same functions keep both to make the established abstraction layers
|
||||
for register and control functions active.
|
||||
|
||||
Downstream has already tested and confirmed a patch. See
|
||||
https://github.com/openwrt/openwrt/pull/19468
|
||||
https://forum.openwrt.org/t/support-for-rtl838x-based-managed-switches/57875/3788
|
||||
|
||||
Tested-by: Stephen Howell <howels@allthatwemight.be>
|
||||
Tested-by: Bjørn Mork <bjorn@mork.no>
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Link: https://lore.kernel.org/r/20250804080328.2609287-3-markus.stockhausen@gmx.de
|
||||
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
---
|
||||
drivers/clocksource/timer-rtl-otto.c | 6 ------
|
||||
1 file changed, 6 deletions(-)
|
||||
|
||||
--- a/drivers/clocksource/timer-rtl-otto.c
|
||||
+++ b/drivers/clocksource/timer-rtl-otto.c
|
||||
@@ -56,11 +56,6 @@ struct rttm_cs {
|
||||
};
|
||||
|
||||
/* Simple internal register functions */
|
||||
-static inline void rttm_set_counter(void __iomem *base, unsigned int counter)
|
||||
-{
|
||||
- iowrite32(counter, base + RTTM_CNT);
|
||||
-}
|
||||
-
|
||||
static inline unsigned int rttm_get_counter(void __iomem *base)
|
||||
{
|
||||
return ioread32(base + RTTM_CNT);
|
||||
@@ -137,7 +132,6 @@ static void rttm_stop_timer(void __iomem
|
||||
|
||||
static void rttm_start_timer(struct timer_of *to, u32 mode)
|
||||
{
|
||||
- rttm_set_counter(to->of_base.base, 0);
|
||||
rttm_enable_timer(to->of_base.base, mode, to->of_clk.rate / RTTM_TICKS_PER_SEC);
|
||||
}
|
||||
|
||||
@ -1,521 +0,0 @@
|
||||
From 3148d0e5b1c5733d69ec51b70c8280e46488750a Mon Sep 17 00:00:00 2001
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Fri, 19 Sep 2025 03:52:01 -0400
|
||||
Subject: mtd: nand: realtek-ecc: Add Realtek external ECC engine support
|
||||
|
||||
The Realtek RTl93xx switch SoC series has a built in ECC controller
|
||||
that can provide BCH6 or BCH12 over 512 data and 6 tag bytes. It
|
||||
generates 10 (BCH6) or 20 (BCH12) bytes of parity.
|
||||
|
||||
This engine will most likely work in conjunction with the Realtek
|
||||
spi-mem based NAND controller but can work on its own. Therefore
|
||||
the initial implementation will be of type external.
|
||||
|
||||
Remark! The engine can support any data blocks that are multiples
|
||||
of 512 bytes. For now limit it to data+oob layouts that have been
|
||||
analyzed from existing devices. This way it keeps compatibility
|
||||
and pre-existing vendor data can be read.
|
||||
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
---
|
||||
drivers/mtd/nand/Kconfig | 8 +
|
||||
drivers/mtd/nand/Makefile | 1 +
|
||||
drivers/mtd/nand/ecc-realtek.c | 464 +++++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 473 insertions(+)
|
||||
create mode 100644 drivers/mtd/nand/ecc-realtek.c
|
||||
|
||||
--- a/drivers/mtd/nand/Kconfig
|
||||
+++ b/drivers/mtd/nand/Kconfig
|
||||
@@ -65,6 +65,14 @@ config MTD_NAND_ECC_MEDIATEK
|
||||
help
|
||||
This enables support for the hardware ECC engine from Mediatek.
|
||||
|
||||
+config MTD_NAND_ECC_REALTEK
|
||||
+ tristate "Realtek RTL93xx hardware ECC engine"
|
||||
+ depends on HAS_IOMEM
|
||||
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
||||
+ select MTD_NAND_ECC
|
||||
+ help
|
||||
+ This enables support for the hardware ECC engine from Realtek.
|
||||
+
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
--- a/drivers/mtd/nand/Makefile
|
||||
+++ b/drivers/mtd/nand/Makefile
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
nandcore-objs := core.o bbt.o
|
||||
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
|
||||
+obj-$(CONFIG_MTD_NAND_ECC_REALTEK) += ecc-realtek.o
|
||||
obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
|
||||
obj-$(CONFIG_MTD_NAND_MTK_BMT) += mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o
|
||||
obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/mtd/nand/ecc-realtek.c
|
||||
@@ -0,0 +1,464 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0
|
||||
+/*
|
||||
+ * Support for Realtek hardware ECC engine in RTL93xx SoCs
|
||||
+ */
|
||||
+
|
||||
+#include <linux/bitfield.h>
|
||||
+#include <linux/dma-mapping.h>
|
||||
+#include <linux/mtd/nand.h>
|
||||
+#include <linux/mutex.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+/*
|
||||
+ * The Realtek ECC engine has two operation modes.
|
||||
+ *
|
||||
+ * - BCH6 : Generate 10 ECC bytes from 512 data bytes plus 6 free bytes
|
||||
+ * - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes
|
||||
+ *
|
||||
+ * It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there
|
||||
+ * are only two known devices in the wild that have NAND flash and make use of this ECC engine
|
||||
+ * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only
|
||||
+ * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte
|
||||
+ * blocks and 64 bytes oob.
|
||||
+ *
|
||||
+ * This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the
|
||||
+ * Realtek naming conventions for the different structures in the OOB area.
|
||||
+ *
|
||||
+ * - BBI : Bad block indicator. The first two bytes of OOB. Protected by ECC!
|
||||
+ * - tag : 6 User/free bytes. First tag "contains" 2 bytes BBI. Protected by ECC!
|
||||
+ * - syndrome : ECC/parity bytes
|
||||
+ *
|
||||
+ * Altogether this gives currently the following block layout.
|
||||
+ *
|
||||
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
|
||||
+ * | 512 | 512 | 512 | 512 | 2 | 4 | 6 | 6 | 6 | 10 | 10 | 10 | 10 |
|
||||
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
|
||||
+ * | data | data | data | data | BBI | free | free | free | free | ECC | ECC | ECC | ECC |
|
||||
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
|
||||
+ */
|
||||
+
|
||||
+#define RTL_ECC_ALLOWED_PAGE_SIZE 2048
|
||||
+#define RTL_ECC_ALLOWED_OOB_SIZE 64
|
||||
+#define RTL_ECC_ALLOWED_STRENGTH 6
|
||||
+
|
||||
+#define RTL_ECC_BLOCK_SIZE 512
|
||||
+#define RTL_ECC_FREE_SIZE 6
|
||||
+#define RTL_ECC_PARITY_SIZE_BCH6 10
|
||||
+#define RTL_ECC_PARITY_SIZE_BCH12 20
|
||||
+
|
||||
+/*
|
||||
+ * The engine is fed with two DMA regions. One for data (always 512 bytes) and one for free bytes
|
||||
+ * and parity (either 16 bytes for BCH6 or 26 bytes for BCH12). Start and length of each must be
|
||||
+ * aligned to a multiple of 4.
|
||||
+ */
|
||||
+
|
||||
+#define RTL_ECC_DMA_FREE_PARITY_SIZE ALIGN(RTL_ECC_FREE_SIZE + RTL_ECC_PARITY_SIZE_BCH12, 4)
|
||||
+#define RTL_ECC_DMA_SIZE (RTL_ECC_BLOCK_SIZE + RTL_ECC_DMA_FREE_PARITY_SIZE)
|
||||
+
|
||||
+#define RTL_ECC_CFG 0x00
|
||||
+#define RTL_ECC_BCH6 0
|
||||
+#define RTL_ECC_BCH12 BIT(28)
|
||||
+#define RTL_ECC_DMA_PRECISE BIT(12)
|
||||
+#define RTL_ECC_BURST_128 GENMASK(1, 0)
|
||||
+#define RTL_ECC_DMA_TRIGGER 0x08
|
||||
+#define RTL_ECC_OP_DECODE 0
|
||||
+#define RTL_ECC_OP_ENCODE BIT(0)
|
||||
+#define RTL_ECC_DMA_START 0x0c
|
||||
+#define RTL_ECC_DMA_TAG 0x10
|
||||
+#define RTL_ECC_STATUS 0x14
|
||||
+#define RTL_ECC_CORR_COUNT GENMASK(19, 12)
|
||||
+#define RTL_ECC_RESULT BIT(8)
|
||||
+#define RTL_ECC_ALL_ONE BIT(4)
|
||||
+#define RTL_ECC_OP_STATUS BIT(0)
|
||||
+
|
||||
+struct rtl_ecc_engine {
|
||||
+ struct device *dev;
|
||||
+ struct nand_ecc_engine engine;
|
||||
+ struct mutex lock;
|
||||
+ char *buf;
|
||||
+ dma_addr_t buf_dma;
|
||||
+ struct regmap *regmap;
|
||||
+};
|
||||
+
|
||||
+struct rtl_ecc_ctx {
|
||||
+ struct rtl_ecc_engine * rtlc;
|
||||
+ struct nand_ecc_req_tweak_ctx req_ctx;
|
||||
+ int steps;
|
||||
+ int bch_mode;
|
||||
+ int strength;
|
||||
+ int parity_size;
|
||||
+};
|
||||
+
|
||||
+static const struct regmap_config rtl_ecc_regmap_config = {
|
||||
+ .reg_bits = 32,
|
||||
+ .val_bits = 32,
|
||||
+ .reg_stride = 4,
|
||||
+};
|
||||
+
|
||||
+static inline void *nand_to_ctx(struct nand_device *nand)
|
||||
+{
|
||||
+ return nand->ecc.ctx.priv;
|
||||
+}
|
||||
+
|
||||
+static inline struct rtl_ecc_engine *nand_to_rtlc(struct nand_device *nand)
|
||||
+{
|
||||
+ struct nand_ecc_engine *eng = nand->ecc.engine;
|
||||
+
|
||||
+ return container_of(eng, struct rtl_ecc_engine, engine);
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
+ struct mtd_oob_region *oobregion)
|
||||
+{
|
||||
+ struct nand_device *nand = mtd_to_nanddev(mtd);
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+
|
||||
+ if (section < 0 || section >= ctx->steps)
|
||||
+ return -ERANGE;
|
||||
+
|
||||
+ oobregion->offset = ctx->steps * RTL_ECC_FREE_SIZE + section * ctx->parity_size;
|
||||
+ oobregion->length = ctx->parity_size;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
+ struct mtd_oob_region *oobregion)
|
||||
+{
|
||||
+ struct nand_device *nand = mtd_to_nanddev(mtd);
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+ int bbm;
|
||||
+
|
||||
+ if (section < 0 || section >= ctx->steps)
|
||||
+ return -ERANGE;
|
||||
+
|
||||
+ /* reserve 2 BBM bytes in first block */
|
||||
+ bbm = section ? 0 : 2;
|
||||
+ oobregion->offset = section * RTL_ECC_FREE_SIZE + bbm;
|
||||
+ oobregion->length = RTL_ECC_FREE_SIZE - bbm;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct mtd_ooblayout_ops rtl_ecc_ooblayout_ops = {
|
||||
+ .ecc = rtl_ecc_ooblayout_ecc,
|
||||
+ .free = rtl_ecc_ooblayout_free,
|
||||
+};
|
||||
+
|
||||
+static void rtl_ecc_kick_engine(struct rtl_ecc_ctx *ctx, int operation)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
|
||||
+
|
||||
+ regmap_write(rtlc->regmap, RTL_ECC_CFG,
|
||||
+ ctx->bch_mode | RTL_ECC_BURST_128 | RTL_ECC_DMA_PRECISE);
|
||||
+
|
||||
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_START, rtlc->buf_dma);
|
||||
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TAG, rtlc->buf_dma + RTL_ECC_BLOCK_SIZE);
|
||||
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TRIGGER, operation);
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_wait_for_engine(struct rtl_ecc_ctx *ctx)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
|
||||
+ int ret, status, bitflips;
|
||||
+ bool all_one;
|
||||
+
|
||||
+ /*
|
||||
+ * The ECC engine needs 6-8 us to encode/decode a BCH6 syndrome for 512 bytes of data
|
||||
+ * and 6 free bytes. In case the NAND area has been erased and all data and oob is
|
||||
+ * set to 0xff, decoding takes 30us (reason unknown). Although the engine can trigger
|
||||
+ * interrupts when finished, use active polling for now. 12 us maximum wait time has
|
||||
+ * proven to be a good tradeoff between performance and overhead.
|
||||
+ */
|
||||
+
|
||||
+ ret = regmap_read_poll_timeout(rtlc->regmap, RTL_ECC_STATUS, status,
|
||||
+ !(status & RTL_ECC_OP_STATUS), 12, 1000000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = FIELD_GET(RTL_ECC_RESULT, status);
|
||||
+ all_one = FIELD_GET(RTL_ECC_ALL_ONE, status);
|
||||
+ bitflips = FIELD_GET(RTL_ECC_CORR_COUNT, status);
|
||||
+
|
||||
+ /* For erased blocks (all bits one) error status can be ignored */
|
||||
+ if (all_one)
|
||||
+ ret = 0;
|
||||
+
|
||||
+ return ret ? -EBADMSG : bitflips;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_run_engine(struct rtl_ecc_ctx *ctx, char *data, char *free,
|
||||
+ char *parity, int operation)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
|
||||
+ char *buf_parity = rtlc->buf + RTL_ECC_BLOCK_SIZE + RTL_ECC_FREE_SIZE;
|
||||
+ char *buf_free = rtlc->buf + RTL_ECC_BLOCK_SIZE;
|
||||
+ char *buf_data = rtlc->buf;
|
||||
+ int ret;
|
||||
+
|
||||
+ mutex_lock(&rtlc->lock);
|
||||
+
|
||||
+ memcpy(buf_data, data, RTL_ECC_BLOCK_SIZE);
|
||||
+ memcpy(buf_free, free, RTL_ECC_FREE_SIZE);
|
||||
+ memcpy(buf_parity, parity, ctx->parity_size);
|
||||
+
|
||||
+ dma_sync_single_for_device(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_TO_DEVICE);
|
||||
+ rtl_ecc_kick_engine(ctx, operation);
|
||||
+ ret = rtl_ecc_wait_for_engine(ctx);
|
||||
+ dma_sync_single_for_cpu(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_FROM_DEVICE);
|
||||
+
|
||||
+ if (ret >= 0) {
|
||||
+ memcpy(data, buf_data, RTL_ECC_BLOCK_SIZE);
|
||||
+ memcpy(free, buf_free, RTL_ECC_FREE_SIZE);
|
||||
+ memcpy(parity, buf_parity, ctx->parity_size);
|
||||
+ }
|
||||
+
|
||||
+ mutex_unlock(&rtlc->lock);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+ char *data, *free, *parity;
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ if (req->mode == MTD_OPS_RAW)
|
||||
+ return 0;
|
||||
+
|
||||
+ nand_ecc_tweak_req(&ctx->req_ctx, req);
|
||||
+
|
||||
+ if (req->type == NAND_PAGE_READ)
|
||||
+ return 0;
|
||||
+
|
||||
+ free = req->oobbuf.in;
|
||||
+ data = req->databuf.in;
|
||||
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
|
||||
+
|
||||
+ for (int i = 0; i < ctx->steps; i++) {
|
||||
+ ret |= rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_ENCODE);
|
||||
+
|
||||
+ free += RTL_ECC_FREE_SIZE;
|
||||
+ data += RTL_ECC_BLOCK_SIZE;
|
||||
+ parity += ctx->parity_size;
|
||||
+ }
|
||||
+
|
||||
+ if (unlikely(ret))
|
||||
+ dev_dbg(rtlc->dev, "ECC calculation failed\n");
|
||||
+
|
||||
+ return ret ? -EBADMSG : 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
+ char *data, *free, *parity;
|
||||
+ bool failure = false;
|
||||
+ int bitflips = 0;
|
||||
+
|
||||
+ if (req->mode == MTD_OPS_RAW)
|
||||
+ return 0;
|
||||
+
|
||||
+ if (req->type == NAND_PAGE_WRITE) {
|
||||
+ nand_ecc_restore_req(&ctx->req_ctx, req);
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ free = req->oobbuf.in;
|
||||
+ data = req->databuf.in;
|
||||
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
|
||||
+
|
||||
+ for (int i = 0 ; i < ctx->steps; i++) {
|
||||
+ int ret = rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_DECODE);
|
||||
+
|
||||
+ if (unlikely(ret < 0))
|
||||
+ /* ECC totally fails for bitflips in erased blocks */
|
||||
+ ret = nand_check_erased_ecc_chunk(data, RTL_ECC_BLOCK_SIZE,
|
||||
+ parity, ctx->parity_size,
|
||||
+ free, RTL_ECC_FREE_SIZE,
|
||||
+ ctx->strength);
|
||||
+ if (unlikely(ret < 0)) {
|
||||
+ failure = true;
|
||||
+ mtd->ecc_stats.failed++;
|
||||
+ } else {
|
||||
+ mtd->ecc_stats.corrected += ret;
|
||||
+ bitflips = max_t(unsigned int, bitflips, ret);
|
||||
+ }
|
||||
+
|
||||
+ free += RTL_ECC_FREE_SIZE;
|
||||
+ data += RTL_ECC_BLOCK_SIZE;
|
||||
+ parity += ctx->parity_size;
|
||||
+ }
|
||||
+
|
||||
+ nand_ecc_restore_req(&ctx->req_ctx, req);
|
||||
+
|
||||
+ if (unlikely(failure))
|
||||
+ dev_dbg(rtlc->dev, "ECC correction failed\n");
|
||||
+ else if (unlikely(bitflips > 2))
|
||||
+ dev_dbg(rtlc->dev, "%d bitflips detected\n", bitflips);
|
||||
+
|
||||
+ return failure ? -EBADMSG : bitflips;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_check_support(struct nand_device *nand)
|
||||
+{
|
||||
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
+ struct device *dev = nand->ecc.engine->dev;
|
||||
+
|
||||
+ if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE ||
|
||||
+ mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) {
|
||||
+ dev_err(dev, "only flash geometry data=%d, oob=%d supported\n",
|
||||
+ RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ if (nand->ecc.user_conf.algo != NAND_ECC_ALGO_BCH ||
|
||||
+ nand->ecc.user_conf.strength != RTL_ECC_ALLOWED_STRENGTH ||
|
||||
+ nand->ecc.user_conf.placement != NAND_ECC_PLACEMENT_OOB ||
|
||||
+ nand->ecc.user_conf.step_size != RTL_ECC_BLOCK_SIZE) {
|
||||
+ dev_err(dev, "only algo=bch, strength=%d, placement=oob, step=%d supported\n",
|
||||
+ RTL_ECC_ALLOWED_STRENGTH, RTL_ECC_BLOCK_SIZE);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl_ecc_init_ctx(struct nand_device *nand)
|
||||
+{
|
||||
+ struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
|
||||
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
|
||||
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
+ int strength = nand->ecc.user_conf.strength;
|
||||
+ struct device *dev = nand->ecc.engine->dev;
|
||||
+ struct rtl_ecc_ctx *ctx;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = rtl_ecc_check_support(nand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
+ if (!ctx)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ nand->ecc.ctx.priv = ctx;
|
||||
+ mtd_set_ooblayout(mtd, &rtl_ecc_ooblayout_ops);
|
||||
+
|
||||
+ conf->algo = NAND_ECC_ALGO_BCH;
|
||||
+ conf->strength = strength;
|
||||
+ conf->step_size = RTL_ECC_BLOCK_SIZE;
|
||||
+ conf->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
|
||||
+
|
||||
+ ctx->rtlc = rtlc;
|
||||
+ ctx->steps = mtd->writesize / RTL_ECC_BLOCK_SIZE;
|
||||
+ ctx->strength = strength;
|
||||
+ ctx->bch_mode = strength == 6 ? RTL_ECC_BCH6 : RTL_ECC_BCH12;
|
||||
+ ctx->parity_size = strength == 6 ? RTL_ECC_PARITY_SIZE_BCH6 : RTL_ECC_PARITY_SIZE_BCH12;
|
||||
+
|
||||
+ ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ dev_dbg(dev, "using bch%d with geometry data=%dx%d, free=%dx6, parity=%dx%d",
|
||||
+ conf->strength, ctx->steps, conf->step_size,
|
||||
+ ctx->steps, ctx->steps, ctx->parity_size);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void rtl_ecc_cleanup_ctx(struct nand_device *nand)
|
||||
+{
|
||||
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
|
||||
+
|
||||
+ if (ctx)
|
||||
+ nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
|
||||
+}
|
||||
+
|
||||
+static struct nand_ecc_engine_ops rtl_ecc_engine_ops = {
|
||||
+ .init_ctx = rtl_ecc_init_ctx,
|
||||
+ .cleanup_ctx = rtl_ecc_cleanup_ctx,
|
||||
+ .prepare_io_req = rtl_ecc_prepare_io_req,
|
||||
+ .finish_io_req = rtl_ecc_finish_io_req,
|
||||
+};
|
||||
+
|
||||
+static int rtl_ecc_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct rtl_ecc_engine *rtlc;
|
||||
+ void __iomem *base;
|
||||
+ int ret;
|
||||
+
|
||||
+ rtlc = devm_kzalloc(dev, sizeof(*rtlc), GFP_KERNEL);
|
||||
+ if (!rtlc)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ base = devm_platform_ioremap_resource(pdev, 0);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ ret = devm_mutex_init(dev, &rtlc->lock);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ rtlc->regmap = devm_regmap_init_mmio(dev, base, &rtl_ecc_regmap_config);
|
||||
+ if (IS_ERR(rtlc->regmap))
|
||||
+ return PTR_ERR(rtlc->regmap);
|
||||
+
|
||||
+ /*
|
||||
+ * Focus on simplicity and use a preallocated DMA buffer for data exchange with the
|
||||
+ * engine. For now make it a noncoherent memory model as invalidating/flushing caches
|
||||
+ * is faster than reading/writing uncached memory on the known architectures.
|
||||
+ */
|
||||
+
|
||||
+ rtlc->buf = dma_alloc_noncoherent(dev, RTL_ECC_DMA_SIZE, &rtlc->buf_dma,
|
||||
+ DMA_BIDIRECTIONAL, GFP_KERNEL);
|
||||
+ if (IS_ERR(rtlc->buf))
|
||||
+ return PTR_ERR(rtlc->buf);
|
||||
+
|
||||
+ rtlc->dev = dev;
|
||||
+ rtlc->engine.dev = dev;
|
||||
+ rtlc->engine.ops = &rtl_ecc_engine_ops;
|
||||
+ rtlc->engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
|
||||
+
|
||||
+ nand_ecc_register_on_host_hw_engine(&rtlc->engine);
|
||||
+
|
||||
+ platform_set_drvdata(pdev, rtlc);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void rtl_ecc_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct rtl_ecc_engine *rtlc = platform_get_drvdata(pdev);
|
||||
+
|
||||
+ nand_ecc_unregister_on_host_hw_engine(&rtlc->engine);
|
||||
+ dma_free_noncoherent(rtlc->dev, RTL_ECC_DMA_SIZE, rtlc->buf, rtlc->buf_dma,
|
||||
+ DMA_BIDIRECTIONAL);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id rtl_ecc_of_ids[] = {
|
||||
+ {
|
||||
+ .compatible = "realtek,rtl9301-ecc",
|
||||
+ },
|
||||
+ { /* sentinel */ },
|
||||
+};
|
||||
+
|
||||
+static struct platform_driver rtl_ecc_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "rtl-nand-ecc-engine",
|
||||
+ .of_match_table = rtl_ecc_of_ids,
|
||||
+ },
|
||||
+ .probe = rtl_ecc_probe,
|
||||
+ .remove = rtl_ecc_remove,
|
||||
+};
|
||||
+module_platform_driver(rtl_ecc_driver);
|
||||
+
|
||||
+MODULE_LICENSE("GPL");
|
||||
+MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
|
||||
+MODULE_DESCRIPTION("Realtek NAND hardware ECC controller");
|
||||
@ -1,56 +0,0 @@
|
||||
From b3f79468c90d8770f007d628a1e32b2d5d44a5c2 Mon Sep 17 00:00:00 2001
|
||||
From: Sander Vanheule <sander@svanheule.net>
|
||||
Date: Sat, 15 May 2021 11:57:32 +0200
|
||||
Subject: [PATCH] gpio: regmap: Bypass cache for shadowed outputs
|
||||
|
||||
Some chips have the read-only input and write-only output data registers
|
||||
aliased to the same offset, but do not perform direction multiplexing on
|
||||
writes. Upon writing the register, this then always updates the output
|
||||
value, even when the pin is configured as input. As a result it is not
|
||||
safe to perform read-modify-writes on output pins, when other pins are
|
||||
still configured as input.
|
||||
|
||||
For example, on a bit-banged I2C bus, where the lines are switched
|
||||
between out-low and in (with external pull-up)
|
||||
|
||||
OUT(L) IN OUT(H)
|
||||
SCK ....../''''''|''''''
|
||||
|
||||
SDA '''''''''\..........
|
||||
^ ^- SCK switches to direction to OUT, but now has a high
|
||||
| value, breaking the clock.
|
||||
|
|
||||
\- Perform RMW to update SDA. This reads the current input
|
||||
value for SCK, updates the SDA value and writes back a 1
|
||||
for SCK as well.
|
||||
|
||||
If a register is used for both the data input and data output (and is
|
||||
not marked as volatile) the driver should ensure the cache is not
|
||||
updated on register reads. This ensures proper functioning of writing
|
||||
the output register with regmap_update_bits(), which will then use and
|
||||
update the cache only on register writes.
|
||||
|
||||
Signed-off-by: Sander Vanheule <sander@svanheule.net>
|
||||
---
|
||||
drivers/gpio/gpio-regmap.c | 10 +++++++++-
|
||||
1 file changed, 9 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/gpio/gpio-regmap.c
|
||||
+++ b/drivers/gpio/gpio-regmap.c
|
||||
@@ -80,7 +80,15 @@ static int gpio_regmap_get(struct gpio_c
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_read(gpio->regmap, reg, &val);
|
||||
+ /*
|
||||
+ * Ensure we don't spoil the register cache with pin input values and
|
||||
+ * perform a bypassed read. This way the cache (if any) is only used and
|
||||
+ * updated on register writes.
|
||||
+ */
|
||||
+ if (gpio->reg_dat_base == gpio->reg_set_base)
|
||||
+ ret = regmap_read_bypassed(gpio->regmap, reg, &val);
|
||||
+ else
|
||||
+ ret = regmap_read(gpio->regmap, reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
From 0c741b8b6963e584b41c284cd743c545636edb04 Mon Sep 17 00:00:00 2001
|
||||
From: Ahmed Naseef <naseefkm@gmail.com>
|
||||
Date: Sat, 7 Feb 2026 11:02:43 +0400
|
||||
Subject: mtd: nand: realtek-ecc: relax OOB size check to minimum
|
||||
|
||||
The ECC engine strictly validates that flash OOB size equals exactly
|
||||
64 bytes. However, some NAND chips have a larger physical OOB while
|
||||
vendor firmware only uses the first 64 bytes for the ECC layout. For
|
||||
example the Macronix MX35LF1G24AD found in the Netlink HG323DAC has
|
||||
128 byte physical OOB but vendor firmware only uses the first 64
|
||||
bytes (24 bytes free + 40 bytes BCH6 parity), leaving bytes 64-127
|
||||
unused.
|
||||
|
||||
Since the engine only operates on the first 64 bytes of OOB
|
||||
regardless of the physical size, change the check from exact match
|
||||
to minimum size. Flash with OOB >= 64 bytes works correctly with
|
||||
the engine's 64-byte layout.
|
||||
|
||||
Suggested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
|
||||
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
---
|
||||
drivers/mtd/nand/ecc-realtek.c | 18 ++++++++++--------
|
||||
1 file changed, 10 insertions(+), 8 deletions(-)
|
||||
|
||||
--- a/drivers/mtd/nand/ecc-realtek.c
|
||||
+++ b/drivers/mtd/nand/ecc-realtek.c
|
||||
@@ -17,10 +17,12 @@
|
||||
* - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes
|
||||
*
|
||||
* It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there
|
||||
- * are only two known devices in the wild that have NAND flash and make use of this ECC engine
|
||||
- * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only
|
||||
- * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte
|
||||
- * blocks and 64 bytes oob.
|
||||
+ * are a few known devices in the wild that make use of this ECC engine
|
||||
+ * (Linksys LGS328C, LGS352C & Netlink HG323DAC). To keep compatibility with vendor firmware,
|
||||
+ * new modes can only be added when new data layouts have been analyzed. For now allow BCH6 on
|
||||
+ * flash with 2048 byte blocks and at least 64 bytes oob. Some vendors make use of
|
||||
+ * 128 bytes OOB NAND chips (e.g. Macronix MX35LF1G24AD) but only use BCH6 and thus the first
|
||||
+ * 64 bytes of the OOB area. In this case the engine leaves any extra bytes unused.
|
||||
*
|
||||
* This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the
|
||||
* Realtek naming conventions for the different structures in the OOB area.
|
||||
@@ -39,7 +41,7 @@
|
||||
*/
|
||||
|
||||
#define RTL_ECC_ALLOWED_PAGE_SIZE 2048
|
||||
-#define RTL_ECC_ALLOWED_OOB_SIZE 64
|
||||
+#define RTL_ECC_ALLOWED_MIN_OOB_SIZE 64
|
||||
#define RTL_ECC_ALLOWED_STRENGTH 6
|
||||
|
||||
#define RTL_ECC_BLOCK_SIZE 512
|
||||
@@ -310,10 +312,10 @@ static int rtl_ecc_check_support(struct
|
||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
struct device *dev = nand->ecc.engine->dev;
|
||||
|
||||
- if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE ||
|
||||
+ if (mtd->oobsize < RTL_ECC_ALLOWED_MIN_OOB_SIZE ||
|
||||
mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) {
|
||||
- dev_err(dev, "only flash geometry data=%d, oob=%d supported\n",
|
||||
- RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE);
|
||||
+ dev_err(dev, "only flash geometry data=%d, oob>=%d supported\n",
|
||||
+ RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_MIN_OOB_SIZE);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
From 49944d6ab7eb951f2aefee69341c623e13434863 Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Thu, 5 Mar 2026 21:11:05 +0500
|
||||
Subject: dt-bindings: gpio: realtek-otto: add rtl9607 compatible
|
||||
|
||||
Add the "realtek,rtl9607-gpio" compatible for GPIO nodes
|
||||
on the RTL9607C SoC series.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Reviewed-by: Linus Walleij <linusw@kernel.org>
|
||||
Reviewed-by: Sander Vanheule <sander@svanheule.net>
|
||||
Link: https://patch.msgid.link/20260305161106.15999-2-adilov@disroot.org
|
||||
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
|
||||
---
|
||||
Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
--- a/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml
|
||||
+++ b/Documentation/devicetree/bindings/gpio/realtek,otto-gpio.yaml
|
||||
@@ -30,6 +30,7 @@ properties:
|
||||
- realtek,rtl8390-gpio
|
||||
- realtek,rtl9300-gpio
|
||||
- realtek,rtl9310-gpio
|
||||
+ - realtek,rtl9607-gpio
|
||||
- const: realtek,otto-gpio
|
||||
|
||||
reg: true
|
||||
@ -1,29 +0,0 @@
|
||||
From 8f0aecf2957e7dba78603544368846133bf6d22e Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Thu, 5 Mar 2026 21:11:06 +0500
|
||||
Subject: gpio: realtek-otto: add rtl9607 support
|
||||
|
||||
The RTL9607C SoC has support for 3 GPIO banks with 32 GPIOs each and
|
||||
the port order is reversed just like in RTL930x.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Reviewed-by: Sander Vanheule <sander@svanheule.net>
|
||||
Link: https://patch.msgid.link/20260305161106.15999-3-adilov@disroot.org
|
||||
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
|
||||
---
|
||||
drivers/gpio/gpio-realtek-otto.c | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
--- a/drivers/gpio/gpio-realtek-otto.c
|
||||
+++ b/drivers/gpio/gpio-realtek-otto.c
|
||||
@@ -350,6 +350,10 @@ static const struct of_device_id realtek
|
||||
{
|
||||
.compatible = "realtek,rtl9310-gpio",
|
||||
},
|
||||
+ {
|
||||
+ .compatible = "realtek,rtl9607-gpio",
|
||||
+ .data = (void *)GPIO_PORTS_REVERSED,
|
||||
+ },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, realtek_gpio_of_match);
|
||||
@ -1,37 +0,0 @@
|
||||
From c1887257a81bf62f48178d3b9d31e23520d67b2c Mon Sep 17 00:00:00 2001
|
||||
From: Damien Dejean <dam.dejean@gmail.com>
|
||||
Date: Wed, 18 Mar 2026 22:54:58 +0100
|
||||
Subject: [PATCH 1/4] dt-bindings: net: ethernet-phy: add property
|
||||
enet-phy-pair-order
|
||||
|
||||
Add property enet-phy-pair-order to the device tree bindings to define
|
||||
the pair order of the PHY. To simplify PCB design some manufacturers
|
||||
allow to wire the pairs in a reverse order, and change the order in
|
||||
software.
|
||||
|
||||
The property can be set to 0 to force the normal pair order (ABCD), or 1
|
||||
to force the reverse pair order (DCBA).
|
||||
|
||||
Signed-off-by: Damien Dejean <dam.dejean@gmail.com>
|
||||
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
|
||||
Link: https://patch.msgid.link/20260318215502.106528-2-dam.dejean@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
Documentation/devicetree/bindings/net/ethernet-phy.yaml | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
--- a/Documentation/devicetree/bindings/net/ethernet-phy.yaml
|
||||
+++ b/Documentation/devicetree/bindings/net/ethernet-phy.yaml
|
||||
@@ -122,6 +122,12 @@ properties:
|
||||
e.g. wrong bootstrap configuration caused by issues in PCB
|
||||
layout design.
|
||||
|
||||
+ enet-phy-pair-order:
|
||||
+ $ref: /schemas/types.yaml#/definitions/uint32
|
||||
+ enum: [0, 1]
|
||||
+ description:
|
||||
+ For normal (0) or reverse (1) order of the pairs (ABCD -> DCBA).
|
||||
+
|
||||
eee-broken-100tx:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
@ -1,124 +0,0 @@
|
||||
From 330296ea9e158758aa65631f5ec64aa74806b7e2 Mon Sep 17 00:00:00 2001
|
||||
From: Damien Dejean <dam.dejean@gmail.com>
|
||||
Date: Wed, 18 Mar 2026 22:54:59 +0100
|
||||
Subject: [PATCH 2/4] net: phy: realtek: add RTL8224 pair order support
|
||||
|
||||
The RTL8224 has a register to configure a pair swap (from ABCD order to
|
||||
DCBA) providing PCB designers more flexbility when wiring the chip. The
|
||||
swap parameter has to be set correctly for each of the 4 ports before
|
||||
the chip can detect a link.
|
||||
|
||||
After a reset, this register is (unfortunately) left in a random state,
|
||||
thus it has to be initialized. On most of the devices the bootloader
|
||||
does it once for all and we can rely on the value set, on some other it
|
||||
is not and the kernel has to do it.
|
||||
|
||||
The MDI pair swap can be set in the device tree using the property
|
||||
enet-phy-pair-order. The property is set to 0 to keep the default order
|
||||
(ABCD), or 1 to reverse the pairs (DCBA).
|
||||
|
||||
Signed-off-by: Damien Dejean <dam.dejean@gmail.com>
|
||||
Link: https://patch.msgid.link/20260318215502.106528-3-dam.dejean@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/phy/realtek/Kconfig | 1 +
|
||||
drivers/net/phy/realtek/realtek_main.c | 64 ++++++++++++++++++++++++++
|
||||
2 files changed, 65 insertions(+)
|
||||
|
||||
--- a/drivers/net/phy/realtek/Kconfig
|
||||
+++ b/drivers/net/phy/realtek/Kconfig
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config REALTEK_PHY
|
||||
tristate "Realtek PHYs"
|
||||
+ select PHY_PACKAGE
|
||||
help
|
||||
Currently supports RTL821x/RTL822x and fast ethernet PHYs
|
||||
|
||||
--- a/drivers/net/phy/realtek/realtek_main.c
|
||||
+++ b/drivers/net/phy/realtek/realtek_main.c
|
||||
@@ -175,6 +175,8 @@
|
||||
#define RTL8221B_PHYCR1_ALDPS_XTAL_OFF_EN BIT(12)
|
||||
#define RTL8221B_PHYCR1_PHYAD_0_EN BIT(13)
|
||||
|
||||
+#define RTL8224_VND1_MDI_PAIR_SWAP 0xa90
|
||||
+
|
||||
#define RTL8366RB_POWER_SAVE 0x15
|
||||
#define RTL8366RB_POWER_SAVE_ON BIT(12)
|
||||
|
||||
@@ -1865,6 +1867,66 @@ static int rtl8224_cable_test_get_status
|
||||
return rtl8224_cable_test_report(phydev, finished);
|
||||
}
|
||||
|
||||
+static int rtl8224_package_modify_mmd(struct phy_device *phydev, int devad,
|
||||
+ u32 regnum, u16 mask, u16 set)
|
||||
+{
|
||||
+ int val, ret;
|
||||
+
|
||||
+ phy_lock_mdio_bus(phydev);
|
||||
+
|
||||
+ val = __phy_package_read_mmd(phydev, 0, devad, regnum);
|
||||
+ if (val < 0) {
|
||||
+ ret = val;
|
||||
+ goto exit;
|
||||
+ }
|
||||
+
|
||||
+ val &= ~mask;
|
||||
+ val |= set;
|
||||
+
|
||||
+ ret = __phy_package_write_mmd(phydev, 0, devad, regnum, val);
|
||||
+
|
||||
+exit:
|
||||
+ phy_unlock_mdio_bus(phydev);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int rtl8224_mdi_config_order(struct phy_device *phydev)
|
||||
+{
|
||||
+ struct device_node *np = phydev->mdio.dev.of_node;
|
||||
+ u8 port_offset = phydev->mdio.addr & 3;
|
||||
+ u32 order = 0;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = of_property_read_u32(np, "enet-phy-pair-order", &order);
|
||||
+
|
||||
+ /* Do nothing in case the property is not present */
|
||||
+ if (ret == -EINVAL || ret == -ENOSYS)
|
||||
+ return 0;
|
||||
+
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (order & ~1)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ return rtl8224_package_modify_mmd(phydev, MDIO_MMD_VEND1,
|
||||
+ RTL8224_VND1_MDI_PAIR_SWAP,
|
||||
+ BIT(port_offset),
|
||||
+ order ? BIT(port_offset) : 0);
|
||||
+}
|
||||
+
|
||||
+static int rtl8224_config_init(struct phy_device *phydev)
|
||||
+{
|
||||
+ return rtl8224_mdi_config_order(phydev);
|
||||
+}
|
||||
+
|
||||
+static int rtl8224_probe(struct phy_device *phydev)
|
||||
+{
|
||||
+ /* Chip exposes 4 ports, join all of them in the same package */
|
||||
+ return devm_phy_package_join(&phydev->mdio.dev, phydev,
|
||||
+ phydev->mdio.addr & ~3, 0);
|
||||
+}
|
||||
+
|
||||
static bool rtlgen_supports_2_5gbps(struct phy_device *phydev)
|
||||
{
|
||||
int val;
|
||||
@@ -2466,6 +2528,8 @@ static struct phy_driver realtek_drvs[]
|
||||
PHY_ID_MATCH_EXACT(0x001ccad0),
|
||||
.name = "RTL8224 2.5Gbps PHY",
|
||||
.flags = PHY_POLL_CABLE_TEST,
|
||||
+ .probe = rtl8224_probe,
|
||||
+ .config_init = rtl8224_config_init,
|
||||
.get_features = rtl822x_c45_get_features,
|
||||
.config_aneg = rtl822x_c45_config_aneg,
|
||||
.read_status = rtl822x_c45_read_status,
|
||||
@ -1,39 +0,0 @@
|
||||
From 58ffb5910f32e5b387d4af31ee21851c40eb31b5 Mon Sep 17 00:00:00 2001
|
||||
From: Damien Dejean <dam.dejean@gmail.com>
|
||||
Date: Wed, 18 Mar 2026 22:55:00 +0100
|
||||
Subject: [PATCH 3/4] dt-bindings: net: ethernet-phy: add property
|
||||
enet-phy-pair-polarity
|
||||
|
||||
Add the property enet-phy-pair-polarity to describe the polarity of the
|
||||
PHY pairs. To ease PCB designs some manufacturers allow to wire the
|
||||
pairs with a reverse polarity and provide a way to configure it.
|
||||
|
||||
The property 'enet-phy-pair-polarity' sets the polarity of each pair.
|
||||
Bit 0 to 3 configure the polarity or pairs A to D, if set to 1 the
|
||||
polarity is reversed for this pair.
|
||||
|
||||
Signed-off-by: Damien Dejean <dam.dejean@gmail.com>
|
||||
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
|
||||
Link: https://patch.msgid.link/20260318215502.106528-4-dam.dejean@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
Documentation/devicetree/bindings/net/ethernet-phy.yaml | 8 ++++++++
|
||||
1 file changed, 8 insertions(+)
|
||||
|
||||
--- a/Documentation/devicetree/bindings/net/ethernet-phy.yaml
|
||||
+++ b/Documentation/devicetree/bindings/net/ethernet-phy.yaml
|
||||
@@ -128,6 +128,14 @@ properties:
|
||||
description:
|
||||
For normal (0) or reverse (1) order of the pairs (ABCD -> DCBA).
|
||||
|
||||
+ enet-phy-pair-polarity:
|
||||
+ $ref: /schemas/types.yaml#/definitions/uint32
|
||||
+ maximum: 0xf
|
||||
+ description:
|
||||
+ A bitmap to describe pair polarity swap. Bit 0 to swap polarity of pair A,
|
||||
+ bit 1 to swap polarity of pair B, bit 2 to swap polarity of pair C and bit
|
||||
+ 3 to swap polarity of pair D.
|
||||
+
|
||||
eee-broken-100tx:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
@ -1,76 +0,0 @@
|
||||
From beed9c0e9b53c98bc66d28d46fbe38c347e9aa74 Mon Sep 17 00:00:00 2001
|
||||
From: Damien Dejean <dam.dejean@gmail.com>
|
||||
Date: Wed, 18 Mar 2026 22:55:01 +0100
|
||||
Subject: [PATCH 4/4] net: phy: realtek: add RTL8224 polarity support
|
||||
|
||||
The RTL8224 has a register to configure the polarity of every pair of
|
||||
each port. It provides device designers more flexbility when wiring the
|
||||
chip.
|
||||
|
||||
Unfortunately, the register is left in an unknown state after a reset.
|
||||
Thus on devices where the bootloader don't initialize it, the driver has
|
||||
to do it to detect and use a link.
|
||||
|
||||
The MDI polarity swap can be set in the device tree using the property
|
||||
enet-phy-pair-polarity. The u32 value is a bitfield where bit[0..3]
|
||||
control the polarity of pairs A..D.
|
||||
|
||||
Signed-off-by: Damien Dejean <dam.dejean@gmail.com>
|
||||
Link: https://patch.msgid.link/20260318215502.106528-5-dam.dejean@gmail.com
|
||||
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||
---
|
||||
drivers/net/phy/realtek/realtek_main.c | 34 +++++++++++++++++++++++++-
|
||||
1 file changed, 33 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/net/phy/realtek/realtek_main.c
|
||||
+++ b/drivers/net/phy/realtek/realtek_main.c
|
||||
@@ -176,6 +176,7 @@
|
||||
#define RTL8221B_PHYCR1_PHYAD_0_EN BIT(13)
|
||||
|
||||
#define RTL8224_VND1_MDI_PAIR_SWAP 0xa90
|
||||
+#define RTL8224_VND1_MDI_POLARITY_SWAP 0xa94
|
||||
|
||||
#define RTL8366RB_POWER_SAVE 0x15
|
||||
#define RTL8366RB_POWER_SAVE_ON BIT(12)
|
||||
@@ -1915,9 +1916,40 @@ static int rtl8224_mdi_config_order(stru
|
||||
order ? BIT(port_offset) : 0);
|
||||
}
|
||||
|
||||
+static int rtl8224_mdi_config_polarity(struct phy_device *phydev)
|
||||
+{
|
||||
+ struct device_node *np = phydev->mdio.dev.of_node;
|
||||
+ u8 offset = (phydev->mdio.addr & 3) * 4;
|
||||
+ u32 polarity = 0;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = of_property_read_u32(np, "enet-phy-pair-polarity", &polarity);
|
||||
+
|
||||
+ /* Do nothing if the property is not present */
|
||||
+ if (ret == -EINVAL || ret == -ENOSYS)
|
||||
+ return 0;
|
||||
+
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (polarity & ~0xf)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ return rtl8224_package_modify_mmd(phydev, MDIO_MMD_VEND1,
|
||||
+ RTL8224_VND1_MDI_POLARITY_SWAP,
|
||||
+ 0xf << offset,
|
||||
+ polarity << offset);
|
||||
+}
|
||||
+
|
||||
static int rtl8224_config_init(struct phy_device *phydev)
|
||||
{
|
||||
- return rtl8224_mdi_config_order(phydev);
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = rtl8224_mdi_config_order(phydev);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return rtl8224_mdi_config_polarity(phydev);
|
||||
}
|
||||
|
||||
static int rtl8224_probe(struct phy_device *phydev)
|
||||
@ -1,54 +0,0 @@
|
||||
From fc31008d5f57e71afa124550ca01b4399434435e Mon Sep 17 00:00:00 2001
|
||||
From: Rosen Penev <rosenp@gmail.com>
|
||||
Date: Tue, 16 Dec 2025 22:30:26 -0800
|
||||
Subject: i2c: rtl9300: remove const cast
|
||||
|
||||
These casts are used to remove const for no good reason. Fix the types
|
||||
instead.
|
||||
|
||||
Signed-off-by: Rosen Penev <rosenp@gmail.com>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20251217063027.37987-2-rosenp@gmail.com
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -129,7 +129,7 @@ static int rtl9310_i2c_select_scl(struct
|
||||
|
||||
static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
{
|
||||
- struct rtl9300_i2c_drv_data *drv_data;
|
||||
+ const struct rtl9300_i2c_drv_data *drv_data;
|
||||
int ret;
|
||||
|
||||
if (i2c->sda_num == chan->sda_num)
|
||||
@@ -139,7 +139,7 @@ static int rtl9300_i2c_config_chan(struc
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+ drv_data = device_get_match_data(i2c->dev);
|
||||
ret = drv_data->select_scl(i2c, i2c->scl_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -372,7 +372,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rtl9300_i2c *i2c;
|
||||
struct fwnode_handle *child;
|
||||
- struct rtl9300_i2c_drv_data *drv_data;
|
||||
+ const struct rtl9300_i2c_drv_data *drv_data;
|
||||
struct reg_field fields[F_NUM_FIELDS];
|
||||
u32 clock_freq, scl_num, sda_num;
|
||||
int ret, i = 0;
|
||||
@@ -399,7 +399,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
|
||||
platform_set_drvdata(pdev, i2c);
|
||||
|
||||
- drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
|
||||
+ drv_data = device_get_match_data(i2c->dev);
|
||||
if (device_get_child_node_count(dev) > drv_data->max_nchan)
|
||||
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
From f6551f7861aca09cb2fdf675d6bb9ca2ffa9038a Mon Sep 17 00:00:00 2001
|
||||
From: Rosen Penev <rosenp@gmail.com>
|
||||
Date: Tue, 16 Dec 2025 22:30:27 -0800
|
||||
Subject: i2c: rtl9300: use of instead of fwnode
|
||||
|
||||
Avoids having to use to_of_node and just assign directly. This is an OF
|
||||
only driver anyway.
|
||||
|
||||
Use _scoped for the for each loop to avoid refcount leaks.
|
||||
|
||||
Signed-off-by: Rosen Penev <rosenp@gmail.com>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20251217063027.37987-3-rosenp@gmail.com
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 9 ++++-----
|
||||
1 file changed, 4 insertions(+), 5 deletions(-)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -371,7 +371,6 @@ static int rtl9300_i2c_probe(struct plat
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rtl9300_i2c *i2c;
|
||||
- struct fwnode_handle *child;
|
||||
const struct rtl9300_i2c_drv_data *drv_data;
|
||||
struct reg_field fields[F_NUM_FIELDS];
|
||||
u32 clock_freq, scl_num, sda_num;
|
||||
@@ -415,15 +414,15 @@ static int rtl9300_i2c_probe(struct plat
|
||||
return ret;
|
||||
|
||||
i = 0;
|
||||
- device_for_each_child_node(dev, child) {
|
||||
+ for_each_child_of_node_scoped(dev->of_node, child) {
|
||||
struct rtl9300_i2c_chan *chan = &i2c->chans[i];
|
||||
struct i2c_adapter *adap = &chan->adap;
|
||||
|
||||
- ret = fwnode_property_read_u32(child, "reg", &sda_num);
|
||||
+ ret = of_property_read_u32(child, "reg", &sda_num);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = fwnode_property_read_u32(child, "clock-frequency", &clock_freq);
|
||||
+ ret = of_property_read_u32(child, "clock-frequency", &clock_freq);
|
||||
if (ret)
|
||||
clock_freq = I2C_MAX_STANDARD_MODE_FREQ;
|
||||
|
||||
@@ -449,7 +448,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
adap->retries = 3;
|
||||
adap->dev.parent = dev;
|
||||
i2c_set_adapdata(adap, chan);
|
||||
- adap->dev.of_node = to_of_node(child);
|
||||
+ adap->dev.of_node = child;
|
||||
snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_num);
|
||||
i++;
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
From 879766b58ea5cba79ff5fe46f062ed8e05e715aa Mon Sep 17 00:00:00 2001
|
||||
From: Jan Kantert <jan-kernel@kantert.net>
|
||||
Date: Fri, 27 Feb 2026 12:11:34 +0100
|
||||
Subject: i2c: rtl9300: add support for 50 kHz and 2.5 MHz bus speeds
|
||||
|
||||
Some SFP modules on certain switches (for example the ONTi ONT-S508CL-8S and
|
||||
XikeStor SKS8300-8X) exhibit unreliable I2C communication at the currently
|
||||
supported speeds. Add support for 50 kHz and 2.5 MHz I2C bus modes on the
|
||||
RTL9300 to improve compatibility with these devices.
|
||||
|
||||
Signed-off-by: Jan Kantert <jan-kernel@kantert.net>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260227111134.2163701-1-jan-kernel@kantert.net
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 16 ++++++++++++++--
|
||||
1 file changed, 14 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -11,10 +11,16 @@
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
enum rtl9300_bus_freq {
|
||||
- RTL9300_I2C_STD_FREQ,
|
||||
- RTL9300_I2C_FAST_FREQ,
|
||||
+ RTL9300_I2C_STD_FREQ, // 100kHz
|
||||
+ RTL9300_I2C_FAST_FREQ, // 400kHz
|
||||
+ RTL9300_I2C_SUPER_FAST_FREQ, // 2.5MHz
|
||||
+ RTL9300_I2C_SLOW_FREQ, // 50kHz
|
||||
};
|
||||
|
||||
+#define RTL9300_I2C_MAX_SUPER_FAST_FREQ 2500000
|
||||
+#define RTL9300_I2C_MAX_SLOW_FREQ 50000
|
||||
+
|
||||
+
|
||||
struct rtl9300_i2c;
|
||||
|
||||
struct rtl9300_i2c_chan {
|
||||
@@ -433,6 +439,12 @@ static int rtl9300_i2c_probe(struct plat
|
||||
case I2C_MAX_FAST_MODE_FREQ:
|
||||
chan->bus_freq = RTL9300_I2C_FAST_FREQ;
|
||||
break;
|
||||
+ case RTL9300_I2C_MAX_SUPER_FAST_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_SUPER_FAST_FREQ;
|
||||
+ break;
|
||||
+ case RTL9300_I2C_MAX_SLOW_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_SLOW_FREQ;
|
||||
+ break;
|
||||
default:
|
||||
dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
|
||||
sda_num, clock_freq);
|
||||
@ -1,112 +0,0 @@
|
||||
From 4c53b2eb4f18102c36d4bcaf8c604a1825701ffb Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Wed, 1 Apr 2026 23:06:41 +0500
|
||||
Subject: i2c: rtl9300: split data_reg into read and write reg
|
||||
|
||||
In RTL9607C i2c controller, there are 2 separate registers for reads
|
||||
and writes as opposed the combined 1 on rtl9300 and rtl9310.
|
||||
|
||||
In preparation for RTL9607C support, split it up into rd_reg and wd_reg
|
||||
properties and change the i2c read and write functions accordingly.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260401180648.337834-2-adilov@disroot.org
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 25 +++++++++++++++----------
|
||||
1 file changed, 15 insertions(+), 10 deletions(-)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -61,7 +61,8 @@ enum rtl9300_i2c_reg_fields {
|
||||
struct rtl9300_i2c_drv_data {
|
||||
struct rtl9300_i2c_reg_field field_desc[F_NUM_FIELDS];
|
||||
int (*select_scl)(struct rtl9300_i2c *i2c, u8 scl);
|
||||
- u32 data_reg;
|
||||
+ u32 rd_reg;
|
||||
+ u32 wd_reg;
|
||||
u8 max_nchan;
|
||||
};
|
||||
|
||||
@@ -74,7 +75,8 @@ struct rtl9300_i2c {
|
||||
struct rtl9300_i2c_chan chans[RTL9310_I2C_MUX_NCHAN];
|
||||
struct regmap_field *fields[F_NUM_FIELDS];
|
||||
u32 reg_base;
|
||||
- u32 data_reg;
|
||||
+ u32 rd_reg;
|
||||
+ u32 wd_reg;
|
||||
u8 scl_num;
|
||||
u8 sda_num;
|
||||
struct mutex lock;
|
||||
@@ -171,7 +173,7 @@ static int rtl9300_i2c_read(struct rtl93
|
||||
if (len > 16)
|
||||
return -EIO;
|
||||
|
||||
- ret = regmap_bulk_read(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals));
|
||||
+ ret = regmap_bulk_read(i2c->regmap, i2c->rd_reg, vals, ARRAY_SIZE(vals));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -198,12 +200,12 @@ static int rtl9300_i2c_write(struct rtl9
|
||||
vals[reg] |= buf[i] << shift;
|
||||
}
|
||||
|
||||
- return regmap_bulk_write(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals));
|
||||
+ return regmap_bulk_write(i2c->regmap, i2c->wd_reg, vals, ARRAY_SIZE(vals));
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
|
||||
{
|
||||
- return regmap_write(i2c->regmap, i2c->data_reg, data);
|
||||
+ return regmap_write(i2c->regmap, i2c->wd_reg, data);
|
||||
}
|
||||
|
||||
static int rtl9300_i2c_prepare_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
|
||||
@@ -268,14 +270,14 @@ static int rtl9300_i2c_do_xfer(struct rt
|
||||
if (!xfer->write) {
|
||||
switch (xfer->type) {
|
||||
case RTL9300_I2C_XFER_BYTE:
|
||||
- ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
+ ret = regmap_read(i2c->regmap, i2c->rd_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*xfer->data = val & 0xff;
|
||||
break;
|
||||
case RTL9300_I2C_XFER_WORD:
|
||||
- ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
|
||||
+ ret = regmap_read(i2c->regmap, i2c->rd_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -408,7 +410,8 @@ static int rtl9300_i2c_probe(struct plat
|
||||
if (device_get_child_node_count(dev) > drv_data->max_nchan)
|
||||
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
|
||||
|
||||
- i2c->data_reg = i2c->reg_base + drv_data->data_reg;
|
||||
+ i2c->rd_reg = i2c->reg_base + drv_data->rd_reg;
|
||||
+ i2c->wd_reg = i2c->reg_base + drv_data->wd_reg;
|
||||
for (i = 0; i < F_NUM_FIELDS; i++) {
|
||||
fields[i] = drv_data->field_desc[i].field;
|
||||
if (drv_data->field_desc[i].scope == REG_SCOPE_MASTER)
|
||||
@@ -499,7 +502,8 @@ static const struct rtl9300_i2c_drv_data
|
||||
[F_SDA_SEL] = GLB_REG_FIELD(RTL9300_I2C_MST_GLB_CTRL, 0, 7),
|
||||
},
|
||||
.select_scl = rtl9300_i2c_select_scl,
|
||||
- .data_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
+ .rd_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
+ .wd_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
.max_nchan = RTL9300_I2C_MUX_NCHAN,
|
||||
};
|
||||
|
||||
@@ -519,7 +523,8 @@ static const struct rtl9300_i2c_drv_data
|
||||
[F_MEM_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_MEMADDR_CTRL, 0, 23),
|
||||
},
|
||||
.select_scl = rtl9310_i2c_select_scl,
|
||||
- .data_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
+ .rd_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
+ .wd_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
.max_nchan = RTL9310_I2C_MUX_NCHAN,
|
||||
};
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
From 98773df61f8416594ac993e8464df596755ee1b8 Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Wed, 1 Apr 2026 23:06:42 +0500
|
||||
Subject: i2c: rtl9300: introduce max length property to driver data
|
||||
|
||||
In RTL9607C i2c controller, theoretical maximum the data length
|
||||
can be is 4 bytes as opposed to 16 bytes on rtl9300 and rtl9310.
|
||||
|
||||
Introduce a new property to the driver data struct for that.
|
||||
Adjust if statement in prepare_xfer function to follow that new
|
||||
property instead of the hardcoded value.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260401180648.337834-3-adilov@disroot.org
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 9 ++++++++-
|
||||
1 file changed, 8 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -64,11 +64,14 @@ struct rtl9300_i2c_drv_data {
|
||||
u32 rd_reg;
|
||||
u32 wd_reg;
|
||||
u8 max_nchan;
|
||||
+ u8 max_data_len;
|
||||
};
|
||||
|
||||
#define RTL9300_I2C_MUX_NCHAN 8
|
||||
#define RTL9310_I2C_MUX_NCHAN 12
|
||||
|
||||
+#define RTL9300_I2C_MAX_DATA_LEN 16
|
||||
+
|
||||
struct rtl9300_i2c {
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
@@ -210,9 +213,11 @@ static int rtl9300_i2c_writel(struct rtl
|
||||
|
||||
static int rtl9300_i2c_prepare_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
|
||||
{
|
||||
+ const struct rtl9300_i2c_drv_data *drv_data;
|
||||
int ret;
|
||||
|
||||
- if (xfer->data_len < 1 || xfer->data_len > 16)
|
||||
+ drv_data = device_get_match_data(i2c->dev);
|
||||
+ if (xfer->data_len < 1 || xfer->data_len > drv_data->max_data_len)
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_field_write(i2c->fields[F_DEV_ADDR], xfer->dev_addr);
|
||||
@@ -505,6 +510,7 @@ static const struct rtl9300_i2c_drv_data
|
||||
.rd_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
.wd_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
.max_nchan = RTL9300_I2C_MUX_NCHAN,
|
||||
+ .max_data_len = RTL9300_I2C_MAX_DATA_LEN,
|
||||
};
|
||||
|
||||
static const struct rtl9300_i2c_drv_data rtl9310_i2c_drv_data = {
|
||||
@@ -526,6 +532,7 @@ static const struct rtl9300_i2c_drv_data
|
||||
.rd_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
.wd_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
.max_nchan = RTL9310_I2C_MUX_NCHAN,
|
||||
+ .max_data_len = RTL9300_I2C_MAX_DATA_LEN,
|
||||
};
|
||||
|
||||
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
@ -1,56 +0,0 @@
|
||||
From 55284a806b63a412846b9ecd3846f2639eaeaff4 Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Wed, 1 Apr 2026 23:06:43 +0500
|
||||
Subject: i2c: rtl9300: introduce F_BUSY to the reg_fields struct
|
||||
|
||||
In RTL9607C i2c controller the busy check operation is done on the
|
||||
separate bit of the command register as opposed to self clearing
|
||||
command trigger bit on the rtl9300 and rtl9310 i2c controllers.
|
||||
|
||||
Introduce a new F_BUSY field to the reg_fields struct for that
|
||||
and change the regmap read poll function to use F_BUSY
|
||||
instead of I2C_TRIG.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260401180648.337834-4-adilov@disroot.org
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 5 ++++-
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -53,6 +53,7 @@ enum rtl9300_i2c_reg_fields {
|
||||
F_SCL_SEL,
|
||||
F_SDA_OUT_SEL,
|
||||
F_SDA_SEL,
|
||||
+ F_BUSY,
|
||||
|
||||
/* keep last */
|
||||
F_NUM_FIELDS
|
||||
@@ -262,7 +263,7 @@ static int rtl9300_i2c_do_xfer(struct rt
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- ret = regmap_field_read_poll_timeout(i2c->fields[F_I2C_TRIG], val, !val, 100, 100000);
|
||||
+ ret = regmap_field_read_poll_timeout(i2c->fields[F_BUSY], val, !val, 100, 100000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -505,6 +506,7 @@ static const struct rtl9300_i2c_drv_data
|
||||
[F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 2, 3),
|
||||
[F_SCL_FREQ] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 0, 1),
|
||||
[F_SDA_SEL] = GLB_REG_FIELD(RTL9300_I2C_MST_GLB_CTRL, 0, 7),
|
||||
+ [F_BUSY] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 0, 0),
|
||||
},
|
||||
.select_scl = rtl9300_i2c_select_scl,
|
||||
.rd_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
@@ -527,6 +529,7 @@ static const struct rtl9300_i2c_drv_data
|
||||
[F_I2C_FAIL] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 1, 1),
|
||||
[F_I2C_TRIG] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 0, 0),
|
||||
[F_MEM_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_MEMADDR_CTRL, 0, 23),
|
||||
+ [F_BUSY] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 0, 0),
|
||||
},
|
||||
.select_scl = rtl9310_i2c_select_scl,
|
||||
.rd_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
@ -1,79 +0,0 @@
|
||||
From 6afde011baaf722aa66c11696b6383f9ce85b653 Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Wed, 1 Apr 2026 23:06:44 +0500
|
||||
Subject: i2c: rtl9300: introduce a property for 8 bit width reg address
|
||||
|
||||
In RTL9607C i2c controller, in order to indicate that the width of
|
||||
memory address is 8 bits, 0 is written to MEM_ADDR_WIDTH field as
|
||||
opposed to 1 for RTL9300 and RTL9310.
|
||||
|
||||
Introduce a new property to a driver data to indicate what value
|
||||
need to written to MEM_ADDR_WIDTH field for this case.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260401180648.337834-5-adilov@disroot.org
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 8 +++++++-
|
||||
1 file changed, 7 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -66,6 +66,7 @@ struct rtl9300_i2c_drv_data {
|
||||
u32 wd_reg;
|
||||
u8 max_nchan;
|
||||
u8 max_data_len;
|
||||
+ u8 reg_addr_8bit_len;
|
||||
};
|
||||
|
||||
#define RTL9300_I2C_MUX_NCHAN 8
|
||||
@@ -111,6 +112,7 @@ struct rtl9300_i2c_xfer {
|
||||
#define RTL9300_I2C_MST_DATA_WORD2 0x10
|
||||
#define RTL9300_I2C_MST_DATA_WORD3 0x14
|
||||
#define RTL9300_I2C_MST_GLB_CTRL 0x384
|
||||
+#define RTL9300_REG_ADDR_8BIT_LEN 1
|
||||
|
||||
#define RTL9310_I2C_MST_IF_CTRL 0x1004
|
||||
#define RTL9310_I2C_MST_IF_SEL 0x1008
|
||||
@@ -305,6 +307,7 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
union i2c_smbus_data *data)
|
||||
{
|
||||
struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap);
|
||||
+ const struct rtl9300_i2c_drv_data *drv_data;
|
||||
struct rtl9300_i2c *i2c = chan->i2c;
|
||||
struct rtl9300_i2c_xfer xfer = {0};
|
||||
int ret;
|
||||
@@ -314,6 +317,7 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
|
||||
guard(rtl9300_i2c)(i2c);
|
||||
|
||||
+ drv_data = device_get_match_data(i2c->dev);
|
||||
ret = rtl9300_i2c_config_chan(i2c, chan);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -321,7 +325,7 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
xfer.dev_addr = addr & 0x7f;
|
||||
xfer.write = (read_write == I2C_SMBUS_WRITE);
|
||||
xfer.reg_addr = command;
|
||||
- xfer.reg_addr_len = 1;
|
||||
+ xfer.reg_addr_len = drv_data->reg_addr_8bit_len;
|
||||
|
||||
switch (size) {
|
||||
case I2C_SMBUS_BYTE:
|
||||
@@ -513,6 +517,7 @@ static const struct rtl9300_i2c_drv_data
|
||||
.wd_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
.max_nchan = RTL9300_I2C_MUX_NCHAN,
|
||||
.max_data_len = RTL9300_I2C_MAX_DATA_LEN,
|
||||
+ .reg_addr_8bit_len = RTL9300_REG_ADDR_8BIT_LEN,
|
||||
};
|
||||
|
||||
static const struct rtl9300_i2c_drv_data rtl9310_i2c_drv_data = {
|
||||
@@ -536,6 +541,7 @@ static const struct rtl9300_i2c_drv_data
|
||||
.wd_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
.max_nchan = RTL9310_I2C_MUX_NCHAN,
|
||||
.max_data_len = RTL9300_I2C_MAX_DATA_LEN,
|
||||
+ .reg_addr_8bit_len = RTL9300_REG_ADDR_8BIT_LEN,
|
||||
};
|
||||
|
||||
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
@ -1,73 +0,0 @@
|
||||
From 1211ce1e11d23ec05d80a85b7187baa6abed3232 Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Wed, 1 Apr 2026 23:06:45 +0500
|
||||
Subject: dt-bindings: i2c: realtek,rtl9301-i2c: extend for clocks and RTL9607C
|
||||
support
|
||||
|
||||
Add the "realtek,rtl9607-i2c" compatible for i2c controller on the
|
||||
RTL9607C SoC series.
|
||||
|
||||
Add a clocks property to the properties to describe the i2c reference
|
||||
clock and make it available for all the compatibles. This i2c reference
|
||||
clock is assumed to be coming from switchcore region via Lexra bus as
|
||||
the other SoC peripherals.
|
||||
|
||||
According to the info available about the existing devices, they also
|
||||
have the i2c master controller clocks.
|
||||
|
||||
RTL9607C requires the "realtek,scl" and "clocks" to be specified
|
||||
and so handle it under separate if check for "realtek,rtl9607-i2c".
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Acked-by: Conor Dooley <conor.dooley@microchip.com>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260401180648.337834-6-adilov@disroot.org
|
||||
---
|
||||
.../devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml | 15 +++++++++++++++
|
||||
1 file changed, 15 insertions(+)
|
||||
|
||||
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
|
||||
@@ -15,6 +15,8 @@ description:
|
||||
assigned to either I2C controller.
|
||||
RTL9310 SoCs have equal capabilities but support 12 common SDA lines which
|
||||
can be assigned to either I2C controller.
|
||||
+ RTL9607C SoCs have equal capabilities but each controller only supports 1
|
||||
+ SCL/SDA line.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
@@ -34,6 +36,7 @@ properties:
|
||||
- enum:
|
||||
- realtek,rtl9301-i2c
|
||||
- realtek,rtl9310-i2c
|
||||
+ - realtek,rtl9607-i2c
|
||||
|
||||
reg:
|
||||
items:
|
||||
@@ -51,6 +54,9 @@ properties:
|
||||
The SCL line number of this I2C controller.
|
||||
enum: [ 0, 1 ]
|
||||
|
||||
+ clocks:
|
||||
+ maxItems: 1
|
||||
+
|
||||
patternProperties:
|
||||
'^i2c@[0-9ab]$':
|
||||
$ref: /schemas/i2c/i2c-controller.yaml
|
||||
@@ -82,6 +88,15 @@ allOf:
|
||||
then:
|
||||
patternProperties:
|
||||
'^i2c@[89ab]$': false
|
||||
+ - if:
|
||||
+ properties:
|
||||
+ compatible:
|
||||
+ contains:
|
||||
+ const: realtek,rtl9607-i2c
|
||||
+ then:
|
||||
+ required:
|
||||
+ - realtek,scl
|
||||
+ - clocks
|
||||
|
||||
required:
|
||||
- compatible
|
||||
@ -1,70 +0,0 @@
|
||||
From f60d27926c9e2d547200fb0d26f61eec9b8291a6 Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Wed, 1 Apr 2026 23:06:46 +0500
|
||||
Subject: i2c: rtl9300: introduce clk struct for upcoming rtl9607 support
|
||||
|
||||
In RTL9607C i2c controller, there is 10 bit CLK_DIV field for
|
||||
setting the clock of i2c interface which depends on the rate
|
||||
of i2c clk (which seems be fixed to 62.5MHz according to Realtek SDK).
|
||||
|
||||
Introduce the clk struct and the respective F_CLK_DIV and clk_div
|
||||
which are going to be used in the upcoming patch for rtl9607c i2c
|
||||
controller support addition.
|
||||
|
||||
devm_clk_get_optional_enabled() function was used for cleaner code
|
||||
as it automatically returns NULL if the clk is not present, which is
|
||||
going to be the case for RTL9300 and RTL9310 i2c controllers.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260401180648.337834-7-adilov@disroot.org
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 8 ++++++++
|
||||
1 file changed, 8 insertions(+)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/bits.h>
|
||||
+#include <linux/clk.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-mux.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
@@ -28,6 +29,7 @@ struct rtl9300_i2c_chan {
|
||||
struct rtl9300_i2c *i2c;
|
||||
enum rtl9300_bus_freq bus_freq;
|
||||
u8 sda_num;
|
||||
+ u32 clk_div;
|
||||
};
|
||||
|
||||
enum rtl9300_i2c_reg_scope {
|
||||
@@ -54,6 +56,7 @@ enum rtl9300_i2c_reg_fields {
|
||||
F_SDA_OUT_SEL,
|
||||
F_SDA_SEL,
|
||||
F_BUSY,
|
||||
+ F_CLK_DIV,
|
||||
|
||||
/* keep last */
|
||||
F_NUM_FIELDS
|
||||
@@ -85,6 +88,7 @@ struct rtl9300_i2c {
|
||||
u8 scl_num;
|
||||
u8 sda_num;
|
||||
struct mutex lock;
|
||||
+ struct clk *clk;
|
||||
};
|
||||
|
||||
DEFINE_GUARD(rtl9300_i2c, struct rtl9300_i2c *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
|
||||
@@ -432,6 +436,10 @@ static int rtl9300_i2c_probe(struct plat
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ i2c->clk = devm_clk_get_optional_enabled(dev, NULL);
|
||||
+ if (IS_ERR(i2c->clk))
|
||||
+ return dev_err_probe(dev, PTR_ERR(i2c->clk), "Failed to enable i2c clock\n");
|
||||
+
|
||||
i = 0;
|
||||
for_each_child_of_node_scoped(dev->of_node, child) {
|
||||
struct rtl9300_i2c_chan *chan = &i2c->chans[i];
|
||||
@ -1,147 +0,0 @@
|
||||
From 991cd899ecd03a1c3ef7d177a0b99e824c6be581 Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Wed, 1 Apr 2026 23:06:47 +0500
|
||||
Subject: i2c: rtl9300: introduce new function properties to driver data
|
||||
|
||||
Due to the very nature of differences between RTL9607C i2c controller
|
||||
and RTL9300 / RTL9310 that are incompatible with each other in some areas
|
||||
of this driver, for example in clock configuration, channel configuration
|
||||
and initialization at the end of the probe, introduce new function
|
||||
properties to the driver data struct to handle those differences.
|
||||
|
||||
With these new properties, create configuration functions for RTL9300 and
|
||||
RTL9310 and assign them to their respective driver data structs.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260401180648.337834-8-adilov@disroot.org
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 66 ++++++++++++++++++++++++++--------------
|
||||
1 file changed, 44 insertions(+), 22 deletions(-)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -65,6 +65,9 @@ enum rtl9300_i2c_reg_fields {
|
||||
struct rtl9300_i2c_drv_data {
|
||||
struct rtl9300_i2c_reg_field field_desc[F_NUM_FIELDS];
|
||||
int (*select_scl)(struct rtl9300_i2c *i2c, u8 scl);
|
||||
+ int (*config_chan)(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan);
|
||||
+ void (*config_clock)(u32 clock_freq, struct rtl9300_i2c_chan *chan);
|
||||
+ int (*misc_init)(struct rtl9300_i2c *i2c);
|
||||
u32 rd_reg;
|
||||
u32 wd_reg;
|
||||
u8 max_nchan;
|
||||
@@ -175,6 +178,30 @@ static int rtl9300_i2c_config_chan(struc
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static void rtl9300_i2c_config_clock(u32 clock_freq, struct rtl9300_i2c_chan *chan)
|
||||
+{
|
||||
+ struct rtl9300_i2c *i2c = chan->i2c;
|
||||
+
|
||||
+ switch (clock_freq) {
|
||||
+ case I2C_MAX_STANDARD_MODE_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_STD_FREQ;
|
||||
+ break;
|
||||
+ case I2C_MAX_FAST_MODE_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_FAST_FREQ;
|
||||
+ break;
|
||||
+ case RTL9300_I2C_MAX_SUPER_FAST_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_SUPER_FAST_FREQ;
|
||||
+ break;
|
||||
+ case RTL9300_I2C_MAX_SLOW_FREQ:
|
||||
+ chan->bus_freq = RTL9300_I2C_SLOW_FREQ;
|
||||
+ break;
|
||||
+ default:
|
||||
+ dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
|
||||
+ chan->sda_num, clock_freq);
|
||||
+ break;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
|
||||
{
|
||||
u32 vals[4] = {};
|
||||
@@ -322,7 +349,7 @@ static int rtl9300_i2c_smbus_xfer(struct
|
||||
guard(rtl9300_i2c)(i2c);
|
||||
|
||||
drv_data = device_get_match_data(i2c->dev);
|
||||
- ret = rtl9300_i2c_config_chan(i2c, chan);
|
||||
+ ret = drv_data->config_chan(i2c, chan);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -389,6 +416,12 @@ static struct i2c_adapter_quirks rtl9300
|
||||
.max_write_len = 16,
|
||||
};
|
||||
|
||||
+static int rtl9300_i2c_init(struct rtl9300_i2c *i2c)
|
||||
+{
|
||||
+ /* only use standard read format */
|
||||
+ return regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
+}
|
||||
+
|
||||
static int rtl9300_i2c_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
@@ -453,27 +486,11 @@ static int rtl9300_i2c_probe(struct plat
|
||||
if (ret)
|
||||
clock_freq = I2C_MAX_STANDARD_MODE_FREQ;
|
||||
|
||||
- switch (clock_freq) {
|
||||
- case I2C_MAX_STANDARD_MODE_FREQ:
|
||||
- chan->bus_freq = RTL9300_I2C_STD_FREQ;
|
||||
- break;
|
||||
- case I2C_MAX_FAST_MODE_FREQ:
|
||||
- chan->bus_freq = RTL9300_I2C_FAST_FREQ;
|
||||
- break;
|
||||
- case RTL9300_I2C_MAX_SUPER_FAST_FREQ:
|
||||
- chan->bus_freq = RTL9300_I2C_SUPER_FAST_FREQ;
|
||||
- break;
|
||||
- case RTL9300_I2C_MAX_SLOW_FREQ:
|
||||
- chan->bus_freq = RTL9300_I2C_SLOW_FREQ;
|
||||
- break;
|
||||
- default:
|
||||
- dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
|
||||
- sda_num, clock_freq);
|
||||
- break;
|
||||
- }
|
||||
-
|
||||
chan->sda_num = sda_num;
|
||||
chan->i2c = i2c;
|
||||
+
|
||||
+ drv_data->config_clock(clock_freq, chan);
|
||||
+
|
||||
adap = &i2c->chans[i].adap;
|
||||
adap->owner = THIS_MODULE;
|
||||
adap->algo = &rtl9300_i2c_algo;
|
||||
@@ -491,8 +508,7 @@ static int rtl9300_i2c_probe(struct plat
|
||||
}
|
||||
i2c->sda_num = 0xff;
|
||||
|
||||
- /* only use standard read format */
|
||||
- ret = regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
+ ret = drv_data->misc_init(i2c);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -521,6 +537,9 @@ static const struct rtl9300_i2c_drv_data
|
||||
[F_BUSY] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 0, 0),
|
||||
},
|
||||
.select_scl = rtl9300_i2c_select_scl,
|
||||
+ .config_chan = rtl9300_i2c_config_chan,
|
||||
+ .config_clock = rtl9300_i2c_config_clock,
|
||||
+ .misc_init = rtl9300_i2c_init,
|
||||
.rd_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
.wd_reg = RTL9300_I2C_MST_DATA_WORD0,
|
||||
.max_nchan = RTL9300_I2C_MUX_NCHAN,
|
||||
@@ -545,6 +564,9 @@ static const struct rtl9300_i2c_drv_data
|
||||
[F_BUSY] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 0, 0),
|
||||
},
|
||||
.select_scl = rtl9310_i2c_select_scl,
|
||||
+ .config_chan = rtl9300_i2c_config_chan,
|
||||
+ .config_clock = rtl9300_i2c_config_clock,
|
||||
+ .misc_init = rtl9300_i2c_init,
|
||||
.rd_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
.wd_reg = RTL9310_I2C_MST_DATA_CTRL,
|
||||
.max_nchan = RTL9310_I2C_MUX_NCHAN,
|
||||
@ -1,154 +0,0 @@
|
||||
From 40890b5fe72b1a0d4913883844854f6641a2f4b3 Mon Sep 17 00:00:00 2001
|
||||
From: Rustam Adilov <adilov@disroot.org>
|
||||
Date: Wed, 1 Apr 2026 23:06:48 +0500
|
||||
Subject: i2c: rtl9300: add RTL9607C i2c controller support
|
||||
|
||||
Add support for the internal I2C controllers of RTL9607C series based
|
||||
SoCs. Add register definitions, chip-specific functions and macros too.
|
||||
|
||||
Make use of the clk introduced from the previous patch to get the clk_div
|
||||
value and use it during the rtl9607c channel configuration.
|
||||
|
||||
Introduce a new EXT_SCK_5MS field to the reg fields struct which is going
|
||||
to be initialized by rtl9607c init function at the end of the probe.
|
||||
|
||||
This patch depends on all the previous patches in this patch series.
|
||||
|
||||
Signed-off-by: Rustam Adilov <adilov@disroot.org>
|
||||
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
|
||||
Link: https://lore.kernel.org/r/20260401180648.337834-9-adilov@disroot.org
|
||||
---
|
||||
drivers/i2c/busses/i2c-rtl9300.c | 70 ++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 70 insertions(+)
|
||||
|
||||
--- a/drivers/i2c/busses/i2c-rtl9300.c
|
||||
+++ b/drivers/i2c/busses/i2c-rtl9300.c
|
||||
@@ -57,6 +57,7 @@ enum rtl9300_i2c_reg_fields {
|
||||
F_SDA_SEL,
|
||||
F_BUSY,
|
||||
F_CLK_DIV,
|
||||
+ F_EXT_SCK_5MS,
|
||||
|
||||
/* keep last */
|
||||
F_NUM_FIELDS
|
||||
@@ -77,8 +78,10 @@ struct rtl9300_i2c_drv_data {
|
||||
|
||||
#define RTL9300_I2C_MUX_NCHAN 8
|
||||
#define RTL9310_I2C_MUX_NCHAN 12
|
||||
+#define RTL9607_I2C_MUX_NCHAN 1
|
||||
|
||||
#define RTL9300_I2C_MAX_DATA_LEN 16
|
||||
+#define RTL9607_I2C_MAX_DATA_LEN 4
|
||||
|
||||
struct rtl9300_i2c {
|
||||
struct regmap *regmap;
|
||||
@@ -127,6 +130,14 @@ struct rtl9300_i2c_xfer {
|
||||
#define RTL9310_I2C_MST_MEMADDR_CTRL 0x4
|
||||
#define RTL9310_I2C_MST_DATA_CTRL 0x8
|
||||
|
||||
+#define RTL9607_I2C_CONFIG 0x22f50
|
||||
+#define RTL9607_IO_MODE_EN 0x23014
|
||||
+#define RTL9607_I2C_IND_WD 0x0
|
||||
+#define RTL9607_I2C_IND_ADR 0x8
|
||||
+#define RTL9607_I2C_IND_CMD 0x10
|
||||
+#define RTL9607_I2C_IND_RD 0x18
|
||||
+#define RTL9607_REG_ADDR_8BIT_LEN 0
|
||||
+
|
||||
static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
|
||||
{
|
||||
int ret;
|
||||
@@ -178,6 +189,27 @@ static int rtl9300_i2c_config_chan(struc
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int rtl9607_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
|
||||
+{
|
||||
+ const struct rtl9300_i2c_drv_data *drv_data;
|
||||
+ int ret;
|
||||
+
|
||||
+ if (i2c->sda_num == chan->sda_num)
|
||||
+ return 0;
|
||||
+
|
||||
+ ret = regmap_field_write(i2c->fields[F_CLK_DIV], chan->clk_div);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ drv_data = device_get_match_data(i2c->dev);
|
||||
+ ret = drv_data->select_scl(i2c, i2c->scl_num);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ i2c->sda_num = chan->sda_num;
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static void rtl9300_i2c_config_clock(u32 clock_freq, struct rtl9300_i2c_chan *chan)
|
||||
{
|
||||
struct rtl9300_i2c *i2c = chan->i2c;
|
||||
@@ -202,6 +234,13 @@ static void rtl9300_i2c_config_clock(u32
|
||||
}
|
||||
}
|
||||
|
||||
+static void rtl9607_i2c_config_clock(u32 clock_freq, struct rtl9300_i2c_chan *chan)
|
||||
+{
|
||||
+ struct rtl9300_i2c *i2c = chan->i2c;
|
||||
+
|
||||
+ chan->clk_div = clk_get_rate(i2c->clk) / clock_freq - 1;
|
||||
+}
|
||||
+
|
||||
static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
|
||||
{
|
||||
u32 vals[4] = {};
|
||||
@@ -422,6 +461,11 @@ static int rtl9300_i2c_init(struct rtl93
|
||||
return regmap_field_write(i2c->fields[F_RD_MODE], 0);
|
||||
}
|
||||
|
||||
+static int rtl9607_i2c_init(struct rtl9300_i2c *i2c)
|
||||
+{
|
||||
+ return regmap_field_write(i2c->fields[F_EXT_SCK_5MS], 1);
|
||||
+}
|
||||
+
|
||||
static int rtl9300_i2c_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
@@ -574,6 +618,31 @@ static const struct rtl9300_i2c_drv_data
|
||||
.reg_addr_8bit_len = RTL9300_REG_ADDR_8BIT_LEN,
|
||||
};
|
||||
|
||||
+static const struct rtl9300_i2c_drv_data rtl9607_i2c_drv_data = {
|
||||
+ .field_desc = {
|
||||
+ [F_SCL_SEL] = GLB_REG_FIELD(RTL9607_IO_MODE_EN, 13, 14),
|
||||
+ [F_EXT_SCK_5MS] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 26, 26),
|
||||
+ [F_DEV_ADDR] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 14, 20),
|
||||
+ [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 12, 13),
|
||||
+ [F_DATA_WIDTH] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 10, 11),
|
||||
+ [F_CLK_DIV] = MST_REG_FIELD(RTL9607_I2C_CONFIG, 0, 9),
|
||||
+ [F_I2C_FAIL] = MST_REG_FIELD(RTL9607_I2C_IND_CMD, 3, 3),
|
||||
+ [F_BUSY] = MST_REG_FIELD(RTL9607_I2C_IND_CMD, 2, 2),
|
||||
+ [F_RWOP] = MST_REG_FIELD(RTL9607_I2C_IND_CMD, 1, 1),
|
||||
+ [F_I2C_TRIG] = MST_REG_FIELD(RTL9607_I2C_IND_CMD, 0, 0),
|
||||
+ [F_MEM_ADDR] = MST_REG_FIELD(RTL9607_I2C_IND_ADR, 0, 31),
|
||||
+ },
|
||||
+ .select_scl = rtl9310_i2c_select_scl,
|
||||
+ .config_chan = rtl9607_i2c_config_chan,
|
||||
+ .config_clock = rtl9607_i2c_config_clock,
|
||||
+ .misc_init = rtl9607_i2c_init,
|
||||
+ .rd_reg = RTL9607_I2C_IND_RD,
|
||||
+ .wd_reg = RTL9607_I2C_IND_WD,
|
||||
+ .max_nchan = RTL9607_I2C_MUX_NCHAN,
|
||||
+ .max_data_len = RTL9607_I2C_MAX_DATA_LEN,
|
||||
+ .reg_addr_8bit_len = RTL9607_REG_ADDR_8BIT_LEN,
|
||||
+};
|
||||
+
|
||||
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
|
||||
{ .compatible = "realtek,rtl9301-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9302b-i2c", .data = (void *) &rtl9300_i2c_drv_data },
|
||||
@@ -583,6 +652,7 @@ static const struct of_device_id i2c_rtl
|
||||
{ .compatible = "realtek,rtl9311-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9312-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
{ .compatible = "realtek,rtl9313-i2c", .data = (void *) &rtl9310_i2c_drv_data },
|
||||
+ { .compatible = "realtek,rtl9607-i2c", .data = (void *) &rtl9607_i2c_drv_data },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
|
||||
@ -1,91 +0,0 @@
|
||||
From fce11f68491b46b93df69de0630cd9edb90bc772 Mon Sep 17 00:00:00 2001
|
||||
From: Birger Koblitz <git@birger-koblitz.de>
|
||||
Date: Wed, 29 Dec 2021 21:54:21 +0100
|
||||
Subject: [PATCH] realtek: Create 4 different Realtek Platforms
|
||||
|
||||
Creates RTL83XX as a basic kernel config parameter for the
|
||||
RTL838X, RTL839x, RTL930X and RTL931X platforms with respective
|
||||
configurations for the SoCs, which are introduced in addition.
|
||||
|
||||
Submitted-by: Birger Koblitz <git@birger-koblitz.de>
|
||||
---
|
||||
--- a/arch/mips/Kbuild.platforms
|
||||
+++ b/arch/mips/Kbuild.platforms
|
||||
@@ -22,6 +22,7 @@ platform-$(CONFIG_MACH_NINTENDO64) += n6
|
||||
platform-$(CONFIG_PIC32MZDA) += pic32/
|
||||
platform-$(CONFIG_RALINK) += ralink/
|
||||
platform-$(CONFIG_MIKROTIK_RB532) += rb532/
|
||||
+platform-$(CONFIG_MACH_REALTEK_RTL) += rtl-otto/
|
||||
platform-$(CONFIG_SGI_IP22) += sgi-ip22/
|
||||
platform-$(CONFIG_SGI_IP27) += sgi-ip27/
|
||||
platform-$(CONFIG_SGI_IP28) += sgi-ip22/
|
||||
--- a/arch/mips/Kconfig
|
||||
+++ b/arch/mips/Kconfig
|
||||
@@ -651,23 +651,24 @@ config RALINK
|
||||
|
||||
config MACH_REALTEK_RTL
|
||||
bool "Realtek RTL838x/RTL839x based machines"
|
||||
- select MIPS_GENERIC
|
||||
- select MACH_GENERIC_CORE
|
||||
select DMA_NONCOHERENT
|
||||
select IRQ_MIPS_CPU
|
||||
- select CSRC_R4K
|
||||
- select CEVT_R4K
|
||||
select SYS_HAS_CPU_MIPS32_R1
|
||||
select SYS_HAS_CPU_MIPS32_R2
|
||||
select SYS_SUPPORTS_BIG_ENDIAN
|
||||
select SYS_SUPPORTS_32BIT_KERNEL
|
||||
select SYS_SUPPORTS_MIPS16
|
||||
- select SYS_SUPPORTS_MULTITHREADING
|
||||
- select SYS_SUPPORTS_VPE_LOADER
|
||||
select BOOT_RAW
|
||||
select PINCTRL
|
||||
select USE_OF
|
||||
select REALTEK_OTTO_TIMER
|
||||
+ select NO_EXCEPT_FILL
|
||||
+ select SYS_SUPPORTS_HIGHMEM
|
||||
+ select SYS_HAS_EARLY_PRINTK
|
||||
+ select SYS_HAS_EARLY_PRINTK_8250
|
||||
+ select USE_GENERIC_EARLY_PRINTK_8250
|
||||
+ select ARCH_HAS_RESET_CONTROLLER
|
||||
+ select RESET_CONTROLLER
|
||||
|
||||
config SGI_IP22
|
||||
bool "SGI IP22 (Indy/Indigo2)"
|
||||
@@ -1011,6 +1012,36 @@ config FIT_IMAGE_FDT_EPM5
|
||||
from Mobileye in the FIT kernel image.
|
||||
This requires u-boot on the platform.
|
||||
|
||||
+config RTL838X
|
||||
+ bool "Realtek RTL838X based platforms"
|
||||
+ depends on MACH_REALTEK_RTL
|
||||
+ select CPU_SUPPORTS_CPUFREQ
|
||||
+ select MIPS_EXTERNAL_TIMER
|
||||
+
|
||||
+config RTL839X
|
||||
+ bool "Realtek RTL839X based platforms"
|
||||
+ depends on MACH_REALTEK_RTL
|
||||
+ select CPU_SUPPORTS_CPUFREQ
|
||||
+ select MIPS_EXTERNAL_TIMER
|
||||
+ select SYS_SUPPORTS_MULTITHREADING
|
||||
+
|
||||
+config RTL930X
|
||||
+ bool "Realtek RTL930X based platforms"
|
||||
+ depends on MACH_REALTEK_RTL
|
||||
+ select MIPS_CPU_SCACHE
|
||||
+ select MIPS_EXTERNAL_TIMER
|
||||
+ select SYS_SUPPORTS_MULTITHREADING
|
||||
+
|
||||
+config RTL931X
|
||||
+ bool "Realtek RTL931X based platforms"
|
||||
+ depends on RTL930X
|
||||
+ select MIPS_GIC
|
||||
+ select COMMON_CLK
|
||||
+ select CLKSRC_MIPS_GIC
|
||||
+ select SYS_SUPPORTS_VPE_LOADER
|
||||
+ select SYS_SUPPORTS_SMP
|
||||
+ select SYS_SUPPORTS_MIPS_CPS
|
||||
+
|
||||
source "arch/mips/alchemy/Kconfig"
|
||||
source "arch/mips/ath25/Kconfig"
|
||||
source "arch/mips/ath79/Kconfig"
|
||||
@ -1,47 +0,0 @@
|
||||
From 2250db8628a0d8293ad2e0671138b848a185fba1 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Sat, 21 Jun 2025 01:49:51 -0400
|
||||
Subject: irqchip/mips-gic: Allow forced affinity
|
||||
|
||||
Devices of the Realtek MIPS Otto platform use the official rtl-otto-timer
|
||||
as clock event generator and CPU clocksource. It is registered for each CPU
|
||||
startup via cpuhp_setup_state() and forces the affinity of the clockevent
|
||||
interrupts to the appropriate CPU via irq_force_affinity().
|
||||
|
||||
On the "smaller" devices with a vendor specific interrupt controller
|
||||
(supported by irq-realtek-rtl) the registration works fine. The "larger"
|
||||
RTL931x series is based on a MIPS interAptiv dual core with a MIPS GIC
|
||||
controller. Interrupt routing setup is cancelled because gic_set_affinity()
|
||||
does not accept the current (not yet online) CPU as a target.
|
||||
|
||||
Relax the checks by evaluating the force parameter that is provided for
|
||||
exactly this purpose like in other drivers. With this the affinity can be
|
||||
set as follows:
|
||||
|
||||
- force = false: allow to set affinity to any online cpu
|
||||
- force = true: allow to set affinity to any cpu
|
||||
|
||||
Co-developed-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
|
||||
Signed-off-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
|
||||
Link: https://lore.kernel.org/all/20250621054952.380374-1-markus.stockhausen@gmx.de
|
||||
---
|
||||
drivers/irqchip/irq-mips-gic.c | 8 ++++++--
|
||||
1 file changed, 6 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/drivers/irqchip/irq-mips-gic.c
|
||||
+++ b/drivers/irqchip/irq-mips-gic.c
|
||||
@@ -263,7 +263,11 @@ static int gic_set_affinity(struct irq_d
|
||||
unsigned long flags;
|
||||
unsigned int cpu;
|
||||
|
||||
- cpu = cpumask_first_and(cpumask, cpu_online_mask);
|
||||
+ if (force)
|
||||
+ cpu = cpumask_first(cpumask);
|
||||
+ else
|
||||
+ cpu = cpumask_first_and(cpumask, cpu_online_mask);
|
||||
+
|
||||
if (cpu >= NR_CPUS)
|
||||
return -EINVAL;
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Subject: [PATCH] realtek: disable upstream Realtek board
|
||||
|
||||
Upstream now has integrated the Realtek target into the generic
|
||||
MIPS initialization. For now disable the board compilation.
|
||||
|
||||
Submitted-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
---
|
||||
--- a/arch/mips/generic/Makefile
|
||||
+++ b/arch/mips/generic/Makefile
|
||||
@@ -13,4 +13,4 @@ obj-$(CONFIG_LEGACY_BOARD_SEAD3) += boar
|
||||
obj-$(CONFIG_LEGACY_BOARD_OCELOT) += board-ocelot.o
|
||||
obj-$(CONFIG_MACH_INGENIC) += board-ingenic.o
|
||||
obj-$(CONFIG_VIRT_BOARD_RANCHU) += board-ranchu.o
|
||||
-obj-$(CONFIG_MACH_REALTEK_RTL) += board-realtek.o
|
||||
+# obj-$(CONFIG_MACH_REALTEK_RTL) += board-realtek.o
|
||||
@ -1,24 +0,0 @@
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Fri, 13 Jun 2025 20:25:37 +0100
|
||||
Subject: [PATCH] realtek: set mtune 4kec for RTL838x targets
|
||||
|
||||
Generic patches will always force the gcc kernel tuning to 24kc. With RTL838x
|
||||
being only 4kec this does not harm but is not right. Match the tuning properly.
|
||||
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
---
|
||||
|
||||
--- a/arch/mips/Makefile
|
||||
+++ b/arch/mips/Makefile
|
||||
@@ -164,6 +164,11 @@ cflags-$(CONFIG_CPU_R4X00) += $(call cc-
|
||||
cflags-$(CONFIG_CPU_TX49XX) += $(call cc-option,-march=r4600,-march=mips3) -Wa,--trap
|
||||
cflags-$(CONFIG_CPU_MIPS32_R1) += -march=mips32 -Wa,--trap
|
||||
cflags-$(CONFIG_CPU_MIPS32_R2) += -march=mips32r2 -mtune=24kc -Wa,--trap
|
||||
+
|
||||
+#ifdef CONFIG_RTL838X
|
||||
+cflags-$(CONFIG_CPU_MIPS32_R2) := $(subst 24kc,4kec,$(cflags-$(CONFIG_CPU_MIPS32_R2)))
|
||||
+#endif
|
||||
+
|
||||
cflags-$(CONFIG_CPU_MIPS32_R5) += -march=mips32r5 -Wa,--trap -modd-spreg
|
||||
cflags-$(CONFIG_CPU_MIPS32_R6) += -march=mips32r6 -Wa,--trap -modd-spreg
|
||||
cflags-$(CONFIG_CPU_MIPS64_R1) += -march=mips64 -Wa,--trap
|
||||
@ -1,427 +0,0 @@
|
||||
From 6c18e9c491959ac0674ebe36b09f9ddc3f2c9bce Mon Sep 17 00:00:00 2001
|
||||
From: Birger Koblitz <git@birger-koblitz.de>
|
||||
Date: Fri, 31 Dec 2021 11:56:49 +0100
|
||||
Subject: [PATCH] realtek: Add VPE support for the IRQ driver
|
||||
|
||||
In order to support VSMP, enable support for both VPEs of the RTL839X
|
||||
and RTL930X SoCs in the irq-realtek-rtl driver. Add support for IRQ
|
||||
affinity setting.
|
||||
|
||||
Up to kernel 5.15 this patch was divided into two parts
|
||||
|
||||
315-irqchip-irq-realtek-rtl-add-VPE-support.patch
|
||||
319-irqchip-irq-realtek-rtl-fix-VPE-affinity.patch
|
||||
|
||||
As both parts will only work in combination they have been merged into
|
||||
one patch.
|
||||
|
||||
Submitted-by: Birger Koblitz <git@birger-koblitz.de>
|
||||
Submitted-by: INAGAKI Hiroshi <musashino.open@gmail.com>
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
---
|
||||
drivers/irqchip/irq-realtek-rtl.c | 296 +++++++++++++++++++++++++-----
|
||||
1 file changed, 249 insertions(+), 47 deletions(-)
|
||||
|
||||
--- a/drivers/irqchip/irq-realtek-rtl.c
|
||||
+++ b/drivers/irqchip/irq-realtek-rtl.c
|
||||
@@ -22,22 +22,58 @@
|
||||
#define RTL_ICTL_IRR3 0x14
|
||||
|
||||
#define RTL_ICTL_NUM_INPUTS 32
|
||||
-
|
||||
-#define REG(x) (realtek_ictl_base + x)
|
||||
+#define RTL_ICTL_NUM_OUTPUTS 15
|
||||
|
||||
static DEFINE_RAW_SPINLOCK(irq_lock);
|
||||
-static void __iomem *realtek_ictl_base;
|
||||
+
|
||||
+#define REG(offset, cpu) (realtek_ictl_base[cpu] + offset)
|
||||
+
|
||||
+static u32 realtek_ictl_unmask[NR_CPUS];
|
||||
+static void __iomem *realtek_ictl_base[NR_CPUS];
|
||||
+static cpumask_t realtek_ictl_cpu_configurable;
|
||||
+
|
||||
+struct realtek_ictl_output {
|
||||
+ /* IRQ controller data */
|
||||
+ struct fwnode_handle *fwnode;
|
||||
+ /* Output specific data */
|
||||
+ unsigned int output_index;
|
||||
+ struct irq_domain *domain;
|
||||
+ u32 child_mask;
|
||||
+};
|
||||
|
||||
/*
|
||||
- * IRR0-IRR3 store 4 bits per interrupt, but Realtek uses inverted numbering,
|
||||
- * placing IRQ 31 in the first four bits. A routing value of '0' means the
|
||||
- * interrupt is left disconnected. Routing values {1..15} connect to output
|
||||
- * lines {0..14}.
|
||||
+ * Per CPU we have a set of 5 registers that determine interrupt handling for
|
||||
+ * 32 external interrupts. GIMR (enable/disable interrupt) plus IRR0-IRR3 that
|
||||
+ * contain "routing" or "priority" values. GIMR uses one bit for each interrupt
|
||||
+ * and IRRx store 4 bits per interrupt. Realtek uses inverted numbering,
|
||||
+ * placing IRQ 31 in the first four bits. The register combinations give the
|
||||
+ * following results for a single interrupt in the wild:
|
||||
+ *
|
||||
+ * a) GIMR = 0 / IRRx > 0 -> no interrupts
|
||||
+ * b) GIMR = 0 / IRRx = 0 -> no interrupts
|
||||
+ * c) GIMR = 1 / IRRx > 0 -> interrupts
|
||||
+ * d) GIMR = 1 / IRRx = 0 -> rare interrupts in SMP environment
|
||||
+ *
|
||||
+ * Combination d) seems to trigger interrupts only on a VPE if the other VPE
|
||||
+ * has GIMR = 0 and IRRx > 0. E.g. busy without interrupts allowed. To provide
|
||||
+ * IRQ balancing features in SMP this driver will handle the registers as
|
||||
+ * follows:
|
||||
+ *
|
||||
+ * 1) set IRRx > 0 for VPE where the interrupt is desired
|
||||
+ * 2) set IRRx = 0 for VPE where the interrupt is not desired
|
||||
+ * 3) set both GIMR = 0 to mask (disabled) interrupt
|
||||
+ * 4) set GIMR = 1 to unmask (enable) interrupt but only for VPE where IRRx > 0
|
||||
*/
|
||||
+
|
||||
#define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32))
|
||||
#define IRR_SHIFT(idx) ((idx * 4) % 32)
|
||||
|
||||
-static void write_irr(void __iomem *irr0, int idx, u32 value)
|
||||
+static inline u32 read_irr(void __iomem *irr0, int idx)
|
||||
+{
|
||||
+ return (readl(irr0 + IRR_OFFSET(idx)) >> IRR_SHIFT(idx)) & 0xf;
|
||||
+}
|
||||
+
|
||||
+static inline void write_irr(void __iomem *irr0, int idx, u32 value)
|
||||
{
|
||||
unsigned int offset = IRR_OFFSET(idx);
|
||||
unsigned int shift = IRR_SHIFT(idx);
|
||||
@@ -48,16 +84,33 @@ static void write_irr(void __iomem *irr0
|
||||
writel(irr, irr0 + offset);
|
||||
}
|
||||
|
||||
+static inline void enable_gimr(int hwirq, int cpu)
|
||||
+{
|
||||
+ u32 value;
|
||||
+
|
||||
+ value = readl(REG(RTL_ICTL_GIMR, cpu));
|
||||
+ value |= (BIT(hwirq) & realtek_ictl_unmask[cpu]);
|
||||
+ writel(value, REG(RTL_ICTL_GIMR, cpu));
|
||||
+}
|
||||
+
|
||||
+static inline void disable_gimr(int hwirq, int cpu)
|
||||
+{
|
||||
+ u32 value;
|
||||
+
|
||||
+ value = readl(REG(RTL_ICTL_GIMR, cpu));
|
||||
+ value &= ~BIT(hwirq);
|
||||
+ writel(value, REG(RTL_ICTL_GIMR, cpu));
|
||||
+}
|
||||
+
|
||||
static void realtek_ictl_unmask_irq(struct irq_data *i)
|
||||
{
|
||||
unsigned long flags;
|
||||
- u32 value;
|
||||
+ int cpu;
|
||||
|
||||
raw_spin_lock_irqsave(&irq_lock, flags);
|
||||
|
||||
- value = readl(REG(RTL_ICTL_GIMR));
|
||||
- value |= BIT(i->hwirq);
|
||||
- writel(value, REG(RTL_ICTL_GIMR));
|
||||
+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
|
||||
+ enable_gimr(i->hwirq, cpu);
|
||||
|
||||
raw_spin_unlock_irqrestore(&irq_lock, flags);
|
||||
}
|
||||
@@ -65,110 +118,259 @@ static void realtek_ictl_unmask_irq(stru
|
||||
static void realtek_ictl_mask_irq(struct irq_data *i)
|
||||
{
|
||||
unsigned long flags;
|
||||
- u32 value;
|
||||
+ int cpu;
|
||||
|
||||
raw_spin_lock_irqsave(&irq_lock, flags);
|
||||
|
||||
- value = readl(REG(RTL_ICTL_GIMR));
|
||||
- value &= ~BIT(i->hwirq);
|
||||
- writel(value, REG(RTL_ICTL_GIMR));
|
||||
+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
|
||||
+ disable_gimr(i->hwirq, cpu);
|
||||
|
||||
raw_spin_unlock_irqrestore(&irq_lock, flags);
|
||||
}
|
||||
|
||||
+static int __maybe_unused realtek_ictl_irq_affinity(struct irq_data *i,
|
||||
+ const struct cpumask *dest, bool force)
|
||||
+{
|
||||
+ struct realtek_ictl_output *output = i->domain->host_data;
|
||||
+ cpumask_t cpu_configure;
|
||||
+ cpumask_t cpu_disable;
|
||||
+ cpumask_t cpu_enable;
|
||||
+ unsigned long flags;
|
||||
+ int cpu;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&irq_lock, flags);
|
||||
+
|
||||
+ cpumask_and(&cpu_configure, cpu_present_mask, &realtek_ictl_cpu_configurable);
|
||||
+
|
||||
+ cpumask_and(&cpu_enable, &cpu_configure, dest);
|
||||
+ cpumask_andnot(&cpu_disable, &cpu_configure, dest);
|
||||
+
|
||||
+ for_each_cpu(cpu, &cpu_disable) {
|
||||
+ write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, 0);
|
||||
+ realtek_ictl_unmask[cpu] &= ~BIT(i->hwirq);
|
||||
+ disable_gimr(i->hwirq, cpu);
|
||||
+ }
|
||||
+
|
||||
+ for_each_cpu(cpu, &cpu_enable) {
|
||||
+ write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, output->output_index + 1);
|
||||
+ realtek_ictl_unmask[cpu] |= BIT(i->hwirq);
|
||||
+ enable_gimr(i->hwirq, cpu);
|
||||
+ }
|
||||
+
|
||||
+ irq_data_update_effective_affinity(i, &cpu_enable);
|
||||
+
|
||||
+ raw_spin_unlock_irqrestore(&irq_lock, flags);
|
||||
+
|
||||
+ return IRQ_SET_MASK_OK;
|
||||
+}
|
||||
+
|
||||
static struct irq_chip realtek_ictl_irq = {
|
||||
.name = "realtek-rtl-intc",
|
||||
.irq_mask = realtek_ictl_mask_irq,
|
||||
.irq_unmask = realtek_ictl_unmask_irq,
|
||||
+#ifdef CONFIG_SMP
|
||||
+ .irq_set_affinity = realtek_ictl_irq_affinity,
|
||||
+#endif
|
||||
};
|
||||
|
||||
static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
|
||||
{
|
||||
+ struct realtek_ictl_output *output = d->host_data;
|
||||
unsigned long flags;
|
||||
|
||||
irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq);
|
||||
|
||||
raw_spin_lock_irqsave(&irq_lock, flags);
|
||||
- write_irr(REG(RTL_ICTL_IRR0), hw, 1);
|
||||
+
|
||||
+ output->child_mask |= BIT(hw);
|
||||
+ write_irr(REG(RTL_ICTL_IRR0, 0), hw, output->output_index + 1);
|
||||
+ realtek_ictl_unmask[0] |= BIT(hw);
|
||||
+
|
||||
raw_spin_unlock_irqrestore(&irq_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int intc_select(struct irq_domain *d, struct irq_fwspec *fwspec,
|
||||
+ enum irq_domain_bus_token bus_token)
|
||||
+{
|
||||
+ struct realtek_ictl_output *output = d->host_data;
|
||||
+ bool routed_elsewhere;
|
||||
+ unsigned long flags;
|
||||
+ u32 routing_old;
|
||||
+ int cpu;
|
||||
+
|
||||
+ if (fwspec->fwnode != output->fwnode)
|
||||
+ return false;
|
||||
+
|
||||
+ /* Original specifiers had only one parameter */
|
||||
+ if (fwspec->param_count < 2)
|
||||
+ return true;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&irq_lock, flags);
|
||||
+
|
||||
+ /*
|
||||
+ * Inputs can only be routed to one output, so they shouldn't be
|
||||
+ * allowed to end up in multiple domains.
|
||||
+ */
|
||||
+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable) {
|
||||
+ routing_old = read_irr(REG(RTL_ICTL_IRR0, cpu), fwspec->param[0]);
|
||||
+ routed_elsewhere = routing_old && fwspec->param[1] != routing_old - 1;
|
||||
+ if (routed_elsewhere) {
|
||||
+ pr_warn("soc int %d already routed to output %d\n",
|
||||
+ fwspec->param[0], routing_old - 1);
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ raw_spin_unlock_irqrestore(&irq_lock, flags);
|
||||
+
|
||||
+ return !routed_elsewhere && fwspec->param[1] == output->output_index;
|
||||
+}
|
||||
+
|
||||
static const struct irq_domain_ops irq_domain_ops = {
|
||||
.map = intc_map,
|
||||
+ .select = intc_select,
|
||||
.xlate = irq_domain_xlate_onecell,
|
||||
};
|
||||
|
||||
static void realtek_irq_dispatch(struct irq_desc *desc)
|
||||
{
|
||||
+ struct realtek_ictl_output *output = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
- struct irq_domain *domain;
|
||||
+ int cpu = smp_processor_id();
|
||||
unsigned long pending;
|
||||
unsigned int soc_int;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
- pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR));
|
||||
+ pending = readl(REG(RTL_ICTL_GIMR, cpu)) & readl(REG(RTL_ICTL_GISR, cpu))
|
||||
+ & output->child_mask;
|
||||
|
||||
if (unlikely(!pending)) {
|
||||
spurious_interrupt();
|
||||
goto out;
|
||||
}
|
||||
|
||||
- domain = irq_desc_get_handler_data(desc);
|
||||
- for_each_set_bit(soc_int, &pending, 32)
|
||||
- generic_handle_domain_irq(domain, soc_int);
|
||||
+ for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS)
|
||||
+ generic_handle_domain_irq(output->domain, soc_int);
|
||||
|
||||
out:
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * SoC interrupts are cascaded to MIPS CPU interrupts according to the
|
||||
+ * interrupt-map in the device tree. Each SoC interrupt gets 4 bits for
|
||||
+ * the CPU interrupt in an Interrupt Routing Register. Max 32 SoC interrupts
|
||||
+ * thus go into 4 IRRs. A routing value of '0' means the interrupt is left
|
||||
+ * disconnected. Routing values {1..15} connect to output lines {0..14}.
|
||||
+ */
|
||||
+static int __init setup_parent_interrupts(struct device_node *node, int *parents,
|
||||
+ unsigned int num_parents)
|
||||
+{
|
||||
+ struct realtek_ictl_output *outputs;
|
||||
+ struct realtek_ictl_output *output;
|
||||
+ struct irq_domain *domain;
|
||||
+ unsigned int p;
|
||||
+
|
||||
+ outputs = kcalloc(num_parents, sizeof(*outputs), GFP_KERNEL);
|
||||
+ if (!outputs)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ for (p = 0; p < num_parents; p++) {
|
||||
+ output = outputs + p;
|
||||
+
|
||||
+ domain = irq_domain_add_linear(node, RTL_ICTL_NUM_INPUTS, &irq_domain_ops, output);
|
||||
+ if (!domain)
|
||||
+ goto domain_err;
|
||||
+
|
||||
+ output->fwnode = of_node_to_fwnode(node);
|
||||
+ output->output_index = p;
|
||||
+ output->domain = domain;
|
||||
+
|
||||
+ irq_set_chained_handler_and_data(parents[p], realtek_irq_dispatch, output);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+domain_err:
|
||||
+ while (p--) {
|
||||
+ irq_set_chained_handler_and_data(parents[p], NULL, NULL);
|
||||
+ irq_domain_remove(outputs[p].domain);
|
||||
+ }
|
||||
+
|
||||
+ kfree(outputs);
|
||||
+
|
||||
+ return -ENOMEM;
|
||||
+}
|
||||
+
|
||||
static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent)
|
||||
{
|
||||
+ int parent_irqs[RTL_ICTL_NUM_OUTPUTS];
|
||||
struct of_phandle_args oirq;
|
||||
- struct irq_domain *domain;
|
||||
+ unsigned int num_parents;
|
||||
unsigned int soc_irq;
|
||||
- int parent_irq;
|
||||
+ unsigned int p;
|
||||
+ int cpu;
|
||||
+
|
||||
+ cpumask_clear(&realtek_ictl_cpu_configurable);
|
||||
|
||||
- realtek_ictl_base = of_iomap(node, 0);
|
||||
- if (!realtek_ictl_base)
|
||||
+ for (cpu = 0; cpu < NR_CPUS; cpu++) {
|
||||
+ realtek_ictl_base[cpu] = of_iomap(node, cpu);
|
||||
+ if (realtek_ictl_base[cpu]) {
|
||||
+ cpumask_set_cpu(cpu, &realtek_ictl_cpu_configurable);
|
||||
+
|
||||
+ /* Disable all cascaded interrupts and clear routing */
|
||||
+ for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++) {
|
||||
+ write_irr(REG(RTL_ICTL_IRR0, cpu), soc_irq, 0);
|
||||
+ realtek_ictl_unmask[cpu] &= ~BIT(soc_irq);
|
||||
+ disable_gimr(soc_irq, cpu);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (cpumask_empty(&realtek_ictl_cpu_configurable))
|
||||
return -ENXIO;
|
||||
|
||||
- /* Disable all cascaded interrupts and clear routing */
|
||||
- writel(0, REG(RTL_ICTL_GIMR));
|
||||
- for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++)
|
||||
- write_irr(REG(RTL_ICTL_IRR0), soc_irq, 0);
|
||||
+ num_parents = of_irq_count(node);
|
||||
+ if (num_parents > RTL_ICTL_NUM_OUTPUTS) {
|
||||
+ pr_err("too many parent interrupts\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
|
||||
- if (WARN_ON(!of_irq_count(node))) {
|
||||
+ for (p = 0; p < num_parents; p++)
|
||||
+ parent_irqs[p] = of_irq_get(node, p);
|
||||
+
|
||||
+ if (WARN_ON(!num_parents)) {
|
||||
/*
|
||||
* If DT contains no parent interrupts, assume MIPS CPU IRQ 2
|
||||
* (HW0) is connected to the first output. This is the case for
|
||||
* all known hardware anyway. "interrupt-map" is deprecated, so
|
||||
* don't bother trying to parse that.
|
||||
+ * Since this is to account for old devicetrees with one-cell
|
||||
+ * interrupt specifiers, only one output domain is needed.
|
||||
*/
|
||||
oirq.np = of_find_compatible_node(NULL, NULL, "mti,cpu-interrupt-controller");
|
||||
- oirq.args_count = 1;
|
||||
- oirq.args[0] = 2;
|
||||
-
|
||||
- parent_irq = irq_create_of_mapping(&oirq);
|
||||
+ if (oirq.np) {
|
||||
+ oirq.args_count = 1;
|
||||
+ oirq.args[0] = 2;
|
||||
+
|
||||
+ parent_irqs[0] = irq_create_of_mapping(&oirq);
|
||||
+ num_parents = 1;
|
||||
+ }
|
||||
|
||||
of_node_put(oirq.np);
|
||||
- } else {
|
||||
- parent_irq = of_irq_get(node, 0);
|
||||
}
|
||||
|
||||
- if (parent_irq < 0)
|
||||
- return parent_irq;
|
||||
- else if (!parent_irq)
|
||||
- return -ENODEV;
|
||||
-
|
||||
- domain = irq_domain_add_linear(node, RTL_ICTL_NUM_INPUTS, &irq_domain_ops, NULL);
|
||||
- if (!domain)
|
||||
- return -ENOMEM;
|
||||
-
|
||||
- irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, domain);
|
||||
+ /* Ensure we haven't collected any errors before proceeding */
|
||||
+ for (p = 0; p < num_parents; p++) {
|
||||
+ if (parent_irqs[p] < 0)
|
||||
+ return parent_irqs[p];
|
||||
+ if (!parent_irqs[p])
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
|
||||
- return 0;
|
||||
+ return setup_parent_interrupts(node, &parent_irqs[0], num_parents);
|
||||
}
|
||||
|
||||
IRQCHIP_DECLARE(realtek_rtl_intc, "realtek,rtl-intc", realtek_rtl_of_init);
|
||||
@ -1,33 +0,0 @@
|
||||
From 800d5fb3c6a16661932c932bacd660e38d06b727 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Thu, 25 Aug 2022 08:22:36 +0200
|
||||
Subject: [PATCH] realtek: add patch to enable new clock driver in kernel
|
||||
|
||||
Allow building the clock driver with kernel config options.
|
||||
|
||||
Submitted-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
---
|
||||
drivers/clk/Kconfig | 1 +
|
||||
drivers/clk/Makefile | 1 +
|
||||
2 files changed, 2 insertions(+)
|
||||
|
||||
--- a/drivers/clk/Kconfig
|
||||
+++ b/drivers/clk/Kconfig
|
||||
@@ -491,6 +491,7 @@ source "drivers/clk/mvebu/Kconfig"
|
||||
source "drivers/clk/nuvoton/Kconfig"
|
||||
source "drivers/clk/pistachio/Kconfig"
|
||||
source "drivers/clk/qcom/Kconfig"
|
||||
+source "drivers/clk/realtek/Kconfig"
|
||||
source "drivers/clk/ralink/Kconfig"
|
||||
source "drivers/clk/renesas/Kconfig"
|
||||
source "drivers/clk/rockchip/Kconfig"
|
||||
--- a/drivers/clk/Makefile
|
||||
+++ b/drivers/clk/Makefile
|
||||
@@ -122,6 +122,7 @@ obj-$(CONFIG_COMMON_CLK_PISTACHIO) += pi
|
||||
obj-$(CONFIG_COMMON_CLK_PXA) += pxa/
|
||||
obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/
|
||||
obj-y += ralink/
|
||||
+obj-$(CONFIG_COMMON_CLK_REALTEK) += realtek/
|
||||
obj-y += renesas/
|
||||
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
|
||||
obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/
|
||||
@ -1,22 +0,0 @@
|
||||
--- a/drivers/thermal/Kconfig
|
||||
+++ b/drivers/thermal/Kconfig
|
||||
@@ -499,4 +499,11 @@ config LOONGSON2_THERMAL
|
||||
is higher than the high temperature threshold or lower than the low
|
||||
temperature threshold, the interrupt will occur.
|
||||
|
||||
+config REALTEK_THERMAL
|
||||
+ tristate "Realtek RTL838x and RTL930x thermal sensor support"
|
||||
+ depends on RTL838X || RTL839X || RTL930X || RTL960X || COMPILE_TEST
|
||||
+ depends on THERMAL_OF
|
||||
+ help
|
||||
+ Support thermal sensor in Realtek RTL838x, RTL839x, RTL930x and RTL960x SoCs
|
||||
+
|
||||
endif
|
||||
--- a/drivers/thermal/Makefile
|
||||
+++ b/drivers/thermal/Makefile
|
||||
@@ -63,4 +63,5 @@ obj-$(CONFIG_AMLOGIC_THERMAL) += aml
|
||||
obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o
|
||||
obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o
|
||||
obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o
|
||||
+obj-$(CONFIG_REALTEK_THERMAL) += realtek-thermal.o
|
||||
obj-$(CONFIG_THERMAL_CORE_TESTING) += testing/
|
||||
@ -1,152 +0,0 @@
|
||||
From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
|
||||
From: John Crispin <john@phrozen.org>
|
||||
Date: Thu, 26 Nov 2020 12:02:21 +0100
|
||||
Subject: realtek dsa/phy: Increase max ports for RTL839X/RTL931X
|
||||
|
||||
Linux standard can only support up to 32 devices per mdio bus and up to
|
||||
12 ports per DSA switch. This is not enough for the large RTL839X and
|
||||
RTL931X devices. Increase the max values accordingly. Additionally take
|
||||
care about the functions that work on bit masks.
|
||||
|
||||
Submitted-by: Bert Vermeulen <bert@biot.com>
|
||||
Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
|
||||
Submitted-by: Sander Vanheule <sander@svanheule.net>
|
||||
Submitted-by: Bjørn Mork <bjorn@mork.no>
|
||||
Submitted-by: John Crispin <john@phrozen.org>
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
---
|
||||
drivers/net/mdio/fwnode_mdio.c | 2 +-
|
||||
include/linux/phy.h | 6 +++---
|
||||
include/linux/platform_data/dsa.h | 2 +-
|
||||
include/net/dsa.h | 14 +++++++-------
|
||||
net/dsa/slave.c | 4 ++--
|
||||
5 files changed, 14 insertions(+), 14 deletions(-)
|
||||
|
||||
--- a/drivers/net/mdio/fwnode_mdio.c
|
||||
+++ b/drivers/net/mdio/fwnode_mdio.c
|
||||
@@ -90,7 +90,7 @@ int fwnode_mdiobus_phy_device_register(s
|
||||
}
|
||||
|
||||
if (fwnode_property_read_bool(child, "broken-turn-around"))
|
||||
- mdio->phy_ignore_ta_mask |= 1 << addr;
|
||||
+ mdio->phy_ignore_ta_mask |= BIT_ULL(addr);
|
||||
|
||||
fwnode_property_read_u32(child, "reset-assert-us",
|
||||
&phy->mdio.reset_assert_delay);
|
||||
--- a/include/linux/phy.h
|
||||
+++ b/include/linux/phy.h
|
||||
@@ -311,7 +311,7 @@ static inline const char *phy_modes(phy_
|
||||
#define PHY_INIT_TIMEOUT 100000
|
||||
#define PHY_FORCE_TIMEOUT 10
|
||||
|
||||
-#define PHY_MAX_ADDR 32
|
||||
+#define PHY_MAX_ADDR 64
|
||||
|
||||
/* Used when trying to connect to a specific phy (mii bus id:phy device id) */
|
||||
#define PHY_ID_FMT "%s:%02x"
|
||||
@@ -431,10 +431,10 @@ struct mii_bus {
|
||||
struct mdio_device *mdio_map[PHY_MAX_ADDR];
|
||||
|
||||
/** @phy_mask: PHY addresses to be ignored when probing */
|
||||
- u32 phy_mask;
|
||||
+ u64 phy_mask;
|
||||
|
||||
/** @phy_ignore_ta_mask: PHY addresses to ignore the TA/read failure */
|
||||
- u32 phy_ignore_ta_mask;
|
||||
+ u64 phy_ignore_ta_mask;
|
||||
|
||||
/**
|
||||
* @irq: An array of interrupts, each PHY's interrupt at the index
|
||||
--- a/include/linux/platform_data/dsa.h
|
||||
+++ b/include/linux/platform_data/dsa.h
|
||||
@@ -6,7 +6,7 @@ struct device;
|
||||
struct net_device;
|
||||
|
||||
#define DSA_MAX_SWITCHES 4
|
||||
-#define DSA_MAX_PORTS 12
|
||||
+#define DSA_MAX_PORTS 54
|
||||
#define DSA_RTABLE_NONE -1
|
||||
|
||||
struct dsa_chip_data {
|
||||
--- a/include/net/dsa.h
|
||||
+++ b/include/net/dsa.h
|
||||
@@ -480,7 +480,7 @@ struct dsa_switch {
|
||||
/*
|
||||
* User mii_bus and devices for the individual ports.
|
||||
*/
|
||||
- u32 phys_mii_mask;
|
||||
+ u64 phys_mii_mask;
|
||||
struct mii_bus *user_mii_bus;
|
||||
|
||||
/* Ageing Time limits in msecs */
|
||||
@@ -616,24 +616,24 @@ static inline bool dsa_is_user_port(stru
|
||||
dsa_switch_for_each_port_continue_reverse((_dp), (_ds)) \
|
||||
if (dsa_port_is_cpu((_dp)))
|
||||
|
||||
-static inline u32 dsa_user_ports(struct dsa_switch *ds)
|
||||
+static inline u64 dsa_user_ports(struct dsa_switch *ds)
|
||||
{
|
||||
struct dsa_port *dp;
|
||||
- u32 mask = 0;
|
||||
+ u64 mask = 0;
|
||||
|
||||
dsa_switch_for_each_user_port(dp, ds)
|
||||
- mask |= BIT(dp->index);
|
||||
+ mask |= BIT_ULL(dp->index);
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
-static inline u32 dsa_cpu_ports(struct dsa_switch *ds)
|
||||
+static inline u64 dsa_cpu_ports(struct dsa_switch *ds)
|
||||
{
|
||||
struct dsa_port *cpu_dp;
|
||||
- u32 mask = 0;
|
||||
+ u64 mask = 0;
|
||||
|
||||
dsa_switch_for_each_cpu_port(cpu_dp, ds)
|
||||
- mask |= BIT(cpu_dp->index);
|
||||
+ mask |= BIT_ULL(cpu_dp->index);
|
||||
|
||||
return mask;
|
||||
}
|
||||
--- a/net/dsa/user.c
|
||||
+++ b/net/dsa/user.c
|
||||
@@ -320,7 +320,7 @@ static int dsa_user_phy_read(struct mii_
|
||||
{
|
||||
struct dsa_switch *ds = bus->priv;
|
||||
|
||||
- if (ds->phys_mii_mask & (1 << addr))
|
||||
+ if (ds->phys_mii_mask & BIT_ULL(addr))
|
||||
return ds->ops->phy_read(ds, addr, reg);
|
||||
|
||||
return 0xffff;
|
||||
@@ -330,7 +330,7 @@ static int dsa_user_phy_write(struct mii
|
||||
{
|
||||
struct dsa_switch *ds = bus->priv;
|
||||
|
||||
- if (ds->phys_mii_mask & (1 << addr))
|
||||
+ if (ds->phys_mii_mask & BIT_ULL(addr))
|
||||
return ds->ops->phy_write(ds, addr, reg, val);
|
||||
|
||||
return 0;
|
||||
--- a/drivers/net/phy/mdio_bus.c
|
||||
+++ b/drivers/net/phy/mdio_bus.c
|
||||
@@ -615,7 +615,7 @@ static int mdiobus_scan_bus_c22(struct m
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++) {
|
||||
- if ((bus->phy_mask & BIT(i)) == 0) {
|
||||
+ if ((bus->phy_mask & BIT_ULL(i)) == 0ULL) {
|
||||
struct phy_device *phydev;
|
||||
|
||||
phydev = mdiobus_scan_c22(bus, i);
|
||||
@@ -631,7 +631,7 @@ static int mdiobus_scan_bus_c45(struct m
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PHY_MAX_ADDR; i++) {
|
||||
- if ((bus->phy_mask & BIT(i)) == 0) {
|
||||
+ if ((bus->phy_mask & BIT_ULL(i)) == 0ULL) {
|
||||
struct phy_device *phydev;
|
||||
|
||||
/* Don't scan C45 if we already have a C22 device */
|
||||
@ -1,428 +0,0 @@
|
||||
From d585c55b9f70cf9e8c66820d7efe7130c683f19e Mon Sep 17 00:00:00 2001
|
||||
From: Antoine Tenart <antoine.tenart@bootlin.com>
|
||||
Date: Fri, 21 Feb 2020 11:51:27 +0100
|
||||
Subject: [PATCH 2/3] net: phy: add an MDIO SMBus library
|
||||
|
||||
Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
|
||||
---
|
||||
drivers/net/mdio/Kconfig | 11 +++++++
|
||||
drivers/net/mdio/Makefile | 1 +
|
||||
drivers/net/mdio/mdio-smbus.c | 62 +++++++++++++++++++++++++++++++++++
|
||||
drivers/net/phy/Kconfig | 1 +
|
||||
include/linux/mdio/mdio-i2c.h | 16 +++++++++
|
||||
5 files changed, 91 insertions(+)
|
||||
create mode 100644 drivers/net/mdio/mdio-smbus.c
|
||||
|
||||
--- a/drivers/net/mdio/Kconfig
|
||||
+++ b/drivers/net/mdio/Kconfig
|
||||
@@ -54,6 +54,17 @@ config MDIO_SUN4I
|
||||
interface units of the Allwinner SoC that have an EMAC (A10,
|
||||
A12, A10s, etc.)
|
||||
|
||||
+config MDIO_SMBUS
|
||||
+ tristate
|
||||
+ depends on I2C_SMBUS
|
||||
+ help
|
||||
+ Support SMBus based PHYs. This provides a MDIO bus bridged
|
||||
+ to SMBus to allow PHYs connected in SMBus mode to be accessed
|
||||
+ using the existing infrastructure.
|
||||
+
|
||||
+ This is library mode.
|
||||
+
|
||||
+
|
||||
config MDIO_XGENE
|
||||
tristate "APM X-Gene SoC MDIO bus controller"
|
||||
depends on ARCH_XGENE || COMPILE_TEST
|
||||
--- a/drivers/net/mdio/Makefile
|
||||
+++ b/drivers/net/mdio/Makefile
|
||||
@@ -20,6 +20,7 @@ obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-ms
|
||||
obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o
|
||||
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
|
||||
obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
|
||||
+obj-$(CONFIG_MDIO_SMBUS) += mdio-smbus.o
|
||||
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
|
||||
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
|
||||
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/net/mdio/mdio-smbus.c
|
||||
@@ -0,0 +1,341 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
+/*
|
||||
+ * MDIO SMBus bridge
|
||||
+ *
|
||||
+ * Copyright (C) 2020 Antoine Tenart
|
||||
+ * Copyright (C) 2025 Bjørn Mork <bjorn@mork.no>
|
||||
+ *
|
||||
+ * Network PHYs can appear on SMBus when they are part of SFP modules.
|
||||
+ */
|
||||
+#include <linux/i2c.h>
|
||||
+#include <linux/phy.h>
|
||||
+#include <linux/mdio/mdio-i2c.h>
|
||||
+#include <linux/sfp.h>
|
||||
+
|
||||
+static int smbus_mii_read_c45(struct mii_bus *mii, int phy_id, int devad, int reg)
|
||||
+{
|
||||
+ u16 bus_addr = i2c_mii_phy_addr(phy_id);
|
||||
+ struct i2c_adapter *i2c = mii->priv;
|
||||
+ union i2c_smbus_data data;
|
||||
+ size_t addrlen;
|
||||
+ u8 buf[5], *p;
|
||||
+ int i, ret;
|
||||
+
|
||||
+ if (!i2c_mii_valid_phy_id(phy_id))
|
||||
+ return 0xffff;
|
||||
+
|
||||
+ p = buf;
|
||||
+ if (devad >= 0) {
|
||||
+ *p++ = 0x20 | devad;
|
||||
+ *p++ = reg >> 8;
|
||||
+ }
|
||||
+ *p++ = reg;
|
||||
+ addrlen = p - buf;
|
||||
+
|
||||
+ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
|
||||
+ if (addrlen > 1) {
|
||||
+ for (i = 1; i < addrlen; i++) {
|
||||
+ data.byte = buf[i];
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, buf[0], I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ goto unlock;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (i = addrlen; i < addrlen + 2; i++) {
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, buf[0], I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ goto unlock;
|
||||
+ buf[i] = data.byte;
|
||||
+ }
|
||||
+
|
||||
+unlock:
|
||||
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
|
||||
+ if (ret < 0)
|
||||
+ return 0xffff;
|
||||
+ return buf[addrlen] << 8 | buf[addrlen + 1];
|
||||
+}
|
||||
+
|
||||
+static int smbus_mii_write_c45(struct mii_bus *mii, int phy_id, int devad, int reg, u16 val)
|
||||
+{
|
||||
+ u16 bus_addr = i2c_mii_phy_addr(phy_id);
|
||||
+ struct i2c_adapter *i2c = mii->priv;
|
||||
+ union i2c_smbus_data data;
|
||||
+ size_t buflen;
|
||||
+ u8 buf[5], *p;
|
||||
+ int i, ret;
|
||||
+
|
||||
+ if (!i2c_mii_valid_phy_id(phy_id))
|
||||
+ return 0;
|
||||
+
|
||||
+ p = buf;
|
||||
+ if (devad >= 0) {
|
||||
+ *p++ = devad;
|
||||
+ *p++ = reg >> 8;
|
||||
+ }
|
||||
+ *p++ = reg;
|
||||
+ *p++ = val >> 8;
|
||||
+ *p++ = val;
|
||||
+ buflen = p - buf;
|
||||
+
|
||||
+ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
|
||||
+ for (i = 1; i < buflen; i++) {
|
||||
+ data.byte = buf[i];
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, buf[0], I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ goto unlock;
|
||||
+ }
|
||||
+unlock:
|
||||
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
|
||||
+ return ret < 0 ? ret : 0;
|
||||
+}
|
||||
+
|
||||
+static int smbus_mii_read_c22(struct mii_bus *bus, int phy_id, int reg)
|
||||
+{
|
||||
+ return smbus_mii_read_c45(bus, phy_id, -1, reg);
|
||||
+}
|
||||
+
|
||||
+static int smbus_mii_write_c22(struct mii_bus *bus, int phy_id, int reg, u16 val)
|
||||
+{
|
||||
+ return smbus_mii_write_c45(bus, phy_id, -1, reg, val);
|
||||
+}
|
||||
+
|
||||
+/* From mdio-i2c.c:
|
||||
+ *
|
||||
+ * RollBall SFPs do not access internal PHY via I2C address 0x56, but
|
||||
+ * instead via address 0x51, when SFP page is set to 0x03 and password to
|
||||
+ * 0xffffffff.
|
||||
+ *
|
||||
+ * address size contents description
|
||||
+ * ------- ---- -------- -----------
|
||||
+ * 0x80 1 CMD 0x01/0x02/0x04 for write/read/done
|
||||
+ * 0x81 1 DEV Clause 45 device
|
||||
+ * 0x82 2 REG Clause 45 register
|
||||
+ * 0x84 2 VAL Register value
|
||||
+ */
|
||||
+#define ROLLBALL_PHY_I2C_ADDR 0x51
|
||||
+
|
||||
+#define ROLLBALL_PASSWORD (SFP_VSL + 3)
|
||||
+
|
||||
+#define ROLLBALL_CMD_ADDR 0x80
|
||||
+#define ROLLBALL_DATA_ADDR 0x81
|
||||
+
|
||||
+#define ROLLBALL_CMD_WRITE 0x01
|
||||
+#define ROLLBALL_CMD_READ 0x02
|
||||
+#define ROLLBALL_CMD_DONE 0x04
|
||||
+
|
||||
+#define SFP_PAGE_ROLLBALL_MDIO 3
|
||||
+
|
||||
+static int smbus_set_sfp_page_lock(struct i2c_adapter *i2c, int bus_addr, u8 page)
|
||||
+{
|
||||
+ union i2c_smbus_data data;
|
||||
+ u8 oldpage;
|
||||
+ int ret;
|
||||
+
|
||||
+ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
|
||||
+
|
||||
+ /* read current page */
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ goto unlock;
|
||||
+
|
||||
+ oldpage = data.byte;
|
||||
+ data.byte = page;
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret == 0)
|
||||
+ return oldpage;
|
||||
+
|
||||
+unlock:
|
||||
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int __smbus_set_sfp_page_unlock(struct i2c_adapter *i2c, int bus_addr, u8 page)
|
||||
+{
|
||||
+ union i2c_smbus_data data;
|
||||
+ int ret;
|
||||
+
|
||||
+ data.byte = page;
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, SFP_PAGE, I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+/* Wait for the ROLLBALL_CMD_ADDR register to read ROLLBALL_CMD_DONE,
|
||||
+ * indicating that the previous command has completed.
|
||||
+ *
|
||||
+ * Quoting from the mdio-i2c.c implementation:
|
||||
+ *
|
||||
+ * By experiment it takes up to 70 ms to access a register for these
|
||||
+ * SFPs. Sleep 20ms between iterations and try 10 times.
|
||||
+ */
|
||||
+static int __smbus_rollball_mii_poll(struct i2c_adapter *i2c , int bus_addr)
|
||||
+{
|
||||
+ union i2c_smbus_data data;
|
||||
+ int i, ret;
|
||||
+
|
||||
+ i = 10;
|
||||
+ do {
|
||||
+ msleep(20);
|
||||
+
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, ROLLBALL_CMD_ADDR, I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (data.byte == ROLLBALL_CMD_DONE)
|
||||
+ return 0;
|
||||
+ } while (i-- > 0);
|
||||
+ dev_dbg(&i2c->dev, "poll timed out\n");
|
||||
+ return -ETIMEDOUT;
|
||||
+}
|
||||
+
|
||||
+static int smbus_mii_read_rollball(struct mii_bus *bus, int phy_id, int devad, int reg)
|
||||
+{
|
||||
+ struct i2c_adapter *i2c = bus->priv;
|
||||
+ union i2c_smbus_data data;
|
||||
+ int i, bus_addr, old, ret;
|
||||
+ u8 buf[6];
|
||||
+
|
||||
+ bus_addr = i2c_mii_phy_addr(phy_id);
|
||||
+ if (bus_addr != ROLLBALL_PHY_I2C_ADDR)
|
||||
+ return 0xffff;
|
||||
+
|
||||
+ old = smbus_set_sfp_page_lock(i2c, bus_addr, SFP_PAGE_ROLLBALL_MDIO);
|
||||
+ if (old < 0)
|
||||
+ return 0xffff;
|
||||
+
|
||||
+ /* set address */
|
||||
+ buf[0] = ROLLBALL_CMD_READ;
|
||||
+ buf[1] = devad;
|
||||
+ buf[2] = reg >> 8;
|
||||
+ buf[3] = reg & 0xff;
|
||||
+
|
||||
+ /* send address */
|
||||
+ for (i = 0; i < 4; i++) {
|
||||
+ data.byte = buf[i];
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ goto unlock;
|
||||
+ }
|
||||
+
|
||||
+ /* wait for command to complete */
|
||||
+ ret = __smbus_rollball_mii_poll(i2c, bus_addr);
|
||||
+ if (ret)
|
||||
+ goto unlock;
|
||||
+
|
||||
+ /* read result */
|
||||
+ for (i = 4; i < 6; i++) {
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_READ, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ goto unlock;
|
||||
+ buf[i] = data.byte;
|
||||
+ }
|
||||
+
|
||||
+unlock:
|
||||
+ __smbus_set_sfp_page_unlock(i2c, bus_addr, old);
|
||||
+ if (ret < 0)
|
||||
+ return 0xffff;
|
||||
+ return buf[4] << 8 | buf[5];
|
||||
+}
|
||||
+
|
||||
+static int smbus_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad, int reg, u16 val)
|
||||
+{
|
||||
+ struct i2c_adapter *i2c = bus->priv;
|
||||
+ union i2c_smbus_data data;
|
||||
+ int i, bus_addr, old, ret;
|
||||
+ u8 buf[6];
|
||||
+
|
||||
+ bus_addr = i2c_mii_phy_addr(phy_id);
|
||||
+ if (bus_addr != ROLLBALL_PHY_I2C_ADDR)
|
||||
+ return 0;
|
||||
+
|
||||
+ old = smbus_set_sfp_page_lock(i2c, bus_addr, SFP_PAGE_ROLLBALL_MDIO);
|
||||
+ if (old < 0)
|
||||
+ return old;
|
||||
+
|
||||
+ /* set address */
|
||||
+ buf[0] = ROLLBALL_CMD_WRITE;
|
||||
+ buf[1] = devad;
|
||||
+ buf[2] = reg >> 8;
|
||||
+ buf[3] = reg & 0xff;
|
||||
+ buf[4] = val >> 8;
|
||||
+ buf[5] = val & 0xff;
|
||||
+
|
||||
+ /* send address and value */
|
||||
+ for (i = 0; i < 6; i++) {
|
||||
+ data.byte = buf[i];
|
||||
+ ret = __i2c_smbus_xfer(i2c, bus_addr, 0, I2C_SMBUS_WRITE, ROLLBALL_CMD_ADDR + i, I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ goto unlock;
|
||||
+ }
|
||||
+
|
||||
+ /* wait for command to complete */
|
||||
+ ret = __smbus_rollball_mii_poll(i2c, bus_addr);
|
||||
+
|
||||
+unlock:
|
||||
+ __smbus_set_sfp_page_unlock(i2c, bus_addr, old);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+/* write "password" - four 0xff bytes - to the ROLLBALL_PASSWORD register */
|
||||
+static int smbus_mii_init_rollball(struct i2c_adapter *i2c)
|
||||
+{
|
||||
+ union i2c_smbus_data data;
|
||||
+ int i, ret;
|
||||
+
|
||||
+ data.byte = 0xff;
|
||||
+ for (i = 0; i < 4; i++) {
|
||||
+ ret = i2c_smbus_xfer(i2c, ROLLBALL_PHY_I2C_ADDR, 0, I2C_SMBUS_WRITE, ROLLBALL_PASSWORD + i, I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c,
|
||||
+ enum mdio_i2c_proto protocol)
|
||||
+{
|
||||
+ struct mii_bus *mii;
|
||||
+ int ret;
|
||||
+
|
||||
+ if (!i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
+ return ERR_PTR(-EINVAL);
|
||||
+
|
||||
+ mii = mdiobus_alloc();
|
||||
+ if (!mii)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+
|
||||
+ snprintf(mii->id, MII_BUS_ID_SIZE, "smbus:%s", dev_name(parent));
|
||||
+ mii->parent = parent;
|
||||
+ mii->priv = i2c;
|
||||
+
|
||||
+ switch (protocol) {
|
||||
+ case MDIO_I2C_ROLLBALL:
|
||||
+ ret = smbus_mii_init_rollball(i2c);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(parent,
|
||||
+ "Cannot initialize RollBall MDIO protocol on SMBus: %d\n",
|
||||
+ ret);
|
||||
+ mdiobus_free(mii);
|
||||
+ return ERR_PTR(ret);
|
||||
+ }
|
||||
+
|
||||
+ mii->read_c45 = smbus_mii_read_rollball;
|
||||
+ mii->write_c45 = smbus_mii_write_rollball;
|
||||
+ break;
|
||||
+ default:
|
||||
+ mii->read = smbus_mii_read_c22;
|
||||
+ mii->write = smbus_mii_write_c22;
|
||||
+ mii->read_c45 = smbus_mii_read_c45;
|
||||
+ mii->write_c45 = smbus_mii_write_c45;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ return mii;
|
||||
+}
|
||||
+
|
||||
+MODULE_AUTHOR("Antoine Tenart");
|
||||
+MODULE_DESCRIPTION("MDIO SMBus bridge library");
|
||||
+MODULE_LICENSE("GPL");
|
||||
--- a/drivers/net/phy/Kconfig
|
||||
+++ b/drivers/net/phy/Kconfig
|
||||
@@ -76,6 +76,7 @@ config SFP
|
||||
depends on I2C && PHYLINK
|
||||
depends on HWMON || HWMON=n
|
||||
select MDIO_I2C
|
||||
+ select MDIO_SMBUS
|
||||
|
||||
comment "Switch configuration API + drivers"
|
||||
|
||||
--- a/include/linux/mdio/mdio-i2c.h
|
||||
+++ b/include/linux/mdio/mdio-i2c.h
|
||||
@@ -20,5 +20,9 @@ enum mdio_i2c_proto {
|
||||
|
||||
struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
|
||||
enum mdio_i2c_proto protocol);
|
||||
+struct mii_bus *mdio_smbus_alloc(struct device *parent, struct i2c_adapter *i2c,
|
||||
+ enum mdio_i2c_proto protocol);
|
||||
+bool i2c_mii_valid_phy_id(int phy_id);
|
||||
+unsigned int i2c_mii_phy_addr(int phy_id);
|
||||
|
||||
#endif
|
||||
--- a/drivers/net/mdio/mdio-i2c.c
|
||||
+++ b/drivers/net/mdio/mdio-i2c.c
|
||||
@@ -20,12 +20,12 @@
|
||||
* specified to be present in SFP modules. These correspond with PHY
|
||||
* addresses 16 and 17. Disallow access to these "phy" addresses.
|
||||
*/
|
||||
-static bool i2c_mii_valid_phy_id(int phy_id)
|
||||
+bool i2c_mii_valid_phy_id(int phy_id)
|
||||
{
|
||||
return phy_id != 0x10 && phy_id != 0x11;
|
||||
}
|
||||
|
||||
-static unsigned int i2c_mii_phy_addr(int phy_id)
|
||||
+unsigned int i2c_mii_phy_addr(int phy_id)
|
||||
{
|
||||
return phy_id + 0x40;
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
From 3cb0bde365d913c484d20224367a54a0eac780a7 Mon Sep 17 00:00:00 2001
|
||||
From: Antoine Tenart <antoine.tenart@bootlin.com>
|
||||
Date: Fri, 21 Feb 2020 11:55:29 +0100
|
||||
Subject: [PATCH 3/3] net: phy: sfp: add support for SMBus
|
||||
|
||||
Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
|
||||
---
|
||||
drivers/net/phy/sfp.c | 92 +++++++++++++++++++++++++++++++++++++++++--
|
||||
1 file changed, 88 insertions(+), 4 deletions(-)
|
||||
|
||||
--- a/drivers/net/phy/sfp.c
|
||||
+++ b/drivers/net/phy/sfp.c
|
||||
@@ -729,10 +729,64 @@ static int sfp_i2c_write(struct sfp *sfp
|
||||
return ret == ARRAY_SIZE(msgs) ? len : 0;
|
||||
}
|
||||
|
||||
+static int sfp_smbus_read(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
|
||||
+ size_t len)
|
||||
+{
|
||||
+ u8 bus_addr = a2 ? 0x51 : 0x50, *val = buf;
|
||||
+ union i2c_smbus_data data;
|
||||
+ int ret;
|
||||
+
|
||||
+ bus_addr -= 0x40;
|
||||
+
|
||||
+ while (len > 0) {
|
||||
+ ret = i2c_smbus_xfer(sfp->i2c, i2c_mii_phy_addr(bus_addr), 0,
|
||||
+ I2C_SMBUS_READ, dev_addr,
|
||||
+ I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ *val++ = data.byte;
|
||||
+ dev_addr++;
|
||||
+ len--;
|
||||
+ }
|
||||
+
|
||||
+ return val - (u8 *)buf;
|
||||
+}
|
||||
+
|
||||
+static int sfp_smbus_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
|
||||
+ size_t len)
|
||||
+{
|
||||
+ u8 bus_addr = a2 ? 0x51 : 0x50, *val = buf;
|
||||
+ union i2c_smbus_data data;
|
||||
+ int ret;
|
||||
+
|
||||
+ bus_addr -= 0x40;
|
||||
+
|
||||
+ while (len > 0) {
|
||||
+ data.byte = *val++;
|
||||
+ ret = i2c_smbus_xfer(sfp->i2c, i2c_mii_phy_addr(bus_addr), 0,
|
||||
+ I2C_SMBUS_WRITE, dev_addr,
|
||||
+ I2C_SMBUS_BYTE_DATA, &data);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ dev_addr++;
|
||||
+ len--;
|
||||
+ }
|
||||
+
|
||||
+ return val - (u8 *)buf;
|
||||
+}
|
||||
+
|
||||
static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
|
||||
{
|
||||
- if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
|
||||
- return -EINVAL;
|
||||
+ if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) {
|
||||
+ if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
+ sfp->i2c = i2c;
|
||||
+ sfp->read = sfp_smbus_read;
|
||||
+ sfp->write = sfp_smbus_write;
|
||||
+
|
||||
+ return 0;
|
||||
+ } else
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
|
||||
sfp->i2c = i2c;
|
||||
sfp->read = sfp_i2c_read;
|
||||
@@ -764,6 +818,29 @@ static int sfp_i2c_mdiobus_create(struct
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int sfp_sm_mdiobus_create(struct sfp *sfp)
|
||||
+{
|
||||
+ struct mii_bus *sm_mii;
|
||||
+ int ret;
|
||||
+
|
||||
+ sm_mii = mdio_smbus_alloc(sfp->dev, sfp->i2c, sfp->mdio_protocol);
|
||||
+ if (IS_ERR(sm_mii))
|
||||
+ return PTR_ERR(sm_mii);
|
||||
+
|
||||
+ sm_mii->name = "SFP SMBus";
|
||||
+ sm_mii->phy_mask = ~0;
|
||||
+
|
||||
+ ret = mdiobus_register(sm_mii);
|
||||
+ if (ret < 0) {
|
||||
+ mdiobus_free(sm_mii);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ sfp->i2c_mii = sm_mii;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static void sfp_i2c_mdiobus_destroy(struct sfp *sfp)
|
||||
{
|
||||
mdiobus_unregister(sfp->i2c_mii);
|
||||
@@ -1938,9 +2015,15 @@ static void sfp_sm_fault(struct sfp *sfp
|
||||
|
||||
static int sfp_sm_add_mdio_bus(struct sfp *sfp)
|
||||
{
|
||||
- if (sfp->mdio_protocol != MDIO_I2C_NONE)
|
||||
+ if (sfp->mdio_protocol == MDIO_I2C_NONE)
|
||||
+ return 0;
|
||||
+
|
||||
+ if (i2c_check_functionality(sfp->i2c, I2C_FUNC_I2C))
|
||||
return sfp_i2c_mdiobus_create(sfp);
|
||||
|
||||
+ if (i2c_check_functionality(sfp->i2c, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
+ return sfp_sm_mdiobus_create(sfp);
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
|
||||
From: John Crispin <john@phrozen.org>
|
||||
Date: Thu, 26 Nov 2020 12:02:21 +0100
|
||||
Subject: net: ethernet: Add support for RTL838x ethernet
|
||||
|
||||
* rename the target to realtek
|
||||
* add refactored DSA driver
|
||||
* add latest gpio driver
|
||||
* lots of arch cleanups
|
||||
* new irq driver
|
||||
* additional boards
|
||||
|
||||
Submitted-by: Bert Vermeulen <bert@biot.com>
|
||||
Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
|
||||
Submitted-by: Sander Vanheule <sander@svanheule.net>
|
||||
Submitted-by: Bjørn Mork <bjorn@mork.no>
|
||||
Submitted-by: John Crispin <john@phrozen.org>
|
||||
---
|
||||
drivers/net/ethernet/Kconfig | 7 +-
|
||||
drivers/net/ethernet/Makefile | 1 +
|
||||
2 files changed, 8 insertions(+)
|
||||
|
||||
--- a/drivers/net/ethernet/Kconfig
|
||||
+++ b/drivers/net/ethernet/Kconfig
|
||||
@@ -179,6 +179,13 @@ source "drivers/net/ethernet/rdc/Kconfig
|
||||
source "drivers/net/ethernet/realtek/Kconfig"
|
||||
source "drivers/net/ethernet/renesas/Kconfig"
|
||||
source "drivers/net/ethernet/rocker/Kconfig"
|
||||
+
|
||||
+config NET_RTL838X
|
||||
+ tristate "Realtek rtl838x Ethernet MAC support"
|
||||
+ depends on MACH_REALTEK_RTL
|
||||
+ help
|
||||
+ Say Y here if you want to use the Realtek rtl838x Gbps Ethernet MAC.
|
||||
+
|
||||
source "drivers/net/ethernet/samsung/Kconfig"
|
||||
source "drivers/net/ethernet/seeq/Kconfig"
|
||||
source "drivers/net/ethernet/sgi/Kconfig"
|
||||
--- a/drivers/net/ethernet/Makefile
|
||||
+++ b/drivers/net/ethernet/Makefile
|
||||
@@ -82,6 +82,7 @@ obj-$(CONFIG_NET_VENDOR_REALTEK) += real
|
||||
obj-$(CONFIG_NET_VENDOR_RENESAS) += renesas/
|
||||
obj-$(CONFIG_NET_VENDOR_RDC) += rdc/
|
||||
obj-$(CONFIG_NET_VENDOR_ROCKER) += rocker/
|
||||
+obj-$(CONFIG_NET_RTL838X) += rtl838x_eth.o
|
||||
obj-$(CONFIG_NET_VENDOR_SAMSUNG) += samsung/
|
||||
obj-$(CONFIG_NET_VENDOR_SEEQ) += seeq/
|
||||
obj-$(CONFIG_NET_VENDOR_SILAN) += silan/
|
||||
@ -1,42 +0,0 @@
|
||||
From 2b88563ee5aafd9571d965b7f2093a0f58d98a31 Mon Sep 17 00:00:00 2001
|
||||
From: John Crispin <john@phrozen.org>
|
||||
Date: Thu, 26 Nov 2020 12:02:21 +0100
|
||||
Subject: net: dsa: Add support for rtl838x switch
|
||||
|
||||
* rename the target to realtek
|
||||
* add refactored DSA driver
|
||||
* add latest gpio driver
|
||||
* lots of arch cleanups
|
||||
* new irq driver
|
||||
* additional boards
|
||||
|
||||
Submitted-by: Bert Vermeulen <bert@biot.com>
|
||||
Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
|
||||
Submitted-by: Sander Vanheule <sander@svanheule.net>
|
||||
Submitted-by: Bjørn Mork <bjorn@mork.no>
|
||||
Submitted-by: John Crispin <john@phrozen.org>
|
||||
---
|
||||
drivers/net/dsa/rtl83xx/Kconfig | 2 ++
|
||||
drivers/net/dsa/rtl83xx/Makefile | 1 +
|
||||
2 files changed, 3 insertions(+)
|
||||
|
||||
--- a/drivers/net/dsa/Kconfig
|
||||
+++ b/drivers/net/dsa/Kconfig
|
||||
@@ -91,6 +91,8 @@ source "drivers/net/dsa/xrs700x/Kconfig"
|
||||
|
||||
source "drivers/net/dsa/realtek/Kconfig"
|
||||
|
||||
+source "drivers/net/dsa/rtl83xx/Kconfig"
|
||||
+
|
||||
config NET_DSA_RZN1_A5PSW
|
||||
tristate "Renesas RZ/N1 A5PSW Ethernet switch support"
|
||||
depends on OF && ARCH_RZN1
|
||||
--- a/drivers/net/dsa/Makefile
|
||||
+++ b/drivers/net/dsa/Makefile
|
||||
@@ -26,5 +26,6 @@ obj-y += mxl862xx/
|
||||
obj-y += ocelot/
|
||||
obj-y += qca/
|
||||
obj-y += realtek/
|
||||
+obj-y += rtl83xx/
|
||||
obj-y += sja1105/
|
||||
obj-y += xrs700x/
|
||||
@ -1,41 +0,0 @@
|
||||
From 89f71ebb355c624320c2b0ace8ae9488ff53cbeb Mon Sep 17 00:00:00 2001
|
||||
From: Birger Koblitz <mail@birger-koblitz.de>
|
||||
Date: Tue, 5 Jan 2021 20:40:52 +0100
|
||||
Subject: PHY: Add realtek PHY
|
||||
|
||||
This fixes the build problems for the REALTEK target by adding a proper
|
||||
configuration option for the phy module.
|
||||
|
||||
Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
|
||||
--- a/drivers/net/phy/realtek/Makefile
|
||||
+++ b/drivers/net/phy/realtek/Makefile
|
||||
@@ -2,3 +2,4 @@
|
||||
realtek-y += realtek_main.o
|
||||
realtek-$(CONFIG_REALTEK_PHY_HWMON) += realtek_hwmon.o
|
||||
obj-$(CONFIG_REALTEK_PHY) += realtek.o
|
||||
+obj-$(CONFIG_REALTEK_PHY_MULTIPORT) += realtek_multiport.o
|
||||
--- a/drivers/net/phy/realtek/Kconfig
|
||||
+++ b/drivers/net/phy/realtek/Kconfig
|
||||
@@ -5,6 +5,11 @@ config REALTEK_PHY
|
||||
help
|
||||
Currently supports RTL821x/RTL822x and fast ethernet PHYs
|
||||
|
||||
+config REALTEK_PHY_MULTIPORT
|
||||
+ tristate "Realtek multiport PHYs"
|
||||
+ help
|
||||
+ Currently supports RTL8214x/RTL8218x gigabit multiport PHYs
|
||||
+
|
||||
if REALTEK_PHY
|
||||
|
||||
config REALTEK_PHY_HWMON
|
||||
--- a/drivers/net/phy/Makefile
|
||||
+++ b/drivers/net/phy/Makefile
|
||||
@@ -109,7 +109,7 @@ obj-$(CONFIG_NXP_CBTX_PHY) += nxp-cbtx.o
|
||||
obj-$(CONFIG_NXP_TJA11XX_PHY) += nxp-tja11xx.o
|
||||
obj-y += qcom/
|
||||
obj-$(CONFIG_QSEMI_PHY) += qsemi.o
|
||||
-obj-$(CONFIG_REALTEK_PHY) += realtek/
|
||||
+obj-y += realtek/
|
||||
obj-y += rtl8261n/
|
||||
obj-$(CONFIG_RENESAS_PHY) += uPD60620.o
|
||||
obj-$(CONFIG_ROCKCHIP_PHY) += rockchip.o
|
||||
@ -1,51 +0,0 @@
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Sun, 1 Feb 2026 10:40:52 +0100
|
||||
Subject: realtek: net: dsa: add suport for tag rtl-otto
|
||||
|
||||
This adds the rtl-otto tag feature for Realtek switches.
|
||||
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
|
||||
--- a/net/dsa/Makefile
|
||||
+++ b/net/dsa/Makefile
|
||||
@@ -37,6 +37,7 @@ obj-$(CONFIG_NET_DSA_TAG_QCA) += tag_qca
|
||||
obj-$(CONFIG_NET_DSA_TAG_RTL4_A) += tag_rtl4_a.o
|
||||
obj-$(CONFIG_NET_DSA_TAG_RTL8_4) += tag_rtl8_4.o
|
||||
obj-$(CONFIG_NET_DSA_TAG_RZN1_A5PSW) += tag_rzn1_a5psw.o
|
||||
+obj-$(CONFIG_NET_DSA_TAG_RTL_OTTO) += tag_rtl_otto.o
|
||||
obj-$(CONFIG_NET_DSA_TAG_SJA1105) += tag_sja1105.o
|
||||
obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
|
||||
obj-$(CONFIG_NET_DSA_TAG_VSC73XX_8021Q) += tag_vsc73xx_8021q.o
|
||||
--- a/net/dsa/Kconfig
|
||||
+++ b/net/dsa/Kconfig
|
||||
@@ -177,6 +177,12 @@ config NET_DSA_TAG_LAN9303
|
||||
Say Y or M if you want to enable support for tagging frames for the
|
||||
SMSC/Microchip LAN9303 family of switches.
|
||||
|
||||
+config NET_DSA_TAG_RTL_OTTO
|
||||
+ tristate "Tag driver for Realtek Otto switches (RTL83xx/RTL93xx)"
|
||||
+ help
|
||||
+ Say Y or M if you want to enable support for tagging frames for the
|
||||
+ Realtek Otto family of switches.
|
||||
+
|
||||
config NET_DSA_TAG_SJA1105
|
||||
tristate "Tag driver for NXP SJA1105 switches"
|
||||
select PACKING
|
||||
--- a/include/net/dsa.h
|
||||
+++ b/include/net/dsa.h
|
||||
@@ -57,6 +57,7 @@ struct tc_action;
|
||||
#define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE 29
|
||||
#define DSA_TAG_PROTO_MXL862_VALUE 30
|
||||
#define DSA_TAG_PROTO_MXL862_8021Q_VALUE 31
|
||||
+#define DSA_TAG_PROTO_RTL_OTTO_VALUE 32
|
||||
|
||||
enum dsa_tag_protocol {
|
||||
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
|
||||
@@ -91,6 +92,7 @@ enum dsa_tag_protocol {
|
||||
DSA_TAG_PROTO_VSC73XX_8021Q = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE,
|
||||
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
|
||||
DSA_TAG_PROTO_MXL862_8021Q = DSA_TAG_PROTO_MXL862_8021Q_VALUE,
|
||||
+ DSA_TAG_PROTO_RTL_OTTO = DSA_TAG_PROTO_RTL_OTTO_VALUE,
|
||||
};
|
||||
|
||||
struct dsa_switch;
|
||||
@ -1,239 +0,0 @@
|
||||
From ffb7da9aa25765b2115e7ff3ee4f6dafa60f5421 Mon Sep 17 00:00:00 2001
|
||||
From: Sander Vanheule <sander@svanheule.net>
|
||||
Date: Fri, 27 Dec 2024 14:55:31 +0100
|
||||
Subject: [PATCH] net: mdio: Add Realtek Otto auxiliary controller
|
||||
|
||||
SoCs in Realtek's Otto platform such as the RTL8380, RTL8391, and
|
||||
RTL9302 have a simple auxiliary MDIO controller that is commonly used to
|
||||
manage RTL8231 GPIO expanders on switch devices.
|
||||
|
||||
Add a new MDIO controller driver supporting the RTL838x (maple), RTL839x
|
||||
(cypress), and RTL930x (longan) SoCs.
|
||||
|
||||
Signed-off-by: Sander Vanheule <sander@svanheule.net>
|
||||
---
|
||||
drivers/net/mdio/Kconfig | 10 ++
|
||||
drivers/net/mdio/Makefile | 1 +
|
||||
drivers/net/mdio/mdio-realtek-otto-aux.c | 175 +++++++++++++++++++++++
|
||||
3 files changed, 186 insertions(+)
|
||||
create mode 100644 drivers/net/mdio/mdio-realtek-otto-aux.c
|
||||
|
||||
--- a/drivers/net/mdio/Kconfig
|
||||
+++ b/drivers/net/mdio/Kconfig
|
||||
@@ -207,6 +207,16 @@ config MDIO_REGMAP
|
||||
regmap. Users willing to use this driver must explicitly select
|
||||
REGMAP.
|
||||
|
||||
+config MDIO_REALTEK_OTTO_AUX
|
||||
+ tristate "Realtek Otto auxiliary MDIO interface support"
|
||||
+ default MACH_REALTEK_RTL
|
||||
+ depends on MACH_REALTEK_RTL
|
||||
+ depends on MFD_SYSCON
|
||||
+ select MDIO_DEVRES
|
||||
+ help
|
||||
+ This driver supports the auxilairy MDIO bus on RTL838x SoCs. This bus
|
||||
+ is typically used to attach RTL8231 GPIO extenders.
|
||||
+
|
||||
config MDIO_THUNDER
|
||||
tristate "ThunderX SOCs MDIO buses"
|
||||
depends on 64BIT
|
||||
--- a/drivers/net/mdio/Makefile
|
||||
+++ b/drivers/net/mdio/Makefile
|
||||
@@ -20,6 +20,7 @@ obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-ms
|
||||
obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o
|
||||
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
|
||||
obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
|
||||
+obj-$(CONFIG_MDIO_REALTEK_OTTO_AUX) += mdio-realtek-otto-aux.o
|
||||
obj-$(CONFIG_MDIO_SMBUS) += mdio-smbus.o
|
||||
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
|
||||
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/net/mdio/mdio-realtek-otto-aux.c
|
||||
@@ -0,0 +1,187 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
+
|
||||
+#include <linux/mfd/core.h>
|
||||
+#include <linux/mfd/syscon.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_mdio.h>
|
||||
+#include <linux/mod_devicetable.h>
|
||||
+#include <linux/phy.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+#define RTL8380_EXT_GPIO_INDIRECT_ACCESS 0xA09C
|
||||
+#define RTL8390_EXT_GPIO_INDIRECT_ACCESS 0x0224
|
||||
+#define RTL9300_EXT_GPIO_INDIRECT_ACCESS 0xC620
|
||||
+#define RTL9310_EXT_GPIO_INDIRECT_ACCESS 0x07F4
|
||||
+
|
||||
+#define RTL83XX_AUX_MDIO_DATA_OFFSET 16
|
||||
+#define RTL83XX_AUX_MDIO_RCMD_FAIL 0
|
||||
+
|
||||
+#define RTL93XX_AUX_MDIO_DATA_OFFSET 12
|
||||
+#define RTL93XX_AUX_MDIO_RCMD_FAIL BIT(28)
|
||||
+
|
||||
+#define REALTEK_AUX_MDIO_REG GENMASK(11, 7)
|
||||
+#define REALTEK_AUX_MDIO_PHY_ADDR GENMASK(6, 2)
|
||||
+#define REALTEK_AUX_MDIO_WRITE BIT(1)
|
||||
+#define REALTEK_AUX_MDIO_READ 0
|
||||
+#define REALTEK_AUX_MDIO_EXEC BIT(0)
|
||||
+
|
||||
+struct realtek_aux_mdio_info {
|
||||
+ unsigned int cmd_reg;
|
||||
+ unsigned int data_offset;
|
||||
+ unsigned int rcmd_fail_mask;
|
||||
+ unsigned int timeout_us;
|
||||
+};
|
||||
+
|
||||
+static const struct realtek_aux_mdio_info info_rtl838x = {
|
||||
+ .cmd_reg = RTL8380_EXT_GPIO_INDIRECT_ACCESS,
|
||||
+ .data_offset = RTL83XX_AUX_MDIO_DATA_OFFSET,
|
||||
+ .rcmd_fail_mask = RTL83XX_AUX_MDIO_RCMD_FAIL,
|
||||
+ .timeout_us = 1700,
|
||||
+};
|
||||
+
|
||||
+static const struct realtek_aux_mdio_info info_rtl839x = {
|
||||
+ .cmd_reg = RTL8390_EXT_GPIO_INDIRECT_ACCESS,
|
||||
+ .data_offset = RTL83XX_AUX_MDIO_DATA_OFFSET,
|
||||
+ .rcmd_fail_mask = RTL83XX_AUX_MDIO_RCMD_FAIL,
|
||||
+ .timeout_us = 4120,
|
||||
+};
|
||||
+
|
||||
+static const struct realtek_aux_mdio_info info_rtl930x = {
|
||||
+ .cmd_reg = RTL9300_EXT_GPIO_INDIRECT_ACCESS,
|
||||
+ .data_offset = RTL93XX_AUX_MDIO_DATA_OFFSET,
|
||||
+ .rcmd_fail_mask = RTL93XX_AUX_MDIO_RCMD_FAIL,
|
||||
+ .timeout_us = 19000,
|
||||
+};
|
||||
+
|
||||
+static const struct realtek_aux_mdio_info info_rtl931x = {
|
||||
+ .cmd_reg = RTL9310_EXT_GPIO_INDIRECT_ACCESS,
|
||||
+ .data_offset = RTL93XX_AUX_MDIO_DATA_OFFSET,
|
||||
+ .rcmd_fail_mask = RTL93XX_AUX_MDIO_RCMD_FAIL,
|
||||
+ .timeout_us = 19000,
|
||||
+};
|
||||
+
|
||||
+struct realtek_aux_mdio_ctrl {
|
||||
+ struct device *dev;
|
||||
+ struct regmap *map;
|
||||
+ const struct realtek_aux_mdio_info *info;
|
||||
+};
|
||||
+
|
||||
+#define mii_bus_to_ctrl(bus) ((struct realtek_aux_mdio_ctrl *) bus->priv)
|
||||
+
|
||||
+static int realtek_aux_mdio_cmd(struct realtek_aux_mdio_ctrl *ctrl, int addr, int regnum,
|
||||
+ u32 rw_bit, u16 *data)
|
||||
+{
|
||||
+ unsigned int cmd;
|
||||
+ int err;
|
||||
+
|
||||
+ cmd = rw_bit | REALTEK_AUX_MDIO_EXEC;
|
||||
+ cmd |= FIELD_PREP(REALTEK_AUX_MDIO_PHY_ADDR, addr);
|
||||
+ cmd |= FIELD_PREP(REALTEK_AUX_MDIO_REG, regnum);
|
||||
+
|
||||
+ if (rw_bit == REALTEK_AUX_MDIO_WRITE)
|
||||
+ cmd |= *data << ctrl->info->data_offset;
|
||||
+
|
||||
+ err = regmap_write(ctrl->map, ctrl->info->cmd_reg, cmd);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ err = regmap_read_poll_timeout_atomic(ctrl->map, ctrl->info->cmd_reg, cmd,
|
||||
+ !(cmd & REALTEK_AUX_MDIO_EXEC), 3, ctrl->info->timeout_us);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ if (rw_bit == REALTEK_AUX_MDIO_READ) {
|
||||
+ if (cmd & ctrl->info->rcmd_fail_mask)
|
||||
+ return -EIO;
|
||||
+
|
||||
+ *data = (cmd >> ctrl->info->data_offset) & GENMASK(15, 0);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int realtek_aux_mdio_read(struct mii_bus *bus, int addr, int regnum)
|
||||
+{
|
||||
+ struct realtek_aux_mdio_ctrl *ctrl = mii_bus_to_ctrl(bus);
|
||||
+ u16 data;
|
||||
+ int err;
|
||||
+
|
||||
+ err = realtek_aux_mdio_cmd(ctrl, addr, regnum, REALTEK_AUX_MDIO_READ, &data);
|
||||
+
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+ else
|
||||
+ return data;
|
||||
+}
|
||||
+
|
||||
+static int realtek_aux_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val)
|
||||
+{
|
||||
+ struct realtek_aux_mdio_ctrl *ctrl = mii_bus_to_ctrl(bus);
|
||||
+
|
||||
+ return realtek_aux_mdio_cmd(ctrl, addr, regnum, REALTEK_AUX_MDIO_WRITE, &val);
|
||||
+}
|
||||
+
|
||||
+static int realtek_aux_mdio_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device_node *np = pdev->dev.of_node;
|
||||
+ struct realtek_aux_mdio_ctrl *ctrl;
|
||||
+ struct mii_bus *bus;
|
||||
+
|
||||
+ bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*ctrl));
|
||||
+ if (!bus)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ ctrl = bus->priv;
|
||||
+ ctrl->dev = &pdev->dev;
|
||||
+ ctrl->info = (const struct realtek_aux_mdio_info *) device_get_match_data(ctrl->dev);
|
||||
+ ctrl->map = syscon_node_to_regmap(np->parent);
|
||||
+ if (IS_ERR(ctrl->map))
|
||||
+ return PTR_ERR(ctrl->map);
|
||||
+
|
||||
+ bus->name = "Realtek auxiliary MDIO bus";
|
||||
+ snprintf(bus->id, MII_BUS_ID_SIZE, "realtek-aux-mdio") ;
|
||||
+ bus->parent = ctrl->dev;
|
||||
+ bus->read = realtek_aux_mdio_read;
|
||||
+ bus->write = realtek_aux_mdio_write;
|
||||
+ /* Don't have interrupts */
|
||||
+ for (unsigned int i = 0; i < PHY_MAX_ADDR; i++)
|
||||
+ bus->irq[i] = PHY_POLL;
|
||||
+
|
||||
+ return devm_of_mdiobus_register(ctrl->dev, bus, np);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id realtek_aux_mdio_of_match[] = {
|
||||
+ {
|
||||
+ .compatible = "realtek,rtl8380-aux-mdio",
|
||||
+ .data = &info_rtl838x,
|
||||
+ },
|
||||
+ {
|
||||
+ .compatible = "realtek,rtl8390-aux-mdio",
|
||||
+ .data = &info_rtl839x,
|
||||
+ },
|
||||
+ {
|
||||
+ .compatible = "realtek,rtl9300-aux-mdio",
|
||||
+ .data = &info_rtl930x,
|
||||
+ },
|
||||
+ {
|
||||
+ .compatible = "realtek,rtl9310-aux-mdio",
|
||||
+ .data = &info_rtl931x,
|
||||
+ },
|
||||
+ { /* sentinel */ }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, realtek_aux_mdio_of_match);
|
||||
+
|
||||
+static struct platform_driver realtek_aux_mdio_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "realtek-otto-aux-mdio",
|
||||
+ .of_match_table = realtek_aux_mdio_of_match
|
||||
+ },
|
||||
+ .probe = realtek_aux_mdio_probe,
|
||||
+};
|
||||
+module_platform_driver(realtek_aux_mdio_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
|
||||
+MODULE_DESCRIPTION("Realtek otto auxiliary MDIO bus");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
@ -1,37 +0,0 @@
|
||||
From ad75da9aaa8765b2115e7b40ee4f6dbcd60c3321 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
Date: Weg, 17 Sep 2025 20:23:31 +0200
|
||||
Subject: [PATCH] net: pcs: Add Realtek Otto SerDes controller
|
||||
|
||||
SoCs in Realtek's Otto platform such as the RTL83xx and RTL93xx
|
||||
have multiple SerDes to drive the PHYs. Provide a PCS driver
|
||||
to configure them.
|
||||
|
||||
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
||||
---
|
||||
--- a/drivers/net/pcs/Kconfig
|
||||
+++ b/drivers/net/pcs/Kconfig
|
||||
@@ -36,6 +36,14 @@ config PCS_MTK_USXGMII
|
||||
1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
|
||||
differential pairs via an embedded LynxI PHY.
|
||||
|
||||
+config PCS_RTL_OTTO
|
||||
+ tristate "Realtek Otto SerDes PCS"
|
||||
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
||||
+ select PHYLINK
|
||||
+ select REGMAP
|
||||
+ help
|
||||
+ This module provides a driver for the Realtek SerDes PCS
|
||||
+
|
||||
config PCS_RZN1_MIIC
|
||||
tristate "Renesas RZ/N1 MII converter"
|
||||
depends on OF && (ARCH_RZN1 || COMPILE_TEST)
|
||||
--- a/drivers/net/pcs/Makefile
|
||||
+++ b/drivers/net/pcs/Makefile
|
||||
@@ -7,5 +7,6 @@ pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.
|
||||
obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
|
||||
obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
|
||||
obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
|
||||
+obj-$(CONFIG_PCS_RTL_OTTO) += pcs-rtl-otto.o
|
||||
obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
|
||||
obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
|
||||
@ -1,190 +0,0 @@
|
||||
From 672a9bfb2e01ecaf40e5b92e9cc564589ffc251d Mon Sep 17 00:00:00 2001
|
||||
From: Jan Hoffmann <jan@3e8.eu>
|
||||
Date: Tue, 23 Dec 2025 20:07:53 +0100
|
||||
Subject: [PATCH] net: phy: realtek: support MDI swapping for RTL8226
|
||||
|
||||
Add support for configuring swapping of MDI pairs (ABCD->DCBA) when the
|
||||
property "enet-phy-pair-order" is specified.
|
||||
|
||||
Unfortunately, no documentation about this feature is available, so the
|
||||
configuration involves magic values. Only enabling MDI swapping is
|
||||
supported, as it is unknown whether the patching step can be safely
|
||||
reversed.
|
||||
|
||||
For now, only implement it for RTL8226, where it is needed to make the
|
||||
PHYs in Zyxel XGS1010-12 rev A1 work. However, parts of this code might
|
||||
also be useful for other PHYs in the future:
|
||||
|
||||
RTL8221B also allows to configure MDI swapping via the same register,
|
||||
but does not need the additional patching step. Since it also supports
|
||||
configuration via strapping pins, there might not be any need for driver
|
||||
support on that PHY, though.
|
||||
|
||||
The patching step itself seems to be the same which is also used by the
|
||||
integrated PHY of some Realtek PCIe/USB NICs.
|
||||
|
||||
Signed-off-by: Jan Hoffmann <jan@3e8.eu>
|
||||
---
|
||||
drivers/net/phy/realtek/realtek_main.c | 159 ++++++++++++++++++++++++-
|
||||
1 file changed, 158 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/net/phy/realtek/realtek_main.c
|
||||
+++ b/drivers/net/phy/realtek/realtek_main.c
|
||||
@@ -1489,6 +1489,148 @@ static unsigned int rtl822x_inband_caps(
|
||||
}
|
||||
}
|
||||
|
||||
+static int rtl8226_set_mdi_swap(struct phy_device *phydev, bool swap_enable)
|
||||
+{
|
||||
+ u16 val = swap_enable ? BIT(5) : 0;
|
||||
+
|
||||
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND1, 0x6a21, BIT(5), val);
|
||||
+}
|
||||
+
|
||||
+static int rtl8226_patch_mdi_swap(struct phy_device *phydev)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u16 vals[4];
|
||||
+
|
||||
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xd068);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (!(ret & BIT(1))) {
|
||||
+ /* already swapped */
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xd068, 0x7, 0x1);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ /* swap adccal_offset */
|
||||
+
|
||||
+ for (int i = 0; i < 4; i++) {
|
||||
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xd068, 0x3 << 3, i << 3);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xd06a);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ vals[i] = ret;
|
||||
+ }
|
||||
+
|
||||
+ for (int i = 0; i < 4; i++) {
|
||||
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xd068, 0x3 << 3, i << 3);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xd06a, vals[3 - i]);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* swap rg_lpf_cap_xg */
|
||||
+
|
||||
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbd5a);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ vals[0] = ret & 0x1f;
|
||||
+ vals[1] = (ret >> 8) & 0x1f;
|
||||
+
|
||||
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbd5c);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ vals[2] = ret & 0x1f;
|
||||
+ vals[3] = (ret >> 8) & 0x1f;
|
||||
+
|
||||
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbd5a, 0x1f1f,
|
||||
+ vals[3] | (vals[2] << 8));
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbd5c, 0x1f1f,
|
||||
+ vals[1] | (vals[0] << 8));
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ /* swap rg_lpf_cap */
|
||||
+
|
||||
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbc18);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ vals[0] = ret & 0x1f;
|
||||
+ vals[1] = (ret >> 8) & 0x1f;
|
||||
+
|
||||
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0xbc1a);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ vals[2] = ret & 0x1f;
|
||||
+ vals[3] = (ret >> 8) & 0x1f;
|
||||
+
|
||||
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbc18, 0x1f1f,
|
||||
+ vals[3] | (vals[2] << 8));
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0xbc1a, 0x1f1f,
|
||||
+ vals[1] | (vals[0] << 8));
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl8226_config_mdi_order(struct phy_device *phydev)
|
||||
+{
|
||||
+ u32 order;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = of_property_read_u32(phydev->mdio.dev.of_node, "enet-phy-pair-order", &order);
|
||||
+
|
||||
+ /* Property not present, nothing to do */
|
||||
+ if (ret == -EINVAL)
|
||||
+ return 0;
|
||||
+
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ /* Only enabling MDI swapping is supported */
|
||||
+ if (order != 1)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ ret = rtl8226_set_mdi_swap(phydev, true);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = rtl8226_patch_mdi_swap(phydev);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int rtl8226_config_init(struct phy_device *phydev)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = rtl8226_config_mdi_order(phydev);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return rtl822x_config_init(phydev);
|
||||
+}
|
||||
+
|
||||
+
|
||||
static int rtl822xb_get_rate_matching(struct phy_device *phydev,
|
||||
phy_interface_t iface)
|
||||
{
|
||||
@@ -2452,7 +2594,7 @@ static struct phy_driver realtek_drvs[]
|
||||
.soft_reset = rtl822x_c45_soft_reset,
|
||||
.get_features = rtl822x_c45_get_features,
|
||||
.config_aneg = rtl822x_c45_config_aneg,
|
||||
- .config_init = rtl822x_config_init,
|
||||
+ .config_init = rtl8226_config_init,
|
||||
.inband_caps = rtl822x_inband_caps,
|
||||
.config_inband = rtl822x_config_inband,
|
||||
.read_status = rtl822xb_c45_read_status,
|
||||
@ -1,332 +0,0 @@
|
||||
From 4e3455e058d40eb2a7326016494e3c81dc506c33 Mon Sep 17 00:00:00 2001
|
||||
From: Sander Vanheule <sander@svanheule.net>
|
||||
Date: Mon, 10 May 2021 18:33:01 +0200
|
||||
Subject: [PATCH] mfd: Add RTL8231 core device
|
||||
|
||||
The RTL8231 is implemented as an MDIO device, and provides a regmap
|
||||
interface for register access by the core and child devices.
|
||||
|
||||
The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
|
||||
Since kernel support for SMI is limited, and no real-world SMI
|
||||
implementations have been encountered for this device, this is currently
|
||||
unimplemented. The use of the regmap interface should make any future
|
||||
support relatively straightforward.
|
||||
|
||||
After reset, all pins are muxed to GPIO inputs before the pin drivers
|
||||
are enabled. This is done to prevent accidental system resets, when a
|
||||
pin is connected to the parent SoC's reset line.
|
||||
|
||||
To provide different read and write semantics for the GPIO data
|
||||
registers, a secondary virtual register range is used to enable separate
|
||||
caching properties of pin input and output values.
|
||||
|
||||
Signed-off-by: Sander Vanheule <sander@svanheule.net>
|
||||
---
|
||||
drivers/mfd/Kconfig | 9 ++
|
||||
drivers/mfd/Makefile | 1 +
|
||||
drivers/mfd/rtl8231.c | 193 ++++++++++++++++++++++++++++++++++++
|
||||
include/linux/mfd/rtl8231.h | 71 +++++++++++++
|
||||
4 files changed, 274 insertions(+)
|
||||
create mode 100644 drivers/mfd/rtl8231.c
|
||||
create mode 100644 include/linux/mfd/rtl8231.h
|
||||
|
||||
--- a/drivers/mfd/Kconfig
|
||||
+++ b/drivers/mfd/Kconfig
|
||||
@@ -1219,6 +1219,15 @@ config MFD_RDC321X
|
||||
southbridge which provides access to GPIOs and Watchdog using the
|
||||
southbridge PCI device configuration space.
|
||||
|
||||
+config MFD_RTL8231
|
||||
+ tristate "Realtek RTL8231 GPIO and LED expander"
|
||||
+ select MFD_CORE
|
||||
+ select REGMAP_MDIO
|
||||
+ help
|
||||
+ Support for the Realtek RTL8231 GPIO and LED expander.
|
||||
+ Provides up to 37 GPIOs, 88 LEDs, and one PWM output.
|
||||
+ When built as a module, this module will be named rtl8231.
|
||||
+
|
||||
config MFD_RT4831
|
||||
tristate "Richtek RT4831 four channel WLED and Display Bias Voltage"
|
||||
depends on I2C
|
||||
--- a/drivers/mfd/Makefile
|
||||
+++ b/drivers/mfd/Makefile
|
||||
@@ -246,6 +246,7 @@ obj-$(CONFIG_MFD_HI6421_PMIC) += hi6421-
|
||||
obj-$(CONFIG_MFD_HI6421_SPMI) += hi6421-spmi-pmic.o
|
||||
obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o
|
||||
obj-$(CONFIG_MFD_DLN2) += dln2.o
|
||||
+obj-$(CONFIG_MFD_RTL8231) += rtl8231.o
|
||||
obj-$(CONFIG_MFD_RT4831) += rt4831.o
|
||||
obj-$(CONFIG_MFD_RT5033) += rt5033.o
|
||||
obj-$(CONFIG_MFD_RT5120) += rt5120.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/mfd/rtl8231.c
|
||||
@@ -0,0 +1,193 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+
|
||||
+#include <linux/bits.h>
|
||||
+#include <linux/bitfield.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/gpio/consumer.h>
|
||||
+#include <linux/mfd/core.h>
|
||||
+#include <linux/mdio.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/property.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+#include <linux/mfd/rtl8231.h>
|
||||
+
|
||||
+static bool rtl8231_volatile_reg(struct device *dev, unsigned int reg)
|
||||
+{
|
||||
+ switch (reg) {
|
||||
+ /*
|
||||
+ * Registers with self-clearing bits, strapping pin values.
|
||||
+ * Don't mark the data registers as volatile, since we need
|
||||
+ * caching for the output values.
|
||||
+ */
|
||||
+ case RTL8231_REG_FUNC0:
|
||||
+ case RTL8231_REG_FUNC1:
|
||||
+ case RTL8231_REG_PIN_HI_CFG:
|
||||
+ case RTL8231_REG_LED_END:
|
||||
+ return true;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static const struct reg_field RTL8231_FIELD_LED_START = REG_FIELD(RTL8231_REG_FUNC0, 1, 1);
|
||||
+
|
||||
+static const struct mfd_cell rtl8231_cells[] = {
|
||||
+ {
|
||||
+ .name = "rtl8231-pinctrl",
|
||||
+ },
|
||||
+ {
|
||||
+ .name = "rtl8231-leds",
|
||||
+ .of_compatible = "realtek,rtl8231-leds",
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+static int rtl8231_soft_reset(struct regmap *map)
|
||||
+{
|
||||
+ const unsigned int all_pins_mask = GENMASK(RTL8231_BITS_VAL - 1, 0);
|
||||
+ unsigned int val;
|
||||
+ int err;
|
||||
+
|
||||
+ /* SOFT_RESET bit self-clears when done */
|
||||
+ regmap_write_bits(map, RTL8231_REG_PIN_HI_CFG,
|
||||
+ RTL8231_PIN_HI_CFG_SOFT_RESET, RTL8231_PIN_HI_CFG_SOFT_RESET);
|
||||
+ err = regmap_read_poll_timeout(map, RTL8231_REG_PIN_HI_CFG, val,
|
||||
+ !(val & RTL8231_PIN_HI_CFG_SOFT_RESET), 50, 1000);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ regcache_mark_dirty(map);
|
||||
+
|
||||
+ /*
|
||||
+ * Chip reset results in a pin configuration that is a mix of LED and GPIO outputs.
|
||||
+ * Select GPI functionality for all pins before enabling pin outputs.
|
||||
+ */
|
||||
+ regmap_write(map, RTL8231_REG_PIN_MODE0, all_pins_mask);
|
||||
+ regmap_write(map, RTL8231_REG_GPIO_DIR0, all_pins_mask);
|
||||
+ regmap_write(map, RTL8231_REG_PIN_MODE1, all_pins_mask);
|
||||
+ regmap_write(map, RTL8231_REG_GPIO_DIR1, all_pins_mask);
|
||||
+ regmap_write(map, RTL8231_REG_PIN_HI_CFG,
|
||||
+ RTL8231_PIN_HI_CFG_MODE_MASK | RTL8231_PIN_HI_CFG_DIR_MASK);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_init(struct device *dev, struct regmap *map)
|
||||
+{
|
||||
+ struct regmap_field *led_start;
|
||||
+ unsigned int started;
|
||||
+ unsigned int val;
|
||||
+ int err;
|
||||
+
|
||||
+ err = regmap_read(map, RTL8231_REG_FUNC1, &val);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "failed to read READY_CODE\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ val = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, val);
|
||||
+ if (val != RTL8231_FUNC1_READY_CODE_VALUE) {
|
||||
+ dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
|
||||
+ val, RTL8231_FUNC1_READY_CODE_VALUE);
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ led_start = dev_get_drvdata(dev);
|
||||
+ err = regmap_field_read(led_start, &started);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ if (!started) {
|
||||
+ err = rtl8231_soft_reset(map);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+ /* LED_START enables power to output pins, and starts the LED engine */
|
||||
+ err = regmap_field_force_write(led_start, 1);
|
||||
+ }
|
||||
+
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+static const struct regmap_config rtl8231_mdio_regmap_config = {
|
||||
+ .val_bits = RTL8231_BITS_VAL,
|
||||
+ .reg_bits = RTL8231_BITS_REG,
|
||||
+ .volatile_reg = rtl8231_volatile_reg,
|
||||
+ .max_register = RTL8231_REG_COUNT - 1,
|
||||
+ .use_single_read = true,
|
||||
+ .use_single_write = true,
|
||||
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
|
||||
+ .val_format_endian = REGMAP_ENDIAN_BIG,
|
||||
+ /* Cannot use REGCACHE_FLAT because it's not smart enough about cache invalidation */
|
||||
+ .cache_type = REGCACHE_RBTREE,
|
||||
+};
|
||||
+
|
||||
+static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
|
||||
+{
|
||||
+ struct device *dev = &mdiodev->dev;
|
||||
+ struct regmap_field *led_start;
|
||||
+ struct regmap *map;
|
||||
+ int err;
|
||||
+
|
||||
+ map = devm_regmap_init_mdio(mdiodev, &rtl8231_mdio_regmap_config);
|
||||
+ if (IS_ERR(map)) {
|
||||
+ dev_err(dev, "failed to init regmap\n");
|
||||
+ return PTR_ERR(map);
|
||||
+ }
|
||||
+
|
||||
+ led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
|
||||
+ if (IS_ERR(led_start))
|
||||
+ return PTR_ERR(led_start);
|
||||
+
|
||||
+ dev_set_drvdata(dev, led_start);
|
||||
+
|
||||
+ mdiodev->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
|
||||
+ if (IS_ERR(mdiodev->reset_gpio))
|
||||
+ return PTR_ERR(mdiodev->reset_gpio);
|
||||
+
|
||||
+ device_property_read_u32(dev, "reset-assert-delay", &mdiodev->reset_assert_delay);
|
||||
+ device_property_read_u32(dev, "reset-deassert-delay", &mdiodev->reset_deassert_delay);
|
||||
+
|
||||
+ err = rtl8231_init(dev, map);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
|
||||
+ ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
|
||||
+}
|
||||
+
|
||||
+__maybe_unused static int rtl8231_suspend(struct device *dev)
|
||||
+{
|
||||
+ struct regmap_field *led_start = dev_get_drvdata(dev);
|
||||
+
|
||||
+ return regmap_field_force_write(led_start, 0);
|
||||
+}
|
||||
+
|
||||
+__maybe_unused static int rtl8231_resume(struct device *dev)
|
||||
+{
|
||||
+ struct regmap_field *led_start = dev_get_drvdata(dev);
|
||||
+
|
||||
+ return regmap_field_force_write(led_start, 1);
|
||||
+}
|
||||
+
|
||||
+static SIMPLE_DEV_PM_OPS(rtl8231_pm_ops, rtl8231_suspend, rtl8231_resume);
|
||||
+
|
||||
+static const struct of_device_id rtl8231_of_match[] = {
|
||||
+ { .compatible = "realtek,rtl8231" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, rtl8231_of_match);
|
||||
+
|
||||
+static struct mdio_driver rtl8231_mdio_driver = {
|
||||
+ .mdiodrv.driver = {
|
||||
+ .name = "rtl8231-expander",
|
||||
+ .of_match_table = rtl8231_of_match,
|
||||
+ .pm = pm_ptr(&rtl8231_pm_ops),
|
||||
+ },
|
||||
+ .probe = rtl8231_mdio_probe,
|
||||
+};
|
||||
+mdio_module_driver(rtl8231_mdio_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
|
||||
+MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander");
|
||||
+MODULE_LICENSE("GPL");
|
||||
--- /dev/null
|
||||
+++ b/include/linux/mfd/rtl8231.h
|
||||
@@ -0,0 +1,73 @@
|
||||
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
+/*
|
||||
+ * Register definitions the RTL8231 GPIO and LED expander chip
|
||||
+ */
|
||||
+
|
||||
+#ifndef __LINUX_MFD_RTL8231_H
|
||||
+#define __LINUX_MFD_RTL8231_H
|
||||
+
|
||||
+#include <linux/bits.h>
|
||||
+
|
||||
+/*
|
||||
+ * Registers addresses are 5 bit, values are 16 bit
|
||||
+ * Also define a duplicated range of virtual addresses, to enable
|
||||
+ * different read/write behaviour on the GPIO data registers
|
||||
+ */
|
||||
+#define RTL8231_BITS_VAL 16
|
||||
+#define RTL8231_BITS_REG 5
|
||||
+
|
||||
+/* Chip control */
|
||||
+#define RTL8231_REG_FUNC0 0x00
|
||||
+#define RTL8231_FUNC0_SCAN_MODE BIT(0)
|
||||
+#define RTL8231_FUNC0_SCAN_SINGLE 0
|
||||
+#define RTL8231_FUNC0_SCAN_BICOLOR BIT(0)
|
||||
+#define RTL8231_ENABLE_SYNC_LED BIT(14)
|
||||
+#define RTL8231_ENABLE_SYNC_GPIO BIT(15)
|
||||
+
|
||||
+#define RTL8231_REG_FUNC1 0x01
|
||||
+#define RTL8231_FUNC1_READY_CODE_VALUE 0x37
|
||||
+#define RTL8231_FUNC1_READY_CODE_MASK GENMASK(9, 4)
|
||||
+#define RTL8231_FUNC1_DEBOUNCE_MASK GENMASK(15, 10)
|
||||
+
|
||||
+/* Pin control */
|
||||
+#define RTL8231_REG_PIN_MODE0 0x02
|
||||
+#define RTL8231_REG_PIN_MODE1 0x03
|
||||
+
|
||||
+#define RTL8231_PIN_MODE_LED 0
|
||||
+#define RTL8231_PIN_MODE_GPIO 1
|
||||
+
|
||||
+/* Pin high config: pin and GPIO control for pins 32-26 */
|
||||
+#define RTL8231_REG_PIN_HI_CFG 0x04
|
||||
+#define RTL8231_PIN_HI_CFG_MODE_MASK GENMASK(4, 0)
|
||||
+#define RTL8231_PIN_HI_CFG_DIR_MASK GENMASK(9, 5)
|
||||
+#define RTL8231_PIN_HI_CFG_INV_MASK GENMASK(14, 10)
|
||||
+#define RTL8231_PIN_HI_CFG_SOFT_RESET BIT(15)
|
||||
+
|
||||
+/* GPIO control registers */
|
||||
+#define RTL8231_REG_GPIO_DIR0 0x05
|
||||
+#define RTL8231_REG_GPIO_DIR1 0x06
|
||||
+#define RTL8231_REG_GPIO_INVERT0 0x07
|
||||
+#define RTL8231_REG_GPIO_INVERT1 0x08
|
||||
+
|
||||
+#define RTL8231_GPIO_DIR_IN 1
|
||||
+#define RTL8231_GPIO_DIR_OUT 0
|
||||
+
|
||||
+/*
|
||||
+ * GPIO data registers
|
||||
+ * Only the output data can be written to these registers, and only the input
|
||||
+ * data can be read.
|
||||
+ */
|
||||
+#define RTL8231_REG_GPIO_DATA0 0x1c
|
||||
+#define RTL8231_REG_GPIO_DATA1 0x1d
|
||||
+#define RTL8231_REG_GPIO_DATA2 0x1e
|
||||
+#define RTL8231_PIN_HI_DATA_MASK GENMASK(4, 0)
|
||||
+
|
||||
+/* LED control base registers */
|
||||
+#define RTL8231_REG_LED0_BASE 0x09
|
||||
+#define RTL8231_REG_LED1_BASE 0x10
|
||||
+#define RTL8231_REG_LED2_BASE 0x17
|
||||
+#define RTL8231_REG_LED_END 0x1b
|
||||
+
|
||||
+#define RTL8231_REG_COUNT 0x1f
|
||||
+
|
||||
+#endif /* __LINUX_MFD_RTL8231_H */
|
||||
@ -1,586 +0,0 @@
|
||||
From 098324288a63a6dcc44e96cc381aef3d5c48d89e Mon Sep 17 00:00:00 2001
|
||||
From: Sander Vanheule <sander@svanheule.net>
|
||||
Date: Mon, 10 May 2021 22:15:31 +0200
|
||||
Subject: [PATCH] pinctrl: Add RTL8231 pin control and GPIO support
|
||||
|
||||
This driver implements the GPIO and pin muxing features provided by the
|
||||
RTL8231. The device should be instantiated as an MFD child, where the
|
||||
parent device has already configured the regmap used for register
|
||||
access.
|
||||
|
||||
Debouncing is only available for the six highest GPIOs, and must be
|
||||
emulated when other pins are used for (button) inputs. Although
|
||||
described in the bindings, drive strength selection is currently not
|
||||
implemented.
|
||||
|
||||
Signed-off-by: Sander Vanheule <sander@svanheule.net>
|
||||
---
|
||||
drivers/pinctrl/Kconfig | 11 +
|
||||
drivers/pinctrl/Makefile | 1 +
|
||||
drivers/pinctrl/pinctrl-rtl8231.c | 521 ++++++++++++++++++++++++++++++
|
||||
3 files changed, 533 insertions(+)
|
||||
create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
|
||||
|
||||
--- a/drivers/pinctrl/Kconfig
|
||||
+++ b/drivers/pinctrl/Kconfig
|
||||
@@ -483,6 +483,17 @@ config PINCTRL_SCMI
|
||||
It uses SCMI Message Protocol to interact with the
|
||||
firmware providing all the pinctrl controls.
|
||||
|
||||
+config PINCTRL_RTL8231
|
||||
+ tristate "Realtek RTL8231 GPIO expander's pin controller"
|
||||
+ depends on MFD_RTL8231
|
||||
+ default MFD_RTL8231
|
||||
+ select GPIO_REGMAP
|
||||
+ select GENERIC_PINCONF
|
||||
+ select GENERIC_PINMUX_FUNCTIONS
|
||||
+ help
|
||||
+ Support for RTL8231 expander's GPIOs and pin controller.
|
||||
+ When built as a module, the module will be called pinctrl-rtl8231.
|
||||
+
|
||||
config PINCTRL_SINGLE
|
||||
tristate "One-register-per-pin type device tree based pinctrl driver"
|
||||
depends on OF
|
||||
--- a/drivers/pinctrl/Makefile
|
||||
+++ b/drivers/pinctrl/Makefile
|
||||
@@ -47,6 +47,7 @@ obj-$(CONFIG_PINCTRL_PIC32) += pinctrl-p
|
||||
obj-$(CONFIG_PINCTRL_PISTACHIO) += pinctrl-pistachio.o
|
||||
obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o
|
||||
obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o
|
||||
+obj-$(CONFIG_PINCTRL_RTL8231) += pinctrl-rtl8231.o
|
||||
obj-$(CONFIG_PINCTRL_SCMI) += pinctrl-scmi.o
|
||||
obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o
|
||||
obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/pinctrl/pinctrl-rtl8231.c
|
||||
@@ -0,0 +1,530 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+
|
||||
+#include <linux/bitfield.h>
|
||||
+#include <linux/gpio/driver.h>
|
||||
+#include <linux/gpio/regmap.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/pinctrl/pinconf.h>
|
||||
+#include <linux/pinctrl/pinctrl.h>
|
||||
+#include <linux/pinctrl/pinmux.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+#include "core.h"
|
||||
+#include "pinmux.h"
|
||||
+#include <linux/mfd/rtl8231.h>
|
||||
+
|
||||
+#define RTL8231_NUM_GPIOS 37
|
||||
+#define RTL8231_DEBOUNCE_USEC 100000
|
||||
+#define RTL8231_DEBOUNCE_MIN_OFFSET 31
|
||||
+
|
||||
+struct rtl8231_pin_ctrl {
|
||||
+ struct pinctrl_desc pctl_desc;
|
||||
+ struct regmap *map;
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * Pin controller functionality
|
||||
+ */
|
||||
+static const char * const rtl8231_pin_function_names[] = {
|
||||
+ "gpio",
|
||||
+ "led",
|
||||
+ "pwm",
|
||||
+};
|
||||
+
|
||||
+enum rtl8231_pin_function {
|
||||
+ RTL8231_PIN_FUNCTION_GPIO = BIT(0),
|
||||
+ RTL8231_PIN_FUNCTION_LED = BIT(1),
|
||||
+ RTL8231_PIN_FUNCTION_PWM = BIT(2),
|
||||
+};
|
||||
+
|
||||
+struct rtl8231_pin_desc {
|
||||
+ const enum rtl8231_pin_function functions;
|
||||
+ const u8 reg;
|
||||
+ const u8 offset;
|
||||
+ const u8 gpio_function_value;
|
||||
+};
|
||||
+
|
||||
+#define RTL8231_PIN_DESC(_num, _func, _reg, _fld, _val) \
|
||||
+ [_num] = { \
|
||||
+ .functions = RTL8231_PIN_FUNCTION_GPIO | _func, \
|
||||
+ .reg = _reg, \
|
||||
+ .offset = _fld, \
|
||||
+ .gpio_function_value = _val, \
|
||||
+ }
|
||||
+#define RTL8231_GPIO_PIN_DESC(_num, _reg, _fld) \
|
||||
+ RTL8231_PIN_DESC(_num, 0, _reg, _fld, RTL8231_PIN_MODE_GPIO)
|
||||
+#define RTL8231_LED_PIN_DESC(_num, _reg, _fld) \
|
||||
+ RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
|
||||
+#define RTL8231_PWM_PIN_DESC(_num, _reg, _fld) \
|
||||
+ RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
|
||||
+
|
||||
+/*
|
||||
+ * All pins have a GPIO/LED mux bit, but the bits for pins 35/36 are read-only. Use this bit
|
||||
+ * for the GPIO-only pin instead of a placeholder, so the rest of the logic can stay generic.
|
||||
+ */
|
||||
+static struct rtl8231_pin_desc rtl8231_pin_data[RTL8231_NUM_GPIOS] = {
|
||||
+ RTL8231_LED_PIN_DESC(0, RTL8231_REG_PIN_MODE0, 0),
|
||||
+ RTL8231_LED_PIN_DESC(1, RTL8231_REG_PIN_MODE0, 1),
|
||||
+ RTL8231_LED_PIN_DESC(2, RTL8231_REG_PIN_MODE0, 2),
|
||||
+ RTL8231_LED_PIN_DESC(3, RTL8231_REG_PIN_MODE0, 3),
|
||||
+ RTL8231_LED_PIN_DESC(4, RTL8231_REG_PIN_MODE0, 4),
|
||||
+ RTL8231_LED_PIN_DESC(5, RTL8231_REG_PIN_MODE0, 5),
|
||||
+ RTL8231_LED_PIN_DESC(6, RTL8231_REG_PIN_MODE0, 6),
|
||||
+ RTL8231_LED_PIN_DESC(7, RTL8231_REG_PIN_MODE0, 7),
|
||||
+ RTL8231_LED_PIN_DESC(8, RTL8231_REG_PIN_MODE0, 8),
|
||||
+ RTL8231_LED_PIN_DESC(9, RTL8231_REG_PIN_MODE0, 9),
|
||||
+ RTL8231_LED_PIN_DESC(10, RTL8231_REG_PIN_MODE0, 10),
|
||||
+ RTL8231_LED_PIN_DESC(11, RTL8231_REG_PIN_MODE0, 11),
|
||||
+ RTL8231_LED_PIN_DESC(12, RTL8231_REG_PIN_MODE0, 12),
|
||||
+ RTL8231_LED_PIN_DESC(13, RTL8231_REG_PIN_MODE0, 13),
|
||||
+ RTL8231_LED_PIN_DESC(14, RTL8231_REG_PIN_MODE0, 14),
|
||||
+ RTL8231_LED_PIN_DESC(15, RTL8231_REG_PIN_MODE0, 15),
|
||||
+ RTL8231_LED_PIN_DESC(16, RTL8231_REG_PIN_MODE1, 0),
|
||||
+ RTL8231_LED_PIN_DESC(17, RTL8231_REG_PIN_MODE1, 1),
|
||||
+ RTL8231_LED_PIN_DESC(18, RTL8231_REG_PIN_MODE1, 2),
|
||||
+ RTL8231_LED_PIN_DESC(19, RTL8231_REG_PIN_MODE1, 3),
|
||||
+ RTL8231_LED_PIN_DESC(20, RTL8231_REG_PIN_MODE1, 4),
|
||||
+ RTL8231_LED_PIN_DESC(21, RTL8231_REG_PIN_MODE1, 5),
|
||||
+ RTL8231_LED_PIN_DESC(22, RTL8231_REG_PIN_MODE1, 6),
|
||||
+ RTL8231_LED_PIN_DESC(23, RTL8231_REG_PIN_MODE1, 7),
|
||||
+ RTL8231_LED_PIN_DESC(24, RTL8231_REG_PIN_MODE1, 8),
|
||||
+ RTL8231_LED_PIN_DESC(25, RTL8231_REG_PIN_MODE1, 9),
|
||||
+ RTL8231_LED_PIN_DESC(26, RTL8231_REG_PIN_MODE1, 10),
|
||||
+ RTL8231_LED_PIN_DESC(27, RTL8231_REG_PIN_MODE1, 11),
|
||||
+ RTL8231_LED_PIN_DESC(28, RTL8231_REG_PIN_MODE1, 12),
|
||||
+ RTL8231_LED_PIN_DESC(29, RTL8231_REG_PIN_MODE1, 13),
|
||||
+ RTL8231_LED_PIN_DESC(30, RTL8231_REG_PIN_MODE1, 14),
|
||||
+ RTL8231_LED_PIN_DESC(31, RTL8231_REG_PIN_MODE1, 15),
|
||||
+ RTL8231_LED_PIN_DESC(32, RTL8231_REG_PIN_HI_CFG, 0),
|
||||
+ RTL8231_LED_PIN_DESC(33, RTL8231_REG_PIN_HI_CFG, 1),
|
||||
+ RTL8231_LED_PIN_DESC(34, RTL8231_REG_PIN_HI_CFG, 2),
|
||||
+ RTL8231_PWM_PIN_DESC(35, RTL8231_REG_FUNC1, 3),
|
||||
+ RTL8231_GPIO_PIN_DESC(36, RTL8231_REG_PIN_HI_CFG, 4),
|
||||
+};
|
||||
+
|
||||
+#define RTL8231_PIN(_num) \
|
||||
+ { \
|
||||
+ .number = _num, \
|
||||
+ .name = "gpio" #_num, \
|
||||
+ .drv_data = &rtl8231_pin_data[_num] \
|
||||
+ }
|
||||
+
|
||||
+static const struct pinctrl_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
|
||||
+ RTL8231_PIN(0),
|
||||
+ RTL8231_PIN(1),
|
||||
+ RTL8231_PIN(2),
|
||||
+ RTL8231_PIN(3),
|
||||
+ RTL8231_PIN(4),
|
||||
+ RTL8231_PIN(5),
|
||||
+ RTL8231_PIN(6),
|
||||
+ RTL8231_PIN(7),
|
||||
+ RTL8231_PIN(8),
|
||||
+ RTL8231_PIN(9),
|
||||
+ RTL8231_PIN(10),
|
||||
+ RTL8231_PIN(11),
|
||||
+ RTL8231_PIN(12),
|
||||
+ RTL8231_PIN(13),
|
||||
+ RTL8231_PIN(14),
|
||||
+ RTL8231_PIN(15),
|
||||
+ RTL8231_PIN(16),
|
||||
+ RTL8231_PIN(17),
|
||||
+ RTL8231_PIN(18),
|
||||
+ RTL8231_PIN(19),
|
||||
+ RTL8231_PIN(20),
|
||||
+ RTL8231_PIN(21),
|
||||
+ RTL8231_PIN(22),
|
||||
+ RTL8231_PIN(23),
|
||||
+ RTL8231_PIN(24),
|
||||
+ RTL8231_PIN(25),
|
||||
+ RTL8231_PIN(26),
|
||||
+ RTL8231_PIN(27),
|
||||
+ RTL8231_PIN(28),
|
||||
+ RTL8231_PIN(29),
|
||||
+ RTL8231_PIN(30),
|
||||
+ RTL8231_PIN(31),
|
||||
+ RTL8231_PIN(32),
|
||||
+ RTL8231_PIN(33),
|
||||
+ RTL8231_PIN(34),
|
||||
+ RTL8231_PIN(35),
|
||||
+ RTL8231_PIN(36),
|
||||
+};
|
||||
+
|
||||
+static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
|
||||
+{
|
||||
+ return ARRAY_SIZE(rtl8231_pins);
|
||||
+}
|
||||
+
|
||||
+static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
|
||||
+{
|
||||
+ return rtl8231_pins[selector].name;
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
|
||||
+ const unsigned int **pins, unsigned int *num_pins)
|
||||
+{
|
||||
+ if (selector >= ARRAY_SIZE(rtl8231_pins))
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ *pins = &rtl8231_pins[selector].number;
|
||||
+ *num_pins = 1;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct pinctrl_ops rtl8231_pinctrl_ops = {
|
||||
+ .get_groups_count = rtl8231_get_groups_count,
|
||||
+ .get_group_name = rtl8231_get_group_name,
|
||||
+ .get_group_pins = rtl8231_get_group_pins,
|
||||
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
|
||||
+ .dt_free_map = pinconf_generic_dt_free_map,
|
||||
+};
|
||||
+
|
||||
+static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
|
||||
+ unsigned int group_selector)
|
||||
+{
|
||||
+ const struct function_desc *func = pinmux_generic_get_function(pctldev, func_selector);
|
||||
+ const struct rtl8231_pin_desc *desc = rtl8231_pins[group_selector].drv_data;
|
||||
+ const struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
|
||||
+ unsigned int func_flag = (uintptr_t) func->data;
|
||||
+ unsigned int function_mask;
|
||||
+ unsigned int gpio_function;
|
||||
+
|
||||
+ if (!(desc->functions & func_flag))
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ function_mask = BIT(desc->offset);
|
||||
+ gpio_function = desc->gpio_function_value << desc->offset;
|
||||
+
|
||||
+ if (func_flag == RTL8231_PIN_FUNCTION_GPIO)
|
||||
+ return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
|
||||
+ else
|
||||
+ return regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
|
||||
+ struct pinctrl_gpio_range *range, unsigned int offset)
|
||||
+{
|
||||
+ const struct rtl8231_pin_desc *desc = rtl8231_pins[offset].drv_data;
|
||||
+ struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
|
||||
+ unsigned int function_mask;
|
||||
+ unsigned int gpio_function;
|
||||
+
|
||||
+ function_mask = BIT(desc->offset);
|
||||
+ gpio_function = desc->gpio_function_value << desc->offset;
|
||||
+
|
||||
+ return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
|
||||
+}
|
||||
+
|
||||
+static const struct pinmux_ops rtl8231_pinmux_ops = {
|
||||
+ .get_functions_count = pinmux_generic_get_function_count,
|
||||
+ .get_function_name = pinmux_generic_get_function_name,
|
||||
+ .get_function_groups = pinmux_generic_get_function_groups,
|
||||
+ .set_mux = rtl8231_set_mux,
|
||||
+ .gpio_request_enable = rtl8231_gpio_request_enable,
|
||||
+ .strict = true,
|
||||
+};
|
||||
+
|
||||
+static int rtl8231_pin_config_get(struct pinctrl_dev *pctldev, unsigned int offset,
|
||||
+ unsigned long *config)
|
||||
+{
|
||||
+ struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
|
||||
+ unsigned int param = pinconf_to_config_param(*config);
|
||||
+ unsigned int arg;
|
||||
+ int err;
|
||||
+ int v;
|
||||
+
|
||||
+ switch (param) {
|
||||
+ case PIN_CONFIG_INPUT_DEBOUNCE:
|
||||
+ if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ err = regmap_read(ctrl->map, RTL8231_REG_FUNC1, &v);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ v = FIELD_GET(RTL8231_FUNC1_DEBOUNCE_MASK, v);
|
||||
+ if (v & BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET))
|
||||
+ arg = RTL8231_DEBOUNCE_USEC;
|
||||
+ else
|
||||
+ arg = 0;
|
||||
+ break;
|
||||
+ default:
|
||||
+ return -ENOTSUPP;
|
||||
+ }
|
||||
+
|
||||
+ *config = pinconf_to_config_packed(param, arg);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_pin_config_set(struct pinctrl_dev *pctldev, unsigned int offset,
|
||||
+ unsigned long *configs, unsigned int num_configs)
|
||||
+{
|
||||
+ struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
|
||||
+ unsigned int param, arg;
|
||||
+ unsigned int pin_mask;
|
||||
+ int err;
|
||||
+ int i;
|
||||
+
|
||||
+ for (i = 0; i < num_configs; i++) {
|
||||
+ param = pinconf_to_config_param(configs[i]);
|
||||
+ arg = pinconf_to_config_argument(configs[i]);
|
||||
+
|
||||
+ switch (param) {
|
||||
+ case PIN_CONFIG_INPUT_DEBOUNCE:
|
||||
+ if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ pin_mask = FIELD_PREP(RTL8231_FUNC1_DEBOUNCE_MASK,
|
||||
+ BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET));
|
||||
+
|
||||
+ switch (arg) {
|
||||
+ case 0:
|
||||
+ err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
|
||||
+ pin_mask, 0);
|
||||
+ break;
|
||||
+ case RTL8231_DEBOUNCE_USEC:
|
||||
+ err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
|
||||
+ pin_mask, pin_mask);
|
||||
+ break;
|
||||
+ default:
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ break;
|
||||
+ default:
|
||||
+ return -ENOTSUPP;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+static const struct pinconf_ops rtl8231_pinconf_ops = {
|
||||
+ .is_generic = true,
|
||||
+ .pin_config_get = rtl8231_pin_config_get,
|
||||
+ .pin_config_set = rtl8231_pin_config_set,
|
||||
+};
|
||||
+
|
||||
+static int rtl8231_pinctrl_init_functions(struct pinctrl_dev *pctl, struct rtl8231_pin_ctrl *ctrl)
|
||||
+{
|
||||
+ const char *function_name;
|
||||
+ const char **groups;
|
||||
+ unsigned int f_idx;
|
||||
+ unsigned int pin;
|
||||
+ int num_groups;
|
||||
+ int err;
|
||||
+
|
||||
+ for (f_idx = 0; f_idx < ARRAY_SIZE(rtl8231_pin_function_names); f_idx++) {
|
||||
+ function_name = rtl8231_pin_function_names[f_idx];
|
||||
+
|
||||
+ for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
|
||||
+ if (rtl8231_pin_data[pin].functions & BIT(f_idx))
|
||||
+ num_groups++;
|
||||
+
|
||||
+ groups = devm_kcalloc(pctl->dev, num_groups, sizeof(*groups), GFP_KERNEL);
|
||||
+ if (!groups)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
|
||||
+ if (rtl8231_pin_data[pin].functions & BIT(f_idx))
|
||||
+ groups[num_groups++] = rtl8231_pins[pin].name;
|
||||
+
|
||||
+ err = pinmux_generic_add_function(pctl, function_name, groups, num_groups,
|
||||
+ (void *) BIT(f_idx));
|
||||
+ if (err < 0)
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+struct pin_field_info {
|
||||
+ const struct reg_field gpio_data;
|
||||
+ const struct reg_field gpio_dir;
|
||||
+ const struct reg_field mode;
|
||||
+};
|
||||
+
|
||||
+static const struct pin_field_info pin_fields[] = {
|
||||
+ {
|
||||
+ .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA0, 0, 15),
|
||||
+ .gpio_dir = REG_FIELD(RTL8231_REG_GPIO_DIR0, 0, 15),
|
||||
+ .mode = REG_FIELD(RTL8231_REG_PIN_MODE0, 0, 15),
|
||||
+ },
|
||||
+ {
|
||||
+ .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA1, 0, 15),
|
||||
+ .gpio_dir = REG_FIELD(RTL8231_REG_GPIO_DIR1, 0, 15),
|
||||
+ .mode = REG_FIELD(RTL8231_REG_PIN_MODE1, 0, 15),
|
||||
+ },
|
||||
+ {
|
||||
+ .gpio_data = REG_FIELD(RTL8231_REG_GPIO_DATA2, 0, 4),
|
||||
+ .gpio_dir = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 5, 9),
|
||||
+ .mode = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 0, 4),
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+static int rtl8231_configure_safe(struct device *dev, struct regmap *map)
|
||||
+{
|
||||
+ struct regmap_field *field_data;
|
||||
+ struct regmap_field *field_mode;
|
||||
+ struct regmap_field *field_dir;
|
||||
+ unsigned int is_output;
|
||||
+ unsigned int is_gpio;
|
||||
+ unsigned int data;
|
||||
+ unsigned int mode;
|
||||
+ unsigned int dir;
|
||||
+ int err;
|
||||
+
|
||||
+ /* Enable immediate GPIO changes, otherwise we need to latch updates */
|
||||
+ err = regmap_update_bits(map, RTL8231_REG_FUNC0, RTL8231_ENABLE_SYNC_GPIO, 0);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ for (unsigned int i = 0; i < ARRAY_SIZE(pin_fields); i++) {
|
||||
+ field_data = devm_regmap_field_alloc(dev, map, pin_fields[i].gpio_data);
|
||||
+ if (IS_ERR(field_data))
|
||||
+ return PTR_ERR(field_data);
|
||||
+
|
||||
+ field_dir = devm_regmap_field_alloc(dev, map, pin_fields[i].gpio_dir);
|
||||
+ if (IS_ERR(field_dir))
|
||||
+ return PTR_ERR(field_dir);
|
||||
+
|
||||
+ field_mode = devm_regmap_field_alloc(dev, map, pin_fields[i].mode);
|
||||
+ if (IS_ERR(field_mode))
|
||||
+ return PTR_ERR(field_mode);
|
||||
+
|
||||
+ /* The register cache is invalid at start-up, so this should read from HW */
|
||||
+ err = regmap_field_read(field_data, &data);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ err = regmap_field_read(field_dir, &dir);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ err = regmap_field_read(field_mode, &mode);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ /* Write back only the GPIO-out values to fix the cache */
|
||||
+ data &= ~dir;
|
||||
+ regmap_field_write(field_data, data);
|
||||
+
|
||||
+ /*
|
||||
+ * Set every pin that is configured as gpio-output but muxed for the alternative
|
||||
+ * (LED) function to gpio-in. That way the pin will be high impedance when it is
|
||||
+ * muxed to GPIO, preventing unwanted glitches.
|
||||
+ * The pin muxes are left as-is, so there are no signal changes.
|
||||
+ */
|
||||
+ is_gpio = mode;
|
||||
+ is_output = ~dir;
|
||||
+ regmap_field_write(field_dir, dir | (~is_gpio & is_output));
|
||||
+
|
||||
+ devm_regmap_field_free(dev, field_data);
|
||||
+ devm_regmap_field_free(dev, field_dir);
|
||||
+ devm_regmap_field_free(dev, field_mode);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
|
||||
+{
|
||||
+ struct pinctrl_dev *pctldev;
|
||||
+ int err;
|
||||
+
|
||||
+ ctrl->pctl_desc.name = "rtl8231-pinctrl";
|
||||
+ ctrl->pctl_desc.owner = THIS_MODULE;
|
||||
+ ctrl->pctl_desc.confops = &rtl8231_pinconf_ops;
|
||||
+ ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops;
|
||||
+ ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops;
|
||||
+ ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
|
||||
+ ctrl->pctl_desc.pins = rtl8231_pins;
|
||||
+
|
||||
+ err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctldev);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "failed to register pin controller\n");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ err = rtl8231_pinctrl_init_functions(pctldev, ctrl);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ err = pinctrl_enable(pctldev);
|
||||
+ if (err)
|
||||
+ dev_err(dev, "failed to enable pin controller\n");
|
||||
+
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * GPIO controller functionality
|
||||
+ */
|
||||
+static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
|
||||
+ unsigned int offset, unsigned int *reg, unsigned int *mask)
|
||||
+{
|
||||
+ unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
|
||||
+
|
||||
+ if (base == RTL8231_REG_GPIO_DATA0 || offset < 32) {
|
||||
+ *reg = base + offset / RTL8231_BITS_VAL;
|
||||
+ *mask = pin_mask;
|
||||
+ } else if (base == RTL8231_REG_GPIO_DIR0) {
|
||||
+ *reg = RTL8231_REG_PIN_HI_CFG;
|
||||
+ *mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
|
||||
+ } else {
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_pinctrl_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct rtl8231_pin_ctrl *ctrl;
|
||||
+ struct gpio_regmap_config gpio_cfg = {};
|
||||
+ int err;
|
||||
+
|
||||
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
|
||||
+ if (!ctrl)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ ctrl->map = dev_get_regmap(dev->parent, NULL);
|
||||
+ if (!ctrl->map)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ err = rtl8231_configure_safe(dev, ctrl->map);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ err = rtl8231_pinctrl_init(dev, ctrl);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ gpio_cfg.regmap = ctrl->map;
|
||||
+ gpio_cfg.parent = dev->parent;
|
||||
+ gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
|
||||
+ gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
|
||||
+
|
||||
+ gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
|
||||
+ gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
|
||||
+ gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
|
||||
+
|
||||
+ gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_cfg));
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver rtl8231_pinctrl_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "rtl8231-pinctrl",
|
||||
+ },
|
||||
+ .probe = rtl8231_pinctrl_probe,
|
||||
+};
|
||||
+module_platform_driver(rtl8231_pinctrl_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
|
||||
+MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
|
||||
+MODULE_LICENSE("GPL");
|
||||
@ -1,343 +0,0 @@
|
||||
From 6b797a97c007e46d6081fc6f4b41ce8407078605 Mon Sep 17 00:00:00 2001
|
||||
From: Sander Vanheule <sander@svanheule.net>
|
||||
Date: Mon, 10 May 2021 22:16:11 +0200
|
||||
Subject: [PATCH] leds: Add support for RTL8231 LED scan matrix
|
||||
|
||||
Both single and bi-color scanning modes are supported. The driver will
|
||||
verify that the addresses are valid for the current mode, before
|
||||
registering the LEDs. LEDs can be turned on, off, or toggled at one of
|
||||
six predefined rates from 40ms to 1280ms.
|
||||
|
||||
Implements a platform device for use as a child device with RTL8231 MFD,
|
||||
and uses the parent regmap to access the required registers.
|
||||
|
||||
Signed-off-by: Sander Vanheule <sander@svanheule.net>
|
||||
---
|
||||
drivers/leds/Kconfig | 10 ++
|
||||
drivers/leds/Makefile | 1 +
|
||||
drivers/leds/leds-rtl8231.c | 291 ++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 302 insertions(+)
|
||||
create mode 100644 drivers/leds/leds-rtl8231.c
|
||||
|
||||
--- a/drivers/leds/Kconfig
|
||||
+++ b/drivers/leds/Kconfig
|
||||
@@ -634,6 +634,16 @@ config LEDS_REGULATOR
|
||||
help
|
||||
This option enables support for regulator driven LEDs.
|
||||
|
||||
+config LEDS_RTL8231
|
||||
+ tristate "RTL8231 LED matrix support"
|
||||
+ depends on LEDS_CLASS
|
||||
+ depends on MFD_RTL8231
|
||||
+ default MFD_RTL8231
|
||||
+ help
|
||||
+ This option enables support for using the LED scanning matrix output
|
||||
+ of the RTL8231 GPIO and LED expander chip.
|
||||
+ When built as a module, this module will be named leds-rtl8231.
|
||||
+
|
||||
config LEDS_BD2606MVV
|
||||
tristate "LED driver for BD2606MVV"
|
||||
depends on LEDS_CLASS
|
||||
--- a/drivers/leds/Makefile
|
||||
+++ b/drivers/leds/Makefile
|
||||
@@ -80,6 +80,7 @@ obj-$(CONFIG_LEDS_PM8058) += leds-pm805
|
||||
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
|
||||
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
|
||||
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
|
||||
+obj-$(CONFIG_LEDS_RTL8231) += leds-rtl8231.o
|
||||
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
|
||||
obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o
|
||||
obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/leds/leds-rtl8231.c
|
||||
@@ -0,0 +1,290 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0-only
|
||||
+
|
||||
+#include <linux/device.h>
|
||||
+#include <linux/leds.h>
|
||||
+#include <linux/mod_devicetable.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/property.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+#include <linux/mfd/rtl8231.h>
|
||||
+
|
||||
+/**
|
||||
+ * struct led_toggle_rate - description of an LED blinking mode
|
||||
+ * @interval_ms: LED toggle rate in milliseconds
|
||||
+ * @mode: Register field value used to activate this mode
|
||||
+ *
|
||||
+ * For LED hardware accelerated blinking, with equal on and off delay.
|
||||
+ * Both delays are given by @interval, so the interval at which the LED blinks
|
||||
+ * (i.e. turn on and off once) is double this value.
|
||||
+ */
|
||||
+struct led_toggle_rate {
|
||||
+ u16 interval_ms;
|
||||
+ u8 mode;
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * struct led_modes - description of all LED modes
|
||||
+ * @toggle_rates: Array of led_toggle_rate values, sorted by ascending interval
|
||||
+ * @num_toggle_rates: Number of elements in @led_toggle_rate
|
||||
+ * @off: Register field value to turn LED off
|
||||
+ * @on: Register field value to turn LED on
|
||||
+ */
|
||||
+struct led_modes {
|
||||
+ const struct led_toggle_rate *toggle_rates;
|
||||
+ unsigned int num_toggle_rates;
|
||||
+ u8 off;
|
||||
+ u8 on;
|
||||
+};
|
||||
+
|
||||
+struct rtl8231_led {
|
||||
+ struct led_classdev led;
|
||||
+ const struct led_modes *modes;
|
||||
+ struct regmap_field *reg_field;
|
||||
+};
|
||||
+#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
|
||||
+
|
||||
+#define RTL8231_NUM_LEDS 3
|
||||
+#define RTL8231_LED_PER_REG 5
|
||||
+#define RTL8231_BITS_PER_LED 3
|
||||
+
|
||||
+static const unsigned int rtl8231_led_port_counts_single[RTL8231_NUM_LEDS] = {32, 32, 24};
|
||||
+static const unsigned int rtl8231_led_port_counts_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
|
||||
+
|
||||
+static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
|
||||
+ RTL8231_REG_LED0_BASE,
|
||||
+ RTL8231_REG_LED1_BASE,
|
||||
+ RTL8231_REG_LED2_BASE,
|
||||
+};
|
||||
+
|
||||
+#define RTL8231_DEFAULT_TOGGLE_INTERVAL_MS 500
|
||||
+
|
||||
+static const struct led_toggle_rate rtl8231_toggle_rates[] = {
|
||||
+ { 40, 1},
|
||||
+ { 80, 2},
|
||||
+ { 160, 3},
|
||||
+ { 320, 4},
|
||||
+ { 640, 5},
|
||||
+ {1280, 6},
|
||||
+};
|
||||
+
|
||||
+static const struct led_modes rtl8231_led_modes = {
|
||||
+ .off = 0,
|
||||
+ .on = 7,
|
||||
+ .num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
|
||||
+ .toggle_rates = rtl8231_toggle_rates,
|
||||
+};
|
||||
+
|
||||
+static int rtl8231_led_brightness_set(struct led_classdev *led_cdev,
|
||||
+ enum led_brightness brightness)
|
||||
+{
|
||||
+ struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
|
||||
+
|
||||
+ if (brightness)
|
||||
+ return regmap_field_write(pled->reg_field, pled->modes->on);
|
||||
+ else
|
||||
+ return regmap_field_write(pled->reg_field, pled->modes->off);
|
||||
+}
|
||||
+
|
||||
+static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
|
||||
+{
|
||||
+ struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
|
||||
+ u32 current_mode = pled->modes->off;
|
||||
+
|
||||
+ regmap_field_read(pled->reg_field, ¤t_mode);
|
||||
+
|
||||
+ if (current_mode == pled->modes->off)
|
||||
+ return LED_OFF;
|
||||
+ else
|
||||
+ return LED_ON;
|
||||
+}
|
||||
+
|
||||
+static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
|
||||
+{
|
||||
+ unsigned int mode;
|
||||
+ unsigned int i;
|
||||
+
|
||||
+ if (regmap_field_read(pled->reg_field, &mode))
|
||||
+ return 0;
|
||||
+
|
||||
+ for (i = 0; i < pled->modes->num_toggle_rates; i++)
|
||||
+ if (mode == pled->modes->toggle_rates[i].mode)
|
||||
+ return pled->modes->toggle_rates[i].interval_ms;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
|
||||
+ unsigned long *delay_off)
|
||||
+{
|
||||
+ struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
|
||||
+ const struct led_toggle_rate *rates = pled->modes->toggle_rates;
|
||||
+ unsigned int num_rates = pled->modes->num_toggle_rates;
|
||||
+ unsigned int interval_ms;
|
||||
+ unsigned int i;
|
||||
+ int err;
|
||||
+
|
||||
+ if (*delay_on == 0 && *delay_off == 0) {
|
||||
+ interval_ms = RTL8231_DEFAULT_TOGGLE_INTERVAL_MS;
|
||||
+ } else {
|
||||
+ /*
|
||||
+ * If the current mode is blinking, choose the delay that (likely) changed.
|
||||
+ * Otherwise, choose the interval that would have the same total delay.
|
||||
+ */
|
||||
+ interval_ms = rtl8231_led_current_interval(pled);
|
||||
+ if (interval_ms > 0 && interval_ms == *delay_off)
|
||||
+ interval_ms = *delay_on;
|
||||
+ else if (interval_ms > 0 && interval_ms == *delay_on)
|
||||
+ interval_ms = *delay_off;
|
||||
+ else
|
||||
+ interval_ms = (*delay_on + *delay_off) / 2;
|
||||
+ }
|
||||
+
|
||||
+ /* Find ceiled (or maximum) toggle interval */
|
||||
+ i = 0;
|
||||
+ while (i < (num_rates - 1) && interval_ms > rates[i].interval_ms)
|
||||
+ i++;
|
||||
+
|
||||
+ interval_ms = rates[i].interval_ms;
|
||||
+
|
||||
+ err = regmap_field_write(pled->reg_field, rates[i].mode);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ *delay_on = interval_ms;
|
||||
+ *delay_off = interval_ms;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_led_read_address(struct fwnode_handle *fwnode, unsigned int *addr_port,
|
||||
+ unsigned int *addr_led)
|
||||
+{
|
||||
+ u32 addr[2];
|
||||
+ int err;
|
||||
+
|
||||
+ err = fwnode_property_count_u32(fwnode, "reg");
|
||||
+ if (err < 0)
|
||||
+ return err;
|
||||
+ if (err != ARRAY_SIZE(addr))
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ err = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ *addr_port = addr[0];
|
||||
+ *addr_led = addr[1];
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct regmap_field *rtl8231_led_get_field(struct device *dev, struct regmap *map,
|
||||
+ unsigned int port_index, unsigned int led_index)
|
||||
+{
|
||||
+ unsigned int offset = port_index / RTL8231_LED_PER_REG;
|
||||
+ unsigned int shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
|
||||
+ const struct reg_field field = REG_FIELD(rtl8231_led_base[led_index] + offset, shift,
|
||||
+ shift + RTL8231_BITS_PER_LED - 1);
|
||||
+
|
||||
+ return devm_regmap_field_alloc(dev, map, field);
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
|
||||
+ const unsigned int *port_counts, struct fwnode_handle *fwnode)
|
||||
+{
|
||||
+ struct led_init_data init_data = {};
|
||||
+ struct rtl8231_led *pled;
|
||||
+ unsigned int port_index;
|
||||
+ unsigned int led_index;
|
||||
+ int err;
|
||||
+
|
||||
+ pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
|
||||
+ if (!pled)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "LED address invalid");
|
||||
+ return err;
|
||||
+ }
|
||||
+
|
||||
+ if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {
|
||||
+ dev_err(dev, "LED address (%d.%d) invalid", port_index, led_index);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ pled->reg_field = rtl8231_led_get_field(dev, map, port_index, led_index);
|
||||
+ if (IS_ERR(pled->reg_field))
|
||||
+ return PTR_ERR(pled->reg_field);
|
||||
+
|
||||
+ pled->modes = &rtl8231_led_modes;
|
||||
+
|
||||
+ pled->led.max_brightness = 1;
|
||||
+ pled->led.brightness_get = rtl8231_led_brightness_get;
|
||||
+ pled->led.brightness_set_blocking = rtl8231_led_brightness_set;
|
||||
+ pled->led.blink_set = rtl8231_led_blink_set;
|
||||
+
|
||||
+ init_data.fwnode = fwnode;
|
||||
+
|
||||
+ return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
|
||||
+}
|
||||
+
|
||||
+static int rtl8231_led_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ const unsigned int *port_counts;
|
||||
+ struct fwnode_handle *child;
|
||||
+ struct regmap *map;
|
||||
+ int err;
|
||||
+
|
||||
+ map = dev_get_regmap(dev->parent, NULL);
|
||||
+ if (!map)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ if (device_property_match_string(dev, "realtek,led-scan-mode", "single-color") >= 0) {
|
||||
+ port_counts = rtl8231_led_port_counts_single;
|
||||
+ regmap_update_bits(map, RTL8231_REG_FUNC0,
|
||||
+ RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
|
||||
+ } else if (device_property_match_string(dev, "realtek,led-scan-mode", "bi-color") >= 0) {
|
||||
+ port_counts = rtl8231_led_port_counts_bicolor;
|
||||
+ regmap_update_bits(map, RTL8231_REG_FUNC0,
|
||||
+ RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
|
||||
+ } else {
|
||||
+ dev_err(dev, "scan mode missing or invalid");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ /* Enable immediate LED changes, otherwise we need to latch updates */
|
||||
+ err = regmap_update_bits(map, RTL8231_REG_FUNC0, RTL8231_ENABLE_SYNC_LED, 0);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ fwnode_for_each_available_child_node(dev->fwnode, child) {
|
||||
+ err = rtl8231_led_probe_single(dev, map, port_counts, child);
|
||||
+ if (err)
|
||||
+ dev_warn(dev, "failed to register LED %pfwP", child);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id of_rtl8231_led_match[] = {
|
||||
+ { .compatible = "realtek,rtl8231-leds" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
|
||||
+
|
||||
+static struct platform_driver rtl8231_led_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "rtl8231-leds",
|
||||
+ .of_match_table = of_rtl8231_led_match,
|
||||
+ },
|
||||
+ .probe = rtl8231_led_probe,
|
||||
+};
|
||||
+module_platform_driver(rtl8231_led_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
|
||||
+MODULE_DESCRIPTION("Realtek RTL8231 LED support");
|
||||
+MODULE_LICENSE("GPL");
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user