mirror of
https://github.com/Ysurac/openmptcprouter.git
synced 2025-03-09 15:40:20 +00:00
Update 6.12 kernel patches
This commit is contained in:
parent
bdb9b0046f
commit
9d83c70ced
247 changed files with 53301 additions and 589 deletions
|
@ -19,7 +19,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
||||||
|
|
||||||
--- a/block/blk.h
|
--- a/block/blk.h
|
||||||
+++ b/block/blk.h
|
+++ b/block/blk.h
|
||||||
@@ -424,6 +424,7 @@ void blk_free_ext_minor(unsigned int min
|
@@ -564,6 +564,7 @@ void blk_free_ext_minor(unsigned int min
|
||||||
#define ADDPART_FLAG_NONE 0
|
#define ADDPART_FLAG_NONE 0
|
||||||
#define ADDPART_FLAG_RAID 1
|
#define ADDPART_FLAG_RAID 1
|
||||||
#define ADDPART_FLAG_WHOLEDISK 2
|
#define ADDPART_FLAG_WHOLEDISK 2
|
||||||
|
@ -41,7 +41,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
||||||
strscpy(info->volname, subpart->name, sizeof(info->volname));
|
strscpy(info->volname, subpart->name, sizeof(info->volname));
|
||||||
--- a/block/partitions/core.c
|
--- a/block/partitions/core.c
|
||||||
+++ b/block/partitions/core.c
|
+++ b/block/partitions/core.c
|
||||||
@@ -392,6 +392,9 @@ static struct block_device *add_partitio
|
@@ -373,6 +373,9 @@ static struct block_device *add_partitio
|
||||||
goto out_del;
|
goto out_del;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
||||||
|
|
||||||
{
|
{
|
||||||
struct device *ddev = disk_to_dev(disk);
|
struct device *ddev = disk_to_dev(disk);
|
||||||
@@ -451,6 +453,8 @@ int __must_check device_add_disk(struct
|
@@ -452,6 +454,8 @@ int __must_check device_add_disk(struct
|
||||||
ddev->parent = parent;
|
ddev->parent = parent;
|
||||||
ddev->groups = groups;
|
ddev->groups = groups;
|
||||||
dev_set_name(ddev, "%s", disk->disk_name);
|
dev_set_name(ddev, "%s", disk->disk_name);
|
||||||
|
@ -57,7 +57,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
||||||
if (!(disk->flags & GENHD_FL_HIDDEN))
|
if (!(disk->flags & GENHD_FL_HIDDEN))
|
||||||
ddev->devt = MKDEV(disk->major, disk->first_minor);
|
ddev->devt = MKDEV(disk->major, disk->first_minor);
|
||||||
ret = device_add(ddev);
|
ret = device_add(ddev);
|
||||||
@@ -552,6 +556,22 @@ out_exit_elevator:
|
@@ -553,6 +557,22 @@ out_exit_elevator:
|
||||||
elevator_exit(disk->queue);
|
elevator_exit(disk->queue);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
||||||
static void blk_report_disk_dead(struct gendisk *disk, bool surprise)
|
static void blk_report_disk_dead(struct gendisk *disk, bool surprise)
|
||||||
--- a/include/linux/blkdev.h
|
--- a/include/linux/blkdev.h
|
||||||
+++ b/include/linux/blkdev.h
|
+++ b/include/linux/blkdev.h
|
||||||
@@ -741,6 +741,9 @@ static inline unsigned int blk_queue_dep
|
@@ -735,6 +735,9 @@ static inline unsigned int blk_queue_dep
|
||||||
#define for_each_bio(_bio) \
|
#define for_each_bio(_bio) \
|
||||||
for (; _bio; _bio = _bio->bi_next)
|
for (; _bio; _bio = _bio->bi_next)
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
||||||
|
|
||||||
--- a/drivers/mmc/core/block.c
|
--- a/drivers/mmc/core/block.c
|
||||||
+++ b/drivers/mmc/core/block.c
|
+++ b/drivers/mmc/core/block.c
|
||||||
@@ -2455,6 +2455,56 @@ static inline int mmc_blk_readonly(struc
|
@@ -2517,6 +2517,56 @@ static inline int mmc_blk_readonly(struc
|
||||||
!(card->csd.cmdclass & CCC_BLOCK_WRITE);
|
!(card->csd.cmdclass & CCC_BLOCK_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
||||||
static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
|
static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
|
||||||
struct device *parent,
|
struct device *parent,
|
||||||
sector_t size,
|
sector_t size,
|
||||||
@@ -2463,6 +2513,7 @@ static struct mmc_blk_data *mmc_blk_allo
|
@@ -2525,6 +2575,7 @@ static struct mmc_blk_data *mmc_blk_allo
|
||||||
int area_type,
|
int area_type,
|
||||||
unsigned int part_type)
|
unsigned int part_type)
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,7 @@ Signed-off-by: Jens Axboe <axboe@kernel.dk>
|
||||||
struct mmc_blk_data *md;
|
struct mmc_blk_data *md;
|
||||||
int devidx, ret;
|
int devidx, ret;
|
||||||
char cap_str[10];
|
char cap_str[10];
|
||||||
@@ -2568,7 +2619,9 @@ static struct mmc_blk_data *mmc_blk_allo
|
@@ -2626,7 +2677,9 @@ static struct mmc_blk_data *mmc_blk_allo
|
||||||
/* used in ->open, must be set before add_disk: */
|
/* used in ->open, must be set before add_disk: */
|
||||||
if (area_type == MMC_BLK_DATA_AREA_MAIN)
|
if (area_type == MMC_BLK_DATA_AREA_MAIN)
|
||||||
dev_set_drvdata(&card->dev, md);
|
dev_set_drvdata(&card->dev, md);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
From ae461cde5c559675fc4c0ba351c7c31ace705f56 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Bohdan Chubuk <chbgdn@gmail.com>
|
||||||
|
Date: Sun, 10 Nov 2024 22:50:47 +0200
|
||||||
|
Subject: [PATCH] mtd: spinand: add support for FORESEE F35SQA001G
|
||||||
|
|
||||||
|
Add support for FORESEE F35SQA001G SPI NAND.
|
||||||
|
|
||||||
|
Similar to F35SQA002G, but differs in capacity.
|
||||||
|
Datasheet:
|
||||||
|
- https://cdn.ozdisan.com/ETicaret_Dosya/704795_871495.pdf
|
||||||
|
|
||||||
|
Tested on Xiaomi AX3000T flashed with OpenWRT.
|
||||||
|
|
||||||
|
Signed-off-by: Bohdan Chubuk <chbgdn@gmail.com>
|
||||||
|
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
||||||
|
---
|
||||||
|
drivers/mtd/nand/spi/foresee.c | 10 ++++++++++
|
||||||
|
1 file changed, 10 insertions(+)
|
||||||
|
|
||||||
|
--- a/drivers/mtd/nand/spi/foresee.c
|
||||||
|
+++ b/drivers/mtd/nand/spi/foresee.c
|
||||||
|
@@ -81,6 +81,16 @@ static const struct spinand_info foresee
|
||||||
|
SPINAND_HAS_QE_BIT,
|
||||||
|
SPINAND_ECCINFO(&f35sqa002g_ooblayout,
|
||||||
|
f35sqa002g_ecc_get_status)),
|
||||||
|
+ SPINAND_INFO("F35SQA001G",
|
||||||
|
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71, 0x71),
|
||||||
|
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
|
||||||
|
+ NAND_ECCREQ(1, 512),
|
||||||
|
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||||
|
+ &write_cache_variants,
|
||||||
|
+ &update_cache_variants),
|
||||||
|
+ SPINAND_HAS_QE_BIT,
|
||||||
|
+ SPINAND_ECCINFO(&f35sqa002g_ooblayout,
|
||||||
|
+ f35sqa002g_ecc_get_status)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct spinand_manufacturer_ops foresee_spinand_manuf_ops = {
|
|
@ -0,0 +1,106 @@
|
||||||
|
From 081c9c0265c91b8333165aa6230c20bcbc6f7cbf Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Thu, 10 Oct 2024 14:07:16 +0100
|
||||||
|
Subject: [PATCH 3/5] net: phy: realtek: read duplex and gbit master from PHYSR
|
||||||
|
register
|
||||||
|
|
||||||
|
The PHYSR MMD register is present and defined equally for all RTL82xx
|
||||||
|
Ethernet PHYs.
|
||||||
|
Read duplex and Gbit master bits from rtlgen_decode_speed() and rename
|
||||||
|
it to rtlgen_decode_physr().
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Link: https://patch.msgid.link/b9a76341da851a18c985bc4774fa295babec79bb.1728565530.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
|
||||||
|
---
|
||||||
|
drivers/net/phy/realtek.c | 41 +++++++++++++++++++++++++++++++--------
|
||||||
|
1 file changed, 33 insertions(+), 8 deletions(-)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/realtek.c
|
||||||
|
+++ b/drivers/net/phy/realtek.c
|
||||||
|
@@ -80,15 +80,18 @@
|
||||||
|
|
||||||
|
#define RTL822X_VND2_GANLPAR 0xa414
|
||||||
|
|
||||||
|
-#define RTL822X_VND2_PHYSR 0xa434
|
||||||
|
-
|
||||||
|
#define RTL8366RB_POWER_SAVE 0x15
|
||||||
|
#define RTL8366RB_POWER_SAVE_ON BIT(12)
|
||||||
|
|
||||||
|
#define RTL9000A_GINMR 0x14
|
||||||
|
#define RTL9000A_GINMR_LINK_STATUS BIT(4)
|
||||||
|
|
||||||
|
-#define RTLGEN_SPEED_MASK 0x0630
|
||||||
|
+#define RTL_VND2_PHYSR 0xa434
|
||||||
|
+#define RTL_VND2_PHYSR_DUPLEX BIT(3)
|
||||||
|
+#define RTL_VND2_PHYSR_SPEEDL GENMASK(5, 4)
|
||||||
|
+#define RTL_VND2_PHYSR_SPEEDH GENMASK(10, 9)
|
||||||
|
+#define RTL_VND2_PHYSR_MASTER BIT(11)
|
||||||
|
+#define RTL_VND2_PHYSR_SPEED_MASK (RTL_VND2_PHYSR_SPEEDL | RTL_VND2_PHYSR_SPEEDH)
|
||||||
|
|
||||||
|
#define RTL_GENERIC_PHYID 0x001cc800
|
||||||
|
#define RTL_8211FVD_PHYID 0x001cc878
|
||||||
|
@@ -660,9 +663,18 @@ static int rtl8366rb_config_init(struct
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get actual speed to cover the downshift case */
|
||||||
|
-static void rtlgen_decode_speed(struct phy_device *phydev, int val)
|
||||||
|
+static void rtlgen_decode_physr(struct phy_device *phydev, int val)
|
||||||
|
{
|
||||||
|
- switch (val & RTLGEN_SPEED_MASK) {
|
||||||
|
+ /* bit 3
|
||||||
|
+ * 0: Half Duplex
|
||||||
|
+ * 1: Full Duplex
|
||||||
|
+ */
|
||||||
|
+ if (val & RTL_VND2_PHYSR_DUPLEX)
|
||||||
|
+ phydev->duplex = DUPLEX_FULL;
|
||||||
|
+ else
|
||||||
|
+ phydev->duplex = DUPLEX_HALF;
|
||||||
|
+
|
||||||
|
+ switch (val & RTL_VND2_PHYSR_SPEED_MASK) {
|
||||||
|
case 0x0000:
|
||||||
|
phydev->speed = SPEED_10;
|
||||||
|
break;
|
||||||
|
@@ -684,6 +696,19 @@ static void rtlgen_decode_speed(struct p
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ /* bit 11
|
||||||
|
+ * 0: Slave Mode
|
||||||
|
+ * 1: Master Mode
|
||||||
|
+ */
|
||||||
|
+ if (phydev->speed >= 1000) {
|
||||||
|
+ if (val & RTL_VND2_PHYSR_MASTER)
|
||||||
|
+ phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
|
||||||
|
+ else
|
||||||
|
+ phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
|
||||||
|
+ } else {
|
||||||
|
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rtlgen_read_status(struct phy_device *phydev)
|
||||||
|
@@ -701,7 +726,7 @@ static int rtlgen_read_status(struct phy
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
|
||||||
|
- rtlgen_decode_speed(phydev, val);
|
||||||
|
+ rtlgen_decode_physr(phydev, val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@@ -1007,11 +1032,11 @@ static int rtl822x_c45_read_status(struc
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Read actual speed from vendor register. */
|
||||||
|
- val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_PHYSR);
|
||||||
|
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_VND2_PHYSR);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
|
||||||
|
- rtlgen_decode_speed(phydev, val);
|
||||||
|
+ rtlgen_decode_physr(phydev, val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
From 68d5cd09e8919679ce13b85950debea4b2e98e04 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Thu, 10 Oct 2024 14:07:26 +0100
|
||||||
|
Subject: [PATCH 4/5] net: phy: realtek: change order of calls in C22
|
||||||
|
read_status()
|
||||||
|
|
||||||
|
Always call rtlgen_read_status() first, so genphy_read_status() which
|
||||||
|
is called by it clears bits in case auto-negotiation has not completed.
|
||||||
|
Also clear 10GBT link-partner advertisement bits in case auto-negotiation
|
||||||
|
is disabled or has not completed.
|
||||||
|
|
||||||
|
Suggested-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Link: https://patch.msgid.link/b15929a41621d215c6b2b57393368086589569ec.1728565530.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
|
||||||
|
---
|
||||||
|
drivers/net/phy/realtek.c | 22 +++++++++++++++-------
|
||||||
|
1 file changed, 15 insertions(+), 7 deletions(-)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/realtek.c
|
||||||
|
+++ b/drivers/net/phy/realtek.c
|
||||||
|
@@ -949,17 +949,25 @@ static void rtl822xb_update_interface(st
|
||||||
|
|
||||||
|
static int rtl822x_read_status(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
- if (phydev->autoneg == AUTONEG_ENABLE) {
|
||||||
|
- int lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
|
||||||
|
+ int lpadv, ret;
|
||||||
|
|
||||||
|
- if (lpadv < 0)
|
||||||
|
- return lpadv;
|
||||||
|
+ ret = rtlgen_read_status(phydev);
|
||||||
|
+ if (ret < 0)
|
||||||
|
+ return ret;
|
||||||
|
|
||||||
|
- mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising,
|
||||||
|
- lpadv);
|
||||||
|
+ if (phydev->autoneg == AUTONEG_DISABLE ||
|
||||||
|
+ !phydev->autoneg_complete) {
|
||||||
|
+ mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
|
||||||
|
+ return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
- return rtlgen_read_status(phydev);
|
||||||
|
+ lpadv = phy_read_paged(phydev, 0xa5d, 0x13);
|
||||||
|
+ if (lpadv < 0)
|
||||||
|
+ return lpadv;
|
||||||
|
+
|
||||||
|
+ mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, lpadv);
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rtl822xb_read_status(struct phy_device *phydev)
|
|
@ -0,0 +1,30 @@
|
||||||
|
From 5cb409b3960e75467cbb0a8e1e5596b4490570e3 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Thu, 10 Oct 2024 14:07:39 +0100
|
||||||
|
Subject: [PATCH 5/5] net: phy: realtek: clear 1000Base-T link partner
|
||||||
|
advertisement
|
||||||
|
|
||||||
|
Clear 1000Base-T link partner advertisement bits in Clause-45
|
||||||
|
read_status() function in case auto-negotiation is disabled or has not
|
||||||
|
been completed.
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Link: https://patch.msgid.link/9dc9b47b2d675708afef3ad366bfd78eb584d958.1728565530.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
|
||||||
|
---
|
||||||
|
drivers/net/phy/realtek.c | 4 ++++
|
||||||
|
1 file changed, 4 insertions(+)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/realtek.c
|
||||||
|
+++ b/drivers/net/phy/realtek.c
|
||||||
|
@@ -1026,6 +1026,10 @@ static int rtl822x_c45_read_status(struc
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
+ if (phydev->autoneg == AUTONEG_DISABLE ||
|
||||||
|
+ !genphy_c45_aneg_done(phydev))
|
||||||
|
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
|
||||||
|
+
|
||||||
|
/* Vendor register as C45 has no standardized support for 1000BaseT */
|
||||||
|
if (phydev->autoneg == AUTONEG_ENABLE) {
|
||||||
|
val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
|
|
@ -0,0 +1,107 @@
|
||||||
|
From a2e1ba275eae96a8171deb19e9c7c2f5978fee7b Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Fri, 4 Oct 2024 17:18:16 +0100
|
||||||
|
Subject: [PATCH] net: phy: aquantia: allow forcing order of MDI pairs
|
||||||
|
|
||||||
|
Despite supporting Auto MDI-X, it looks like Aquantia only supports
|
||||||
|
swapping pair (1,2) with pair (3,6) like it used to be for MDI-X on
|
||||||
|
100MBit/s networks.
|
||||||
|
|
||||||
|
When all 4 pairs are in use (for 1000MBit/s or faster) the link does not
|
||||||
|
come up with pair order is not configured correctly, either using
|
||||||
|
MDI_CFG pin or using the "PMA Receive Reserved Vendor Provisioning 1"
|
||||||
|
register.
|
||||||
|
|
||||||
|
Normally, the order of MDI pairs being either ABCD or DCBA is configured
|
||||||
|
by pulling the MDI_CFG pin.
|
||||||
|
|
||||||
|
However, some hardware designs require overriding the value configured
|
||||||
|
by that bootstrap pin. The PHY allows doing that by setting a bit in
|
||||||
|
"PMA Receive Reserved Vendor Provisioning 1" register which allows
|
||||||
|
ignoring the state of the MDI_CFG pin and another bit configuring
|
||||||
|
whether the order of MDI pairs should be normal (ABCD) or reverse
|
||||||
|
(DCBA). Pair polarity is not affected and remains identical in both
|
||||||
|
settings.
|
||||||
|
|
||||||
|
Introduce property "marvell,mdi-cfg-order" which allows forcing either
|
||||||
|
normal or reverse order of the MDI pairs from DT.
|
||||||
|
|
||||||
|
If the property isn't present, the behavior is unchanged and MDI pair
|
||||||
|
order configuration is untouched (ie. either the result of MDI_CFG pin
|
||||||
|
pull-up/pull-down, or pair order override already configured by the
|
||||||
|
bootloader before Linux is started).
|
||||||
|
|
||||||
|
Forcing normal pair order is required on the Adtran SDG-8733A Wi-Fi 7
|
||||||
|
residential gateway.
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://patch.msgid.link/9ed760ff87d5fc456f31e407ead548bbb754497d.1728058550.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||||
|
---
|
||||||
|
drivers/net/phy/aquantia/aquantia_main.c | 33 ++++++++++++++++++++++++
|
||||||
|
1 file changed, 33 insertions(+)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/aquantia/aquantia_main.c
|
||||||
|
+++ b/drivers/net/phy/aquantia/aquantia_main.c
|
||||||
|
@@ -11,6 +11,7 @@
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/bitfield.h>
|
||||||
|
+#include <linux/of.h>
|
||||||
|
#include <linux/phy.h>
|
||||||
|
|
||||||
|
#include "aquantia.h"
|
||||||
|
@@ -71,6 +72,11 @@
|
||||||
|
#define MDIO_AN_TX_VEND_INT_MASK2 0xd401
|
||||||
|
#define MDIO_AN_TX_VEND_INT_MASK2_LINK BIT(0)
|
||||||
|
|
||||||
|
+#define PMAPMD_RSVD_VEND_PROV 0xe400
|
||||||
|
+#define PMAPMD_RSVD_VEND_PROV_MDI_CONF GENMASK(1, 0)
|
||||||
|
+#define PMAPMD_RSVD_VEND_PROV_MDI_REVERSE BIT(0)
|
||||||
|
+#define PMAPMD_RSVD_VEND_PROV_MDI_FORCE BIT(1)
|
||||||
|
+
|
||||||
|
#define MDIO_AN_RX_LP_STAT1 0xe820
|
||||||
|
#define MDIO_AN_RX_LP_STAT1_1000BASET_FULL BIT(15)
|
||||||
|
#define MDIO_AN_RX_LP_STAT1_1000BASET_HALF BIT(14)
|
||||||
|
@@ -485,6 +491,29 @@ static void aqr107_chip_info(struct phy_
|
||||||
|
fw_major, fw_minor, build_id, prov_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
+static int aqr107_config_mdi(struct phy_device *phydev)
|
||||||
|
+{
|
||||||
|
+ struct device_node *np = phydev->mdio.dev.of_node;
|
||||||
|
+ u32 mdi_conf;
|
||||||
|
+ int ret;
|
||||||
|
+
|
||||||
|
+ ret = of_property_read_u32(np, "marvell,mdi-cfg-order", &mdi_conf);
|
||||||
|
+
|
||||||
|
+ /* Do nothing in case property "marvell,mdi-cfg-order" is not present */
|
||||||
|
+ if (ret == -ENOENT)
|
||||||
|
+ return 0;
|
||||||
|
+
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ if (mdi_conf & ~PMAPMD_RSVD_VEND_PROV_MDI_REVERSE)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_RSVD_VEND_PROV,
|
||||||
|
+ PMAPMD_RSVD_VEND_PROV_MDI_CONF,
|
||||||
|
+ mdi_conf | PMAPMD_RSVD_VEND_PROV_MDI_FORCE);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
static int aqr107_config_init(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
struct aqr107_priv *priv = phydev->priv;
|
||||||
|
@@ -514,6 +543,10 @@ static int aqr107_config_init(struct phy
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
+ ret = aqr107_config_mdi(phydev);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
/* Restore LED polarity state after reset */
|
||||||
|
for_each_set_bit(led_active_low, &priv->leds_active_low, AQR_MAX_LEDS) {
|
||||||
|
ret = aqr_phy_led_active_low_set(phydev, led_active_low, true);
|
|
@ -0,0 +1,31 @@
|
||||||
|
From ce21b8fb255ebf0b49913fb4c62741d7eb05c6f6 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Fri, 11 Oct 2024 22:28:43 +0100
|
||||||
|
Subject: [PATCH] net: phy: aquantia: fix return value check in
|
||||||
|
aqr107_config_mdi()
|
||||||
|
|
||||||
|
of_property_read_u32() returns -EINVAL in case the property cannot be
|
||||||
|
found rather than -ENOENT. Fix the check to not abort probing in case
|
||||||
|
of the property being missing, and also in case CONFIG_OF is not set
|
||||||
|
which will result in -ENOSYS.
|
||||||
|
|
||||||
|
Fixes: a2e1ba275eae ("net: phy: aquantia: allow forcing order of MDI pairs")
|
||||||
|
Reported-by: Jon Hunter <jonathanh@nvidia.com>
|
||||||
|
Closes: https://lore.kernel.org/all/114b4c03-5d16-42ed-945d-cf78eabea12b@nvidia.com/
|
||||||
|
Suggested-by: Hans-Frieder Vogt <hfdevel@gmx.net>
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
---
|
||||||
|
drivers/net/phy/aquantia/aquantia_main.c | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/aquantia/aquantia_main.c
|
||||||
|
+++ b/drivers/net/phy/aquantia/aquantia_main.c
|
||||||
|
@@ -500,7 +500,7 @@ static int aqr107_config_mdi(struct phy_
|
||||||
|
ret = of_property_read_u32(np, "marvell,mdi-cfg-order", &mdi_conf);
|
||||||
|
|
||||||
|
/* Do nothing in case property "marvell,mdi-cfg-order" is not present */
|
||||||
|
- if (ret == -ENOENT)
|
||||||
|
+ if (ret == -EINVAL || ret == -ENOSYS)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (ret)
|
|
@ -0,0 +1,53 @@
|
||||||
|
From a274465cc3bef2dfd9c9ea5100848dda0a8641e1 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Thu, 10 Oct 2024 13:54:19 +0100
|
||||||
|
Subject: [PATCH 1/4] net: phy: support 'active-high' property for PHY LEDs
|
||||||
|
|
||||||
|
In addition to 'active-low' and 'inactive-high-impedance' also
|
||||||
|
support 'active-high' property for PHY LED pin configuration.
|
||||||
|
As only either 'active-high' or 'active-low' can be set at the
|
||||||
|
same time, WARN and return an error in case both are set.
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://patch.msgid.link/91598487773d768f254d5faf06cf65b13e972f0e.1728558223.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
|
||||||
|
---
|
||||||
|
drivers/net/phy/phy_device.c | 6 ++++++
|
||||||
|
include/linux/phy.h | 5 +++--
|
||||||
|
2 files changed, 9 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/phy_device.c
|
||||||
|
+++ b/drivers/net/phy/phy_device.c
|
||||||
|
@@ -3358,11 +3358,17 @@ static int of_phy_led(struct phy_device
|
||||||
|
if (index > U8_MAX)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
+ if (of_property_read_bool(led, "active-high"))
|
||||||
|
+ set_bit(PHY_LED_ACTIVE_HIGH, &modes);
|
||||||
|
if (of_property_read_bool(led, "active-low"))
|
||||||
|
set_bit(PHY_LED_ACTIVE_LOW, &modes);
|
||||||
|
if (of_property_read_bool(led, "inactive-high-impedance"))
|
||||||
|
set_bit(PHY_LED_INACTIVE_HIGH_IMPEDANCE, &modes);
|
||||||
|
|
||||||
|
+ if (WARN_ON(modes & BIT(PHY_LED_ACTIVE_LOW) &&
|
||||||
|
+ modes & BIT(PHY_LED_ACTIVE_HIGH)))
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
if (modes) {
|
||||||
|
/* Return error if asked to set polarity modes but not supported */
|
||||||
|
if (!phydev->drv->led_polarity_set)
|
||||||
|
--- a/include/linux/phy.h
|
||||||
|
+++ b/include/linux/phy.h
|
||||||
|
@@ -877,8 +877,9 @@ struct phy_plca_status {
|
||||||
|
|
||||||
|
/* Modes for PHY LED configuration */
|
||||||
|
enum phy_led_modes {
|
||||||
|
- PHY_LED_ACTIVE_LOW = 0,
|
||||||
|
- PHY_LED_INACTIVE_HIGH_IMPEDANCE = 1,
|
||||||
|
+ PHY_LED_ACTIVE_HIGH = 0,
|
||||||
|
+ PHY_LED_ACTIVE_LOW = 1,
|
||||||
|
+ PHY_LED_INACTIVE_HIGH_IMPEDANCE = 2,
|
||||||
|
|
||||||
|
/* keep it last */
|
||||||
|
__PHY_LED_MODES_NUM,
|
|
@ -0,0 +1,108 @@
|
||||||
|
From 9d55e68b19f222e6334ef4021c5527998f5ab537 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Thu, 10 Oct 2024 13:55:00 +0100
|
||||||
|
Subject: [PATCH 2/4] net: phy: aquantia: correctly describe LED polarity
|
||||||
|
override
|
||||||
|
|
||||||
|
Use newly defined 'active-high' property to set the
|
||||||
|
VEND1_GLOBAL_LED_DRIVE_VDD bit and let 'active-low' clear that bit. This
|
||||||
|
reflects the technical reality which was inverted in the previous
|
||||||
|
description in which the 'active-low' property was used to actually set
|
||||||
|
the VEND1_GLOBAL_LED_DRIVE_VDD bit, which means that VDD (ie. supply
|
||||||
|
voltage) of the LED is driven rather than GND.
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://patch.msgid.link/86a413b4387c42dcb54f587cc2433a06f16aae83.1728558223.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
|
||||||
|
---
|
||||||
|
drivers/net/phy/aquantia/aquantia.h | 1 +
|
||||||
|
drivers/net/phy/aquantia/aquantia_leds.c | 19 ++++++++++++++-----
|
||||||
|
drivers/net/phy/aquantia/aquantia_main.c | 12 +++++++++---
|
||||||
|
3 files changed, 24 insertions(+), 8 deletions(-)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/aquantia/aquantia.h
|
||||||
|
+++ b/drivers/net/phy/aquantia/aquantia.h
|
||||||
|
@@ -177,6 +177,7 @@ static const struct aqr107_hw_stat aqr10
|
||||||
|
struct aqr107_priv {
|
||||||
|
u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
|
||||||
|
unsigned long leds_active_low;
|
||||||
|
+ unsigned long leds_active_high;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if IS_REACHABLE(CONFIG_HWMON)
|
||||||
|
--- a/drivers/net/phy/aquantia/aquantia_leds.c
|
||||||
|
+++ b/drivers/net/phy/aquantia/aquantia_leds.c
|
||||||
|
@@ -121,13 +121,13 @@ int aqr_phy_led_active_low_set(struct ph
|
||||||
|
{
|
||||||
|
return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_DRIVE(index),
|
||||||
|
VEND1_GLOBAL_LED_DRIVE_VDD,
|
||||||
|
- enable ? VEND1_GLOBAL_LED_DRIVE_VDD : 0);
|
||||||
|
+ enable ? 0 : VEND1_GLOBAL_LED_DRIVE_VDD);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aqr_phy_led_polarity_set(struct phy_device *phydev, int index, unsigned long modes)
|
||||||
|
{
|
||||||
|
+ bool force_active_low = false, force_active_high = false;
|
||||||
|
struct aqr107_priv *priv = phydev->priv;
|
||||||
|
- bool active_low = false;
|
||||||
|
u32 mode;
|
||||||
|
|
||||||
|
if (index >= AQR_MAX_LEDS)
|
||||||
|
@@ -136,7 +136,10 @@ int aqr_phy_led_polarity_set(struct phy_
|
||||||
|
for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
|
||||||
|
switch (mode) {
|
||||||
|
case PHY_LED_ACTIVE_LOW:
|
||||||
|
- active_low = true;
|
||||||
|
+ force_active_low = true;
|
||||||
|
+ break;
|
||||||
|
+ case PHY_LED_ACTIVE_HIGH:
|
||||||
|
+ force_active_high = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
@@ -144,8 +147,14 @@ int aqr_phy_led_polarity_set(struct phy_
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save LED driver vdd state to restore on SW reset */
|
||||||
|
- if (active_low)
|
||||||
|
+ if (force_active_low)
|
||||||
|
priv->leds_active_low |= BIT(index);
|
||||||
|
|
||||||
|
- return aqr_phy_led_active_low_set(phydev, index, active_low);
|
||||||
|
+ if (force_active_high)
|
||||||
|
+ priv->leds_active_high |= BIT(index);
|
||||||
|
+
|
||||||
|
+ if (force_active_high || force_active_low)
|
||||||
|
+ return aqr_phy_led_active_low_set(phydev, index, force_active_low);
|
||||||
|
+
|
||||||
|
+ unreachable();
|
||||||
|
}
|
||||||
|
--- a/drivers/net/phy/aquantia/aquantia_main.c
|
||||||
|
+++ b/drivers/net/phy/aquantia/aquantia_main.c
|
||||||
|
@@ -517,7 +517,7 @@ static int aqr107_config_mdi(struct phy_
|
||||||
|
static int aqr107_config_init(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
struct aqr107_priv *priv = phydev->priv;
|
||||||
|
- u32 led_active_low;
|
||||||
|
+ u32 led_idx;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Check that the PHY interface type is compatible */
|
||||||
|
@@ -548,8 +548,14 @@ static int aqr107_config_init(struct phy
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Restore LED polarity state after reset */
|
||||||
|
- for_each_set_bit(led_active_low, &priv->leds_active_low, AQR_MAX_LEDS) {
|
||||||
|
- ret = aqr_phy_led_active_low_set(phydev, led_active_low, true);
|
||||||
|
+ for_each_set_bit(led_idx, &priv->leds_active_low, AQR_MAX_LEDS) {
|
||||||
|
+ ret = aqr_phy_led_active_low_set(phydev, led_idx, true);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ for_each_set_bit(led_idx, &priv->leds_active_high, AQR_MAX_LEDS) {
|
||||||
|
+ ret = aqr_phy_led_active_low_set(phydev, led_idx, false);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -0,0 +1,332 @@
|
||||||
|
From 78997e9a5e4d8a4df561e083a92c91ae23010e07 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Tue, 1 Oct 2024 01:17:18 +0100
|
||||||
|
Subject: [PATCH] net: phy: mxl-gpy: add basic LED support
|
||||||
|
|
||||||
|
Add basic support for LEDs connected to MaxLinear GPY2xx and GPY115 PHYs.
|
||||||
|
The PHYs allow up to 4 LEDs to be connected.
|
||||||
|
Implement controlling LEDs in software as well as netdev trigger offloading
|
||||||
|
and LED polarity setup.
|
||||||
|
|
||||||
|
The hardware claims to support 16 PWM brightness levels but there is no
|
||||||
|
documentation on how to use that feature, hence this is not supported.
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://patch.msgid.link/b6ec9050339f8244ff898898a1cecc33b13a48fc.1727741563.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||||
|
---
|
||||||
|
drivers/net/phy/mxl-gpy.c | 218 ++++++++++++++++++++++++++++++++++++++
|
||||||
|
1 file changed, 218 insertions(+)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/mxl-gpy.c
|
||||||
|
+++ b/drivers/net/phy/mxl-gpy.c
|
||||||
|
@@ -38,6 +38,7 @@
|
||||||
|
#define PHY_MIISTAT 0x18 /* MII state */
|
||||||
|
#define PHY_IMASK 0x19 /* interrupt mask */
|
||||||
|
#define PHY_ISTAT 0x1A /* interrupt status */
|
||||||
|
+#define PHY_LED 0x1B /* LEDs */
|
||||||
|
#define PHY_FWV 0x1E /* firmware version */
|
||||||
|
|
||||||
|
#define PHY_MIISTAT_SPD_MASK GENMASK(2, 0)
|
||||||
|
@@ -61,6 +62,11 @@
|
||||||
|
PHY_IMASK_ADSC | \
|
||||||
|
PHY_IMASK_ANC)
|
||||||
|
|
||||||
|
+#define GPY_MAX_LEDS 4
|
||||||
|
+#define PHY_LED_POLARITY(idx) BIT(12 + (idx))
|
||||||
|
+#define PHY_LED_HWCONTROL(idx) BIT(8 + (idx))
|
||||||
|
+#define PHY_LED_ON(idx) BIT(idx)
|
||||||
|
+
|
||||||
|
#define PHY_FWV_REL_MASK BIT(15)
|
||||||
|
#define PHY_FWV_MAJOR_MASK GENMASK(11, 8)
|
||||||
|
#define PHY_FWV_MINOR_MASK GENMASK(7, 0)
|
||||||
|
@@ -72,6 +78,23 @@
|
||||||
|
#define PHY_MDI_MDI_X_CD 0x1
|
||||||
|
#define PHY_MDI_MDI_X_CROSS 0x0
|
||||||
|
|
||||||
|
+/* LED */
|
||||||
|
+#define VSPEC1_LED(idx) (1 + (idx))
|
||||||
|
+#define VSPEC1_LED_BLINKS GENMASK(15, 12)
|
||||||
|
+#define VSPEC1_LED_PULSE GENMASK(11, 8)
|
||||||
|
+#define VSPEC1_LED_CON GENMASK(7, 4)
|
||||||
|
+#define VSPEC1_LED_BLINKF GENMASK(3, 0)
|
||||||
|
+
|
||||||
|
+#define VSPEC1_LED_LINK10 BIT(0)
|
||||||
|
+#define VSPEC1_LED_LINK100 BIT(1)
|
||||||
|
+#define VSPEC1_LED_LINK1000 BIT(2)
|
||||||
|
+#define VSPEC1_LED_LINK2500 BIT(3)
|
||||||
|
+
|
||||||
|
+#define VSPEC1_LED_TXACT BIT(0)
|
||||||
|
+#define VSPEC1_LED_RXACT BIT(1)
|
||||||
|
+#define VSPEC1_LED_COL BIT(2)
|
||||||
|
+#define VSPEC1_LED_NO_CON BIT(3)
|
||||||
|
+
|
||||||
|
/* SGMII */
|
||||||
|
#define VSPEC1_SGMII_CTRL 0x08
|
||||||
|
#define VSPEC1_SGMII_CTRL_ANEN BIT(12) /* Aneg enable */
|
||||||
|
@@ -835,6 +858,156 @@ static int gpy115_loopback(struct phy_de
|
||||||
|
return genphy_soft_reset(phydev);
|
||||||
|
}
|
||||||
|
|
||||||
|
+static int gpy_led_brightness_set(struct phy_device *phydev,
|
||||||
|
+ u8 index, enum led_brightness value)
|
||||||
|
+{
|
||||||
|
+ int ret;
|
||||||
|
+
|
||||||
|
+ if (index >= GPY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ /* clear HWCONTROL and set manual LED state */
|
||||||
|
+ ret = phy_modify(phydev, PHY_LED,
|
||||||
|
+ ((value == LED_OFF) ? PHY_LED_HWCONTROL(index) : 0) |
|
||||||
|
+ PHY_LED_ON(index),
|
||||||
|
+ (value == LED_OFF) ? 0 : PHY_LED_ON(index));
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ /* ToDo: set PWM brightness */
|
||||||
|
+
|
||||||
|
+ /* clear HW LED setup */
|
||||||
|
+ if (value == LED_OFF)
|
||||||
|
+ return phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), 0);
|
||||||
|
+ else
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_100) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_1000) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_2500) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_RX) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_TX));
|
||||||
|
+
|
||||||
|
+static int gpy_led_hw_is_supported(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long rules)
|
||||||
|
+{
|
||||||
|
+ if (index >= GPY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ /* All combinations of the supported triggers are allowed */
|
||||||
|
+ if (rules & ~supported_triggers)
|
||||||
|
+ return -EOPNOTSUPP;
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int gpy_led_hw_control_get(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long *rules)
|
||||||
|
+{
|
||||||
|
+ int val;
|
||||||
|
+
|
||||||
|
+ if (index >= GPY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index));
|
||||||
|
+ if (val < 0)
|
||||||
|
+ return val;
|
||||||
|
+
|
||||||
|
+ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK10)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK_10);
|
||||||
|
+
|
||||||
|
+ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK100)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK_100);
|
||||||
|
+
|
||||||
|
+ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK1000)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
|
||||||
|
+
|
||||||
|
+ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK2500)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK_2500);
|
||||||
|
+
|
||||||
|
+ if (FIELD_GET(VSPEC1_LED_CON, val) == (VSPEC1_LED_LINK10 |
|
||||||
|
+ VSPEC1_LED_LINK100 |
|
||||||
|
+ VSPEC1_LED_LINK1000 |
|
||||||
|
+ VSPEC1_LED_LINK2500))
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK);
|
||||||
|
+
|
||||||
|
+ if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_TXACT)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_TX);
|
||||||
|
+
|
||||||
|
+ if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_RXACT)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_RX);
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int gpy_led_hw_control_set(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long rules)
|
||||||
|
+{
|
||||||
|
+ u16 val = 0;
|
||||||
|
+ int ret;
|
||||||
|
+
|
||||||
|
+ if (index >= GPY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
||||||
|
+ rules & BIT(TRIGGER_NETDEV_LINK_10))
|
||||||
|
+ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK10);
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
||||||
|
+ rules & BIT(TRIGGER_NETDEV_LINK_100))
|
||||||
|
+ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK100);
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
||||||
|
+ rules & BIT(TRIGGER_NETDEV_LINK_1000))
|
||||||
|
+ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK1000);
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
||||||
|
+ rules & BIT(TRIGGER_NETDEV_LINK_2500))
|
||||||
|
+ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK2500);
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_TX))
|
||||||
|
+ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_TXACT);
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_RX))
|
||||||
|
+ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_RXACT);
|
||||||
|
+
|
||||||
|
+ /* allow RX/TX pulse without link indication */
|
||||||
|
+ if ((rules & BIT(TRIGGER_NETDEV_TX) || rules & BIT(TRIGGER_NETDEV_RX)) &&
|
||||||
|
+ !(val & VSPEC1_LED_CON))
|
||||||
|
+ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_NO_CON) | VSPEC1_LED_CON;
|
||||||
|
+
|
||||||
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), val);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ return phy_set_bits(phydev, PHY_LED, PHY_LED_HWCONTROL(index));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int gpy_led_polarity_set(struct phy_device *phydev, int index,
|
||||||
|
+ unsigned long modes)
|
||||||
|
+{
|
||||||
|
+ bool active_low = false;
|
||||||
|
+ u32 mode;
|
||||||
|
+
|
||||||
|
+ if (index >= GPY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
|
||||||
|
+ switch (mode) {
|
||||||
|
+ case PHY_LED_ACTIVE_LOW:
|
||||||
|
+ active_low = true;
|
||||||
|
+ break;
|
||||||
|
+ default:
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return phy_modify(phydev, PHY_LED, PHY_LED_POLARITY(index),
|
||||||
|
+ active_low ? 0 : PHY_LED_POLARITY(index));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
static struct phy_driver gpy_drivers[] = {
|
||||||
|
{
|
||||||
|
PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
|
||||||
|
@@ -852,6 +1025,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.phy_id = PHY_ID_GPY115B,
|
||||||
|
@@ -870,6 +1048,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy115_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
|
||||||
|
@@ -887,6 +1070,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy115_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.phy_id = PHY_ID_GPY211B,
|
||||||
|
@@ -905,6 +1093,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
|
||||||
|
@@ -922,6 +1115,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.phy_id = PHY_ID_GPY212B,
|
||||||
|
@@ -940,6 +1138,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
|
||||||
|
@@ -957,6 +1160,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.phy_id = PHY_ID_GPY215B,
|
||||||
|
@@ -975,6 +1183,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
|
||||||
|
@@ -992,6 +1205,11 @@ static struct phy_driver gpy_drivers[] =
|
||||||
|
.set_wol = gpy_set_wol,
|
||||||
|
.get_wol = gpy_get_wol,
|
||||||
|
.set_loopback = gpy_loopback,
|
||||||
|
+ .led_brightness_set = gpy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = gpy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = gpy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = gpy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = gpy_led_polarity_set,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),
|
|
@ -0,0 +1,28 @@
|
||||||
|
From f95b4725e796b12e5f347a0d161e1d3843142aa8 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Fri, 4 Oct 2024 16:56:35 +0100
|
||||||
|
Subject: [PATCH] net: phy: mxl-gpy: add missing support for
|
||||||
|
TRIGGER_NETDEV_LINK_10
|
||||||
|
|
||||||
|
The PHY also support 10MBit/s links as well as the corresponding link
|
||||||
|
indication trigger to be offloaded. Add TRIGGER_NETDEV_LINK_10 to the
|
||||||
|
supported triggers.
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://patch.msgid.link/cc5da0a989af8b0d49d823656d88053c4de2ab98.1728057367.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||||
|
---
|
||||||
|
drivers/net/phy/mxl-gpy.c | 1 +
|
||||||
|
1 file changed, 1 insertion(+)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/mxl-gpy.c
|
||||||
|
+++ b/drivers/net/phy/mxl-gpy.c
|
||||||
|
@@ -884,6 +884,7 @@ static int gpy_led_brightness_set(struct
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_10) |
|
||||||
|
BIT(TRIGGER_NETDEV_LINK_100) |
|
||||||
|
BIT(TRIGGER_NETDEV_LINK_1000) |
|
||||||
|
BIT(TRIGGER_NETDEV_LINK_2500) |
|
|
@ -0,0 +1,58 @@
|
||||||
|
From eb89c79c1b8f17fc1611540768678e60df89ac42 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Thu, 10 Oct 2024 13:55:17 +0100
|
||||||
|
Subject: [PATCH 3/4] net: phy: mxl-gpy: correctly describe LED polarity
|
||||||
|
|
||||||
|
According the datasheet covering the LED (0x1b) register:
|
||||||
|
0B Active High LEDx pin driven high when activated
|
||||||
|
1B Active Low LEDx pin driven low when activated
|
||||||
|
|
||||||
|
Make use of the now available 'active-high' property and correctly
|
||||||
|
reflect the polarity setting which was previously inverted.
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://patch.msgid.link/180ccafa837f09908b852a8a874a3808c5ecd2d0.1728558223.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
|
||||||
|
---
|
||||||
|
drivers/net/phy/mxl-gpy.c | 16 ++++++++++++----
|
||||||
|
1 file changed, 12 insertions(+), 4 deletions(-)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/mxl-gpy.c
|
||||||
|
+++ b/drivers/net/phy/mxl-gpy.c
|
||||||
|
@@ -989,7 +989,7 @@ static int gpy_led_hw_control_set(struct
|
||||||
|
static int gpy_led_polarity_set(struct phy_device *phydev, int index,
|
||||||
|
unsigned long modes)
|
||||||
|
{
|
||||||
|
- bool active_low = false;
|
||||||
|
+ bool force_active_low = false, force_active_high = false;
|
||||||
|
u32 mode;
|
||||||
|
|
||||||
|
if (index >= GPY_MAX_LEDS)
|
||||||
|
@@ -998,15 +998,23 @@ static int gpy_led_polarity_set(struct p
|
||||||
|
for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
|
||||||
|
switch (mode) {
|
||||||
|
case PHY_LED_ACTIVE_LOW:
|
||||||
|
- active_low = true;
|
||||||
|
+ force_active_low = true;
|
||||||
|
+ break;
|
||||||
|
+ case PHY_LED_ACTIVE_HIGH:
|
||||||
|
+ force_active_high = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- return phy_modify(phydev, PHY_LED, PHY_LED_POLARITY(index),
|
||||||
|
- active_low ? 0 : PHY_LED_POLARITY(index));
|
||||||
|
+ if (force_active_low)
|
||||||
|
+ return phy_set_bits(phydev, PHY_LED, PHY_LED_POLARITY(index));
|
||||||
|
+
|
||||||
|
+ if (force_active_high)
|
||||||
|
+ return phy_clear_bits(phydev, PHY_LED, PHY_LED_POLARITY(index));
|
||||||
|
+
|
||||||
|
+ unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct phy_driver gpy_drivers[] = {
|
|
@ -0,0 +1,379 @@
|
||||||
|
From 1758af47b98c17da464cb45f476875150955dd48 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Date: Thu, 10 Oct 2024 13:55:29 +0100
|
||||||
|
Subject: [PATCH 4/4] net: phy: intel-xway: add support for PHY LEDs
|
||||||
|
|
||||||
|
The intel-xway PHY driver predates the PHY LED framework and currently
|
||||||
|
initializes all LED pins to equal default values.
|
||||||
|
|
||||||
|
Add PHY LED functions to the drivers and don't set default values if
|
||||||
|
LEDs are defined in device tree.
|
||||||
|
|
||||||
|
According the datasheets 3 LEDs are supported on all Intel XWAY PHYs.
|
||||||
|
|
||||||
|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://patch.msgid.link/81f4717ab9acf38f3239727a4540ae96fd01109b.1728558223.git.daniel@makrotopia.org
|
||||||
|
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
|
||||||
|
---
|
||||||
|
drivers/net/phy/intel-xway.c | 253 +++++++++++++++++++++++++++++++++--
|
||||||
|
1 file changed, 244 insertions(+), 9 deletions(-)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/intel-xway.c
|
||||||
|
+++ b/drivers/net/phy/intel-xway.c
|
||||||
|
@@ -151,6 +151,13 @@
|
||||||
|
#define XWAY_MMD_LED3H 0x01E8
|
||||||
|
#define XWAY_MMD_LED3L 0x01E9
|
||||||
|
|
||||||
|
+#define XWAY_GPHY_MAX_LEDS 3
|
||||||
|
+#define XWAY_GPHY_LED_INV(idx) BIT(12 + (idx))
|
||||||
|
+#define XWAY_GPHY_LED_EN(idx) BIT(8 + (idx))
|
||||||
|
+#define XWAY_GPHY_LED_DA(idx) BIT(idx)
|
||||||
|
+#define XWAY_MMD_LEDxH(idx) (XWAY_MMD_LED0H + 2 * (idx))
|
||||||
|
+#define XWAY_MMD_LEDxL(idx) (XWAY_MMD_LED0L + 2 * (idx))
|
||||||
|
+
|
||||||
|
#define PHY_ID_PHY11G_1_3 0x030260D1
|
||||||
|
#define PHY_ID_PHY22F_1_3 0x030260E1
|
||||||
|
#define PHY_ID_PHY11G_1_4 0xD565A400
|
||||||
|
@@ -229,20 +236,12 @@ static int xway_gphy_rgmii_init(struct p
|
||||||
|
XWAY_MDIO_MIICTRL_TXSKEW_MASK, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
-static int xway_gphy_config_init(struct phy_device *phydev)
|
||||||
|
+static int xway_gphy_init_leds(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
u32 ledxh;
|
||||||
|
u32 ledxl;
|
||||||
|
|
||||||
|
- /* Mask all interrupts */
|
||||||
|
- err = phy_write(phydev, XWAY_MDIO_IMASK, 0);
|
||||||
|
- if (err)
|
||||||
|
- return err;
|
||||||
|
-
|
||||||
|
- /* Clear all pending interrupts */
|
||||||
|
- phy_read(phydev, XWAY_MDIO_ISTAT);
|
||||||
|
-
|
||||||
|
/* Ensure that integrated led function is enabled for all leds */
|
||||||
|
err = phy_write(phydev, XWAY_MDIO_LED,
|
||||||
|
XWAY_MDIO_LED_LED0_EN |
|
||||||
|
@@ -276,6 +275,26 @@ static int xway_gphy_config_init(struct
|
||||||
|
phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2H, ledxh);
|
||||||
|
phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2L, ledxl);
|
||||||
|
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int xway_gphy_config_init(struct phy_device *phydev)
|
||||||
|
+{
|
||||||
|
+ struct device_node *np = phydev->mdio.dev.of_node;
|
||||||
|
+ int err;
|
||||||
|
+
|
||||||
|
+ /* Mask all interrupts */
|
||||||
|
+ err = phy_write(phydev, XWAY_MDIO_IMASK, 0);
|
||||||
|
+ if (err)
|
||||||
|
+ return err;
|
||||||
|
+
|
||||||
|
+ /* Use default LED configuration if 'leds' node isn't defined */
|
||||||
|
+ if (!of_get_child_by_name(np, "leds"))
|
||||||
|
+ xway_gphy_init_leds(phydev);
|
||||||
|
+
|
||||||
|
+ /* Clear all pending interrupts */
|
||||||
|
+ phy_read(phydev, XWAY_MDIO_ISTAT);
|
||||||
|
+
|
||||||
|
err = xway_gphy_rgmii_init(phydev);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
@@ -347,6 +366,172 @@ static irqreturn_t xway_gphy_handle_inte
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
+static int xway_gphy_led_brightness_set(struct phy_device *phydev,
|
||||||
|
+ u8 index, enum led_brightness value)
|
||||||
|
+{
|
||||||
|
+ int ret;
|
||||||
|
+
|
||||||
|
+ if (index >= XWAY_GPHY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ /* clear EN and set manual LED state */
|
||||||
|
+ ret = phy_modify(phydev, XWAY_MDIO_LED,
|
||||||
|
+ ((value == LED_OFF) ? XWAY_GPHY_LED_EN(index) : 0) |
|
||||||
|
+ XWAY_GPHY_LED_DA(index),
|
||||||
|
+ (value == LED_OFF) ? 0 : XWAY_GPHY_LED_DA(index));
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ /* clear HW LED setup */
|
||||||
|
+ if (value == LED_OFF) {
|
||||||
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), 0);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), 0);
|
||||||
|
+ } else {
|
||||||
|
+ return 0;
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_10) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_100) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_1000) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_RX) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_TX));
|
||||||
|
+
|
||||||
|
+static int xway_gphy_led_hw_is_supported(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long rules)
|
||||||
|
+{
|
||||||
|
+ if (index >= XWAY_GPHY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ /* activity triggers are not possible without combination with a link
|
||||||
|
+ * trigger.
|
||||||
|
+ */
|
||||||
|
+ if (rules & (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) &&
|
||||||
|
+ !(rules & (BIT(TRIGGER_NETDEV_LINK) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_10) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_100) |
|
||||||
|
+ BIT(TRIGGER_NETDEV_LINK_1000))))
|
||||||
|
+ return -EOPNOTSUPP;
|
||||||
|
+
|
||||||
|
+ /* All other combinations of the supported triggers are allowed */
|
||||||
|
+ if (rules & ~supported_triggers)
|
||||||
|
+ return -EOPNOTSUPP;
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int xway_gphy_led_hw_control_get(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long *rules)
|
||||||
|
+{
|
||||||
|
+ int lval, hval;
|
||||||
|
+
|
||||||
|
+ if (index >= XWAY_GPHY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ hval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index));
|
||||||
|
+ if (hval < 0)
|
||||||
|
+ return hval;
|
||||||
|
+
|
||||||
|
+ lval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index));
|
||||||
|
+ if (lval < 0)
|
||||||
|
+ return lval;
|
||||||
|
+
|
||||||
|
+ if (hval & XWAY_MMD_LEDxH_CON_LINK10)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK_10);
|
||||||
|
+
|
||||||
|
+ if (hval & XWAY_MMD_LEDxH_CON_LINK100)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK_100);
|
||||||
|
+
|
||||||
|
+ if (hval & XWAY_MMD_LEDxH_CON_LINK1000)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
|
||||||
|
+
|
||||||
|
+ if ((hval & XWAY_MMD_LEDxH_CON_LINK10) &&
|
||||||
|
+ (hval & XWAY_MMD_LEDxH_CON_LINK100) &&
|
||||||
|
+ (hval & XWAY_MMD_LEDxH_CON_LINK1000))
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_LINK);
|
||||||
|
+
|
||||||
|
+ if (lval & XWAY_MMD_LEDxL_PULSE_TXACT)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_TX);
|
||||||
|
+
|
||||||
|
+ if (lval & XWAY_MMD_LEDxL_PULSE_RXACT)
|
||||||
|
+ *rules |= BIT(TRIGGER_NETDEV_RX);
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int xway_gphy_led_hw_control_set(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long rules)
|
||||||
|
+{
|
||||||
|
+ u16 hval = 0, lval = 0;
|
||||||
|
+ int ret;
|
||||||
|
+
|
||||||
|
+ if (index >= XWAY_GPHY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
||||||
|
+ rules & BIT(TRIGGER_NETDEV_LINK_10))
|
||||||
|
+ hval |= XWAY_MMD_LEDxH_CON_LINK10;
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
||||||
|
+ rules & BIT(TRIGGER_NETDEV_LINK_100))
|
||||||
|
+ hval |= XWAY_MMD_LEDxH_CON_LINK100;
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
||||||
|
+ rules & BIT(TRIGGER_NETDEV_LINK_1000))
|
||||||
|
+ hval |= XWAY_MMD_LEDxH_CON_LINK1000;
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_TX))
|
||||||
|
+ lval |= XWAY_MMD_LEDxL_PULSE_TXACT;
|
||||||
|
+
|
||||||
|
+ if (rules & BIT(TRIGGER_NETDEV_RX))
|
||||||
|
+ lval |= XWAY_MMD_LEDxL_PULSE_RXACT;
|
||||||
|
+
|
||||||
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), hval);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), lval);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_EN(index));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int xway_gphy_led_polarity_set(struct phy_device *phydev, int index,
|
||||||
|
+ unsigned long modes)
|
||||||
|
+{
|
||||||
|
+ bool force_active_low = false, force_active_high = false;
|
||||||
|
+ u32 mode;
|
||||||
|
+
|
||||||
|
+ if (index >= XWAY_GPHY_MAX_LEDS)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
|
||||||
|
+ switch (mode) {
|
||||||
|
+ case PHY_LED_ACTIVE_LOW:
|
||||||
|
+ force_active_low = true;
|
||||||
|
+ break;
|
||||||
|
+ case PHY_LED_ACTIVE_HIGH:
|
||||||
|
+ force_active_high = true;
|
||||||
|
+ break;
|
||||||
|
+ default:
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (force_active_low)
|
||||||
|
+ return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index));
|
||||||
|
+
|
||||||
|
+ if (force_active_high)
|
||||||
|
+ return phy_clear_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index));
|
||||||
|
+
|
||||||
|
+ unreachable();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
static struct phy_driver xway_gphy[] = {
|
||||||
|
{
|
||||||
|
.phy_id = PHY_ID_PHY11G_1_3,
|
||||||
|
@@ -359,6 +544,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY22F_1_3,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -370,6 +560,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY11G_1_4,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -381,6 +576,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY22F_1_4,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -392,6 +592,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY11G_1_5,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -402,6 +607,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY22F_1_5,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -412,6 +622,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY11G_VR9_1_1,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -422,6 +637,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY22F_VR9_1_1,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -432,6 +652,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY11G_VR9_1_2,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -442,6 +667,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
}, {
|
||||||
|
.phy_id = PHY_ID_PHY22F_VR9_1_2,
|
||||||
|
.phy_id_mask = 0xffffffff,
|
||||||
|
@@ -452,6 +682,11 @@ static struct phy_driver xway_gphy[] = {
|
||||||
|
.config_intr = xway_gphy_config_intr,
|
||||||
|
.suspend = genphy_suspend,
|
||||||
|
.resume = genphy_resume,
|
||||||
|
+ .led_brightness_set = xway_gphy_led_brightness_set,
|
||||||
|
+ .led_hw_is_supported = xway_gphy_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_get = xway_gphy_led_hw_control_get,
|
||||||
|
+ .led_hw_control_set = xway_gphy_led_hw_control_set,
|
||||||
|
+ .led_polarity_set = xway_gphy_led_polarity_set,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
module_phy_driver(xway_gphy);
|
File diff suppressed because it is too large
Load diff
|
@ -8489,13 +8489,3 @@ CONFIG_PROC_MEM_ALWAYS_FORCE=y
|
||||||
# CONFIG_NET_DSA_MV88E6XXX_LEDS is not set
|
# CONFIG_NET_DSA_MV88E6XXX_LEDS is not set
|
||||||
# CONFIG_OF_PARTITION is not set
|
# CONFIG_OF_PARTITION is not set
|
||||||
# CONFIG_HISILICON_ERRATUM_162100801 is not set
|
# CONFIG_HISILICON_ERRATUM_162100801 is not set
|
||||||
# CONFIG_VIDEO_TW9900 is not set
|
|
||||||
# CONFIG_SND_UTIMER is not set
|
|
||||||
# CONFIG_SND_SOC_AK4619 is not set
|
|
||||||
# CONFIG_SND_SOC_AW87390 is not set
|
|
||||||
# CONFIG_SND_SOC_AW88399 is not set
|
|
||||||
# CONFIG_SND_SOC_CS530X_I2C is not set
|
|
||||||
# CONFIG_SND_SOC_ES8311 is not set
|
|
||||||
# CONFIG_SND_SOC_PCM6240 is not set
|
|
||||||
# CONFIG_SND_SOC_RTQ9128 is not set
|
|
||||||
# CONFIG_SND_SOC_MT6357 is not set
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||||
|
%YAML 1.2
|
||||||
|
---
|
||||||
|
$id: http://devicetree.org/schemas/mtd/partitions/openwrt,uimage.yaml#
|
||||||
|
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||||
|
|
||||||
|
title: OpenWrt variations of U-Boot Image partitions
|
||||||
|
|
||||||
|
maintainers:
|
||||||
|
- Bjørn Mork <bjorn@mork.no>
|
||||||
|
|
||||||
|
description: |
|
||||||
|
The image format defined by the boot loader "Das U-Boot" is often
|
||||||
|
modified or extended by device vendors. This defines a few optional
|
||||||
|
properties which can be used to describe such modifications.
|
||||||
|
|
||||||
|
# partition.txt defines common properties, but has not yet been
|
||||||
|
# converted to YAML
|
||||||
|
#allOf:
|
||||||
|
# - $ref: ../partition.yaml#
|
||||||
|
|
||||||
|
properties:
|
||||||
|
compatible:
|
||||||
|
items:
|
||||||
|
- enum:
|
||||||
|
- openwrt,uimage
|
||||||
|
- const: denx,uimage
|
||||||
|
|
||||||
|
openwrt,padding:
|
||||||
|
description: Number of padding bytes between header and data
|
||||||
|
$ref: /schemas/types.yaml#/definitions/uint32
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
openwrt,ih-magic:
|
||||||
|
description: U-Boot Image Header magic number.
|
||||||
|
$ref: /schemas/types.yaml#/definitions/uint32
|
||||||
|
default: 0x27051956 # IH_MAGIC
|
||||||
|
|
||||||
|
openwrt,ih-type:
|
||||||
|
description: U-Boot Image type
|
||||||
|
$ref: /schemas/types.yaml#/definitions/uint32
|
||||||
|
default: 2 # IH_TYPE_KERNEL
|
||||||
|
|
||||||
|
openwrt,offset:
|
||||||
|
description:
|
||||||
|
Offset between partition start and U-Boot Image in bytes
|
||||||
|
$ref: /schemas/types.yaml#/definitions/uint32
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
openwrt,partition-magic:
|
||||||
|
description:
|
||||||
|
Magic number found at the start of the partition. Will only be
|
||||||
|
validated if both this property and openwrt,offset is non-zero
|
||||||
|
$ref: /schemas/types.yaml#/definitions/uint32
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
required:
|
||||||
|
- compatible
|
||||||
|
- reg
|
||||||
|
|
||||||
|
#unevaluatedProperties: false
|
||||||
|
additionalProperties: false
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- |
|
||||||
|
// device with non-default magic
|
||||||
|
partition@300000 {
|
||||||
|
compatible = "openwrt,uimage", "denx,uimage";
|
||||||
|
reg = <0x00300000 0xe80000>;
|
||||||
|
label = "firmware";
|
||||||
|
openwrt,ih-magic = <0x4e474520>;
|
||||||
|
};
|
||||||
|
- |
|
||||||
|
// device with U-Boot Image at an offset, with a partition magic value
|
||||||
|
partition@70000 {
|
||||||
|
compatible = "openwrt,uimage", "denx,uimage";
|
||||||
|
reg = <0x00070000 0x00790000>;
|
||||||
|
label = "firmware";
|
||||||
|
openwrt,offset = <20>;
|
||||||
|
openwrt,partition-magic = <0x43535953>;
|
||||||
|
};
|
||||||
|
- |
|
||||||
|
// device using a non-default image type
|
||||||
|
#include "dt-bindings/mtd/partitions/uimage.h"
|
||||||
|
partition@6c0000 {
|
||||||
|
compatible = "openwrt,uimage", "denx,uimage";
|
||||||
|
reg = <0x6c0000 0x1900000>;
|
||||||
|
label = "firmware";
|
||||||
|
openwrt,ih-magic = <0x33373033>;
|
||||||
|
openwrt,ih-type = <IH_TYPE_FILESYSTEM>;
|
||||||
|
};
|
|
@ -0,0 +1,110 @@
|
||||||
|
-------
|
||||||
|
|
||||||
|
ADM6996FC / ADM6996M switch chip driver
|
||||||
|
|
||||||
|
|
||||||
|
1. General information
|
||||||
|
|
||||||
|
This driver supports the FC and M models only. The ADM6996F and L are
|
||||||
|
completely different chips.
|
||||||
|
|
||||||
|
Support for the FC model is extremely limited at the moment. There is no VLAN
|
||||||
|
support as of yet. The driver will not offer an swconfig interface for the FC
|
||||||
|
chip.
|
||||||
|
|
||||||
|
1.1 VLAN IDs
|
||||||
|
|
||||||
|
It is possible to define 16 different VLANs. Every VLAN has an identifier, its
|
||||||
|
VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the
|
||||||
|
swconfig based configuration is very straightforward. To define two VLANs with
|
||||||
|
IDs 4 and 5, you can invoke, for example:
|
||||||
|
|
||||||
|
# swconfig dev ethX vlan 4 set ports '0 1t 2 5t'
|
||||||
|
# swconfig dev ethX vlan 5 set ports '0t 1t 5t'
|
||||||
|
|
||||||
|
The swconfig framework will automatically invoke 'port Y set pvid Z' for every
|
||||||
|
port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In
|
||||||
|
this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port
|
||||||
|
is the VLAN ID associated with untagged packets coming in on that port.
|
||||||
|
|
||||||
|
But if you wish to use VLAN IDs outside the range 0-15, this automatic
|
||||||
|
behaviour of the swconfig framework becomes a problem. The 16 VLANs that
|
||||||
|
swconfig can configure on the ADM6996 also have a "vid" setting. By default,
|
||||||
|
this is the same as the number of the VLAN entry, to make the simple behaviour
|
||||||
|
above possible. To still support a VLAN with a VLAN ID higher than 15
|
||||||
|
(presumably because you are in a network where such VLAN IDs are already in
|
||||||
|
use), you can change the "vid" setting of the VLAN to anything in the range
|
||||||
|
0-1023. But suppose you did the following:
|
||||||
|
|
||||||
|
# swconfig dev ethX vlan 0 set vid 998
|
||||||
|
# swconfig dev ethX vlan 0 set ports '0 2 5t'
|
||||||
|
|
||||||
|
Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set pvid
|
||||||
|
0'. But the "pvid" should be set to 998, so you are responsible for manually
|
||||||
|
fixing this!
|
||||||
|
|
||||||
|
1.2 VLAN filtering
|
||||||
|
|
||||||
|
The switch is configured to apply source port filtering. This means that
|
||||||
|
packets are only accepted when the port the packets came in on is a member of
|
||||||
|
the VLAN the packet should go to.
|
||||||
|
|
||||||
|
Only membership of a VLAN is tested, it does not matter whether it is a tagged
|
||||||
|
or untagged membership.
|
||||||
|
|
||||||
|
For untagged packets, the destination VLAN is the Primary VLAN ID of the
|
||||||
|
incoming port. So if the PVID of a port is 0, but that port is not a member of
|
||||||
|
the VLAN with ID 0, this means that untagged packets on that port are dropped.
|
||||||
|
This can be used as a roundabout way of dropping untagged packets from a port,
|
||||||
|
a mode often referred to as "Admit only tagged packets".
|
||||||
|
|
||||||
|
1.3 Reset
|
||||||
|
|
||||||
|
The two supported chip models do not have a sofware-initiated reset. When the
|
||||||
|
driver is initialised, as well as when the 'reset' swconfig option is invoked,
|
||||||
|
the driver will set those registers it knows about and supports to the correct
|
||||||
|
default value. But there are a lot of registers in the chip that the driver
|
||||||
|
does not support. If something changed those registers, invoking 'reset' or
|
||||||
|
performing a warm reboot might still leave the chip in a "broken" state. Only
|
||||||
|
a hardware reset will bring it back in the default state.
|
||||||
|
|
||||||
|
2. Technical details on PHYs and the ADM6996
|
||||||
|
|
||||||
|
From the viewpoint of the Linux kernel, it is common that an Ethernet adapter
|
||||||
|
can be seen as a separate MAC entity and a separate PHY entity. The PHY entity
|
||||||
|
can be queried and set through registers accessible via an MDIO bus. A PHY
|
||||||
|
normally has a single address on that bus, in the range 0 through 31.
|
||||||
|
|
||||||
|
The ADM6996 has special-purpose registers in the range of PHYs 0 through 10.
|
||||||
|
Even though all these registers control a single ADM6996 chip, the Linux
|
||||||
|
kernel treats this as 11 separate PHYs. The driver will bind to these
|
||||||
|
addresses to prevent a different PHY driver from binding and corrupting these
|
||||||
|
registers.
|
||||||
|
|
||||||
|
What Linux sees as the PHY on address 0 is meant for the Ethernet MAC
|
||||||
|
connected to the CPU port of the ADM6996 switch chip (port 5). This is the
|
||||||
|
Ethernet MAC you will use to send and receive data through the switch.
|
||||||
|
|
||||||
|
The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of
|
||||||
|
the switch chip. These can be accessed with the Generic PHY driver, as the
|
||||||
|
registers have the common layout.
|
||||||
|
|
||||||
|
If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC
|
||||||
|
needs to bind to PHY address 20 for the port to work correctly.
|
||||||
|
|
||||||
|
The ADM6996 switch driver will reset the ports 0 through 3 on startup and when
|
||||||
|
'reset' is invoked. This could clash with a different PHY driver if the kernel
|
||||||
|
binds a PHY driver to address 16 through 19.
|
||||||
|
|
||||||
|
If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the ADM6996
|
||||||
|
driver will simply always report a connected 100 Mbit/s full-duplex link for
|
||||||
|
that PHY, and provide no other functionality. This is most likely not what you
|
||||||
|
want. So if you see a message in your log
|
||||||
|
|
||||||
|
ethX: PHY overlaps ADM6996, providing fixed PHY yy.
|
||||||
|
|
||||||
|
This is most likely an indication that ethX will not work properly, and your
|
||||||
|
kernel needs to be configured to attach a different PHY to that Ethernet MAC.
|
||||||
|
|
||||||
|
Controlling the mapping between MACs and PHYs is usually done in platform- or
|
||||||
|
board-specific fixup code. The ADM6996 driver has no influence over this.
|
|
@ -0,0 +1,5 @@
|
||||||
|
#
|
||||||
|
# Makefile for the Compex's MyLoader support on MIPS architecture
|
||||||
|
#
|
||||||
|
|
||||||
|
lib-y += myloader.o
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Compex's MyLoader specific prom routines
|
||||||
|
*
|
||||||
|
* Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
|
||||||
|
#include <asm/addrspace.h>
|
||||||
|
#include <asm/fw/myloader/myloader.h>
|
||||||
|
|
||||||
|
#define SYS_PARAMS_ADDR KSEG1ADDR(0x80000800)
|
||||||
|
#define BOARD_PARAMS_ADDR KSEG1ADDR(0x80000A00)
|
||||||
|
#define PART_TABLE_ADDR KSEG1ADDR(0x80000C00)
|
||||||
|
#define BOOT_PARAMS_ADDR KSEG1ADDR(0x80000E00)
|
||||||
|
|
||||||
|
static struct myloader_info myloader_info __initdata;
|
||||||
|
static int myloader_found __initdata;
|
||||||
|
|
||||||
|
struct myloader_info * __init myloader_get_info(void)
|
||||||
|
{
|
||||||
|
struct mylo_system_params *sysp;
|
||||||
|
struct mylo_board_params *boardp;
|
||||||
|
struct mylo_partition_table *parts;
|
||||||
|
|
||||||
|
if (myloader_found)
|
||||||
|
return &myloader_info;
|
||||||
|
|
||||||
|
sysp = (struct mylo_system_params *)(SYS_PARAMS_ADDR);
|
||||||
|
boardp = (struct mylo_board_params *)(BOARD_PARAMS_ADDR);
|
||||||
|
parts = (struct mylo_partition_table *)(PART_TABLE_ADDR);
|
||||||
|
|
||||||
|
printk(KERN_DEBUG "MyLoader: sysp=%08x, boardp=%08x, parts=%08x\n",
|
||||||
|
sysp->magic, boardp->magic, parts->magic);
|
||||||
|
|
||||||
|
/* Check for some magic numbers */
|
||||||
|
if (sysp->magic != MYLO_MAGIC_SYS_PARAMS ||
|
||||||
|
boardp->magic != MYLO_MAGIC_BOARD_PARAMS ||
|
||||||
|
le32_to_cpu(parts->magic) != MYLO_MAGIC_PARTITIONS)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
printk(KERN_DEBUG "MyLoader: id=%04x:%04x, sub_id=%04x:%04x\n",
|
||||||
|
sysp->vid, sysp->did, sysp->svid, sysp->sdid);
|
||||||
|
|
||||||
|
myloader_info.vid = sysp->vid;
|
||||||
|
myloader_info.did = sysp->did;
|
||||||
|
myloader_info.svid = sysp->svid;
|
||||||
|
myloader_info.sdid = sysp->sdid;
|
||||||
|
|
||||||
|
memcpy(myloader_info.macs, boardp->addr, sizeof(myloader_info.macs));
|
||||||
|
|
||||||
|
myloader_found = 1;
|
||||||
|
|
||||||
|
return &myloader_info;
|
||||||
|
}
|
534
6.12/target/linux/generic/files/drivers/bcma/fallback-sprom.c
Normal file
534
6.12/target/linux/generic/files/drivers/bcma/fallback-sprom.c
Normal file
|
@ -0,0 +1,534 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* BCMA Fallback SPROM Driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
|
||||||
|
* Copyright (C) 2014 Jonas Gorski <jonas.gorski@gmail.com>
|
||||||
|
* Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
|
||||||
|
* Copyright (C) 2008 Florian Fainelli <f.fainelli@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/bcma/bcma.h>
|
||||||
|
#include <linux/etherdevice.h>
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/of_net.h>
|
||||||
|
#include <linux/of_platform.h>
|
||||||
|
#include "fallback-sprom.h"
|
||||||
|
|
||||||
|
#define BCMA_FBS_MAX_SIZE 468
|
||||||
|
|
||||||
|
/* SPROM Extraction */
|
||||||
|
#define SPOFF(offset) ((offset) / sizeof(u16))
|
||||||
|
|
||||||
|
#define SPEX(_outvar, _offset, _mask, _shift) \
|
||||||
|
out->_outvar = ((in[SPOFF(_offset)] & (_mask)) >> (_shift))
|
||||||
|
|
||||||
|
#define SPEX32(_outvar, _offset, _mask, _shift) \
|
||||||
|
out->_outvar = ((((u32)in[SPOFF((_offset)+2)] << 16 | \
|
||||||
|
in[SPOFF(_offset)]) & (_mask)) >> (_shift))
|
||||||
|
|
||||||
|
#define SPEX_ARRAY8(_field, _offset, _mask, _shift) \
|
||||||
|
do { \
|
||||||
|
SPEX(_field[0], _offset + 0, _mask, _shift); \
|
||||||
|
SPEX(_field[1], _offset + 2, _mask, _shift); \
|
||||||
|
SPEX(_field[2], _offset + 4, _mask, _shift); \
|
||||||
|
SPEX(_field[3], _offset + 6, _mask, _shift); \
|
||||||
|
SPEX(_field[4], _offset + 8, _mask, _shift); \
|
||||||
|
SPEX(_field[5], _offset + 10, _mask, _shift); \
|
||||||
|
SPEX(_field[6], _offset + 12, _mask, _shift); \
|
||||||
|
SPEX(_field[7], _offset + 14, _mask, _shift); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
struct bcma_fbs {
|
||||||
|
struct device *dev;
|
||||||
|
struct list_head list;
|
||||||
|
struct ssb_sprom sprom;
|
||||||
|
u32 pci_bus;
|
||||||
|
u32 pci_dev;
|
||||||
|
bool devid_override;
|
||||||
|
};
|
||||||
|
|
||||||
|
static DEFINE_SPINLOCK(bcma_fbs_lock);
|
||||||
|
static struct list_head bcma_fbs_list = LIST_HEAD_INIT(bcma_fbs_list);
|
||||||
|
|
||||||
|
int bcma_get_fallback_sprom(struct bcma_bus *bus, struct ssb_sprom *out)
|
||||||
|
{
|
||||||
|
struct bcma_fbs *pos;
|
||||||
|
u32 pci_bus, pci_dev;
|
||||||
|
|
||||||
|
if (bus->hosttype != BCMA_HOSTTYPE_PCI)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
pci_bus = bus->host_pci->bus->number;
|
||||||
|
pci_dev = PCI_SLOT(bus->host_pci->devfn);
|
||||||
|
|
||||||
|
list_for_each_entry(pos, &bcma_fbs_list, list) {
|
||||||
|
if (pos->pci_bus != pci_bus ||
|
||||||
|
pos->pci_dev != pci_dev)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pos->devid_override)
|
||||||
|
bus->host_pci->device = pos->sprom.dev_id;
|
||||||
|
|
||||||
|
memcpy(out, &pos->sprom, sizeof(struct ssb_sprom));
|
||||||
|
dev_info(pos->dev, "requested by [%x:%x]",
|
||||||
|
pos->pci_bus, pos->pci_dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_err("unable to fill SPROM for [%x:%x]\n", pci_bus, pci_dev);
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static s8 sprom_extract_antgain(const u16 *in, u16 offset, u16 mask, u16 shift)
|
||||||
|
{
|
||||||
|
u16 v;
|
||||||
|
u8 gain;
|
||||||
|
|
||||||
|
v = in[SPOFF(offset)];
|
||||||
|
gain = (v & mask) >> shift;
|
||||||
|
if (gain == 0xFF) {
|
||||||
|
gain = 8; /* If unset use 2dBm */
|
||||||
|
} else {
|
||||||
|
/* Q5.2 Fractional part is stored in 0xC0 */
|
||||||
|
gain = ((gain & 0xC0) >> 6) | ((gain & 0x3F) << 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (s8)gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sprom_extract_r8(struct ssb_sprom *out, const u16 *in)
|
||||||
|
{
|
||||||
|
static const u16 pwr_info_offset[] = {
|
||||||
|
SSB_SROM8_PWR_INFO_CORE0, SSB_SROM8_PWR_INFO_CORE1,
|
||||||
|
SSB_SROM8_PWR_INFO_CORE2, SSB_SROM8_PWR_INFO_CORE3
|
||||||
|
};
|
||||||
|
u16 o;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) !=
|
||||||
|
ARRAY_SIZE(out->core_pwr_info));
|
||||||
|
|
||||||
|
SPEX(board_rev, SSB_SPROM8_BOARDREV, ~0, 0);
|
||||||
|
SPEX(board_type, SSB_SPROM1_SPID, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(txpid2g[0], SSB_SPROM4_TXPID2G01, SSB_SPROM4_TXPID2G0,
|
||||||
|
SSB_SPROM4_TXPID2G0_SHIFT);
|
||||||
|
SPEX(txpid2g[1], SSB_SPROM4_TXPID2G01, SSB_SPROM4_TXPID2G1,
|
||||||
|
SSB_SPROM4_TXPID2G1_SHIFT);
|
||||||
|
SPEX(txpid2g[2], SSB_SPROM4_TXPID2G23, SSB_SPROM4_TXPID2G2,
|
||||||
|
SSB_SPROM4_TXPID2G2_SHIFT);
|
||||||
|
SPEX(txpid2g[3], SSB_SPROM4_TXPID2G23, SSB_SPROM4_TXPID2G3,
|
||||||
|
SSB_SPROM4_TXPID2G3_SHIFT);
|
||||||
|
|
||||||
|
SPEX(txpid5gl[0], SSB_SPROM4_TXPID5GL01, SSB_SPROM4_TXPID5GL0,
|
||||||
|
SSB_SPROM4_TXPID5GL0_SHIFT);
|
||||||
|
SPEX(txpid5gl[1], SSB_SPROM4_TXPID5GL01, SSB_SPROM4_TXPID5GL1,
|
||||||
|
SSB_SPROM4_TXPID5GL1_SHIFT);
|
||||||
|
SPEX(txpid5gl[2], SSB_SPROM4_TXPID5GL23, SSB_SPROM4_TXPID5GL2,
|
||||||
|
SSB_SPROM4_TXPID5GL2_SHIFT);
|
||||||
|
SPEX(txpid5gl[3], SSB_SPROM4_TXPID5GL23, SSB_SPROM4_TXPID5GL3,
|
||||||
|
SSB_SPROM4_TXPID5GL3_SHIFT);
|
||||||
|
|
||||||
|
SPEX(txpid5g[0], SSB_SPROM4_TXPID5G01, SSB_SPROM4_TXPID5G0,
|
||||||
|
SSB_SPROM4_TXPID5G0_SHIFT);
|
||||||
|
SPEX(txpid5g[1], SSB_SPROM4_TXPID5G01, SSB_SPROM4_TXPID5G1,
|
||||||
|
SSB_SPROM4_TXPID5G1_SHIFT);
|
||||||
|
SPEX(txpid5g[2], SSB_SPROM4_TXPID5G23, SSB_SPROM4_TXPID5G2,
|
||||||
|
SSB_SPROM4_TXPID5G2_SHIFT);
|
||||||
|
SPEX(txpid5g[3], SSB_SPROM4_TXPID5G23, SSB_SPROM4_TXPID5G3,
|
||||||
|
SSB_SPROM4_TXPID5G3_SHIFT);
|
||||||
|
|
||||||
|
SPEX(txpid5gh[0], SSB_SPROM4_TXPID5GH01, SSB_SPROM4_TXPID5GH0,
|
||||||
|
SSB_SPROM4_TXPID5GH0_SHIFT);
|
||||||
|
SPEX(txpid5gh[1], SSB_SPROM4_TXPID5GH01, SSB_SPROM4_TXPID5GH1,
|
||||||
|
SSB_SPROM4_TXPID5GH1_SHIFT);
|
||||||
|
SPEX(txpid5gh[2], SSB_SPROM4_TXPID5GH23, SSB_SPROM4_TXPID5GH2,
|
||||||
|
SSB_SPROM4_TXPID5GH2_SHIFT);
|
||||||
|
SPEX(txpid5gh[3], SSB_SPROM4_TXPID5GH23, SSB_SPROM4_TXPID5GH3,
|
||||||
|
SSB_SPROM4_TXPID5GH3_SHIFT);
|
||||||
|
|
||||||
|
SPEX(boardflags_lo, SSB_SPROM8_BFLLO, ~0, 0);
|
||||||
|
SPEX(boardflags_hi, SSB_SPROM8_BFLHI, ~0, 0);
|
||||||
|
SPEX(boardflags2_lo, SSB_SPROM8_BFL2LO, ~0, 0);
|
||||||
|
SPEX(boardflags2_hi, SSB_SPROM8_BFL2HI, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(alpha2[0], SSB_SPROM8_CCODE, 0xff00, 8);
|
||||||
|
SPEX(alpha2[1], SSB_SPROM8_CCODE, 0x00ff, 0);
|
||||||
|
|
||||||
|
/* Extract core's power info */
|
||||||
|
for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) {
|
||||||
|
o = pwr_info_offset[i];
|
||||||
|
SPEX(core_pwr_info[i].itssi_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM8_2G_ITSSI, SSB_SPROM8_2G_ITSSI_SHIFT);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM8_2G_MAXP, 0);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SROM8_2G_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SROM8_2G_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SROM8_2G_PA_2, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].itssi_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM8_5G_ITSSI, SSB_SPROM8_5G_ITSSI_SHIFT);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM8_5G_MAXP, 0);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM8_5GHL_MAXP,
|
||||||
|
SSB_SPROM8_5GH_MAXP, 0);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM8_5GHL_MAXP,
|
||||||
|
SSB_SPROM8_5GL_MAXP, SSB_SPROM8_5GL_MAXP_SHIFT);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SROM8_5GL_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SROM8_5GL_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SROM8_5GL_PA_2, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SROM8_5G_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SROM8_5G_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SROM8_5G_PA_2, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SROM8_5GH_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SROM8_5GH_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SROM8_5GH_PA_2, ~0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SPEX(fem.ghz2.tssipos, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_TSSIPOS,
|
||||||
|
SSB_SROM8_FEM_TSSIPOS_SHIFT);
|
||||||
|
SPEX(fem.ghz2.extpa_gain, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_EXTPA_GAIN,
|
||||||
|
SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
|
||||||
|
SPEX(fem.ghz2.pdet_range, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_PDET_RANGE,
|
||||||
|
SSB_SROM8_FEM_PDET_RANGE_SHIFT);
|
||||||
|
SPEX(fem.ghz2.tr_iso, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_TR_ISO,
|
||||||
|
SSB_SROM8_FEM_TR_ISO_SHIFT);
|
||||||
|
SPEX(fem.ghz2.antswlut, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_ANTSWLUT,
|
||||||
|
SSB_SROM8_FEM_ANTSWLUT_SHIFT);
|
||||||
|
|
||||||
|
SPEX(fem.ghz5.tssipos, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_TSSIPOS,
|
||||||
|
SSB_SROM8_FEM_TSSIPOS_SHIFT);
|
||||||
|
SPEX(fem.ghz5.extpa_gain, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_EXTPA_GAIN,
|
||||||
|
SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
|
||||||
|
SPEX(fem.ghz5.pdet_range, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_PDET_RANGE,
|
||||||
|
SSB_SROM8_FEM_PDET_RANGE_SHIFT);
|
||||||
|
SPEX(fem.ghz5.tr_iso, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_TR_ISO,
|
||||||
|
SSB_SROM8_FEM_TR_ISO_SHIFT);
|
||||||
|
SPEX(fem.ghz5.antswlut, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_ANTSWLUT,
|
||||||
|
SSB_SROM8_FEM_ANTSWLUT_SHIFT);
|
||||||
|
|
||||||
|
SPEX(ant_available_a, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_A,
|
||||||
|
SSB_SPROM8_ANTAVAIL_A_SHIFT);
|
||||||
|
SPEX(ant_available_bg, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_BG,
|
||||||
|
SSB_SPROM8_ANTAVAIL_BG_SHIFT);
|
||||||
|
SPEX(maxpwr_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_MAXP_BG_MASK, 0);
|
||||||
|
SPEX(itssi_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_ITSSI_BG,
|
||||||
|
SSB_SPROM8_ITSSI_BG_SHIFT);
|
||||||
|
SPEX(maxpwr_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_MAXP_A_MASK, 0);
|
||||||
|
SPEX(itssi_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_ITSSI_A,
|
||||||
|
SSB_SPROM8_ITSSI_A_SHIFT);
|
||||||
|
SPEX(maxpwr_ah, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AH_MASK, 0);
|
||||||
|
SPEX(maxpwr_al, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AL_MASK,
|
||||||
|
SSB_SPROM8_MAXP_AL_SHIFT);
|
||||||
|
SPEX(gpio0, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P0, 0);
|
||||||
|
SPEX(gpio1, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P1,
|
||||||
|
SSB_SPROM8_GPIOA_P1_SHIFT);
|
||||||
|
SPEX(gpio2, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P2, 0);
|
||||||
|
SPEX(gpio3, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P3,
|
||||||
|
SSB_SPROM8_GPIOB_P3_SHIFT);
|
||||||
|
SPEX(tri2g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI2G, 0);
|
||||||
|
SPEX(tri5g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI5G,
|
||||||
|
SSB_SPROM8_TRI5G_SHIFT);
|
||||||
|
SPEX(tri5gl, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GL, 0);
|
||||||
|
SPEX(tri5gh, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GH,
|
||||||
|
SSB_SPROM8_TRI5GH_SHIFT);
|
||||||
|
SPEX(rxpo2g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO2G,
|
||||||
|
SSB_SPROM8_RXPO2G_SHIFT);
|
||||||
|
SPEX(rxpo5g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO5G,
|
||||||
|
SSB_SPROM8_RXPO5G_SHIFT);
|
||||||
|
SPEX(rssismf2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMF2G, 0);
|
||||||
|
SPEX(rssismc2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMC2G,
|
||||||
|
SSB_SPROM8_RSSISMC2G_SHIFT);
|
||||||
|
SPEX(rssisav2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISAV2G,
|
||||||
|
SSB_SPROM8_RSSISAV2G_SHIFT);
|
||||||
|
SPEX(bxa2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_BXA2G,
|
||||||
|
SSB_SPROM8_BXA2G_SHIFT);
|
||||||
|
SPEX(rssismf5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMF5G, 0);
|
||||||
|
SPEX(rssismc5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMC5G,
|
||||||
|
SSB_SPROM8_RSSISMC5G_SHIFT);
|
||||||
|
SPEX(rssisav5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISAV5G,
|
||||||
|
SSB_SPROM8_RSSISAV5G_SHIFT);
|
||||||
|
SPEX(bxa5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_BXA5G,
|
||||||
|
SSB_SPROM8_BXA5G_SHIFT);
|
||||||
|
|
||||||
|
SPEX(pa0b0, SSB_SPROM8_PA0B0, ~0, 0);
|
||||||
|
SPEX(pa0b1, SSB_SPROM8_PA0B1, ~0, 0);
|
||||||
|
SPEX(pa0b2, SSB_SPROM8_PA0B2, ~0, 0);
|
||||||
|
SPEX(pa1b0, SSB_SPROM8_PA1B0, ~0, 0);
|
||||||
|
SPEX(pa1b1, SSB_SPROM8_PA1B1, ~0, 0);
|
||||||
|
SPEX(pa1b2, SSB_SPROM8_PA1B2, ~0, 0);
|
||||||
|
SPEX(pa1lob0, SSB_SPROM8_PA1LOB0, ~0, 0);
|
||||||
|
SPEX(pa1lob1, SSB_SPROM8_PA1LOB1, ~0, 0);
|
||||||
|
SPEX(pa1lob2, SSB_SPROM8_PA1LOB2, ~0, 0);
|
||||||
|
SPEX(pa1hib0, SSB_SPROM8_PA1HIB0, ~0, 0);
|
||||||
|
SPEX(pa1hib1, SSB_SPROM8_PA1HIB1, ~0, 0);
|
||||||
|
SPEX(pa1hib2, SSB_SPROM8_PA1HIB2, ~0, 0);
|
||||||
|
SPEX(cck2gpo, SSB_SPROM8_CCK2GPO, ~0, 0);
|
||||||
|
SPEX32(ofdm2gpo, SSB_SPROM8_OFDM2GPO, ~0, 0);
|
||||||
|
SPEX32(ofdm5glpo, SSB_SPROM8_OFDM5GLPO, ~0, 0);
|
||||||
|
SPEX32(ofdm5gpo, SSB_SPROM8_OFDM5GPO, ~0, 0);
|
||||||
|
SPEX32(ofdm5ghpo, SSB_SPROM8_OFDM5GHPO, ~0, 0);
|
||||||
|
|
||||||
|
/* Extract the antenna gain values. */
|
||||||
|
out->antenna_gain.a0 = sprom_extract_antgain(in,
|
||||||
|
SSB_SPROM8_AGAIN01,
|
||||||
|
SSB_SPROM8_AGAIN0,
|
||||||
|
SSB_SPROM8_AGAIN0_SHIFT);
|
||||||
|
out->antenna_gain.a1 = sprom_extract_antgain(in,
|
||||||
|
SSB_SPROM8_AGAIN01,
|
||||||
|
SSB_SPROM8_AGAIN1,
|
||||||
|
SSB_SPROM8_AGAIN1_SHIFT);
|
||||||
|
out->antenna_gain.a2 = sprom_extract_antgain(in,
|
||||||
|
SSB_SPROM8_AGAIN23,
|
||||||
|
SSB_SPROM8_AGAIN2,
|
||||||
|
SSB_SPROM8_AGAIN2_SHIFT);
|
||||||
|
out->antenna_gain.a3 = sprom_extract_antgain(in,
|
||||||
|
SSB_SPROM8_AGAIN23,
|
||||||
|
SSB_SPROM8_AGAIN3,
|
||||||
|
SSB_SPROM8_AGAIN3_SHIFT);
|
||||||
|
|
||||||
|
SPEX(leddc_on_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_ON,
|
||||||
|
SSB_SPROM8_LEDDC_ON_SHIFT);
|
||||||
|
SPEX(leddc_off_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_OFF,
|
||||||
|
SSB_SPROM8_LEDDC_OFF_SHIFT);
|
||||||
|
|
||||||
|
SPEX(txchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_TXCHAIN,
|
||||||
|
SSB_SPROM8_TXRXC_TXCHAIN_SHIFT);
|
||||||
|
SPEX(rxchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_RXCHAIN,
|
||||||
|
SSB_SPROM8_TXRXC_RXCHAIN_SHIFT);
|
||||||
|
SPEX(antswitch, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_SWITCH,
|
||||||
|
SSB_SPROM8_TXRXC_SWITCH_SHIFT);
|
||||||
|
|
||||||
|
SPEX(opo, SSB_SPROM8_OFDM2GPO, 0x00ff, 0);
|
||||||
|
|
||||||
|
SPEX_ARRAY8(mcs2gpo, SSB_SPROM8_2G_MCSPO, ~0, 0);
|
||||||
|
SPEX_ARRAY8(mcs5gpo, SSB_SPROM8_5G_MCSPO, ~0, 0);
|
||||||
|
SPEX_ARRAY8(mcs5glpo, SSB_SPROM8_5GL_MCSPO, ~0, 0);
|
||||||
|
SPEX_ARRAY8(mcs5ghpo, SSB_SPROM8_5GH_MCSPO, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(rawtempsense, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_RAWTEMP,
|
||||||
|
SSB_SPROM8_RAWTS_RAWTEMP_SHIFT);
|
||||||
|
SPEX(measpower, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_MEASPOWER,
|
||||||
|
SSB_SPROM8_RAWTS_MEASPOWER_SHIFT);
|
||||||
|
SPEX(tempsense_slope, SSB_SPROM8_OPT_CORRX,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMP_SLOPE,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMP_SLOPE_SHIFT);
|
||||||
|
SPEX(tempcorrx, SSB_SPROM8_OPT_CORRX, SSB_SPROM8_OPT_CORRX_TEMPCORRX,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMPCORRX_SHIFT);
|
||||||
|
SPEX(tempsense_option, SSB_SPROM8_OPT_CORRX,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMP_OPTION,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMP_OPTION_SHIFT);
|
||||||
|
SPEX(freqoffset_corr, SSB_SPROM8_HWIQ_IQSWP,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR_SHIFT);
|
||||||
|
SPEX(iqcal_swp_dis, SSB_SPROM8_HWIQ_IQSWP,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP_SHIFT);
|
||||||
|
SPEX(hw_iqcal_en, SSB_SPROM8_HWIQ_IQSWP, SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL_SHIFT);
|
||||||
|
|
||||||
|
SPEX(bw40po, SSB_SPROM8_BW40PO, ~0, 0);
|
||||||
|
SPEX(cddpo, SSB_SPROM8_CDDPO, ~0, 0);
|
||||||
|
SPEX(stbcpo, SSB_SPROM8_STBCPO, ~0, 0);
|
||||||
|
SPEX(bwduppo, SSB_SPROM8_BWDUPPO, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(tempthresh, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_TRESH,
|
||||||
|
SSB_SPROM8_THERMAL_TRESH_SHIFT);
|
||||||
|
SPEX(tempoffset, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_OFFSET,
|
||||||
|
SSB_SPROM8_THERMAL_OFFSET_SHIFT);
|
||||||
|
SPEX(phycal_tempdelta, SSB_SPROM8_TEMPDELTA,
|
||||||
|
SSB_SPROM8_TEMPDELTA_PHYCAL,
|
||||||
|
SSB_SPROM8_TEMPDELTA_PHYCAL_SHIFT);
|
||||||
|
SPEX(temps_period, SSB_SPROM8_TEMPDELTA, SSB_SPROM8_TEMPDELTA_PERIOD,
|
||||||
|
SSB_SPROM8_TEMPDELTA_PERIOD_SHIFT);
|
||||||
|
SPEX(temps_hysteresis, SSB_SPROM8_TEMPDELTA,
|
||||||
|
SSB_SPROM8_TEMPDELTA_HYSTERESIS,
|
||||||
|
SSB_SPROM8_TEMPDELTA_HYSTERESIS_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sprom_extract(struct bcma_fbs *priv, const u16 *in, u16 size)
|
||||||
|
{
|
||||||
|
struct ssb_sprom *out = &priv->sprom;
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(*out));
|
||||||
|
|
||||||
|
out->revision = in[size - 1] & 0x00FF;
|
||||||
|
if (out->revision < 8 || out->revision > 11) {
|
||||||
|
dev_warn(priv->dev,
|
||||||
|
"Unsupported SPROM revision %d detected."
|
||||||
|
" Will extract v8\n",
|
||||||
|
out->revision);
|
||||||
|
out->revision = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprom_extract_r8(out, in);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bcma_fbs_fixup(struct bcma_fbs *priv, u16 *sprom)
|
||||||
|
{
|
||||||
|
struct device_node *node = priv->dev->of_node;
|
||||||
|
u32 fixups, off, val;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
if (!of_get_property(node, "brcm,sprom-fixups", &fixups))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixups /= sizeof(u32);
|
||||||
|
|
||||||
|
dev_info(priv->dev, "patching SPROM with %u fixups...\n", fixups >> 1);
|
||||||
|
|
||||||
|
while (i < fixups) {
|
||||||
|
if (of_property_read_u32_index(node, "brcm,sprom-fixups",
|
||||||
|
i++, &off)) {
|
||||||
|
dev_err(priv->dev, "error reading fixup[%u] offset\n",
|
||||||
|
i - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (of_property_read_u32_index(node, "brcm,sprom-fixups",
|
||||||
|
i++, &val)) {
|
||||||
|
dev_err(priv->dev, "error reading fixup[%u] value\n",
|
||||||
|
i - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_dbg(priv->dev, "fixup[%d]=0x%04x\n", off, val);
|
||||||
|
|
||||||
|
sprom[off] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sprom_override_devid(struct bcma_fbs *priv, struct ssb_sprom *out,
|
||||||
|
const u16 *in)
|
||||||
|
{
|
||||||
|
SPEX(dev_id, 0x0060, 0xFFFF, 0);
|
||||||
|
return !!out->dev_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bcma_fbs_set(struct bcma_fbs *priv, struct device_node *node)
|
||||||
|
{
|
||||||
|
struct ssb_sprom *sprom = &priv->sprom;
|
||||||
|
const struct firmware *fw;
|
||||||
|
const char *sprom_name;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (of_property_read_string(node, "brcm,sprom", &sprom_name))
|
||||||
|
sprom_name = NULL;
|
||||||
|
|
||||||
|
if (sprom_name) {
|
||||||
|
err = request_firmware_direct(&fw, sprom_name, priv->dev);
|
||||||
|
if (err)
|
||||||
|
dev_err(priv->dev, "%s load error\n", sprom_name);
|
||||||
|
} else {
|
||||||
|
err = -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
sprom->revision = 0x02;
|
||||||
|
sprom->board_rev = 0x0017;
|
||||||
|
sprom->country_code = 0x00;
|
||||||
|
sprom->ant_available_bg = 0x03;
|
||||||
|
sprom->pa0b0 = 0x15ae;
|
||||||
|
sprom->pa0b1 = 0xfa85;
|
||||||
|
sprom->pa0b2 = 0xfe8d;
|
||||||
|
sprom->pa1b0 = 0xffff;
|
||||||
|
sprom->pa1b1 = 0xffff;
|
||||||
|
sprom->pa1b2 = 0xffff;
|
||||||
|
sprom->gpio0 = 0xff;
|
||||||
|
sprom->gpio1 = 0xff;
|
||||||
|
sprom->gpio2 = 0xff;
|
||||||
|
sprom->gpio3 = 0xff;
|
||||||
|
sprom->maxpwr_bg = 0x4c;
|
||||||
|
sprom->itssi_bg = 0x00;
|
||||||
|
sprom->boardflags_lo = 0x2848;
|
||||||
|
sprom->boardflags_hi = 0x0000;
|
||||||
|
priv->devid_override = false;
|
||||||
|
|
||||||
|
dev_warn(priv->dev, "using basic SPROM\n");
|
||||||
|
} else {
|
||||||
|
size_t size = min(fw->size, (size_t) BCMA_FBS_MAX_SIZE);
|
||||||
|
u16 tmp_sprom[BCMA_FBS_MAX_SIZE >> 1];
|
||||||
|
u32 i, j;
|
||||||
|
|
||||||
|
for (i = 0, j = 0; i < size; i += 2, j++)
|
||||||
|
tmp_sprom[j] = (fw->data[i] << 8) | fw->data[i + 1];
|
||||||
|
|
||||||
|
release_firmware(fw);
|
||||||
|
bcma_fbs_fixup(priv, tmp_sprom);
|
||||||
|
sprom_extract(priv, tmp_sprom, size >> 1);
|
||||||
|
|
||||||
|
priv->devid_override = sprom_override_devid(priv, sprom,
|
||||||
|
tmp_sprom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bcma_fbs_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct device_node *node = dev->of_node;
|
||||||
|
struct bcma_fbs *priv;
|
||||||
|
unsigned long flags;
|
||||||
|
u8 mac[ETH_ALEN];
|
||||||
|
|
||||||
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||||
|
if (!priv)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
priv->dev = dev;
|
||||||
|
|
||||||
|
bcma_fbs_set(priv, node);
|
||||||
|
|
||||||
|
of_property_read_u32(node, "pci-bus", &priv->pci_bus);
|
||||||
|
of_property_read_u32(node, "pci-dev", &priv->pci_dev);
|
||||||
|
|
||||||
|
of_get_mac_address(node, mac);
|
||||||
|
if (is_valid_ether_addr(mac)) {
|
||||||
|
dev_info(dev, "mtd mac %pM\n", mac);
|
||||||
|
} else {
|
||||||
|
eth_random_addr(mac);
|
||||||
|
dev_info(dev, "random mac %pM\n", mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(priv->sprom.il0mac, mac, ETH_ALEN);
|
||||||
|
memcpy(priv->sprom.et0mac, mac, ETH_ALEN);
|
||||||
|
memcpy(priv->sprom.et1mac, mac, ETH_ALEN);
|
||||||
|
memcpy(priv->sprom.et2mac, mac, ETH_ALEN);
|
||||||
|
|
||||||
|
spin_lock_irqsave(&bcma_fbs_lock, flags);
|
||||||
|
list_add(&priv->list, &bcma_fbs_list);
|
||||||
|
spin_unlock_irqrestore(&bcma_fbs_lock, flags);
|
||||||
|
|
||||||
|
dev_info(dev, "registered SPROM for [%x:%x]\n",
|
||||||
|
priv->pci_bus, priv->pci_dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id bcma_fbs_of_match[] = {
|
||||||
|
{ .compatible = "brcm,bcma-sprom", },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, bcma_fbs_of_match);
|
||||||
|
|
||||||
|
static struct platform_driver bcma_fbs_driver = {
|
||||||
|
.probe = bcma_fbs_probe,
|
||||||
|
.driver = {
|
||||||
|
.name = "bcma-sprom",
|
||||||
|
.of_match_table = bcma_fbs_of_match,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
int __init bcma_fbs_register(void)
|
||||||
|
{
|
||||||
|
return platform_driver_register(&bcma_fbs_driver);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
#ifndef _FALLBACK_SPROM_H
|
||||||
|
#define _FALLBACK_SPROM_H
|
||||||
|
|
||||||
|
int __init bcma_fbs_register(void);
|
||||||
|
int bcma_get_fallback_sprom(struct bcma_bus *dev, struct ssb_sprom *out);
|
||||||
|
|
||||||
|
#endif /* _FALLBACK_SPROM_H */
|
112
6.12/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig
Normal file
112
6.12/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
config MTD_SPLIT
|
||||||
|
def_bool n
|
||||||
|
help
|
||||||
|
Generic MTD split support.
|
||||||
|
|
||||||
|
config MTD_SPLIT_SUPPORT
|
||||||
|
def_bool MTD = y
|
||||||
|
|
||||||
|
comment "Rootfs partition parsers"
|
||||||
|
|
||||||
|
config MTD_SPLIT_SQUASHFS_ROOT
|
||||||
|
bool "Squashfs based root partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
help
|
||||||
|
This provides a parsing function which allows to detect the
|
||||||
|
offset and size of the unused portion of a rootfs partition
|
||||||
|
containing a squashfs.
|
||||||
|
|
||||||
|
comment "Firmware partition parsers"
|
||||||
|
|
||||||
|
config MTD_SPLIT_BCM63XX_FW
|
||||||
|
bool "BCM63xx firmware parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_BCM_WFI_FW
|
||||||
|
bool "Broadcom Whole Flash Image parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_CFE_BOOTFS
|
||||||
|
bool "Parser finding rootfs appended to the CFE bootfs"
|
||||||
|
depends on MTD_SPLIT_SUPPORT && (ARCH_BCM4908 || ARCH_BCMBCA)
|
||||||
|
select MTD_SPLIT
|
||||||
|
help
|
||||||
|
cferom on BCM4908 (and bcm63xx) uses JFFS2 bootfs partition
|
||||||
|
for storing kernel, cferam and some device specific files.
|
||||||
|
There isn't any straight way of storing rootfs so it gets
|
||||||
|
appended to the JFFS2 bootfs partition. Kernel needs to find
|
||||||
|
it and run init from it. This parser is responsible for
|
||||||
|
finding appended rootfs.
|
||||||
|
|
||||||
|
config MTD_SPLIT_SEAMA_FW
|
||||||
|
bool "Seama firmware parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_WRGG_FW
|
||||||
|
bool "WRGG firmware parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_UIMAGE_FW
|
||||||
|
bool "uImage based firmware partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_FIT_FW
|
||||||
|
bool "FIT based firmware partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_LZMA_FW
|
||||||
|
bool "LZMA compressed kernel based firmware partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_TPLINK_FW
|
||||||
|
bool "TP-Link firmware parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_TRX_FW
|
||||||
|
bool "TRX image based firmware partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_BRNIMAGE_FW
|
||||||
|
bool "brnImage (brnboot image) firmware parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_EVA_FW
|
||||||
|
bool "EVA image based firmware partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_MINOR_FW
|
||||||
|
bool "Mikrotik NOR image based firmware partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_JIMAGE_FW
|
||||||
|
bool "JBOOT Image based firmware partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_ELF_FW
|
||||||
|
bool "ELF loader firmware partition parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_H3C_VFS
|
||||||
|
bool "Parser finding rootfs appended to H3C VFS"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
||||||
|
|
||||||
|
config MTD_SPLIT_SEIL_FW
|
||||||
|
bool "IIJ SEIL firmware parser"
|
||||||
|
depends on MTD_SPLIT_SUPPORT
|
||||||
|
select MTD_SPLIT
|
|
@ -0,0 +1,19 @@
|
||||||
|
obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_BCM63XX_FW) += mtdsplit_bcm63xx.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_BCM_WFI_FW) += mtdsplit_bcm_wfi.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_CFE_BOOTFS) += mtdsplit_cfe_bootfs.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_SEIL_FW) += mtdsplit_seil.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_ELF_FW) += mtdsplit_elf.o
|
||||||
|
obj-$(CONFIG_MTD_SPLIT_H3C_VFS) += mtdsplit_h3c_vfs.o
|
130
6.12/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c
Normal file
130
6.12/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
|
||||||
|
* Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
* Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
|
||||||
|
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) "mtdsplit: " fmt
|
||||||
|
|
||||||
|
#include <linux/export.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/magic.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define UBI_EC_MAGIC 0x55424923 /* UBI# */
|
||||||
|
|
||||||
|
struct squashfs_super_block {
|
||||||
|
__le32 s_magic;
|
||||||
|
__le32 pad0[9];
|
||||||
|
__le64 bytes_used;
|
||||||
|
};
|
||||||
|
|
||||||
|
int mtd_get_squashfs_len(struct mtd_info *master,
|
||||||
|
size_t offset,
|
||||||
|
size_t *squashfs_len)
|
||||||
|
{
|
||||||
|
struct squashfs_super_block sb;
|
||||||
|
size_t retlen;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb);
|
||||||
|
if (err || (retlen != sizeof(sb))) {
|
||||||
|
pr_alert("error occured while reading from \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) {
|
||||||
|
pr_alert("no squashfs found in \"%s\"\n", master->name);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
retlen = le64_to_cpu(sb.bytes_used);
|
||||||
|
if (retlen <= 0) {
|
||||||
|
pr_alert("squashfs is empty in \"%s\"\n", master->name);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + retlen > master->size) {
|
||||||
|
pr_alert("squashfs has invalid size in \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*squashfs_len = retlen;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(mtd_get_squashfs_len);
|
||||||
|
|
||||||
|
static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset)
|
||||||
|
{
|
||||||
|
return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
|
||||||
|
enum mtdsplit_part_type *type)
|
||||||
|
{
|
||||||
|
u32 magic;
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mtd_read(mtd, offset, sizeof(magic), &retlen,
|
||||||
|
(unsigned char *) &magic);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (retlen != sizeof(magic))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (le32_to_cpu(magic) == SQUASHFS_MAGIC) {
|
||||||
|
if (type)
|
||||||
|
*type = MTDSPLIT_PART_TYPE_SQUASHFS;
|
||||||
|
return 0;
|
||||||
|
} else if (magic == 0x19852003) {
|
||||||
|
if (type)
|
||||||
|
*type = MTDSPLIT_PART_TYPE_JFFS2;
|
||||||
|
return 0;
|
||||||
|
} else if (be32_to_cpu(magic) == UBI_EC_MAGIC) {
|
||||||
|
if (type)
|
||||||
|
*type = MTDSPLIT_PART_TYPE_UBI;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic);
|
||||||
|
|
||||||
|
int mtd_find_rootfs_from(struct mtd_info *mtd,
|
||||||
|
size_t from,
|
||||||
|
size_t limit,
|
||||||
|
size_t *ret_offset,
|
||||||
|
enum mtdsplit_part_type *type)
|
||||||
|
{
|
||||||
|
size_t offset;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
for (offset = from; offset < limit;
|
||||||
|
offset = mtd_next_eb(mtd, offset)) {
|
||||||
|
err = mtd_check_rootfs_magic(mtd, offset, type);
|
||||||
|
if (err)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
*ret_offset = offset;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(mtd_find_rootfs_from);
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
|
||||||
|
* Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
* Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
|
||||||
|
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _MTDSPLIT_H
|
||||||
|
#define _MTDSPLIT_H
|
||||||
|
|
||||||
|
#define KERNEL_PART_NAME "kernel"
|
||||||
|
#define ROOTFS_PART_NAME "rootfs"
|
||||||
|
#define UBI_PART_NAME "ubi"
|
||||||
|
|
||||||
|
#define ROOTFS_SPLIT_NAME "rootfs_data"
|
||||||
|
|
||||||
|
enum mtdsplit_part_type {
|
||||||
|
MTDSPLIT_PART_TYPE_UNK = 0,
|
||||||
|
MTDSPLIT_PART_TYPE_SQUASHFS,
|
||||||
|
MTDSPLIT_PART_TYPE_JFFS2,
|
||||||
|
MTDSPLIT_PART_TYPE_UBI,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_SPLIT
|
||||||
|
int mtd_get_squashfs_len(struct mtd_info *master,
|
||||||
|
size_t offset,
|
||||||
|
size_t *squashfs_len);
|
||||||
|
|
||||||
|
int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
|
||||||
|
enum mtdsplit_part_type *type);
|
||||||
|
|
||||||
|
int mtd_find_rootfs_from(struct mtd_info *mtd,
|
||||||
|
size_t from,
|
||||||
|
size_t limit,
|
||||||
|
size_t *ret_offset,
|
||||||
|
enum mtdsplit_part_type *type);
|
||||||
|
|
||||||
|
#else
|
||||||
|
static inline int mtd_get_squashfs_len(struct mtd_info *master,
|
||||||
|
size_t offset,
|
||||||
|
size_t *squashfs_len)
|
||||||
|
{
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset,
|
||||||
|
enum mtdsplit_part_type *type)
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int mtd_find_rootfs_from(struct mtd_info *mtd,
|
||||||
|
size_t from,
|
||||||
|
size_t limit,
|
||||||
|
size_t *ret_offset,
|
||||||
|
enum mtdsplit_part_type *type)
|
||||||
|
{
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_MTD_SPLIT */
|
||||||
|
|
||||||
|
#endif /* _MTDSPLIT_H */
|
|
@ -0,0 +1,186 @@
|
||||||
|
/*
|
||||||
|
* Firmware MTD split for BCM63XX, based on bcm63xxpart.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006-2008 Florian Fainelli <florian@openwrt.org>
|
||||||
|
* Copyright (C) 2006-2008 Mike Albon <malbon@openwrt.org>
|
||||||
|
* Copyright (C) 2009-2010 Daniel Dickinson <openwrt@cshore.neomailbox.net>
|
||||||
|
* Copyright (C) 2011-2013 Jonas Gorski <jonas.gorski@gmail.com>
|
||||||
|
* Copyright (C) 2015 Simon Arlott <simon@fire.lp0.eu>
|
||||||
|
* Copyright (C) 2017 Álvaro Fernández Rojas <noltari@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/bcm963xx_tag.h>
|
||||||
|
#include <linux/crc32.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
/* Ensure strings read from flash structs are null terminated */
|
||||||
|
#define STR_NULL_TERMINATE(x) \
|
||||||
|
do { char *_str = (x); _str[sizeof(x) - 1] = 0; } while (0)
|
||||||
|
|
||||||
|
#define BCM63XX_NR_PARTS 2
|
||||||
|
|
||||||
|
static int bcm63xx_read_image_tag(struct mtd_info *master, loff_t offset,
|
||||||
|
struct bcm_tag *hdr)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
size_t retlen;
|
||||||
|
u32 computed_crc;
|
||||||
|
|
||||||
|
ret = mtd_read(master, offset, sizeof(*hdr), &retlen, (void *) hdr);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (retlen != sizeof(*hdr))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
computed_crc = crc32_le(IMAGETAG_CRC_START, (u8 *)hdr,
|
||||||
|
offsetof(struct bcm_tag, header_crc));
|
||||||
|
if (computed_crc == hdr->header_crc) {
|
||||||
|
STR_NULL_TERMINATE(hdr->board_id);
|
||||||
|
STR_NULL_TERMINATE(hdr->tag_version);
|
||||||
|
|
||||||
|
pr_info("CFE image tag found at 0x%llx with version %s, "
|
||||||
|
"board type %s\n", offset, hdr->tag_version,
|
||||||
|
hdr->board_id);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
pr_err("CFE image tag at 0x%llx CRC invalid "
|
||||||
|
"(expected %08x, actual %08x)\n",
|
||||||
|
offset, hdr->header_crc, computed_crc);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bcm63xx_parse_partitions(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct bcm_tag *hdr)
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
unsigned int flash_image_start;
|
||||||
|
unsigned int kernel_address;
|
||||||
|
unsigned int kernel_length;
|
||||||
|
size_t kernel_offset = 0, kernel_size = 0;
|
||||||
|
size_t rootfs_offset = 0, rootfs_size = 0;
|
||||||
|
int kernel_part, rootfs_part;
|
||||||
|
|
||||||
|
STR_NULL_TERMINATE(hdr->flash_image_start);
|
||||||
|
if (kstrtouint(hdr->flash_image_start, 10, &flash_image_start) ||
|
||||||
|
flash_image_start < BCM963XX_EXTENDED_SIZE) {
|
||||||
|
pr_err("invalid rootfs address: %*ph\n",
|
||||||
|
(int) sizeof(hdr->flash_image_start),
|
||||||
|
hdr->flash_image_start);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
STR_NULL_TERMINATE(hdr->kernel_address);
|
||||||
|
if (kstrtouint(hdr->kernel_address, 10, &kernel_address) ||
|
||||||
|
kernel_address < BCM963XX_EXTENDED_SIZE) {
|
||||||
|
pr_err("invalid kernel address: %*ph\n",
|
||||||
|
(int) sizeof(hdr->kernel_address), hdr->kernel_address);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
STR_NULL_TERMINATE(hdr->kernel_length);
|
||||||
|
if (kstrtouint(hdr->kernel_length, 10, &kernel_length) ||
|
||||||
|
!kernel_length) {
|
||||||
|
pr_err("invalid kernel length: %*ph\n",
|
||||||
|
(int) sizeof(hdr->kernel_length), hdr->kernel_length);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel_offset = kernel_address - BCM963XX_EXTENDED_SIZE -
|
||||||
|
mtdpart_get_offset(master);
|
||||||
|
kernel_size = kernel_length;
|
||||||
|
|
||||||
|
if (flash_image_start < kernel_address) {
|
||||||
|
/* rootfs first */
|
||||||
|
rootfs_part = 0;
|
||||||
|
kernel_part = 1;
|
||||||
|
rootfs_offset = flash_image_start - BCM963XX_EXTENDED_SIZE -
|
||||||
|
mtdpart_get_offset(master);
|
||||||
|
rootfs_size = kernel_offset - rootfs_offset;
|
||||||
|
} else {
|
||||||
|
/* kernel first */
|
||||||
|
kernel_part = 0;
|
||||||
|
rootfs_part = 1;
|
||||||
|
rootfs_offset = kernel_offset + kernel_size;
|
||||||
|
rootfs_size = master->size - rootfs_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mtd_check_rootfs_magic(master, rootfs_offset, NULL))
|
||||||
|
pr_warn("rootfs magic not found\n");
|
||||||
|
|
||||||
|
parts = kzalloc(BCM63XX_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[kernel_part].name = KERNEL_PART_NAME;
|
||||||
|
parts[kernel_part].offset = kernel_offset;
|
||||||
|
parts[kernel_part].size = kernel_size;
|
||||||
|
|
||||||
|
parts[rootfs_part].name = ROOTFS_PART_NAME;
|
||||||
|
parts[rootfs_part].offset = rootfs_offset;
|
||||||
|
parts[rootfs_part].size = rootfs_size;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return BCM63XX_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtdsplit_parse_bcm63xx(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct bcm_tag hdr;
|
||||||
|
loff_t offset;
|
||||||
|
|
||||||
|
if (mtd_type_is_nand(master))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* find bcm63xx_cfe image on erase block boundaries */
|
||||||
|
for (offset = 0; offset < master->size; offset += master->erasesize) {
|
||||||
|
if (!bcm63xx_read_image_tag(master, offset, (void *) &hdr))
|
||||||
|
return bcm63xx_parse_partitions(master, pparts,
|
||||||
|
(void *) &hdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_fit_of_match_table[] = {
|
||||||
|
{ .compatible = "brcm,bcm963xx-imagetag" },
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_bcm63xx_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "bcm63xx-fw",
|
||||||
|
.of_match_table = mtdsplit_fit_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_bcm63xx,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_bcm63xx_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_bcm63xx_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(mtdsplit_bcm63xx_init);
|
|
@ -0,0 +1,535 @@
|
||||||
|
/*
|
||||||
|
* MTD split for Broadcom Whole Flash Image
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define je16_to_cpu(x) ((x).v16)
|
||||||
|
#define je32_to_cpu(x) ((x).v32)
|
||||||
|
|
||||||
|
#include <linux/crc32.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/jffs2.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define char_to_num(c) ((c >= '0' && c <= '9') ? (c - '0') : (0))
|
||||||
|
|
||||||
|
#define BCM_WFI_PARTS 3
|
||||||
|
#define BCM_WFI_SPLIT_PARTS 2
|
||||||
|
|
||||||
|
#define CFERAM_NAME "cferam"
|
||||||
|
#define CFERAM_NAME_LEN (sizeof(CFERAM_NAME) - 1)
|
||||||
|
#define CFERAM_NAME_MAX_LEN 32
|
||||||
|
#define KERNEL_NAME "vmlinux.lz"
|
||||||
|
#define KERNEL_NAME_LEN (sizeof(KERNEL_NAME) - 1)
|
||||||
|
#define OPENWRT_NAME "1-openwrt"
|
||||||
|
#define OPENWRT_NAME_LEN (sizeof(OPENWRT_NAME) - 1)
|
||||||
|
|
||||||
|
#define UBI_MAGIC 0x55424923
|
||||||
|
|
||||||
|
#define CFE_MAGIC_PFX "cferam."
|
||||||
|
#define CFE_MAGIC_PFX_LEN (sizeof(CFE_MAGIC_PFX) - 1)
|
||||||
|
#define CFE_MAGIC "cferam.000"
|
||||||
|
#define CFE_MAGIC_LEN (sizeof(CFE_MAGIC) - 1)
|
||||||
|
#define SERCOMM_MAGIC_PFX "eRcOmM."
|
||||||
|
#define SERCOMM_MAGIC_PFX_LEN (sizeof(SERCOMM_MAGIC_PFX) - 1)
|
||||||
|
#define SERCOMM_MAGIC "eRcOmM.000"
|
||||||
|
#define SERCOMM_MAGIC_LEN (sizeof(SERCOMM_MAGIC) - 1)
|
||||||
|
|
||||||
|
#define PART_CFERAM "cferam"
|
||||||
|
#define PART_FIRMWARE "firmware"
|
||||||
|
#define PART_IMAGE_1 "img1"
|
||||||
|
#define PART_IMAGE_2 "img2"
|
||||||
|
|
||||||
|
static u32 jffs2_dirent_crc(struct jffs2_raw_dirent *node)
|
||||||
|
{
|
||||||
|
return crc32(0, node, sizeof(struct jffs2_raw_dirent) - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool jffs2_dirent_valid(struct jffs2_raw_dirent *node)
|
||||||
|
{
|
||||||
|
return ((je16_to_cpu(node->magic) == JFFS2_MAGIC_BITMASK) &&
|
||||||
|
(je16_to_cpu(node->nodetype) == JFFS2_NODETYPE_DIRENT) &&
|
||||||
|
je32_to_cpu(node->ino) &&
|
||||||
|
je32_to_cpu(node->node_crc) == jffs2_dirent_crc(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int jffs2_find_file(struct mtd_info *mtd, uint8_t *buf,
|
||||||
|
const char *name, size_t name_len,
|
||||||
|
loff_t *offs, loff_t size,
|
||||||
|
char **out_name, size_t *out_name_len)
|
||||||
|
{
|
||||||
|
const loff_t end = *offs + size;
|
||||||
|
struct jffs2_raw_dirent *node;
|
||||||
|
bool valid = false;
|
||||||
|
size_t retlen;
|
||||||
|
uint16_t magic;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
for (; *offs < end; *offs += mtd->erasesize) {
|
||||||
|
unsigned int block_offs = 0;
|
||||||
|
|
||||||
|
/* Skip CFE erased blocks */
|
||||||
|
rc = mtd_read(mtd, *offs, sizeof(magic), &retlen,
|
||||||
|
(void *) &magic);
|
||||||
|
if (rc || retlen != sizeof(magic)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip blocks not starting with JFFS2 magic */
|
||||||
|
if (magic != JFFS2_MAGIC_BITMASK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Read full block */
|
||||||
|
rc = mtd_read(mtd, *offs, mtd->erasesize, &retlen,
|
||||||
|
(void *) buf);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
if (retlen != mtd->erasesize)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
while (block_offs < mtd->erasesize) {
|
||||||
|
node = (struct jffs2_raw_dirent *) &buf[block_offs];
|
||||||
|
|
||||||
|
if (!jffs2_dirent_valid(node)) {
|
||||||
|
block_offs += 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!memcmp(node->name, OPENWRT_NAME,
|
||||||
|
OPENWRT_NAME_LEN)) {
|
||||||
|
valid = true;
|
||||||
|
} else if (!memcmp(node->name, name, name_len)) {
|
||||||
|
if (!valid)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (out_name)
|
||||||
|
*out_name = kstrndup(node->name,
|
||||||
|
node->nsize,
|
||||||
|
GFP_KERNEL);
|
||||||
|
|
||||||
|
if (out_name_len)
|
||||||
|
*out_name_len = node->nsize;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
block_offs += je32_to_cpu(node->totlen);
|
||||||
|
block_offs = (block_offs + 0x3) & ~0x3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ubifs_find(struct mtd_info *mtd, loff_t *offs, loff_t size)
|
||||||
|
{
|
||||||
|
const loff_t end = *offs + size;
|
||||||
|
uint32_t magic;
|
||||||
|
size_t retlen;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
for (; *offs < end; *offs += mtd->erasesize) {
|
||||||
|
rc = mtd_read(mtd, *offs, sizeof(magic), &retlen,
|
||||||
|
(unsigned char *) &magic);
|
||||||
|
if (rc || retlen != sizeof(magic))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (be32_to_cpu(magic) == UBI_MAGIC)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_bcm_wfi(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
uint8_t *buf, loff_t off, loff_t size, bool cfe_part)
|
||||||
|
{
|
||||||
|
struct device_node *mtd_node;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
loff_t cfe_off, kernel_off, rootfs_off;
|
||||||
|
unsigned int num_parts = BCM_WFI_PARTS, cur_part = 0;
|
||||||
|
const char *cferam_name = CFERAM_NAME;
|
||||||
|
size_t cferam_name_len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mtd_node = mtd_get_of_node(master);
|
||||||
|
if (mtd_node)
|
||||||
|
of_property_read_string(mtd_node, "brcm,cferam", &cferam_name);
|
||||||
|
|
||||||
|
cferam_name_len = strnlen(cferam_name, CFERAM_NAME_MAX_LEN);
|
||||||
|
if (cferam_name_len > 0)
|
||||||
|
cferam_name_len--;
|
||||||
|
|
||||||
|
if (cfe_part) {
|
||||||
|
num_parts++;
|
||||||
|
cfe_off = off;
|
||||||
|
|
||||||
|
ret = jffs2_find_file(master, buf, cferam_name,
|
||||||
|
cferam_name_len, &cfe_off,
|
||||||
|
size - (cfe_off - off), NULL, NULL);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
kernel_off = cfe_off + master->erasesize;
|
||||||
|
} else {
|
||||||
|
kernel_off = off;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = jffs2_find_file(master, buf, KERNEL_NAME, KERNEL_NAME_LEN,
|
||||||
|
&kernel_off, size - (kernel_off - off),
|
||||||
|
NULL, NULL);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
rootfs_off = kernel_off + master->erasesize;
|
||||||
|
ret = ubifs_find(master, &rootfs_off, size - (rootfs_off - off));
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
parts = kzalloc(num_parts * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (cfe_part) {
|
||||||
|
parts[cur_part].name = PART_CFERAM;
|
||||||
|
parts[cur_part].mask_flags = MTD_WRITEABLE;
|
||||||
|
parts[cur_part].offset = cfe_off;
|
||||||
|
parts[cur_part].size = kernel_off - cfe_off;
|
||||||
|
cur_part++;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[cur_part].name = PART_FIRMWARE;
|
||||||
|
parts[cur_part].offset = kernel_off;
|
||||||
|
parts[cur_part].size = size - (kernel_off - off);
|
||||||
|
cur_part++;
|
||||||
|
|
||||||
|
parts[cur_part].name = KERNEL_PART_NAME;
|
||||||
|
parts[cur_part].offset = kernel_off;
|
||||||
|
parts[cur_part].size = rootfs_off - kernel_off;
|
||||||
|
cur_part++;
|
||||||
|
|
||||||
|
parts[cur_part].name = UBI_PART_NAME;
|
||||||
|
parts[cur_part].offset = rootfs_off;
|
||||||
|
parts[cur_part].size = size - (rootfs_off - off);
|
||||||
|
cur_part++;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
|
||||||
|
return num_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtdsplit_parse_bcm_wfi(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct device_node *mtd_node;
|
||||||
|
bool cfe_part = true;
|
||||||
|
uint8_t *buf;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mtd_node = mtd_get_of_node(master);
|
||||||
|
if (!mtd_node)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
buf = kzalloc(master->erasesize, GFP_KERNEL);
|
||||||
|
if (!buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (of_property_read_bool(mtd_node, "brcm,no-cferam"))
|
||||||
|
cfe_part = false;
|
||||||
|
|
||||||
|
ret = parse_bcm_wfi(master, pparts, buf, 0, master->size, cfe_part);
|
||||||
|
|
||||||
|
kfree(buf);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_bcm_wfi_of_match[] = {
|
||||||
|
{ .compatible = "brcm,wfi" },
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_bcm_wfi_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "bcm-wfi-fw",
|
||||||
|
.of_match_table = mtdsplit_bcm_wfi_of_match,
|
||||||
|
.parse_fn = mtdsplit_parse_bcm_wfi,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int cferam_bootflag_value(const char *name, size_t name_len)
|
||||||
|
{
|
||||||
|
int rc = -ENOENT;
|
||||||
|
|
||||||
|
if (name &&
|
||||||
|
(name_len >= CFE_MAGIC_LEN) &&
|
||||||
|
!memcmp(name, CFE_MAGIC_PFX, CFE_MAGIC_PFX_LEN)) {
|
||||||
|
rc = char_to_num(name[CFE_MAGIC_PFX_LEN + 0]) * 100;
|
||||||
|
rc += char_to_num(name[CFE_MAGIC_PFX_LEN + 1]) * 10;
|
||||||
|
rc += char_to_num(name[CFE_MAGIC_PFX_LEN + 2]) * 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtdsplit_parse_bcm_wfi_split(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
loff_t cfe_off;
|
||||||
|
loff_t img1_off = 0;
|
||||||
|
loff_t img2_off = master->size / 2;
|
||||||
|
loff_t img1_size = (img2_off - img1_off);
|
||||||
|
loff_t img2_size = (master->size - img2_off);
|
||||||
|
loff_t active_off, inactive_off;
|
||||||
|
loff_t active_size, inactive_size;
|
||||||
|
const char *inactive_name;
|
||||||
|
uint8_t *buf;
|
||||||
|
char *cfe1_name = NULL, *cfe2_name = NULL;
|
||||||
|
size_t cfe1_size = 0, cfe2_size = 0;
|
||||||
|
int ret;
|
||||||
|
int bf1, bf2;
|
||||||
|
|
||||||
|
buf = kzalloc(master->erasesize, GFP_KERNEL);
|
||||||
|
if (!buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
cfe_off = img1_off;
|
||||||
|
ret = jffs2_find_file(master, buf, CFERAM_NAME, CFERAM_NAME_LEN,
|
||||||
|
&cfe_off, img1_size, &cfe1_name, &cfe1_size);
|
||||||
|
|
||||||
|
cfe_off = img2_off;
|
||||||
|
ret = jffs2_find_file(master, buf, CFERAM_NAME, CFERAM_NAME_LEN,
|
||||||
|
&cfe_off, img2_size, &cfe2_name, &cfe2_size);
|
||||||
|
|
||||||
|
bf1 = cferam_bootflag_value(cfe1_name, cfe1_size);
|
||||||
|
if (bf1 >= 0)
|
||||||
|
printk("cferam: bootflag1=%d\n", bf1);
|
||||||
|
|
||||||
|
bf2 = cferam_bootflag_value(cfe2_name, cfe2_size);
|
||||||
|
if (bf2 >= 0)
|
||||||
|
printk("cferam: bootflag2=%d\n", bf2);
|
||||||
|
|
||||||
|
kfree(cfe1_name);
|
||||||
|
kfree(cfe2_name);
|
||||||
|
|
||||||
|
if (bf1 >= bf2) {
|
||||||
|
active_off = img1_off;
|
||||||
|
active_size = img1_size;
|
||||||
|
inactive_off = img2_off;
|
||||||
|
inactive_size = img2_size;
|
||||||
|
inactive_name = PART_IMAGE_2;
|
||||||
|
} else {
|
||||||
|
active_off = img2_off;
|
||||||
|
active_size = img2_size;
|
||||||
|
inactive_off = img1_off;
|
||||||
|
inactive_size = img1_size;
|
||||||
|
inactive_name = PART_IMAGE_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = parse_bcm_wfi(master, pparts, buf, active_off, active_size, true);
|
||||||
|
|
||||||
|
kfree(buf);
|
||||||
|
|
||||||
|
if (ret > 0) {
|
||||||
|
parts = kzalloc((ret + 1) * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
memcpy(parts, *pparts, ret * sizeof(*parts));
|
||||||
|
kfree(*pparts);
|
||||||
|
|
||||||
|
parts[ret].name = inactive_name;
|
||||||
|
parts[ret].offset = inactive_off;
|
||||||
|
parts[ret].size = inactive_size;
|
||||||
|
ret++;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
} else {
|
||||||
|
parts = kzalloc(BCM_WFI_SPLIT_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
|
||||||
|
parts[0].name = PART_IMAGE_1;
|
||||||
|
parts[0].offset = img1_off;
|
||||||
|
parts[0].size = img1_size;
|
||||||
|
|
||||||
|
parts[1].name = PART_IMAGE_2;
|
||||||
|
parts[1].offset = img2_off;
|
||||||
|
parts[1].size = img2_size;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_bcm_wfi_split_of_match[] = {
|
||||||
|
{ .compatible = "brcm,wfi-split" },
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_bcm_wfi_split_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "bcm-wfi-split-fw",
|
||||||
|
.of_match_table = mtdsplit_bcm_wfi_split_of_match,
|
||||||
|
.parse_fn = mtdsplit_parse_bcm_wfi_split,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int sercomm_bootflag_value(struct mtd_info *mtd, uint8_t *buf)
|
||||||
|
{
|
||||||
|
size_t retlen;
|
||||||
|
loff_t offs;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
for (offs = 0; offs < mtd->size; offs += mtd->erasesize) {
|
||||||
|
rc = mtd_read(mtd, offs, SERCOMM_MAGIC_LEN, &retlen, buf);
|
||||||
|
if (rc || retlen != SERCOMM_MAGIC_LEN)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (memcmp(buf, SERCOMM_MAGIC_PFX, SERCOMM_MAGIC_PFX_LEN))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rc = char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 0]) * 100;
|
||||||
|
rc += char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 1]) * 10;
|
||||||
|
rc += char_to_num(buf[SERCOMM_MAGIC_PFX_LEN + 2]) * 1;
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtdsplit_parse_ser_wfi(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
struct mtd_info *mtd_bf1, *mtd_bf2;
|
||||||
|
loff_t img1_off = 0;
|
||||||
|
loff_t img2_off = master->size / 2;
|
||||||
|
loff_t img1_size = (img2_off - img1_off);
|
||||||
|
loff_t img2_size = (master->size - img2_off);
|
||||||
|
loff_t active_off, inactive_off;
|
||||||
|
loff_t active_size, inactive_size;
|
||||||
|
const char *inactive_name;
|
||||||
|
uint8_t *buf;
|
||||||
|
int bf1, bf2;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mtd_bf1 = get_mtd_device_nm("bootflag1");
|
||||||
|
if (IS_ERR(mtd_bf1))
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
mtd_bf2 = get_mtd_device_nm("bootflag2");
|
||||||
|
if (IS_ERR(mtd_bf2))
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
buf = kzalloc(master->erasesize, GFP_KERNEL);
|
||||||
|
if (!buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
bf1 = sercomm_bootflag_value(mtd_bf1, buf);
|
||||||
|
if (bf1 >= 0)
|
||||||
|
printk("sercomm: bootflag1=%d\n", bf1);
|
||||||
|
|
||||||
|
bf2 = sercomm_bootflag_value(mtd_bf2, buf);
|
||||||
|
if (bf2 >= 0)
|
||||||
|
printk("sercomm: bootflag2=%d\n", bf2);
|
||||||
|
|
||||||
|
if (bf1 == bf2 && bf2 >= 0) {
|
||||||
|
struct erase_info bf_erase;
|
||||||
|
|
||||||
|
bf2 = -ENOENT;
|
||||||
|
bf_erase.addr = 0;
|
||||||
|
bf_erase.len = mtd_bf2->size;
|
||||||
|
mtd_erase(mtd_bf2, &bf_erase);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bf1 >= bf2) {
|
||||||
|
active_off = img1_off;
|
||||||
|
active_size = img1_size;
|
||||||
|
inactive_off = img2_off;
|
||||||
|
inactive_size = img2_size;
|
||||||
|
inactive_name = PART_IMAGE_2;
|
||||||
|
} else {
|
||||||
|
active_off = img2_off;
|
||||||
|
active_size = img2_size;
|
||||||
|
inactive_off = img1_off;
|
||||||
|
inactive_size = img1_size;
|
||||||
|
inactive_name = PART_IMAGE_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = parse_bcm_wfi(master, pparts, buf, active_off, active_size, false);
|
||||||
|
|
||||||
|
kfree(buf);
|
||||||
|
|
||||||
|
if (ret > 0) {
|
||||||
|
parts = kzalloc((ret + 1) * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
memcpy(parts, *pparts, ret * sizeof(*parts));
|
||||||
|
kfree(*pparts);
|
||||||
|
|
||||||
|
parts[ret].name = inactive_name;
|
||||||
|
parts[ret].offset = inactive_off;
|
||||||
|
parts[ret].size = inactive_size;
|
||||||
|
ret++;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
} else {
|
||||||
|
parts = kzalloc(BCM_WFI_SPLIT_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
|
||||||
|
parts[0].name = PART_IMAGE_1;
|
||||||
|
parts[0].offset = img1_off;
|
||||||
|
parts[0].size = img1_size;
|
||||||
|
|
||||||
|
parts[1].name = PART_IMAGE_2;
|
||||||
|
parts[1].offset = img2_off;
|
||||||
|
parts[1].size = img2_size;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_ser_wfi_of_match[] = {
|
||||||
|
{ .compatible = "sercomm,wfi" },
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_ser_wfi_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "ser-wfi-fw",
|
||||||
|
.of_match_table = mtdsplit_ser_wfi_of_match,
|
||||||
|
.parse_fn = mtdsplit_parse_ser_wfi,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_bcm_wfi_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_bcm_wfi_parser);
|
||||||
|
register_mtd_parser(&mtdsplit_bcm_wfi_split_parser);
|
||||||
|
register_mtd_parser(&mtdsplit_ser_wfi_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(mtdsplit_bcm_wfi_init);
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012 John Crispin <blogic@openwrt.org>
|
||||||
|
* Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define BRNIMAGE_NR_PARTS 2
|
||||||
|
|
||||||
|
#define BRNIMAGE_ALIGN_BYTES 0x400
|
||||||
|
#define BRNIMAGE_FOOTER_SIZE 12
|
||||||
|
|
||||||
|
#define BRNIMAGE_MIN_OVERHEAD (BRNIMAGE_FOOTER_SIZE)
|
||||||
|
#define BRNIMAGE_MAX_OVERHEAD (BRNIMAGE_ALIGN_BYTES + BRNIMAGE_FOOTER_SIZE)
|
||||||
|
|
||||||
|
static int mtdsplit_parse_brnimage(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
uint32_t buf;
|
||||||
|
unsigned long rootfs_offset, rootfs_size, kernel_size;
|
||||||
|
size_t len;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
for (rootfs_offset = 0; rootfs_offset < master->size;
|
||||||
|
rootfs_offset += BRNIMAGE_ALIGN_BYTES) {
|
||||||
|
ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
|
||||||
|
if (!ret)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (rootfs_offset >= master->size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = mtd_read(master, rootfs_offset - BRNIMAGE_FOOTER_SIZE, 4, &len,
|
||||||
|
(void *)&buf);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (len != 4)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
kernel_size = le32_to_cpu(buf);
|
||||||
|
|
||||||
|
if (kernel_size > (rootfs_offset - BRNIMAGE_MIN_OVERHEAD))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (kernel_size < (rootfs_offset - BRNIMAGE_MAX_OVERHEAD))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The footer must be untouched as it contains the checksum of the
|
||||||
|
* original brnImage (kernel + squashfs)!
|
||||||
|
*/
|
||||||
|
rootfs_size = master->size - rootfs_offset - BRNIMAGE_FOOTER_SIZE;
|
||||||
|
|
||||||
|
parts = kzalloc(BRNIMAGE_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = kernel_size;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = rootfs_size;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return BRNIMAGE_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_brnimage_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "brnimage-fw",
|
||||||
|
.parse_fn = mtdsplit_parse_brnimage,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_brnimage_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_brnimage_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_brnimage_init);
|
|
@ -0,0 +1,90 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/jffs2.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define je16_to_cpu(x) ((x).v16)
|
||||||
|
#define je32_to_cpu(x) ((x).v32)
|
||||||
|
|
||||||
|
#define NR_PARTS 2
|
||||||
|
|
||||||
|
static int mtdsplit_cfe_bootfs_parse(struct mtd_info *mtd,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct jffs2_raw_dirent node;
|
||||||
|
enum mtdsplit_part_type type;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
size_t retlen;
|
||||||
|
size_t offset;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Don't parse backup partitions */
|
||||||
|
if (strcmp(mtd->name, "firmware"))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Find the end of JFFS2 bootfs partition */
|
||||||
|
offset = 0;
|
||||||
|
do {
|
||||||
|
err = mtd_read(mtd, offset, sizeof(node), &retlen, (void *)&node);
|
||||||
|
if (err || retlen != sizeof(node))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (je16_to_cpu(node.magic) != JFFS2_MAGIC_BITMASK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
offset += je32_to_cpu(node.totlen);
|
||||||
|
offset = (offset + 0x3) & ~0x3;
|
||||||
|
} while (offset < mtd->size);
|
||||||
|
|
||||||
|
/* Find rootfs partition that follows the bootfs */
|
||||||
|
err = mtd_find_rootfs_from(mtd, mtd->erasesize, mtd->size, &rootfs_offset, &type);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
parts = kzalloc(NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = "bootfs";
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = rootfs_offset;
|
||||||
|
|
||||||
|
if (type == MTDSPLIT_PART_TYPE_UBI)
|
||||||
|
parts[1].name = UBI_PART_NAME;
|
||||||
|
else
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = mtd->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
|
||||||
|
return NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_cfe_bootfs_of_match_table[] = {
|
||||||
|
{ .compatible = "brcm,bcm4908-firmware" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mtdsplit_cfe_bootfs_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_cfe_bootfs_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "cfe-bootfs",
|
||||||
|
.of_match_table = mtdsplit_cfe_bootfs_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_cfe_bootfs_parse,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_mtd_part_parser(mtdsplit_cfe_bootfs_parser);
|
|
@ -0,0 +1,287 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* MTD splitter for ELF loader firmware partitions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Sander Vanheule <sander@svanheule.net>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation; version 2.
|
||||||
|
*
|
||||||
|
* To parse the ELF kernel loader, a small ELF parser is used that can
|
||||||
|
* handle both ELF32 or ELF64 class loaders. The splitter assumes that the
|
||||||
|
* kernel is always located before the rootfs, whether it is embedded in the
|
||||||
|
* loader or not.
|
||||||
|
*
|
||||||
|
* The kernel image is preferably embedded inside the ELF loader, so the end
|
||||||
|
* of the loader equals the end of the kernel partition. This is due to the
|
||||||
|
* way mtd_find_rootfs_from searches for the the rootfs:
|
||||||
|
* - if the kernel image is embedded in the loader, the appended rootfs may
|
||||||
|
* follow the loader immediately, within the same erase block.
|
||||||
|
* - if the kernel image is not embedded in the loader, but placed at some
|
||||||
|
* offset behind the loader (OKLI-style loader), the rootfs must be
|
||||||
|
* aligned to an erase-block after the loader and kernel image.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define ELF_NR_PARTS 2
|
||||||
|
|
||||||
|
#define ELF_MAGIC 0x7f454c46 /* 0x7f E L F */
|
||||||
|
#define ELF_CLASS_32 1
|
||||||
|
#define ELF_CLASS_64 2
|
||||||
|
|
||||||
|
struct elf_header_ident {
|
||||||
|
uint32_t magic;
|
||||||
|
uint8_t class;
|
||||||
|
uint8_t data;
|
||||||
|
uint8_t version;
|
||||||
|
uint8_t osabi;
|
||||||
|
uint8_t abiversion;
|
||||||
|
uint8_t pad[7];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct elf_header_32 {
|
||||||
|
uint16_t type;
|
||||||
|
uint16_t machine;
|
||||||
|
uint32_t version;
|
||||||
|
uint32_t entry;
|
||||||
|
uint32_t phoff;
|
||||||
|
uint32_t shoff;
|
||||||
|
uint32_t flags;
|
||||||
|
uint16_t ehsize;
|
||||||
|
uint16_t phentsize;
|
||||||
|
uint16_t phnum;
|
||||||
|
uint16_t shentsize;
|
||||||
|
uint16_t shnum;
|
||||||
|
uint16_t shstrndx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct elf_header_64 {
|
||||||
|
uint16_t type;
|
||||||
|
uint16_t machine;
|
||||||
|
uint32_t version;
|
||||||
|
uint64_t entry;
|
||||||
|
uint64_t phoff;
|
||||||
|
uint64_t shoff;
|
||||||
|
uint32_t flags;
|
||||||
|
uint16_t ehsize;
|
||||||
|
uint16_t phentsize;
|
||||||
|
uint16_t phnum;
|
||||||
|
uint16_t shentsize;
|
||||||
|
uint16_t shnum;
|
||||||
|
uint16_t shstrndx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct elf_header {
|
||||||
|
struct elf_header_ident ident;
|
||||||
|
union {
|
||||||
|
struct elf_header_32 elf32;
|
||||||
|
struct elf_header_64 elf64;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct elf_program_header_32 {
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t offset;
|
||||||
|
uint32_t vaddr;
|
||||||
|
uint32_t paddr;
|
||||||
|
uint32_t filesize;
|
||||||
|
uint32_t memsize;
|
||||||
|
uint32_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct elf_program_header_64 {
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t flags;
|
||||||
|
uint64_t offset;
|
||||||
|
uint64_t vaddr;
|
||||||
|
uint64_t paddr;
|
||||||
|
uint64_t filesize;
|
||||||
|
uint64_t memsize;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static int mtdsplit_elf_read_mtd(struct mtd_info *mtd, size_t offset,
|
||||||
|
uint8_t *dst, size_t len)
|
||||||
|
{
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mtd_read(mtd, offset, len, &retlen, dst);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug("read error in \"%s\"\n", mtd->name);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retlen != len) {
|
||||||
|
pr_debug("short read in \"%s\"\n", mtd->name);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int elf32_determine_size(struct mtd_info *mtd, struct elf_header *hdr,
|
||||||
|
size_t *size)
|
||||||
|
{
|
||||||
|
struct elf_header_32 *hdr32 = &(hdr->elf32);
|
||||||
|
int err;
|
||||||
|
size_t section_end, ph_table_end, ph_entry;
|
||||||
|
struct elf_program_header_32 ph;
|
||||||
|
|
||||||
|
*size = 0;
|
||||||
|
|
||||||
|
if (hdr32->shoff > 0) {
|
||||||
|
*size = hdr32->shoff + hdr32->shentsize * hdr32->shnum;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph_entry = hdr32->phoff;
|
||||||
|
ph_table_end = hdr32->phoff + hdr32->phentsize * hdr32->phnum;
|
||||||
|
|
||||||
|
while (ph_entry < ph_table_end) {
|
||||||
|
err = mtdsplit_elf_read_mtd(mtd, ph_entry, (uint8_t *)(&ph),
|
||||||
|
sizeof(ph));
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
section_end = ph.offset + ph.filesize;
|
||||||
|
if (section_end > *size)
|
||||||
|
*size = section_end;
|
||||||
|
|
||||||
|
ph_entry += hdr32->phentsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int elf64_determine_size(struct mtd_info *mtd, struct elf_header *hdr,
|
||||||
|
size_t *size)
|
||||||
|
{
|
||||||
|
struct elf_header_64 *hdr64 = &(hdr->elf64);
|
||||||
|
int err;
|
||||||
|
size_t section_end, ph_table_end, ph_entry;
|
||||||
|
struct elf_program_header_64 ph;
|
||||||
|
|
||||||
|
*size = 0;
|
||||||
|
|
||||||
|
if (hdr64->shoff > 0) {
|
||||||
|
*size = hdr64->shoff + hdr64->shentsize * hdr64->shnum;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph_entry = hdr64->phoff;
|
||||||
|
ph_table_end = hdr64->phoff + hdr64->phentsize * hdr64->phnum;
|
||||||
|
|
||||||
|
while (ph_entry < ph_table_end) {
|
||||||
|
err = mtdsplit_elf_read_mtd(mtd, ph_entry, (uint8_t *)(&ph),
|
||||||
|
sizeof(ph));
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
section_end = ph.offset + ph.filesize;
|
||||||
|
if (section_end > *size)
|
||||||
|
*size = section_end;
|
||||||
|
|
||||||
|
ph_entry += hdr64->phentsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtdsplit_parse_elf(struct mtd_info *mtd,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct elf_header hdr;
|
||||||
|
size_t loader_size, rootfs_offset;
|
||||||
|
enum mtdsplit_part_type type;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = mtdsplit_elf_read_mtd(mtd, 0, (uint8_t *)&hdr, sizeof(hdr));
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (be32_to_cpu(hdr.ident.magic) != ELF_MAGIC) {
|
||||||
|
pr_debug("invalid ELF magic %08x\n",
|
||||||
|
be32_to_cpu(hdr.ident.magic));
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (hdr.ident.class) {
|
||||||
|
case ELF_CLASS_32:
|
||||||
|
err = elf32_determine_size(mtd, &hdr, &loader_size);
|
||||||
|
break;
|
||||||
|
case ELF_CLASS_64:
|
||||||
|
err = elf64_determine_size(mtd, &hdr, &loader_size);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pr_debug("invalid ELF class %i\n", hdr.ident.class);
|
||||||
|
err = -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = mtd_find_rootfs_from(mtd, loader_size, mtd->size,
|
||||||
|
&rootfs_offset, &type);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (rootfs_offset == mtd->size) {
|
||||||
|
pr_debug("no rootfs found in \"%s\"\n", mtd->name);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = kzalloc(ELF_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = rootfs_offset;
|
||||||
|
|
||||||
|
if (type == MTDSPLIT_PART_TYPE_UBI)
|
||||||
|
parts[1].name = UBI_PART_NAME;
|
||||||
|
else
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = mtd->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return ELF_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_elf_of_match_table[] = {
|
||||||
|
{ .compatible = "openwrt,elf" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mtdsplit_elf_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_elf_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "elf-loader-fw",
|
||||||
|
.of_match_table = mtdsplit_elf_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_elf,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_elf_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_elf_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_elf_init);
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012 John Crispin <blogic@openwrt.org>
|
||||||
|
* Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define EVA_NR_PARTS 2
|
||||||
|
#define EVA_MAGIC 0xfeed1281
|
||||||
|
#define EVA_FOOTER_SIZE 0x18
|
||||||
|
#define EVA_DUMMY_SQUASHFS_SIZE 0x100
|
||||||
|
|
||||||
|
struct eva_image_header {
|
||||||
|
uint32_t magic;
|
||||||
|
uint32_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mtdsplit_parse_eva(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
struct eva_image_header hdr;
|
||||||
|
size_t retlen;
|
||||||
|
unsigned long kernel_size, rootfs_offset;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = mtd_read(master, 0, sizeof(hdr), &retlen, (void *) &hdr);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (retlen != sizeof(hdr))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (le32_to_cpu(hdr.magic) != EVA_MAGIC)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
kernel_size = le32_to_cpu(hdr.size) + EVA_FOOTER_SIZE;
|
||||||
|
|
||||||
|
/* rootfs starts at the next 0x10000 boundary: */
|
||||||
|
rootfs_offset = round_up(kernel_size, 0x10000);
|
||||||
|
|
||||||
|
/* skip the dummy EVA squashfs partition (with wrong endianness): */
|
||||||
|
rootfs_offset += EVA_DUMMY_SQUASHFS_SIZE;
|
||||||
|
|
||||||
|
if (rootfs_offset >= master->size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
err = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
parts = kzalloc(EVA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = kernel_size;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = master->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return EVA_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_eva_of_match_table[] = {
|
||||||
|
{ .compatible = "avm,eva-firmware" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_eva_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "eva-fw",
|
||||||
|
.of_match_table = mtdsplit_eva_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_eva,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_eva_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_eva_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_eva_init);
|
|
@ -0,0 +1,365 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015 The Linux Foundation
|
||||||
|
* Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/libfdt.h>
|
||||||
|
#include <linux/of_fdt.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
// string macros from git://git.denx.de/u-boot.git/include/image.h
|
||||||
|
|
||||||
|
#define FIT_IMAGES_PATH "/images"
|
||||||
|
#define FIT_DATA_PROP "data"
|
||||||
|
#define FIT_DATA_POSITION_PROP "data-position"
|
||||||
|
#define FIT_DATA_OFFSET_PROP "data-offset"
|
||||||
|
#define FIT_DATA_SIZE_PROP "data-size"
|
||||||
|
|
||||||
|
// functions from git://git.denx.de/u-boot.git/common/image-fit.c
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fit_image_get_data - get data property and its size for a given component image node
|
||||||
|
* @fit: pointer to the FIT format image header
|
||||||
|
* @noffset: component image node offset
|
||||||
|
* @data: double pointer to void, will hold data property's data address
|
||||||
|
* @size: pointer to size_t, will hold data property's data size
|
||||||
|
*
|
||||||
|
* fit_image_get_data() finds data property in a given component image node.
|
||||||
|
* If the property is found its data start address and size are returned to
|
||||||
|
* the caller.
|
||||||
|
*
|
||||||
|
* returns:
|
||||||
|
* 0, on success
|
||||||
|
* -1, on failure
|
||||||
|
*/
|
||||||
|
static int fit_image_get_data(const void *fit, int noffset,
|
||||||
|
const void **data, size_t *size)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
|
||||||
|
*data = fdt_getprop(fit, noffset, FIT_DATA_PROP, &len);
|
||||||
|
if (*data == NULL) {
|
||||||
|
*size = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*size = len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get 'data-offset' property from a given image node.
|
||||||
|
*
|
||||||
|
* @fit: pointer to the FIT image header
|
||||||
|
* @noffset: component image node offset
|
||||||
|
* @data_offset: holds the data-offset property
|
||||||
|
*
|
||||||
|
* returns:
|
||||||
|
* 0, on success
|
||||||
|
* -ENOENT if the property could not be found
|
||||||
|
*/
|
||||||
|
static int fit_image_get_data_offset(const void *fit, int noffset, int *data_offset)
|
||||||
|
{
|
||||||
|
const fdt32_t *val;
|
||||||
|
|
||||||
|
val = fdt_getprop(fit, noffset, FIT_DATA_OFFSET_PROP, NULL);
|
||||||
|
if (!val)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
*data_offset = fdt32_to_cpu(*val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get 'data-position' property from a given image node.
|
||||||
|
*
|
||||||
|
* @fit: pointer to the FIT image header
|
||||||
|
* @noffset: component image node offset
|
||||||
|
* @data_position: holds the data-position property
|
||||||
|
*
|
||||||
|
* returns:
|
||||||
|
* 0, on success
|
||||||
|
* -ENOENT if the property could not be found
|
||||||
|
*/
|
||||||
|
static int fit_image_get_data_position(const void *fit, int noffset,
|
||||||
|
int *data_position)
|
||||||
|
{
|
||||||
|
const fdt32_t *val;
|
||||||
|
|
||||||
|
val = fdt_getprop(fit, noffset, FIT_DATA_POSITION_PROP, NULL);
|
||||||
|
if (!val)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
*data_position = fdt32_to_cpu(*val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get 'data-size' property from a given image node.
|
||||||
|
*
|
||||||
|
* @fit: pointer to the FIT image header
|
||||||
|
* @noffset: component image node offset
|
||||||
|
* @data_size: holds the data-size property
|
||||||
|
*
|
||||||
|
* returns:
|
||||||
|
* 0, on success
|
||||||
|
* -ENOENT if the property could not be found
|
||||||
|
*/
|
||||||
|
static int fit_image_get_data_size(const void *fit, int noffset, int *data_size)
|
||||||
|
{
|
||||||
|
const fdt32_t *val;
|
||||||
|
|
||||||
|
val = fdt_getprop(fit, noffset, FIT_DATA_SIZE_PROP, NULL);
|
||||||
|
if (!val)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
*data_size = fdt32_to_cpu(*val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fit_image_get_data_and_size - get data and its size including
|
||||||
|
* both embedded and external data
|
||||||
|
* @fit: pointer to the FIT format image header
|
||||||
|
* @noffset: component image node offset
|
||||||
|
* @data: double pointer to void, will hold data property's data address
|
||||||
|
* @size: pointer to size_t, will hold data property's data size
|
||||||
|
*
|
||||||
|
* fit_image_get_data_and_size() finds data and its size including
|
||||||
|
* both embedded and external data. If the property is found
|
||||||
|
* its data start address and size are returned to the caller.
|
||||||
|
*
|
||||||
|
* returns:
|
||||||
|
* 0, on success
|
||||||
|
* otherwise, on failure
|
||||||
|
*/
|
||||||
|
static int fit_image_get_data_and_size(const void *fit, int noffset,
|
||||||
|
const void **data, size_t *size)
|
||||||
|
{
|
||||||
|
bool external_data = false;
|
||||||
|
int offset;
|
||||||
|
int len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!fit_image_get_data_position(fit, noffset, &offset)) {
|
||||||
|
external_data = true;
|
||||||
|
} else if (!fit_image_get_data_offset(fit, noffset, &offset)) {
|
||||||
|
external_data = true;
|
||||||
|
/*
|
||||||
|
* For FIT with external data, figure out where
|
||||||
|
* the external images start. This is the base
|
||||||
|
* for the data-offset properties in each image.
|
||||||
|
*/
|
||||||
|
offset += ((fdt_totalsize(fit) + 3) & ~3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (external_data) {
|
||||||
|
ret = fit_image_get_data_size(fit, noffset, &len);
|
||||||
|
if (!ret) {
|
||||||
|
*data = fit + offset;
|
||||||
|
*size = len;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret = fit_image_get_data(fit, noffset, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtdsplit_fit_parse(struct mtd_info *mtd,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct device_node *np = mtd_get_of_node(mtd);
|
||||||
|
const char *cmdline_match = NULL;
|
||||||
|
struct fdt_header hdr;
|
||||||
|
size_t hdr_len, retlen;
|
||||||
|
size_t offset;
|
||||||
|
u32 offset_start = 0;
|
||||||
|
size_t fit_offset, fit_size;
|
||||||
|
size_t rootfs_offset, rootfs_size;
|
||||||
|
size_t data_size, img_total, max_size = 0;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
int ret, ndepth, noffset, images_noffset;
|
||||||
|
const void *img_data;
|
||||||
|
void *fit;
|
||||||
|
|
||||||
|
of_property_read_string(np, "openwrt,cmdline-match", &cmdline_match);
|
||||||
|
if (cmdline_match && !strstr(saved_command_line, cmdline_match))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
of_property_read_u32(np, "openwrt,fit-offset", &offset_start);
|
||||||
|
|
||||||
|
hdr_len = sizeof(struct fdt_header);
|
||||||
|
|
||||||
|
/* Parse the MTD device & search for the FIT image location */
|
||||||
|
for(offset = 0; offset + hdr_len <= mtd->size; offset += mtd->erasesize) {
|
||||||
|
ret = mtd_read(mtd, offset + offset_start, hdr_len, &retlen, (void*) &hdr);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("read error in \"%s\" at offset 0x%llx\n",
|
||||||
|
mtd->name, (unsigned long long) offset);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retlen != hdr_len) {
|
||||||
|
pr_err("short read in \"%s\"\n", mtd->name);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check the magic - see if this is a FIT image */
|
||||||
|
if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) {
|
||||||
|
pr_debug("no valid FIT image found in \"%s\" at offset %llx\n",
|
||||||
|
mtd->name, (unsigned long long) offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We found a FIT image. Let's keep going */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fit_offset = offset;
|
||||||
|
fit_size = be32_to_cpu(hdr.totalsize);
|
||||||
|
|
||||||
|
if (fit_size == 0) {
|
||||||
|
pr_err("FIT image in \"%s\" at offset %llx has null size\n",
|
||||||
|
mtd->name, (unsigned long long) fit_offset);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Classic uImage.FIT has all data embedded into the FDT
|
||||||
|
* data structure. Hence the total size of the image equals
|
||||||
|
* the total size of the FDT structure.
|
||||||
|
* Modern uImage.FIT may have only references to data in FDT,
|
||||||
|
* hence we need to parse FDT structure to find the end of the
|
||||||
|
* last external data refernced.
|
||||||
|
*/
|
||||||
|
if (fit_size > 0x1000) {
|
||||||
|
enum mtdsplit_part_type type;
|
||||||
|
|
||||||
|
/* Search for the rootfs partition after the FIT image */
|
||||||
|
ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size + offset_start, mtd->size,
|
||||||
|
&rootfs_offset, &type);
|
||||||
|
if (ret) {
|
||||||
|
pr_info("no rootfs found after FIT image in \"%s\"\n",
|
||||||
|
mtd->name);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs_size = mtd->size - rootfs_offset;
|
||||||
|
|
||||||
|
parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = fit_offset;
|
||||||
|
parts[0].size = mtd_roundup_to_eb(fit_size + offset_start, mtd);
|
||||||
|
|
||||||
|
if (type == MTDSPLIT_PART_TYPE_UBI)
|
||||||
|
parts[1].name = UBI_PART_NAME;
|
||||||
|
else
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = rootfs_size;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
} else {
|
||||||
|
/* Search for rootfs_data after FIT external data */
|
||||||
|
fit = kzalloc(fit_size, GFP_KERNEL);
|
||||||
|
ret = mtd_read(mtd, offset, fit_size + offset_start, &retlen, fit);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("read error in \"%s\" at offset 0x%llx\n",
|
||||||
|
mtd->name, (unsigned long long) offset);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
|
||||||
|
if (images_noffset < 0) {
|
||||||
|
pr_err("Can't find images parent node '%s' (%s)\n",
|
||||||
|
FIT_IMAGES_PATH, fdt_strerror(images_noffset));
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ndepth = 0,
|
||||||
|
noffset = fdt_next_node(fit, images_noffset, &ndepth);
|
||||||
|
(noffset >= 0) && (ndepth > 0);
|
||||||
|
noffset = fdt_next_node(fit, noffset, &ndepth)) {
|
||||||
|
if (ndepth == 1) {
|
||||||
|
ret = fit_image_get_data_and_size(fit, noffset, &img_data, &data_size);
|
||||||
|
if (ret)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
img_total = data_size + (img_data - fit);
|
||||||
|
|
||||||
|
max_size = (max_size > img_total) ? max_size : img_total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = kzalloc(sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = ROOTFS_SPLIT_NAME;
|
||||||
|
parts[0].offset = fit_offset + mtd_roundup_to_eb(max_size, mtd);
|
||||||
|
parts[0].size = mtd->size - parts[0].offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
|
||||||
|
kfree(fit);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_fit_of_match_table[] = {
|
||||||
|
{ .compatible = "denx,fit" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser uimage_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "fit-fw",
|
||||||
|
.of_match_table = mtdsplit_fit_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_fit_parse,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
* Init
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
static int __init mtdsplit_fit_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&uimage_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(mtdsplit_fit_init);
|
|
@ -0,0 +1,170 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Some devices made by H3C use a "VFS" filesystem to store firmware images.
|
||||||
|
* This parses the start of the filesystem to read the length of the first
|
||||||
|
* file (the kernel image). It then searches for the rootfs after the end of
|
||||||
|
* the file data. This driver assumes that the filesystem was generated by
|
||||||
|
* mkh3cvfs, and only works if the filesystem matches the expected layout,
|
||||||
|
* which includes the file name of the kernel image.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define VFS_ERASEBLOCK_SIZE 0x10000
|
||||||
|
#define VFS_BLOCK_SIZE 0x400
|
||||||
|
#define VFS_BLOCKS_PER_ERASEBLOCK (VFS_ERASEBLOCK_SIZE / VFS_BLOCK_SIZE)
|
||||||
|
|
||||||
|
#define FORMAT_FLAG_OFFSET 0x0
|
||||||
|
|
||||||
|
#define FORMAT_FLAG (VFS_ERASEBLOCK_SIZE << 12 | VFS_BLOCK_SIZE)
|
||||||
|
|
||||||
|
#define FILE_ENTRY_OFFSET 0x800
|
||||||
|
|
||||||
|
#define FILE_ENTRY_FLAGS 0x3f
|
||||||
|
#define FILE_ENTRY_PARENT_BLOCK 0
|
||||||
|
#define FILE_ENTRY_PARENT_INDEX 0
|
||||||
|
#define FILE_ENTRY_DATA_BLOCK 2
|
||||||
|
#define FILE_ENTRY_NAME "openwrt-kernel.bin"
|
||||||
|
|
||||||
|
#define NR_PARTS 2
|
||||||
|
|
||||||
|
struct file_entry {
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
uint8_t res0[5];
|
||||||
|
|
||||||
|
uint16_t year;
|
||||||
|
uint8_t month;
|
||||||
|
uint8_t day;
|
||||||
|
uint8_t hour;
|
||||||
|
uint8_t minute;
|
||||||
|
uint8_t second;
|
||||||
|
|
||||||
|
uint8_t res1[3];
|
||||||
|
|
||||||
|
uint32_t length;
|
||||||
|
|
||||||
|
uint32_t parent_block;
|
||||||
|
uint16_t parent_index;
|
||||||
|
|
||||||
|
uint8_t res2[2];
|
||||||
|
|
||||||
|
uint32_t data_block;
|
||||||
|
|
||||||
|
char name[96];
|
||||||
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
|
static inline size_t block_offset(int block)
|
||||||
|
{
|
||||||
|
return VFS_ERASEBLOCK_SIZE * (block / (VFS_BLOCKS_PER_ERASEBLOCK-1))
|
||||||
|
+ VFS_BLOCK_SIZE * (1 + (block % (VFS_BLOCKS_PER_ERASEBLOCK-1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int block_count(size_t size)
|
||||||
|
{
|
||||||
|
return (size + VFS_BLOCK_SIZE - 1) / VFS_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtdsplit_h3c_vfs_parse(struct mtd_info *mtd,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
uint32_t format_flag;
|
||||||
|
struct file_entry file_entry;
|
||||||
|
size_t retlen;
|
||||||
|
int err;
|
||||||
|
size_t kernel_size;
|
||||||
|
size_t expected_offset;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
|
||||||
|
if (mtd->erasesize != VFS_ERASEBLOCK_SIZE)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Check format flag */
|
||||||
|
err = mtd_read(mtd, FORMAT_FLAG_OFFSET, sizeof(format_flag), &retlen,
|
||||||
|
(void *) &format_flag);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (retlen != sizeof(format_flag))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (format_flag != FORMAT_FLAG)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Check file entry */
|
||||||
|
err = mtd_read(mtd, FILE_ENTRY_OFFSET, sizeof(file_entry), &retlen,
|
||||||
|
(void *) &file_entry);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (retlen != sizeof(file_entry))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (file_entry.flags != FILE_ENTRY_FLAGS)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (file_entry.parent_block != FILE_ENTRY_PARENT_BLOCK)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (file_entry.parent_index != FILE_ENTRY_PARENT_INDEX)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (file_entry.data_block != FILE_ENTRY_DATA_BLOCK)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (strncmp(file_entry.name, FILE_ENTRY_NAME, sizeof(file_entry.name)) != 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Find rootfs offset */
|
||||||
|
kernel_size = block_offset(file_entry.data_block +
|
||||||
|
block_count(file_entry.length) - 1) +
|
||||||
|
VFS_BLOCK_SIZE;
|
||||||
|
|
||||||
|
expected_offset = mtd_roundup_to_eb(kernel_size, mtd);
|
||||||
|
|
||||||
|
err = mtd_find_rootfs_from(mtd, expected_offset, mtd->size,
|
||||||
|
&rootfs_offset, NULL);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
parts = kzalloc(NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = rootfs_offset;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = mtd->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_h3c_vfs_of_match_table[] = {
|
||||||
|
{ .compatible = "h3c,vfs-firmware" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mtdsplit_h3c_vfs_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_h3c_vfs_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "h3c-vfs",
|
||||||
|
.of_match_table = mtdsplit_h3c_vfs_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_h3c_vfs_parse,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_mtd_part_parser(mtdsplit_h3c_vfs_parser);
|
|
@ -0,0 +1,284 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com>
|
||||||
|
*
|
||||||
|
* Based on: mtdsplit_uimage.c
|
||||||
|
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE )
|
||||||
|
|
||||||
|
#define STAG_SIZE 16
|
||||||
|
#define STAG_ID 0x04
|
||||||
|
#define STAG_MAGIC 0x2B24
|
||||||
|
|
||||||
|
#define SCH2_SIZE 40
|
||||||
|
#define SCH2_MAGIC 0x2124
|
||||||
|
#define SCH2_VER 0x02
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Jboot image header,
|
||||||
|
* all data in little endian.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct jimage_header //stag + sch2 jboot joined headers
|
||||||
|
{
|
||||||
|
uint8_t stag_cmark; // in factory 0xFF , in sysupgrade must be the same as stag_id
|
||||||
|
uint8_t stag_id; // 0x04
|
||||||
|
uint16_t stag_magic; //magic 0x2B24
|
||||||
|
uint32_t stag_time_stamp; // timestamp calculated in jboot way
|
||||||
|
uint32_t stag_image_length; // lentgh of kernel + sch2 header
|
||||||
|
uint16_t stag_image_checksum; // negated jboot_checksum of sch2 + kernel
|
||||||
|
uint16_t stag_tag_checksum; // negated jboot_checksum of stag header data
|
||||||
|
uint16_t sch2_magic; // magic 0x2124
|
||||||
|
uint8_t sch2_cp_type; // 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma
|
||||||
|
uint8_t sch2_version; // 0x02 for sch2
|
||||||
|
uint32_t sch2_ram_addr; // ram entry address
|
||||||
|
uint32_t sch2_image_len; // kernel image length
|
||||||
|
uint32_t sch2_image_crc32; // kernel image crc
|
||||||
|
uint32_t sch2_start_addr; // ram start address
|
||||||
|
uint32_t sch2_rootfs_addr; // rootfs flash address
|
||||||
|
uint32_t sch2_rootfs_len; // rootfls length
|
||||||
|
uint32_t sch2_rootfs_crc32; // rootfs crc32
|
||||||
|
uint32_t sch2_header_crc32; // sch2 header crc32, durring calculation this area is replaced by zero
|
||||||
|
uint16_t sch2_header_length; // sch2 header length: 0x28
|
||||||
|
uint16_t sch2_cmd_line_length; // cmd line length, known zeros
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
read_jimage_header(struct mtd_info *mtd, size_t offset, u_char *buf,
|
||||||
|
size_t header_len)
|
||||||
|
{
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mtd_read(mtd, offset, header_len, &retlen, buf);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug("read error in \"%s\"\n", mtd->name);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retlen != header_len) {
|
||||||
|
pr_debug("short read in \"%s\"\n", mtd->name);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __mtdsplit_parse_jimage - scan partition and create kernel + rootfs parts
|
||||||
|
*
|
||||||
|
* @find_header: function to call for a block of data that will return offset
|
||||||
|
* of a valid jImage header if found
|
||||||
|
*/
|
||||||
|
static int __mtdsplit_parse_jimage(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data,
|
||||||
|
ssize_t (*find_header)(u_char *buf, size_t len))
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
u_char *buf;
|
||||||
|
int nr_parts;
|
||||||
|
size_t offset;
|
||||||
|
size_t jimage_offset;
|
||||||
|
size_t jimage_size = 0;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
size_t rootfs_size = 0;
|
||||||
|
int jimage_part, rf_part;
|
||||||
|
int ret;
|
||||||
|
enum mtdsplit_part_type type;
|
||||||
|
|
||||||
|
nr_parts = 2;
|
||||||
|
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
buf = vmalloc(MAX_HEADER_LEN);
|
||||||
|
if (!buf) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err_free_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find jImage on erase block boundaries */
|
||||||
|
for (offset = 0; offset < master->size; offset += master->erasesize) {
|
||||||
|
struct jimage_header *header;
|
||||||
|
|
||||||
|
jimage_size = 0;
|
||||||
|
|
||||||
|
ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN);
|
||||||
|
if (ret)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ret = find_header(buf, MAX_HEADER_LEN);
|
||||||
|
if (ret < 0) {
|
||||||
|
pr_debug("no valid jImage found in \"%s\" at offset %llx\n",
|
||||||
|
master->name, (unsigned long long) offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
header = (struct jimage_header *)(buf + ret);
|
||||||
|
|
||||||
|
jimage_size = sizeof(*header) + header->sch2_image_len + ret;
|
||||||
|
if ((offset + jimage_size) > master->size) {
|
||||||
|
pr_debug("jImage exceeds MTD device \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jimage_size == 0) {
|
||||||
|
pr_debug("no jImage found in \"%s\"\n", master->name);
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_free_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
jimage_offset = offset;
|
||||||
|
|
||||||
|
if (jimage_offset == 0) {
|
||||||
|
jimage_part = 0;
|
||||||
|
rf_part = 1;
|
||||||
|
|
||||||
|
/* find the roots after the jImage */
|
||||||
|
ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size,
|
||||||
|
master->size, &rootfs_offset, &type);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug("no rootfs after jImage in \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
goto err_free_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs_size = master->size - rootfs_offset;
|
||||||
|
jimage_size = rootfs_offset - jimage_offset;
|
||||||
|
} else {
|
||||||
|
rf_part = 0;
|
||||||
|
jimage_part = 1;
|
||||||
|
|
||||||
|
/* check rootfs presence at offset 0 */
|
||||||
|
ret = mtd_check_rootfs_magic(master, 0, &type);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug("no rootfs before jImage in \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
goto err_free_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs_offset = 0;
|
||||||
|
rootfs_size = jimage_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootfs_size == 0) {
|
||||||
|
pr_debug("no rootfs found in \"%s\"\n", master->name);
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_free_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[jimage_part].name = KERNEL_PART_NAME;
|
||||||
|
parts[jimage_part].offset = jimage_offset;
|
||||||
|
parts[jimage_part].size = jimage_size;
|
||||||
|
|
||||||
|
if (type == MTDSPLIT_PART_TYPE_UBI)
|
||||||
|
parts[rf_part].name = UBI_PART_NAME;
|
||||||
|
else
|
||||||
|
parts[rf_part].name = ROOTFS_PART_NAME;
|
||||||
|
parts[rf_part].offset = rootfs_offset;
|
||||||
|
parts[rf_part].size = rootfs_size;
|
||||||
|
|
||||||
|
vfree(buf);
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return nr_parts;
|
||||||
|
|
||||||
|
err_free_buf:
|
||||||
|
vfree(buf);
|
||||||
|
|
||||||
|
err_free_parts:
|
||||||
|
kfree(parts);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t jimage_verify_default(u_char *buf, size_t len)
|
||||||
|
{
|
||||||
|
struct jimage_header *header = (struct jimage_header *)buf;
|
||||||
|
|
||||||
|
/* default sanity checks */
|
||||||
|
if (header->stag_magic != STAG_MAGIC) {
|
||||||
|
pr_debug("invalid jImage stag header magic: %04x\n",
|
||||||
|
header->stag_magic);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
if (header->sch2_magic != SCH2_MAGIC) {
|
||||||
|
pr_debug("invalid jImage sch2 header magic: %04x\n",
|
||||||
|
header->stag_magic);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
if (header->stag_cmark != header->stag_id) {
|
||||||
|
pr_debug("invalid jImage stag header cmark: %02x\n",
|
||||||
|
header->stag_magic);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
if (header->stag_id != STAG_ID) {
|
||||||
|
pr_debug("invalid jImage stag header id: %02x\n",
|
||||||
|
header->stag_magic);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
if (header->sch2_version != SCH2_VER) {
|
||||||
|
pr_debug("invalid jImage sch2 header version: %02x\n",
|
||||||
|
header->stag_magic);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtdsplit_jimage_parse_generic(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
return __mtdsplit_parse_jimage(master, pparts, data,
|
||||||
|
jimage_verify_default);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_jimage_of_match_table[] = {
|
||||||
|
{ .compatible = "amit,jimage" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser jimage_generic_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "jimage-fw",
|
||||||
|
.of_match_table = mtdsplit_jimage_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_jimage_parse_generic,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
* Init
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
static int __init mtdsplit_jimage_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&jimage_generic_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(mtdsplit_jimage_init);
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include <asm/unaligned.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define LZMA_NR_PARTS 2
|
||||||
|
#define LZMA_PROPERTIES_SIZE 5
|
||||||
|
|
||||||
|
struct lzma_header {
|
||||||
|
u8 props[LZMA_PROPERTIES_SIZE];
|
||||||
|
u8 size_low[4];
|
||||||
|
u8 size_high[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mtdsplit_parse_lzma(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct lzma_header hdr;
|
||||||
|
size_t hdr_len, retlen;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
u32 t;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
hdr_len = sizeof(hdr);
|
||||||
|
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (retlen != hdr_len)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
/* verify LZMA properties */
|
||||||
|
if (hdr.props[0] >= (9 * 5 * 5))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
t = get_unaligned_le32(&hdr.props[1]);
|
||||||
|
if (!is_power_of_2(t))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
t = get_unaligned_le32(&hdr.size_high);
|
||||||
|
if (t)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
err = mtd_find_rootfs_from(master, master->erasesize, master->size,
|
||||||
|
&rootfs_offset, NULL);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = rootfs_offset;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = master->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return LZMA_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_lzma_of_match_table[] = {
|
||||||
|
{ .compatible = "lzma" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mtdsplit_lzma_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_lzma_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "lzma-fw",
|
||||||
|
.of_match_table = mtdsplit_lzma_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_lzma,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_lzma_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_lzma_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_lzma_init);
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* MTD splitter for MikroTik NOR devices
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 Thibaut VARENE <varenet@parisc-linux.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* The rootfs is expected at erase-block boundary due to the use of
|
||||||
|
* mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header
|
||||||
|
* for two main reasons:
|
||||||
|
* - the original header uses weakly defined types (int, enum...) which can
|
||||||
|
* vary in length depending on build host (and the struct is not packed),
|
||||||
|
* and the name field can have a different total length depending on
|
||||||
|
* whether or not the yaffs code was _built_ with unicode support.
|
||||||
|
* - the only field that could be of real use here (file_size_low) contains
|
||||||
|
* invalid data in the header generated by kernel2minor, so we cannot use
|
||||||
|
* it to infer the exact position of the rootfs and do away with
|
||||||
|
* mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define YAFFS_OBJECT_TYPE_FILE 0x1
|
||||||
|
#define YAFFS_OBJECTID_ROOT 0x1
|
||||||
|
#define YAFFS_SUM_UNUSED 0xFFFF
|
||||||
|
#define YAFFS_MAX_NAME_LENGTH 127
|
||||||
|
#define YAFFS_NAME_KERNEL "kernel"
|
||||||
|
#define YAFFS_NAME_BOOTIMAGE "bootimage"
|
||||||
|
|
||||||
|
#define MINOR_NR_PARTS 2
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This structure is based on yaffs_obj_hdr from yaffs_guts.h
|
||||||
|
* The weak types match upstream. The fields have cpu-endianness
|
||||||
|
*/
|
||||||
|
struct minor_header {
|
||||||
|
int yaffs_type;
|
||||||
|
int yaffs_obj_id;
|
||||||
|
u16 yaffs_sum_unused;
|
||||||
|
char yaffs_name[YAFFS_MAX_NAME_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mtdsplit_parse_minor(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct minor_header hdr;
|
||||||
|
size_t hdr_len, retlen;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
hdr_len = sizeof(hdr);
|
||||||
|
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
|
||||||
|
if (err) {
|
||||||
|
pr_err("MiNOR mtd_read error: %d\n", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retlen != hdr_len) {
|
||||||
|
pr_err("MiNOR mtd_read too short\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* match header */
|
||||||
|
if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE) {
|
||||||
|
pr_info("MiNOR YAFFS first type not matched\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT) {
|
||||||
|
pr_info("MiNOR YAFFS first objectid not matched\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED) {
|
||||||
|
pr_info("MiNOR YAFFS first sum not matched\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((memcmp(hdr.yaffs_name, YAFFS_NAME_KERNEL, sizeof(YAFFS_NAME_KERNEL))) &&
|
||||||
|
(memcmp(hdr.yaffs_name, YAFFS_NAME_BOOTIMAGE, sizeof(YAFFS_NAME_BOOTIMAGE)))) {
|
||||||
|
pr_info("MiNOR YAFFS first name not matched\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mtd_find_rootfs_from(master, master->erasesize, master->size,
|
||||||
|
&rootfs_offset, NULL);
|
||||||
|
if (err) {
|
||||||
|
pr_info("MiNOR mtd_find_rootfs_from error: %d\n", err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = rootfs_offset;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = master->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return MINOR_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_minor_of_match_table[] = {
|
||||||
|
{ .compatible = "mikrotik,minor" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mtdsplit_minor_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_minor_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "minor-fw",
|
||||||
|
.of_match_table = mtdsplit_minor_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_minor,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_minor_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_minor_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_minor_init);
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define SEAMA_MAGIC 0x5EA3A417
|
||||||
|
#define SEAMA_NR_PARTS 2
|
||||||
|
#define SEAMA_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
|
||||||
|
|
||||||
|
struct seama_header {
|
||||||
|
__be32 magic; /* should always be SEAMA_MAGIC. */
|
||||||
|
__be16 reserved; /* reserved for */
|
||||||
|
__be16 metasize; /* size of the META data */
|
||||||
|
__be32 size; /* size of the image */
|
||||||
|
u8 md5[16]; /* digest */
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mtdsplit_parse_seama(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct seama_header hdr;
|
||||||
|
size_t hdr_len, retlen, kernel_ent_size;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
enum mtdsplit_part_type type;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
hdr_len = sizeof(hdr);
|
||||||
|
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (retlen != hdr_len)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
/* sanity checks */
|
||||||
|
if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
kernel_ent_size = hdr_len + be32_to_cpu(hdr.size) +
|
||||||
|
be16_to_cpu(hdr.metasize);
|
||||||
|
if (kernel_ent_size > master->size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Check for the rootfs right after Seama entity with a kernel. */
|
||||||
|
err = mtd_check_rootfs_magic(master, kernel_ent_size, &type);
|
||||||
|
if (!err) {
|
||||||
|
rootfs_offset = kernel_ent_size;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* On some devices firmware entity might contain both: kernel
|
||||||
|
* and rootfs. We can't determine kernel size so we just have to
|
||||||
|
* look for rootfs magic.
|
||||||
|
* Start the search from an arbitrary offset.
|
||||||
|
*/
|
||||||
|
err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS,
|
||||||
|
master->size, &rootfs_offset, &type);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = sizeof hdr + be16_to_cpu(hdr.metasize);
|
||||||
|
parts[0].size = rootfs_offset - parts[0].offset;
|
||||||
|
|
||||||
|
if (type == MTDSPLIT_PART_TYPE_UBI)
|
||||||
|
parts[1].name = UBI_PART_NAME;
|
||||||
|
else
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = master->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return SEAMA_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_seama_of_match_table[] = {
|
||||||
|
{ .compatible = "seama" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mtdsplit_seama_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_seama_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "seama-fw",
|
||||||
|
.of_match_table = mtdsplit_seama_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_seama,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_seama_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_seama_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_seama_init);
|
|
@ -0,0 +1,191 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/* a mtdsplit driver for IIJ SEIL devices */
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define NR_PARTS 2
|
||||||
|
#define SEIL_VFMT 1
|
||||||
|
#define LDR_ENV_PART_NAME "bootloader-env"
|
||||||
|
#define LDR_ENV_KEY_BOOTDEV "BOOTDEV"
|
||||||
|
|
||||||
|
struct seil_header {
|
||||||
|
uint64_t id; /* Identifier */
|
||||||
|
uint8_t copy[80]; /* Copyright */
|
||||||
|
uint32_t dcrc; /* Data CRC Checksum */
|
||||||
|
uint32_t vfmt; /* Image Version Format */
|
||||||
|
uint32_t vmjr; /* Image Version Major */
|
||||||
|
uint32_t vmnr; /* Image Version Minor */
|
||||||
|
uint8_t vrel[32]; /* Image Version Release */
|
||||||
|
uint32_t dxor; /* xor value for Data? */
|
||||||
|
uint32_t dlen; /* Data Length */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check whether the current mtd device is active or not
|
||||||
|
*
|
||||||
|
* example of BOOTDEV value (IIJ SA-W2):
|
||||||
|
* - "flash" : primary image on flash
|
||||||
|
* - "rescue" : secondary image on flash
|
||||||
|
* - "usb" : usb storage
|
||||||
|
* - "lan0/1" : network
|
||||||
|
*/
|
||||||
|
static bool seil_bootdev_is_active(struct device_node *np)
|
||||||
|
{
|
||||||
|
struct mtd_info *env_mtd;
|
||||||
|
char *buf, *var, *value, *eq;
|
||||||
|
const char *devnm;
|
||||||
|
size_t rdlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* read bootdev name of the partition
|
||||||
|
* if doesn't exist, return true and skip checking of active device
|
||||||
|
*/
|
||||||
|
ret = of_property_read_string(np, "iij,bootdev-name", &devnm);
|
||||||
|
if (ret == -EINVAL)
|
||||||
|
return true;
|
||||||
|
else if (ret < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
env_mtd = get_mtd_device_nm(LDR_ENV_PART_NAME);
|
||||||
|
if (IS_ERR(env_mtd)) {
|
||||||
|
pr_err("failed to get mtd device \"%s\"", LDR_ENV_PART_NAME);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = vmalloc(env_mtd->size);
|
||||||
|
if (!buf)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ret = mtd_read(env_mtd, 0, env_mtd->size, &rdlen, buf);
|
||||||
|
if (ret || rdlen != env_mtd->size) {
|
||||||
|
pr_err("failed to read from mtd (%d)\n", ret);
|
||||||
|
ret = 0;
|
||||||
|
goto exit_vfree;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var = buf, ret = false;
|
||||||
|
var < buf + env_mtd->size && *var;
|
||||||
|
var = value + strlen(value) + 1) {
|
||||||
|
eq = strchr(var, '=');
|
||||||
|
if (!eq)
|
||||||
|
break;
|
||||||
|
*eq = '\0';
|
||||||
|
value = eq + 1;
|
||||||
|
|
||||||
|
pr_debug("ENV: %s=%s\n", var, value);
|
||||||
|
if (!strcmp(var, LDR_ENV_KEY_BOOTDEV)) {
|
||||||
|
ret = !strcmp(devnm, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_vfree:
|
||||||
|
vfree(buf);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtdsplit_parse_seil_fw(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct device_node *np = mtd_get_of_node(master);
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
struct seil_header header;
|
||||||
|
size_t image_size = 0;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
size_t hdrlen = sizeof(header);
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
u64 id;
|
||||||
|
|
||||||
|
if (!seil_bootdev_is_active(np))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
ret = of_property_read_u64(np, "iij,seil-id", &id);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("failed to get iij,seil-id from dt\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
pr_debug("got seil-id=0x%016llx from dt\n", id);
|
||||||
|
|
||||||
|
parts = kcalloc(NR_PARTS, sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = mtd_read(master, 0, hdrlen, &retlen, (void *)&header);
|
||||||
|
if (ret)
|
||||||
|
goto err_free_parts;
|
||||||
|
|
||||||
|
if (retlen != hdrlen) {
|
||||||
|
ret = -EIO;
|
||||||
|
goto err_free_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (be64_to_cpu(header.id) != id ||
|
||||||
|
be32_to_cpu(header.vfmt) != SEIL_VFMT) {
|
||||||
|
pr_debug("no valid seil image found in \"%s\"\n", master->name);
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_free_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
image_size = hdrlen + be32_to_cpu(header.dlen);
|
||||||
|
if (image_size > master->size) {
|
||||||
|
pr_err("seil image exceeds MTD device \"%s\"\n", master->name);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err_free_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find the roots after the seil image */
|
||||||
|
ret = mtd_find_rootfs_from(master, image_size,
|
||||||
|
master->size, &rootfs_offset, NULL);
|
||||||
|
if (ret || (master->size - rootfs_offset) == 0) {
|
||||||
|
pr_debug("no rootfs after seil image in \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_free_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = rootfs_offset;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = master->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return NR_PARTS;
|
||||||
|
|
||||||
|
err_free_parts:
|
||||||
|
kfree(parts);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_seil_fw_of_match_table[] = {
|
||||||
|
{ .compatible = "iij,seil-firmware" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mtdsplit_seil_fw_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_seil_fw_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "seil-fw",
|
||||||
|
.of_match_table = mtdsplit_seil_fw_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_seil_fw,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_mtd_part_parser(mtdsplit_seil_fw_parser);
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Felix Fietkau <nbd@nbd.name>
|
||||||
|
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/magic.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtdsplit_parse_squashfs(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct mtd_partition *part;
|
||||||
|
struct mtd_info *parent_mtd;
|
||||||
|
size_t part_offset;
|
||||||
|
size_t squashfs_len;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = mtd_get_squashfs_len(master, 0, &squashfs_len);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
parent_mtd = mtd_get_master(master);
|
||||||
|
part_offset = mtdpart_get_offset(master);
|
||||||
|
|
||||||
|
part = kzalloc(sizeof(*part), GFP_KERNEL);
|
||||||
|
if (!part) {
|
||||||
|
pr_alert("unable to allocate memory for \"%s\" partition\n",
|
||||||
|
ROOTFS_SPLIT_NAME);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
part->name = ROOTFS_SPLIT_NAME;
|
||||||
|
part->offset = mtd_roundup_to_eb(part_offset + squashfs_len,
|
||||||
|
parent_mtd) - part_offset;
|
||||||
|
part->size = mtd_rounddown_to_eb(master->size - part->offset, master);
|
||||||
|
|
||||||
|
*pparts = part;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_squashfs_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "squashfs-split",
|
||||||
|
.parse_fn = mtdsplit_parse_squashfs,
|
||||||
|
.type = MTD_PARSER_TYPE_ROOTFS,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_squashfs_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_squashfs_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_squashfs_init);
|
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define TPLINK_NR_PARTS 2
|
||||||
|
#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
|
||||||
|
|
||||||
|
#define MD5SUM_LEN 16
|
||||||
|
|
||||||
|
struct fw_v1 {
|
||||||
|
char vendor_name[24];
|
||||||
|
char fw_version[36];
|
||||||
|
uint32_t hw_id; /* hardware id */
|
||||||
|
uint32_t hw_rev; /* hardware revision */
|
||||||
|
uint32_t unk1;
|
||||||
|
uint8_t md5sum1[MD5SUM_LEN];
|
||||||
|
uint32_t unk2;
|
||||||
|
uint8_t md5sum2[MD5SUM_LEN];
|
||||||
|
uint32_t unk3;
|
||||||
|
uint32_t kernel_la; /* kernel load address */
|
||||||
|
uint32_t kernel_ep; /* kernel entry point */
|
||||||
|
uint32_t fw_length; /* total length of the firmware */
|
||||||
|
uint32_t kernel_ofs; /* kernel data offset */
|
||||||
|
uint32_t kernel_len; /* kernel data length */
|
||||||
|
uint32_t rootfs_ofs; /* rootfs data offset */
|
||||||
|
uint32_t rootfs_len; /* rootfs data length */
|
||||||
|
uint32_t boot_ofs; /* bootloader data offset */
|
||||||
|
uint32_t boot_len; /* bootloader data length */
|
||||||
|
uint8_t pad[360];
|
||||||
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
|
struct fw_v2 {
|
||||||
|
char fw_version[48]; /* 0x04: fw version string */
|
||||||
|
uint32_t hw_id; /* 0x34: hardware id */
|
||||||
|
uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */
|
||||||
|
uint32_t unk1; /* 0x3c: 0x00000000 */
|
||||||
|
uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */
|
||||||
|
uint32_t unk2; /* 0x50: 0x00000000 */
|
||||||
|
uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */
|
||||||
|
uint32_t unk3; /* 0x64: 0xffffffff */
|
||||||
|
|
||||||
|
uint32_t kernel_la; /* 0x68: kernel load address */
|
||||||
|
uint32_t kernel_ep; /* 0x6c: kernel entry point */
|
||||||
|
uint32_t fw_length; /* 0x70: total length of the image */
|
||||||
|
uint32_t kernel_ofs; /* 0x74: kernel data offset */
|
||||||
|
uint32_t kernel_len; /* 0x78: kernel data length */
|
||||||
|
uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */
|
||||||
|
uint32_t rootfs_len; /* 0x80: rootfs data length */
|
||||||
|
uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */
|
||||||
|
uint32_t boot_len; /* 0x88: FIXME: seems to be unused */
|
||||||
|
uint16_t unk4; /* 0x8c: 0x55aa */
|
||||||
|
uint8_t sver_hi; /* 0x8e */
|
||||||
|
uint8_t sver_lo; /* 0x8f */
|
||||||
|
uint8_t unk5; /* 0x90: magic: 0xa5 */
|
||||||
|
uint8_t ver_hi; /* 0x91 */
|
||||||
|
uint8_t ver_mid; /* 0x92 */
|
||||||
|
uint8_t ver_lo; /* 0x93 */
|
||||||
|
uint8_t pad[364];
|
||||||
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
|
struct tplink_fw_header {
|
||||||
|
uint32_t version;
|
||||||
|
union {
|
||||||
|
struct fw_v1 v1;
|
||||||
|
struct fw_v2 v2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mtdsplit_parse_tplink(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct tplink_fw_header hdr;
|
||||||
|
size_t hdr_len, retlen, kernel_size;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
hdr_len = sizeof(hdr);
|
||||||
|
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (retlen != hdr_len)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
switch (le32_to_cpu(hdr.version)) {
|
||||||
|
case 1:
|
||||||
|
if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len);
|
||||||
|
rootfs_offset = be32_to_cpu(hdr.v1.rootfs_ofs);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len);
|
||||||
|
rootfs_offset = be32_to_cpu(hdr.v2.rootfs_ofs);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kernel_size > master->size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Find the rootfs */
|
||||||
|
err = mtd_check_rootfs_magic(master, rootfs_offset, NULL);
|
||||||
|
if (err) {
|
||||||
|
/*
|
||||||
|
* The size in the header might cover the rootfs as well.
|
||||||
|
* Start the search from an arbitrary offset.
|
||||||
|
*/
|
||||||
|
err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS,
|
||||||
|
master->size, &rootfs_offset, NULL);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = kernel_size;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = master->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return TPLINK_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_tplink_of_match_table[] = {
|
||||||
|
{ .compatible = "tplink,firmware" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_tplink_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "tplink-fw",
|
||||||
|
.of_match_table = mtdsplit_tplink_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_tplink,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_tplink_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_tplink_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_tplink_init);
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define TRX_MAGIC 0x30524448 /* "HDR0" */
|
||||||
|
|
||||||
|
struct trx_header {
|
||||||
|
__le32 magic;
|
||||||
|
__le32 len;
|
||||||
|
__le32 crc32;
|
||||||
|
__le32 flag_version;
|
||||||
|
__le32 offset[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
read_trx_header(struct mtd_info *mtd, size_t offset,
|
||||||
|
struct trx_header *header)
|
||||||
|
{
|
||||||
|
size_t header_len;
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
header_len = sizeof(*header);
|
||||||
|
ret = mtd_read(mtd, offset, header_len, &retlen,
|
||||||
|
(unsigned char *) header);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug("read error in \"%s\"\n", mtd->name);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retlen != header_len) {
|
||||||
|
pr_debug("short read in \"%s\"\n", mtd->name);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtdsplit_parse_trx(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
struct trx_header hdr;
|
||||||
|
int nr_parts;
|
||||||
|
size_t offset;
|
||||||
|
size_t trx_offset;
|
||||||
|
size_t trx_size = 0;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
size_t rootfs_size = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
nr_parts = 2;
|
||||||
|
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* find trx image on erase block boundaries */
|
||||||
|
for (offset = 0; offset < master->size; offset += master->erasesize) {
|
||||||
|
trx_size = 0;
|
||||||
|
|
||||||
|
ret = read_trx_header(master, offset, &hdr);
|
||||||
|
if (ret)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (hdr.magic != cpu_to_le32(TRX_MAGIC)) {
|
||||||
|
pr_debug("no valid trx header found in \"%s\" at offset %llx\n",
|
||||||
|
master->name, (unsigned long long) offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
trx_size = le32_to_cpu(hdr.len);
|
||||||
|
if ((offset + trx_size) > master->size) {
|
||||||
|
pr_debug("trx image exceeds MTD device \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trx_size == 0) {
|
||||||
|
pr_debug("no trx header found in \"%s\"\n", master->name);
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
trx_offset = offset + hdr.offset[0];
|
||||||
|
rootfs_offset = offset + hdr.offset[1];
|
||||||
|
rootfs_size = master->size - rootfs_offset;
|
||||||
|
trx_size = rootfs_offset - trx_offset;
|
||||||
|
|
||||||
|
if (rootfs_size == 0) {
|
||||||
|
pr_debug("no rootfs found in \"%s\"\n", master->name);
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = trx_offset;
|
||||||
|
parts[0].size = trx_size;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = rootfs_size;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return nr_parts;
|
||||||
|
|
||||||
|
err:
|
||||||
|
kfree(parts);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id trx_parser_of_match_table[] = {
|
||||||
|
{ .compatible = "openwrt,trx" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, trx_parser_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser trx_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "trx-fw",
|
||||||
|
.of_match_table = trx_parser_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_trx,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_trx_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&trx_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(mtdsplit_trx_init);
|
|
@ -0,0 +1,282 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <dt-bindings/mtd/partitions/uimage.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Legacy format image header,
|
||||||
|
* all data in network byte order (aka natural aka bigendian).
|
||||||
|
*/
|
||||||
|
struct uimage_header {
|
||||||
|
uint32_t ih_magic; /* Image Header Magic Number */
|
||||||
|
uint32_t ih_hcrc; /* Image Header CRC Checksum */
|
||||||
|
uint32_t ih_time; /* Image Creation Timestamp */
|
||||||
|
uint32_t ih_size; /* Image Data Size */
|
||||||
|
uint32_t ih_load; /* Data Load Address */
|
||||||
|
uint32_t ih_ep; /* Entry Point Address */
|
||||||
|
uint32_t ih_dcrc; /* Image Data CRC Checksum */
|
||||||
|
uint8_t ih_os; /* Operating System */
|
||||||
|
uint8_t ih_arch; /* CPU architecture */
|
||||||
|
uint8_t ih_type; /* Image Type */
|
||||||
|
uint8_t ih_comp; /* Compression Type */
|
||||||
|
uint8_t ih_name[IH_NMLEN]; /* Image Name */
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf,
|
||||||
|
size_t header_len)
|
||||||
|
{
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mtd_read(mtd, offset, header_len, &retlen, buf);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug("read error in \"%s\"\n", mtd->name);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retlen != header_len) {
|
||||||
|
pr_debug("short read in \"%s\"\n", mtd->name);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void uimage_parse_dt(struct mtd_info *master, int *extralen,
|
||||||
|
u32 *ih_magic, u32 *ih_type,
|
||||||
|
u32 *header_offset, u32 *part_magic)
|
||||||
|
{
|
||||||
|
struct device_node *np = mtd_get_of_node(master);
|
||||||
|
|
||||||
|
if (!np || !of_device_is_compatible(np, "openwrt,uimage"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!of_property_read_u32(np, "openwrt,padding", extralen))
|
||||||
|
pr_debug("got openwrt,padding=%d from device-tree\n", *extralen);
|
||||||
|
if (!of_property_read_u32(np, "openwrt,ih-magic", ih_magic))
|
||||||
|
pr_debug("got openwrt,ih-magic=%08x from device-tree\n", *ih_magic);
|
||||||
|
if (!of_property_read_u32(np, "openwrt,ih-type", ih_type))
|
||||||
|
pr_debug("got openwrt,ih-type=%08x from device-tree\n", *ih_type);
|
||||||
|
if (!of_property_read_u32(np, "openwrt,offset", header_offset))
|
||||||
|
pr_debug("got ih-start=%u from device-tree\n", *header_offset);
|
||||||
|
if (!of_property_read_u32(np, "openwrt,partition-magic", part_magic))
|
||||||
|
pr_debug("got openwrt,partition-magic=%08x from device-tree\n", *part_magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t uimage_verify_default(u_char *buf, u32 ih_magic, u32 ih_type)
|
||||||
|
{
|
||||||
|
struct uimage_header *header = (struct uimage_header *)buf;
|
||||||
|
|
||||||
|
/* default sanity checks */
|
||||||
|
if (be32_to_cpu(header->ih_magic) != ih_magic) {
|
||||||
|
pr_debug("invalid uImage magic: %08x != %08x\n",
|
||||||
|
be32_to_cpu(header->ih_magic), ih_magic);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->ih_os != IH_OS_LINUX) {
|
||||||
|
pr_debug("invalid uImage OS: %08x != %08x\n",
|
||||||
|
be32_to_cpu(header->ih_os), IH_OS_LINUX);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->ih_type != ih_type) {
|
||||||
|
pr_debug("invalid uImage type: %08x != %08x\n",
|
||||||
|
be32_to_cpu(header->ih_type), ih_type);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts
|
||||||
|
*
|
||||||
|
* @find_header: function to call for a block of data that will return offset
|
||||||
|
* and tail padding length of a valid uImage header if found
|
||||||
|
*/
|
||||||
|
static int __mtdsplit_parse_uimage(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
u_char *buf;
|
||||||
|
int nr_parts;
|
||||||
|
size_t offset;
|
||||||
|
size_t uimage_offset;
|
||||||
|
size_t uimage_size = 0;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
size_t rootfs_size = 0;
|
||||||
|
size_t buflen;
|
||||||
|
int uimage_part, rf_part;
|
||||||
|
int ret;
|
||||||
|
int extralen = 0;
|
||||||
|
u32 ih_magic = IH_MAGIC;
|
||||||
|
u32 ih_type = IH_TYPE_KERNEL;
|
||||||
|
u32 header_offset = 0;
|
||||||
|
u32 part_magic = 0;
|
||||||
|
enum mtdsplit_part_type type;
|
||||||
|
|
||||||
|
nr_parts = 2;
|
||||||
|
parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
uimage_parse_dt(master, &extralen, &ih_magic, &ih_type, &header_offset, &part_magic);
|
||||||
|
buflen = sizeof(struct uimage_header) + header_offset;
|
||||||
|
buf = vmalloc(buflen);
|
||||||
|
if (!buf) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err_free_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find uImage on erase block boundaries */
|
||||||
|
for (offset = 0; offset < master->size; offset += master->erasesize) {
|
||||||
|
struct uimage_header *header;
|
||||||
|
|
||||||
|
uimage_size = 0;
|
||||||
|
|
||||||
|
ret = read_uimage_header(master, offset, buf, buflen);
|
||||||
|
if (ret)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* verify optional partition magic before uimage header */
|
||||||
|
if (header_offset && part_magic && (be32_to_cpu(*(u32 *)buf) != part_magic))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ret = uimage_verify_default(buf + header_offset, ih_magic, ih_type);
|
||||||
|
if (ret < 0) {
|
||||||
|
pr_debug("no valid uImage found in \"%s\" at offset %llx\n",
|
||||||
|
master->name, (unsigned long long) offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
header = (struct uimage_header *)(buf + header_offset);
|
||||||
|
|
||||||
|
uimage_size = sizeof(*header) +
|
||||||
|
be32_to_cpu(header->ih_size) + header_offset + extralen;
|
||||||
|
|
||||||
|
if ((offset + uimage_size) > master->size) {
|
||||||
|
pr_debug("uImage exceeds MTD device \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uimage_size == 0) {
|
||||||
|
pr_debug("no uImage found in \"%s\"\n", master->name);
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_free_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
uimage_offset = offset;
|
||||||
|
|
||||||
|
if (uimage_offset == 0) {
|
||||||
|
uimage_part = 0;
|
||||||
|
rf_part = 1;
|
||||||
|
|
||||||
|
/* find the roots after the uImage */
|
||||||
|
ret = mtd_find_rootfs_from(master, uimage_offset + uimage_size,
|
||||||
|
master->size, &rootfs_offset, &type);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug("no rootfs after uImage in \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
goto err_free_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs_size = master->size - rootfs_offset;
|
||||||
|
uimage_size = rootfs_offset - uimage_offset;
|
||||||
|
} else {
|
||||||
|
rf_part = 0;
|
||||||
|
uimage_part = 1;
|
||||||
|
|
||||||
|
/* check rootfs presence at offset 0 */
|
||||||
|
ret = mtd_check_rootfs_magic(master, 0, &type);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug("no rootfs before uImage in \"%s\"\n",
|
||||||
|
master->name);
|
||||||
|
goto err_free_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfs_offset = 0;
|
||||||
|
rootfs_size = uimage_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootfs_size == 0) {
|
||||||
|
pr_debug("no rootfs found in \"%s\"\n", master->name);
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_free_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[uimage_part].name = KERNEL_PART_NAME;
|
||||||
|
parts[uimage_part].offset = uimage_offset;
|
||||||
|
parts[uimage_part].size = uimage_size;
|
||||||
|
|
||||||
|
if (type == MTDSPLIT_PART_TYPE_UBI)
|
||||||
|
parts[rf_part].name = UBI_PART_NAME;
|
||||||
|
else
|
||||||
|
parts[rf_part].name = ROOTFS_PART_NAME;
|
||||||
|
parts[rf_part].offset = rootfs_offset;
|
||||||
|
parts[rf_part].size = rootfs_size;
|
||||||
|
|
||||||
|
vfree(buf);
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return nr_parts;
|
||||||
|
|
||||||
|
err_free_buf:
|
||||||
|
vfree(buf);
|
||||||
|
|
||||||
|
err_free_parts:
|
||||||
|
kfree(parts);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_uimage_of_match_table[] = {
|
||||||
|
{ .compatible = "denx,uimage" },
|
||||||
|
{ .compatible = "openwrt,uimage" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mtd_part_parser uimage_generic_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "uimage-fw",
|
||||||
|
.of_match_table = mtdsplit_uimage_of_match_table,
|
||||||
|
.parse_fn = __mtdsplit_parse_uimage,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
* Init
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
static int __init mtdsplit_uimage_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&uimage_generic_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(mtdsplit_uimage_init);
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
* Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
|
||||||
|
* Copyright (C) 2016 Stijn Tintel <stijn@linux-ipv6.be>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/byteorder/generic.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
#include "mtdsplit.h"
|
||||||
|
|
||||||
|
#define WRGG_NR_PARTS 2
|
||||||
|
#define WRGG_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */
|
||||||
|
#define WRGG03_MAGIC 0x20080321
|
||||||
|
#define WRG_MAGIC 0x20040220
|
||||||
|
|
||||||
|
struct wrgg03_header {
|
||||||
|
char signature[32];
|
||||||
|
uint32_t magic1;
|
||||||
|
uint32_t magic2;
|
||||||
|
char version[16];
|
||||||
|
char model[16];
|
||||||
|
uint32_t flag[2];
|
||||||
|
uint32_t reserve[2];
|
||||||
|
char buildno[16];
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t offset;
|
||||||
|
char devname[32];
|
||||||
|
char digest[16];
|
||||||
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
|
struct wrg_header {
|
||||||
|
char signature[32];
|
||||||
|
uint32_t magic1;
|
||||||
|
uint32_t magic2;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t offset;
|
||||||
|
char devname[32];
|
||||||
|
char digest[16];
|
||||||
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
|
|
||||||
|
static int mtdsplit_parse_wrgg(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct wrgg03_header hdr;
|
||||||
|
size_t hdr_len, retlen, kernel_ent_size;
|
||||||
|
size_t rootfs_offset;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
enum mtdsplit_part_type type;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
hdr_len = sizeof(hdr);
|
||||||
|
err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (retlen != hdr_len)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
/* sanity checks */
|
||||||
|
if (le32_to_cpu(hdr.magic1) == WRGG03_MAGIC) {
|
||||||
|
kernel_ent_size = hdr_len + be32_to_cpu(hdr.size);
|
||||||
|
/*
|
||||||
|
* If this becomes silly big it's probably because the
|
||||||
|
* WRGG image is little-endian.
|
||||||
|
*/
|
||||||
|
if (kernel_ent_size > master->size)
|
||||||
|
kernel_ent_size = hdr_len + le32_to_cpu(hdr.size);
|
||||||
|
|
||||||
|
/* Now what ?! It's neither */
|
||||||
|
if (kernel_ent_size > master->size)
|
||||||
|
return -EINVAL;
|
||||||
|
} else if (le32_to_cpu(hdr.magic1) == WRG_MAGIC) {
|
||||||
|
kernel_ent_size = sizeof(struct wrg_header) + le32_to_cpu(
|
||||||
|
((struct wrg_header*)&hdr)->size);
|
||||||
|
} else {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kernel_ent_size > master->size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The size in the header covers the rootfs as well.
|
||||||
|
* Start the search from an arbitrary offset.
|
||||||
|
*/
|
||||||
|
err = mtd_find_rootfs_from(master, WRGG_MIN_ROOTFS_OFFS,
|
||||||
|
master->size, &rootfs_offset, &type);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
parts = kzalloc(WRGG_NR_PARTS * sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
parts[0].name = KERNEL_PART_NAME;
|
||||||
|
parts[0].offset = 0;
|
||||||
|
parts[0].size = rootfs_offset;
|
||||||
|
|
||||||
|
parts[1].name = ROOTFS_PART_NAME;
|
||||||
|
parts[1].offset = rootfs_offset;
|
||||||
|
parts[1].size = master->size - rootfs_offset;
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return WRGG_NR_PARTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mtdsplit_wrgg_of_match_table[] = {
|
||||||
|
{ .compatible = "wrg" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mtdsplit_wrgg_of_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser mtdsplit_wrgg_parser = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "wrgg-fw",
|
||||||
|
.of_match_table = mtdsplit_wrgg_of_match_table,
|
||||||
|
.parse_fn = mtdsplit_parse_wrgg,
|
||||||
|
.type = MTD_PARSER_TYPE_FIRMWARE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mtdsplit_wrgg_init(void)
|
||||||
|
{
|
||||||
|
register_mtd_parser(&mtdsplit_wrgg_parser);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(mtdsplit_wrgg_init);
|
465
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt.c
Normal file
465
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt.c
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2017 MediaTek Inc.
|
||||||
|
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
|
||||||
|
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/gfp.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/bits.h>
|
||||||
|
#include "mtk_bmt.h"
|
||||||
|
|
||||||
|
struct bmt_desc bmtd = {};
|
||||||
|
|
||||||
|
/* -------- Nand operations wrapper -------- */
|
||||||
|
int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset)
|
||||||
|
{
|
||||||
|
int pages = bmtd.blk_size >> bmtd.pg_shift;
|
||||||
|
loff_t src = (loff_t)src_blk << bmtd.blk_shift;
|
||||||
|
loff_t dest = (loff_t)dest_blk << bmtd.blk_shift;
|
||||||
|
loff_t offset = 0;
|
||||||
|
uint8_t oob[64];
|
||||||
|
int i, ret;
|
||||||
|
|
||||||
|
for (i = 0; i < pages; i++) {
|
||||||
|
struct mtd_oob_ops rd_ops = {
|
||||||
|
.mode = MTD_OPS_PLACE_OOB,
|
||||||
|
.oobbuf = oob,
|
||||||
|
.ooblen = min_t(int, bmtd.mtd->oobsize / pages, sizeof(oob)),
|
||||||
|
.datbuf = bmtd.data_buf,
|
||||||
|
.len = bmtd.pg_size,
|
||||||
|
};
|
||||||
|
struct mtd_oob_ops wr_ops = {
|
||||||
|
.mode = MTD_OPS_PLACE_OOB,
|
||||||
|
.oobbuf = oob,
|
||||||
|
.datbuf = bmtd.data_buf,
|
||||||
|
.len = bmtd.pg_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (offset >= max_offset)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ret = bmtd._read_oob(bmtd.mtd, src + offset, &rd_ops);
|
||||||
|
if (ret < 0 && !mtd_is_bitflip(ret))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (!rd_ops.retlen)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ret = bmtd._write_oob(bmtd.mtd, dest + offset, &wr_ops);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
wr_ops.ooblen = rd_ops.oobretlen;
|
||||||
|
offset += rd_ops.retlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- Bad Blocks Management -------- */
|
||||||
|
bool mapping_block_in_range(int block, int *start, int *end)
|
||||||
|
{
|
||||||
|
const __be32 *cur = bmtd.remap_range;
|
||||||
|
u32 addr = block << bmtd.blk_shift;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!cur || !bmtd.remap_range_len) {
|
||||||
|
*start = 0;
|
||||||
|
*end = bmtd.total_blks;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < bmtd.remap_range_len; i++, cur += 2) {
|
||||||
|
if (addr < be32_to_cpu(cur[0]) || addr >= be32_to_cpu(cur[1]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
*start = be32_to_cpu(cur[0]);
|
||||||
|
*end = be32_to_cpu(cur[1]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
mtk_bmt_remap_block(u32 block, u32 mapped_block, int copy_len)
|
||||||
|
{
|
||||||
|
int start, end;
|
||||||
|
|
||||||
|
if (!mapping_block_in_range(block, &start, &end))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return bmtd.ops->remap_block(block, mapped_block, copy_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtk_bmt_read(struct mtd_info *mtd, loff_t from,
|
||||||
|
struct mtd_oob_ops *ops)
|
||||||
|
{
|
||||||
|
struct mtd_oob_ops cur_ops = *ops;
|
||||||
|
int retry_count = 0;
|
||||||
|
loff_t cur_from;
|
||||||
|
int ret = 0;
|
||||||
|
int max_bitflips = 0;
|
||||||
|
|
||||||
|
ops->retlen = 0;
|
||||||
|
ops->oobretlen = 0;
|
||||||
|
|
||||||
|
while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) {
|
||||||
|
int cur_ret;
|
||||||
|
|
||||||
|
u32 offset = from & (bmtd.blk_size - 1);
|
||||||
|
u32 block = from >> bmtd.blk_shift;
|
||||||
|
int cur_block;
|
||||||
|
|
||||||
|
cur_block = bmtd.ops->get_mapping_block(block);
|
||||||
|
if (cur_block < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
cur_from = ((loff_t)cur_block << bmtd.blk_shift) + offset;
|
||||||
|
|
||||||
|
cur_ops.oobretlen = 0;
|
||||||
|
cur_ops.retlen = 0;
|
||||||
|
cur_ops.len = min_t(u32, mtd->erasesize - offset,
|
||||||
|
ops->len - ops->retlen);
|
||||||
|
cur_ret = bmtd._read_oob(mtd, cur_from, &cur_ops);
|
||||||
|
if (cur_ret < 0)
|
||||||
|
ret = cur_ret;
|
||||||
|
else
|
||||||
|
max_bitflips = max_t(int, max_bitflips, cur_ret);
|
||||||
|
if (cur_ret < 0 && !mtd_is_bitflip(cur_ret)) {
|
||||||
|
if (mtk_bmt_remap_block(block, cur_block, mtd->erasesize) &&
|
||||||
|
retry_count++ < 10)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mtd->bitflip_threshold && cur_ret >= mtd->bitflip_threshold)
|
||||||
|
mtk_bmt_remap_block(block, cur_block, mtd->erasesize);
|
||||||
|
|
||||||
|
ops->retlen += cur_ops.retlen;
|
||||||
|
ops->oobretlen += cur_ops.oobretlen;
|
||||||
|
|
||||||
|
cur_ops.ooboffs = 0;
|
||||||
|
cur_ops.datbuf += cur_ops.retlen;
|
||||||
|
cur_ops.oobbuf += cur_ops.oobretlen;
|
||||||
|
cur_ops.ooblen -= cur_ops.oobretlen;
|
||||||
|
|
||||||
|
if (!cur_ops.len)
|
||||||
|
cur_ops.len = mtd->erasesize - offset;
|
||||||
|
|
||||||
|
from += cur_ops.len;
|
||||||
|
retry_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return max_bitflips;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtk_bmt_write(struct mtd_info *mtd, loff_t to,
|
||||||
|
struct mtd_oob_ops *ops)
|
||||||
|
{
|
||||||
|
struct mtd_oob_ops cur_ops = *ops;
|
||||||
|
int retry_count = 0;
|
||||||
|
loff_t cur_to;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ops->retlen = 0;
|
||||||
|
ops->oobretlen = 0;
|
||||||
|
|
||||||
|
while (ops->retlen < ops->len || ops->oobretlen < ops->ooblen) {
|
||||||
|
u32 offset = to & (bmtd.blk_size - 1);
|
||||||
|
u32 block = to >> bmtd.blk_shift;
|
||||||
|
int cur_block;
|
||||||
|
|
||||||
|
cur_block = bmtd.ops->get_mapping_block(block);
|
||||||
|
if (cur_block < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
cur_to = ((loff_t)cur_block << bmtd.blk_shift) + offset;
|
||||||
|
|
||||||
|
cur_ops.oobretlen = 0;
|
||||||
|
cur_ops.retlen = 0;
|
||||||
|
cur_ops.len = min_t(u32, bmtd.blk_size - offset,
|
||||||
|
ops->len - ops->retlen);
|
||||||
|
ret = bmtd._write_oob(mtd, cur_to, &cur_ops);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (mtk_bmt_remap_block(block, cur_block, offset) &&
|
||||||
|
retry_count++ < 10)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ops->retlen += cur_ops.retlen;
|
||||||
|
ops->oobretlen += cur_ops.oobretlen;
|
||||||
|
|
||||||
|
cur_ops.ooboffs = 0;
|
||||||
|
cur_ops.datbuf += cur_ops.retlen;
|
||||||
|
cur_ops.oobbuf += cur_ops.oobretlen;
|
||||||
|
cur_ops.ooblen -= cur_ops.oobretlen;
|
||||||
|
|
||||||
|
if (!cur_ops.len)
|
||||||
|
cur_ops.len = mtd->erasesize - offset;
|
||||||
|
|
||||||
|
to += cur_ops.len;
|
||||||
|
retry_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtk_bmt_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||||
|
{
|
||||||
|
struct erase_info mapped_instr = {
|
||||||
|
.len = bmtd.blk_size,
|
||||||
|
};
|
||||||
|
int retry_count = 0;
|
||||||
|
u64 start_addr, end_addr;
|
||||||
|
int ret;
|
||||||
|
u16 orig_block;
|
||||||
|
int block;
|
||||||
|
|
||||||
|
start_addr = instr->addr & (~mtd->erasesize_mask);
|
||||||
|
end_addr = instr->addr + instr->len;
|
||||||
|
|
||||||
|
while (start_addr < end_addr) {
|
||||||
|
orig_block = start_addr >> bmtd.blk_shift;
|
||||||
|
block = bmtd.ops->get_mapping_block(orig_block);
|
||||||
|
if (block < 0)
|
||||||
|
return -EIO;
|
||||||
|
mapped_instr.addr = (loff_t)block << bmtd.blk_shift;
|
||||||
|
ret = bmtd._erase(mtd, &mapped_instr);
|
||||||
|
if (ret) {
|
||||||
|
if (mtk_bmt_remap_block(orig_block, block, 0) &&
|
||||||
|
retry_count++ < 10)
|
||||||
|
continue;
|
||||||
|
instr->fail_addr = start_addr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start_addr += mtd->erasesize;
|
||||||
|
retry_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
static int
|
||||||
|
mtk_bmt_block_isbad(struct mtd_info *mtd, loff_t ofs)
|
||||||
|
{
|
||||||
|
int retry_count = 0;
|
||||||
|
u16 orig_block = ofs >> bmtd.blk_shift;
|
||||||
|
u16 block;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
retry:
|
||||||
|
block = bmtd.ops->get_mapping_block(orig_block);
|
||||||
|
ret = bmtd._block_isbad(mtd, (loff_t)block << bmtd.blk_shift);
|
||||||
|
if (ret) {
|
||||||
|
if (mtk_bmt_remap_block(orig_block, block, bmtd.blk_size) &&
|
||||||
|
retry_count++ < 10)
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtk_bmt_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
||||||
|
{
|
||||||
|
u16 orig_block = ofs >> bmtd.blk_shift;
|
||||||
|
int block;
|
||||||
|
|
||||||
|
block = bmtd.ops->get_mapping_block(orig_block);
|
||||||
|
if (block < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
mtk_bmt_remap_block(orig_block, block, bmtd.blk_size);
|
||||||
|
|
||||||
|
return bmtd._block_markbad(mtd, (loff_t)block << bmtd.blk_shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mtk_bmt_replace_ops(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
bmtd._read_oob = mtd->_read_oob;
|
||||||
|
bmtd._write_oob = mtd->_write_oob;
|
||||||
|
bmtd._erase = mtd->_erase;
|
||||||
|
bmtd._block_isbad = mtd->_block_isbad;
|
||||||
|
bmtd._block_markbad = mtd->_block_markbad;
|
||||||
|
|
||||||
|
mtd->_read_oob = mtk_bmt_read;
|
||||||
|
mtd->_write_oob = mtk_bmt_write;
|
||||||
|
mtd->_erase = mtk_bmt_mtd_erase;
|
||||||
|
mtd->_block_isbad = mtk_bmt_block_isbad;
|
||||||
|
mtd->_block_markbad = mtk_bmt_block_markbad;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtk_bmt_debug_repair(void *data, u64 val)
|
||||||
|
{
|
||||||
|
int block = val >> bmtd.blk_shift;
|
||||||
|
int prev_block, new_block;
|
||||||
|
|
||||||
|
prev_block = bmtd.ops->get_mapping_block(block);
|
||||||
|
if (prev_block < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
bmtd.ops->unmap_block(block);
|
||||||
|
new_block = bmtd.ops->get_mapping_block(block);
|
||||||
|
if (new_block < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
if (prev_block == new_block)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bbt_nand_erase(new_block);
|
||||||
|
bbt_nand_copy(new_block, prev_block, bmtd.blk_size);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtk_bmt_debug_mark_good(void *data, u64 val)
|
||||||
|
{
|
||||||
|
bmtd.ops->unmap_block(val >> bmtd.blk_shift);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtk_bmt_debug_mark_bad(void *data, u64 val)
|
||||||
|
{
|
||||||
|
u32 block = val >> bmtd.blk_shift;
|
||||||
|
int cur_block;
|
||||||
|
|
||||||
|
cur_block = bmtd.ops->get_mapping_block(block);
|
||||||
|
if (cur_block < 0)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
mtk_bmt_remap_block(block, cur_block, bmtd.blk_size);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtk_bmt_debug(void *data, u64 val)
|
||||||
|
{
|
||||||
|
return bmtd.ops->debug(data, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DEFINE_DEBUGFS_ATTRIBUTE(fops_repair, NULL, mtk_bmt_debug_repair, "%llu\n");
|
||||||
|
DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_good, NULL, mtk_bmt_debug_mark_good, "%llu\n");
|
||||||
|
DEFINE_DEBUGFS_ATTRIBUTE(fops_mark_bad, NULL, mtk_bmt_debug_mark_bad, "%llu\n");
|
||||||
|
DEFINE_DEBUGFS_ATTRIBUTE(fops_debug, NULL, mtk_bmt_debug, "%llu\n");
|
||||||
|
|
||||||
|
static void
|
||||||
|
mtk_bmt_add_debugfs(void)
|
||||||
|
{
|
||||||
|
struct dentry *dir;
|
||||||
|
|
||||||
|
dir = bmtd.debugfs_dir = debugfs_create_dir("mtk-bmt", NULL);
|
||||||
|
if (!dir)
|
||||||
|
return;
|
||||||
|
|
||||||
|
debugfs_create_file_unsafe("repair", S_IWUSR, dir, NULL, &fops_repair);
|
||||||
|
debugfs_create_file_unsafe("mark_good", S_IWUSR, dir, NULL, &fops_mark_good);
|
||||||
|
debugfs_create_file_unsafe("mark_bad", S_IWUSR, dir, NULL, &fops_mark_bad);
|
||||||
|
debugfs_create_file_unsafe("debug", S_IWUSR, dir, NULL, &fops_debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mtk_bmt_detach(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
if (bmtd.mtd != mtd)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (bmtd.debugfs_dir)
|
||||||
|
debugfs_remove_recursive(bmtd.debugfs_dir);
|
||||||
|
bmtd.debugfs_dir = NULL;
|
||||||
|
|
||||||
|
kfree(bmtd.bbt_buf);
|
||||||
|
kfree(bmtd.data_buf);
|
||||||
|
|
||||||
|
mtd->_read_oob = bmtd._read_oob;
|
||||||
|
mtd->_write_oob = bmtd._write_oob;
|
||||||
|
mtd->_erase = bmtd._erase;
|
||||||
|
mtd->_block_isbad = bmtd._block_isbad;
|
||||||
|
mtd->_block_markbad = bmtd._block_markbad;
|
||||||
|
mtd->size = bmtd.total_blks << bmtd.blk_shift;
|
||||||
|
|
||||||
|
memset(&bmtd, 0, sizeof(bmtd));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int mtk_bmt_attach(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
struct device_node *np;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (bmtd.mtd)
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
|
np = mtd_get_of_node(mtd);
|
||||||
|
if (!np)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (of_property_read_bool(np, "mediatek,bmt-v2"))
|
||||||
|
bmtd.ops = &mtk_bmt_v2_ops;
|
||||||
|
else if (of_property_read_bool(np, "mediatek,nmbm"))
|
||||||
|
bmtd.ops = &mtk_bmt_nmbm_ops;
|
||||||
|
else if (of_property_read_bool(np, "mediatek,bbt"))
|
||||||
|
bmtd.ops = &mtk_bmt_bbt_ops;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bmtd.remap_range = of_get_property(np, "mediatek,bmt-remap-range",
|
||||||
|
&bmtd.remap_range_len);
|
||||||
|
bmtd.remap_range_len /= 8;
|
||||||
|
|
||||||
|
bmtd.mtd = mtd;
|
||||||
|
mtk_bmt_replace_ops(mtd);
|
||||||
|
|
||||||
|
bmtd.blk_size = mtd->erasesize;
|
||||||
|
bmtd.blk_shift = ffs(bmtd.blk_size) - 1;
|
||||||
|
bmtd.pg_size = mtd->writesize;
|
||||||
|
bmtd.pg_shift = ffs(bmtd.pg_size) - 1;
|
||||||
|
bmtd.total_blks = mtd->size >> bmtd.blk_shift;
|
||||||
|
|
||||||
|
bmtd.data_buf = kzalloc(bmtd.pg_size + bmtd.mtd->oobsize, GFP_KERNEL);
|
||||||
|
if (!bmtd.data_buf) {
|
||||||
|
pr_info("nand: FATAL ERR: allocate buffer failed!\n");
|
||||||
|
ret = -1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(bmtd.data_buf, 0xff, bmtd.pg_size + bmtd.mtd->oobsize);
|
||||||
|
|
||||||
|
ret = bmtd.ops->init(np);
|
||||||
|
if (ret)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
mtk_bmt_add_debugfs();
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
mtk_bmt_detach(mtd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Xiangsheng Hou <xiangsheng.hou@mediatek.com>, Felix Fietkau <nbd@nbd.name>");
|
||||||
|
MODULE_DESCRIPTION("Bad Block mapping management v2 for MediaTek NAND Flash Driver");
|
||||||
|
|
137
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt.h
Normal file
137
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt.h
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
#ifndef __MTK_BMT_PRIV_H
|
||||||
|
#define __MTK_BMT_PRIV_H
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/mtd/mtk_bmt.h>
|
||||||
|
#include <linux/debugfs.h>
|
||||||
|
|
||||||
|
#define MAIN_SIGNATURE_OFFSET 0
|
||||||
|
#define OOB_SIGNATURE_OFFSET 1
|
||||||
|
|
||||||
|
#define BBT_LOG(fmt, ...) pr_debug("[BBT][%s|%d] "fmt"\n", __func__, __LINE__, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
struct mtk_bmt_ops {
|
||||||
|
char *sig;
|
||||||
|
unsigned int sig_len;
|
||||||
|
int (*init)(struct device_node *np);
|
||||||
|
bool (*remap_block)(u16 block, u16 mapped_block, int copy_len);
|
||||||
|
void (*unmap_block)(u16 block);
|
||||||
|
int (*get_mapping_block)(int block);
|
||||||
|
int (*debug)(void *data, u64 val);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bbbt;
|
||||||
|
struct nmbm_instance;
|
||||||
|
|
||||||
|
struct bmt_desc {
|
||||||
|
struct mtd_info *mtd;
|
||||||
|
unsigned char *bbt_buf;
|
||||||
|
unsigned char *data_buf;
|
||||||
|
|
||||||
|
int (*_read_oob) (struct mtd_info *mtd, loff_t from,
|
||||||
|
struct mtd_oob_ops *ops);
|
||||||
|
int (*_write_oob) (struct mtd_info *mtd, loff_t to,
|
||||||
|
struct mtd_oob_ops *ops);
|
||||||
|
int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
|
||||||
|
int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
|
||||||
|
int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
|
||||||
|
|
||||||
|
const struct mtk_bmt_ops *ops;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct bbbt *bbt;
|
||||||
|
struct nmbm_instance *ni;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dentry *debugfs_dir;
|
||||||
|
|
||||||
|
u32 table_size;
|
||||||
|
u32 pg_size;
|
||||||
|
u32 blk_size;
|
||||||
|
u16 pg_shift;
|
||||||
|
u16 blk_shift;
|
||||||
|
/* bbt logical address */
|
||||||
|
u16 pool_lba;
|
||||||
|
/* bbt physical address */
|
||||||
|
u16 pool_pba;
|
||||||
|
/* Maximum count of bad blocks that the vendor guaranteed */
|
||||||
|
u16 bb_max;
|
||||||
|
/* Total blocks of the Nand Chip */
|
||||||
|
u16 total_blks;
|
||||||
|
/* The block(n) BMT is located at (bmt_tbl[n]) */
|
||||||
|
u16 bmt_blk_idx;
|
||||||
|
/* How many pages needs to store 'struct bbbt' */
|
||||||
|
u32 bmt_pgs;
|
||||||
|
|
||||||
|
const __be32 *remap_range;
|
||||||
|
int remap_range_len;
|
||||||
|
|
||||||
|
/* to compensate for driver level remapping */
|
||||||
|
u8 oob_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct bmt_desc bmtd;
|
||||||
|
extern const struct mtk_bmt_ops mtk_bmt_v2_ops;
|
||||||
|
extern const struct mtk_bmt_ops mtk_bmt_bbt_ops;
|
||||||
|
extern const struct mtk_bmt_ops mtk_bmt_nmbm_ops;
|
||||||
|
|
||||||
|
static inline u32 blk_pg(u16 block)
|
||||||
|
{
|
||||||
|
return (u32)(block << (bmtd.blk_shift - bmtd.pg_shift));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
bbt_nand_read(u32 page, unsigned char *dat, int dat_len,
|
||||||
|
unsigned char *fdm, int fdm_len)
|
||||||
|
{
|
||||||
|
struct mtd_oob_ops ops = {
|
||||||
|
.mode = MTD_OPS_PLACE_OOB,
|
||||||
|
.ooboffs = bmtd.oob_offset,
|
||||||
|
.oobbuf = fdm,
|
||||||
|
.ooblen = fdm_len,
|
||||||
|
.datbuf = dat,
|
||||||
|
.len = dat_len,
|
||||||
|
};
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = bmtd._read_oob(bmtd.mtd, page << bmtd.pg_shift, &ops);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret)
|
||||||
|
pr_info("%s: %d bitflips\n", __func__, ret);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int bbt_nand_erase(u16 block)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = bmtd.mtd;
|
||||||
|
struct erase_info instr = {
|
||||||
|
.addr = (loff_t)block << bmtd.blk_shift,
|
||||||
|
.len = bmtd.blk_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
return bmtd._erase(mtd, &instr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int write_bmt(u16 block, unsigned char *dat)
|
||||||
|
{
|
||||||
|
struct mtd_oob_ops ops = {
|
||||||
|
.mode = MTD_OPS_PLACE_OOB,
|
||||||
|
.ooboffs = OOB_SIGNATURE_OFFSET + bmtd.oob_offset,
|
||||||
|
.oobbuf = bmtd.ops->sig,
|
||||||
|
.ooblen = bmtd.ops->sig_len,
|
||||||
|
.datbuf = dat,
|
||||||
|
.len = bmtd.bmt_pgs << bmtd.pg_shift,
|
||||||
|
};
|
||||||
|
loff_t addr = (loff_t)block << bmtd.blk_shift;
|
||||||
|
|
||||||
|
return bmtd._write_oob(bmtd.mtd, addr, &ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bbt_nand_copy(u16 dest_blk, u16 src_blk, loff_t max_offset);
|
||||||
|
bool mapping_block_in_range(int block, int *start, int *end);
|
||||||
|
|
||||||
|
#endif
|
203
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt_bbt.c
Normal file
203
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt_bbt.c
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2017 MediaTek Inc.
|
||||||
|
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
|
||||||
|
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include "mtk_bmt.h"
|
||||||
|
|
||||||
|
static bool
|
||||||
|
bbt_block_is_bad(u16 block)
|
||||||
|
{
|
||||||
|
u8 cur = bmtd.bbt_buf[block / 4];
|
||||||
|
|
||||||
|
return cur & (3 << ((block % 4) * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bbt_set_block_state(u16 block, bool bad)
|
||||||
|
{
|
||||||
|
u8 mask = (3 << ((block % 4) * 2));
|
||||||
|
|
||||||
|
if (bad)
|
||||||
|
bmtd.bbt_buf[block / 4] |= mask;
|
||||||
|
else
|
||||||
|
bmtd.bbt_buf[block / 4] &= ~mask;
|
||||||
|
|
||||||
|
bbt_nand_erase(bmtd.bmt_blk_idx);
|
||||||
|
write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
get_mapping_block_index_bbt(int block)
|
||||||
|
{
|
||||||
|
int start, end, ofs;
|
||||||
|
int bad_blocks = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!mapping_block_in_range(block, &start, &end))
|
||||||
|
return block;
|
||||||
|
|
||||||
|
start >>= bmtd.blk_shift;
|
||||||
|
end >>= bmtd.blk_shift;
|
||||||
|
/* skip bad blocks within the mapping range */
|
||||||
|
ofs = block - start;
|
||||||
|
for (i = start; i < end; i++) {
|
||||||
|
if (bbt_block_is_bad(i))
|
||||||
|
bad_blocks++;
|
||||||
|
else if (ofs)
|
||||||
|
ofs--;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < end)
|
||||||
|
return i;
|
||||||
|
|
||||||
|
/* when overflowing, remap remaining blocks to bad ones */
|
||||||
|
for (i = end - 1; bad_blocks > 0; i--) {
|
||||||
|
if (!bbt_block_is_bad(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bad_blocks--;
|
||||||
|
if (bad_blocks <= ofs)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool remap_block_bbt(u16 block, u16 mapped_blk, int copy_len)
|
||||||
|
{
|
||||||
|
int start, end;
|
||||||
|
u16 new_blk;
|
||||||
|
|
||||||
|
if (!mapping_block_in_range(block, &start, &end))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bbt_set_block_state(mapped_blk, true);
|
||||||
|
|
||||||
|
new_blk = get_mapping_block_index_bbt(block);
|
||||||
|
bbt_nand_erase(new_blk);
|
||||||
|
if (copy_len > 0)
|
||||||
|
bbt_nand_copy(new_blk, mapped_blk, copy_len);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unmap_block_bbt(u16 block)
|
||||||
|
{
|
||||||
|
bbt_set_block_state(block, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtk_bmt_read_bbt(void)
|
||||||
|
{
|
||||||
|
u8 oob_buf[8];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = bmtd.total_blks - 1; i >= bmtd.total_blks - 5; i--) {
|
||||||
|
u32 page = i << (bmtd.blk_shift - bmtd.pg_shift);
|
||||||
|
|
||||||
|
if (bbt_nand_read(page, bmtd.bbt_buf, bmtd.pg_size,
|
||||||
|
oob_buf, sizeof(oob_buf))) {
|
||||||
|
pr_info("read_bbt: could not read block %d\n", i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oob_buf[0] != 0xff) {
|
||||||
|
pr_info("read_bbt: bad block at %d\n", i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(&oob_buf[1], "mtknand", 7) != 0) {
|
||||||
|
pr_info("read_bbt: signature mismatch in block %d\n", i);
|
||||||
|
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, oob_buf, 8, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_info("read_bbt: found bbt at block %d\n", i);
|
||||||
|
bmtd.bmt_blk_idx = i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
mtk_bmt_init_bbt(struct device_node *np)
|
||||||
|
{
|
||||||
|
int buf_size = round_up(bmtd.total_blks >> 2, bmtd.blk_size);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
bmtd.bbt_buf = kmalloc(buf_size, GFP_KERNEL);
|
||||||
|
if (!bmtd.bbt_buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
memset(bmtd.bbt_buf, 0xff, buf_size);
|
||||||
|
bmtd.mtd->size -= 4 * bmtd.mtd->erasesize;
|
||||||
|
|
||||||
|
ret = mtk_bmt_read_bbt();
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
bmtd.bmt_pgs = buf_size / bmtd.pg_size;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtk_bmt_debug_bbt(void *data, u64 val)
|
||||||
|
{
|
||||||
|
char buf[5];
|
||||||
|
int i, k;
|
||||||
|
|
||||||
|
switch (val) {
|
||||||
|
case 0:
|
||||||
|
for (i = 0; i < bmtd.total_blks; i += 4) {
|
||||||
|
u8 cur = bmtd.bbt_buf[i / 4];
|
||||||
|
|
||||||
|
for (k = 0; k < 4; k++, cur >>= 2)
|
||||||
|
buf[k] = (cur & 3) ? 'B' : '.';
|
||||||
|
|
||||||
|
buf[4] = 0;
|
||||||
|
printk("[%06x] %s\n", i * bmtd.blk_size, buf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 100:
|
||||||
|
#if 0
|
||||||
|
for (i = bmtd.bmt_blk_idx; i < bmtd.total_blks - 1; i++)
|
||||||
|
bbt_nand_erase(bmtd.bmt_blk_idx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bmtd.bmt_blk_idx = bmtd.total_blks - 1;
|
||||||
|
bbt_nand_erase(bmtd.bmt_blk_idx);
|
||||||
|
write_bmt(bmtd.bmt_blk_idx, bmtd.bbt_buf);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct mtk_bmt_ops mtk_bmt_bbt_ops = {
|
||||||
|
.sig = "mtknand",
|
||||||
|
.sig_len = 7,
|
||||||
|
.init = mtk_bmt_init_bbt,
|
||||||
|
.remap_block = remap_block_bbt,
|
||||||
|
.unmap_block = unmap_block_bbt,
|
||||||
|
.get_mapping_block = get_mapping_block_index_bbt,
|
||||||
|
.debug = mtk_bmt_debug_bbt,
|
||||||
|
};
|
2348
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt_nmbm.c
Normal file
2348
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt_nmbm.c
Normal file
File diff suppressed because it is too large
Load diff
506
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt_v2.c
Normal file
506
6.12/target/linux/generic/files/drivers/mtd/nand/mtk_bmt_v2.c
Normal file
|
@ -0,0 +1,506 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2017 MediaTek Inc.
|
||||||
|
* Author: Xiangsheng Hou <xiangsheng.hou@mediatek.com>
|
||||||
|
* Copyright (c) 2020-2022 Felix Fietkau <nbd@nbd.name>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include "mtk_bmt.h"
|
||||||
|
|
||||||
|
struct bbbt {
|
||||||
|
char signature[3];
|
||||||
|
/* This version is used to distinguish the legacy and new algorithm */
|
||||||
|
#define BBMT_VERSION 2
|
||||||
|
unsigned char version;
|
||||||
|
/* Below 2 tables will be written in SLC */
|
||||||
|
u16 bb_tbl[];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bbmt {
|
||||||
|
u16 block;
|
||||||
|
#define NO_MAPPED 0
|
||||||
|
#define NORMAL_MAPPED 1
|
||||||
|
#define BMT_MAPPED 2
|
||||||
|
u16 mapped;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Maximum 8k blocks */
|
||||||
|
#define BBPOOL_RATIO 2
|
||||||
|
#define BB_TABLE_MAX bmtd.table_size
|
||||||
|
#define BMT_TABLE_MAX (BB_TABLE_MAX * BBPOOL_RATIO / 100)
|
||||||
|
#define BMT_TBL_DEF_VAL 0x0
|
||||||
|
|
||||||
|
static inline struct bbmt *bmt_tbl(struct bbbt *bbbt)
|
||||||
|
{
|
||||||
|
return (struct bbmt *)&bbbt->bb_tbl[bmtd.table_size];
|
||||||
|
}
|
||||||
|
|
||||||
|
static u16 find_valid_block(u16 block)
|
||||||
|
{
|
||||||
|
u8 fdm[4];
|
||||||
|
int ret;
|
||||||
|
int loop = 0;
|
||||||
|
|
||||||
|
retry:
|
||||||
|
if (block >= bmtd.total_blks)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ret = bbt_nand_read(blk_pg(block), bmtd.data_buf, bmtd.pg_size,
|
||||||
|
fdm, sizeof(fdm));
|
||||||
|
/* Read the 1st byte of FDM to judge whether it's a bad
|
||||||
|
* or not
|
||||||
|
*/
|
||||||
|
if (ret || fdm[0] != 0xff) {
|
||||||
|
pr_info("nand: found bad block 0x%x\n", block);
|
||||||
|
if (loop >= bmtd.bb_max) {
|
||||||
|
pr_info("nand: FATAL ERR: too many bad blocks!!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop++;
|
||||||
|
block++;
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find out all bad blocks, and fill in the mapping table */
|
||||||
|
static int scan_bad_blocks(struct bbbt *bbt)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u16 block = 0;
|
||||||
|
|
||||||
|
/* First time download, the block0 MUST NOT be a bad block,
|
||||||
|
* this is guaranteed by vendor
|
||||||
|
*/
|
||||||
|
bbt->bb_tbl[0] = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct the mapping table of Normal data area(non-PMT/BMTPOOL)
|
||||||
|
* G - Good block; B - Bad block
|
||||||
|
* ---------------------------
|
||||||
|
* physical |G|G|B|G|B|B|G|G|G|G|B|G|B|
|
||||||
|
* ---------------------------
|
||||||
|
* What bb_tbl[i] looks like:
|
||||||
|
* physical block(i):
|
||||||
|
* 0 1 2 3 4 5 6 7 8 9 a b c
|
||||||
|
* mapped block(bb_tbl[i]):
|
||||||
|
* 0 1 3 6 7 8 9 b ......
|
||||||
|
* ATTENTION:
|
||||||
|
* If new bad block ocurred(n), search bmt_tbl to find
|
||||||
|
* a available block(x), and fill in the bb_tbl[n] = x;
|
||||||
|
*/
|
||||||
|
for (i = 1; i < bmtd.pool_lba; i++) {
|
||||||
|
bbt->bb_tbl[i] = find_valid_block(bbt->bb_tbl[i - 1] + 1);
|
||||||
|
BBT_LOG("bb_tbl[0x%x] = 0x%x", i, bbt->bb_tbl[i]);
|
||||||
|
if (bbt->bb_tbl[i] == 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Physical Block start Address of BMT pool */
|
||||||
|
bmtd.pool_pba = bbt->bb_tbl[i - 1] + 1;
|
||||||
|
if (bmtd.pool_pba >= bmtd.total_blks - 2) {
|
||||||
|
pr_info("nand: FATAL ERR: Too many bad blocks!!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
BBT_LOG("pool_pba=0x%x", bmtd.pool_pba);
|
||||||
|
i = 0;
|
||||||
|
block = bmtd.pool_pba;
|
||||||
|
/*
|
||||||
|
* The bmt table is used for runtime bad block mapping
|
||||||
|
* G - Good block; B - Bad block
|
||||||
|
* ---------------------------
|
||||||
|
* physical |G|G|B|G|B|B|G|G|G|G|B|G|B|
|
||||||
|
* ---------------------------
|
||||||
|
* block: 0 1 2 3 4 5 6 7 8 9 a b c
|
||||||
|
* What bmt_tbl[i] looks like in initial state:
|
||||||
|
* i:
|
||||||
|
* 0 1 2 3 4 5 6 7
|
||||||
|
* bmt_tbl[i].block:
|
||||||
|
* 0 1 3 6 7 8 9 b
|
||||||
|
* bmt_tbl[i].mapped:
|
||||||
|
* N N N N N N N B
|
||||||
|
* N - Not mapped(Available)
|
||||||
|
* M - Mapped
|
||||||
|
* B - BMT
|
||||||
|
* ATTENTION:
|
||||||
|
* BMT always in the last valid block in pool
|
||||||
|
*/
|
||||||
|
while ((block = find_valid_block(block)) != 0) {
|
||||||
|
bmt_tbl(bbt)[i].block = block;
|
||||||
|
bmt_tbl(bbt)[i].mapped = NO_MAPPED;
|
||||||
|
BBT_LOG("bmt_tbl[%d].block = 0x%x", i, block);
|
||||||
|
block++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* i - How many available blocks in pool, which is the length of bmt_tbl[]
|
||||||
|
* bmtd.bmt_blk_idx - bmt_tbl[bmtd.bmt_blk_idx].block => the BMT block
|
||||||
|
*/
|
||||||
|
bmtd.bmt_blk_idx = i - 1;
|
||||||
|
bmt_tbl(bbt)[bmtd.bmt_blk_idx].mapped = BMT_MAPPED;
|
||||||
|
|
||||||
|
if (i < 1) {
|
||||||
|
pr_info("nand: FATAL ERR: no space to store BMT!!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_info("[BBT] %d available blocks in BMT pool\n", i);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_valid_bmt(unsigned char *buf, unsigned char *fdm)
|
||||||
|
{
|
||||||
|
struct bbbt *bbt = (struct bbbt *)buf;
|
||||||
|
u8 *sig = (u8*)bbt->signature + MAIN_SIGNATURE_OFFSET;
|
||||||
|
|
||||||
|
|
||||||
|
if (memcmp(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3) == 0 &&
|
||||||
|
memcmp(fdm + OOB_SIGNATURE_OFFSET, "bmt", 3) == 0) {
|
||||||
|
if (bbt->version == BBMT_VERSION)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
BBT_LOG("[BBT] BMT Version not match,upgrage preloader and uboot please! sig=%02x%02x%02x, fdm=%02x%02x%02x",
|
||||||
|
sig[0], sig[1], sig[2],
|
||||||
|
fdm[1], fdm[2], fdm[3]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u16 get_bmt_index(struct bbmt *bmt)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (bmt[i].block != BMT_TBL_DEF_VAL) {
|
||||||
|
if (bmt[i].mapped == BMT_MAPPED)
|
||||||
|
return i;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the Burner Bad Block Table to Nand Flash
|
||||||
|
* n - write BMT to bmt_tbl[n]
|
||||||
|
*/
|
||||||
|
static u16 upload_bmt(struct bbbt *bbt, int n)
|
||||||
|
{
|
||||||
|
u16 block;
|
||||||
|
|
||||||
|
retry:
|
||||||
|
if (n < 0 || bmt_tbl(bbt)[n].mapped == NORMAL_MAPPED) {
|
||||||
|
pr_info("nand: FATAL ERR: no space to store BMT!\n");
|
||||||
|
return (u16)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
block = bmt_tbl(bbt)[n].block;
|
||||||
|
BBT_LOG("n = 0x%x, block = 0x%x", n, block);
|
||||||
|
if (bbt_nand_erase(block)) {
|
||||||
|
bmt_tbl(bbt)[n].block = 0;
|
||||||
|
/* erase failed, try the previous block: bmt_tbl[n - 1].block */
|
||||||
|
n--;
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The signature offset is fixed set to 0,
|
||||||
|
* oob signature offset is fixed set to 1
|
||||||
|
*/
|
||||||
|
memcpy(bbt->signature + MAIN_SIGNATURE_OFFSET, "BMT", 3);
|
||||||
|
bbt->version = BBMT_VERSION;
|
||||||
|
|
||||||
|
if (write_bmt(block, (unsigned char *)bbt)) {
|
||||||
|
bmt_tbl(bbt)[n].block = 0;
|
||||||
|
|
||||||
|
/* write failed, try the previous block in bmt_tbl[n - 1] */
|
||||||
|
n--;
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the current index(n) of BMT pool (bmt_tbl[n]) */
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u16 find_valid_block_in_pool(struct bbbt *bbt)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (bmtd.bmt_blk_idx == 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
for (i = 0; i < bmtd.bmt_blk_idx; i++) {
|
||||||
|
if (bmt_tbl(bbt)[i].block != 0 && bmt_tbl(bbt)[i].mapped == NO_MAPPED) {
|
||||||
|
bmt_tbl(bbt)[i].mapped = NORMAL_MAPPED;
|
||||||
|
return bmt_tbl(bbt)[i].block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error:
|
||||||
|
pr_info("nand: FATAL ERR: BMT pool is run out!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We met a bad block, mark it as bad and map it to a valid block in pool,
|
||||||
|
* if it's a write failure, we need to write the data to mapped block
|
||||||
|
*/
|
||||||
|
static bool remap_block_v2(u16 block, u16 mapped_block, int copy_len)
|
||||||
|
{
|
||||||
|
u16 new_block;
|
||||||
|
struct bbbt *bbt;
|
||||||
|
|
||||||
|
bbt = bmtd.bbt;
|
||||||
|
new_block = find_valid_block_in_pool(bbt);
|
||||||
|
if (new_block == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Map new bad block to available block in pool */
|
||||||
|
bbt->bb_tbl[block] = new_block;
|
||||||
|
|
||||||
|
/* Erase new block */
|
||||||
|
bbt_nand_erase(new_block);
|
||||||
|
if (copy_len > 0)
|
||||||
|
bbt_nand_copy(new_block, mapped_block, copy_len);
|
||||||
|
|
||||||
|
bmtd.bmt_blk_idx = upload_bmt(bbt, bmtd.bmt_blk_idx);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_mapping_block_index_v2(int block)
|
||||||
|
{
|
||||||
|
int start, end;
|
||||||
|
|
||||||
|
if (block >= bmtd.pool_lba)
|
||||||
|
return block;
|
||||||
|
|
||||||
|
if (!mapping_block_in_range(block, &start, &end))
|
||||||
|
return block;
|
||||||
|
|
||||||
|
return bmtd.bbt->bb_tbl[block];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unmap_block_v2(u16 block)
|
||||||
|
{
|
||||||
|
bmtd.bbt->bb_tbl[block] = block;
|
||||||
|
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long *
|
||||||
|
mtk_bmt_get_mapping_mask(void)
|
||||||
|
{
|
||||||
|
struct bbmt *bbmt = bmt_tbl(bmtd.bbt);
|
||||||
|
int main_blocks = bmtd.mtd->size >> bmtd.blk_shift;
|
||||||
|
unsigned long *used;
|
||||||
|
int i, k;
|
||||||
|
|
||||||
|
used = kcalloc(sizeof(unsigned long), BIT_WORD(bmtd.bmt_blk_idx) + 1, GFP_KERNEL);
|
||||||
|
if (!used)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (i = 1; i < main_blocks; i++) {
|
||||||
|
if (bmtd.bbt->bb_tbl[i] == i)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (k = 0; k < bmtd.bmt_blk_idx; k++) {
|
||||||
|
if (bmtd.bbt->bb_tbl[i] != bbmt[k].block)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
set_bit(k, used);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return used;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtk_bmt_debug_v2(void *data, u64 val)
|
||||||
|
{
|
||||||
|
struct bbmt *bbmt = bmt_tbl(bmtd.bbt);
|
||||||
|
struct mtd_info *mtd = bmtd.mtd;
|
||||||
|
unsigned long *used;
|
||||||
|
int main_blocks = mtd->size >> bmtd.blk_shift;
|
||||||
|
int n_remap = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
used = mtk_bmt_get_mapping_mask();
|
||||||
|
if (!used)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
switch (val) {
|
||||||
|
case 0:
|
||||||
|
for (i = 1; i < main_blocks; i++) {
|
||||||
|
if (bmtd.bbt->bb_tbl[i] == i)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
printk("remap [%x->%x]\n", i, bmtd.bbt->bb_tbl[i]);
|
||||||
|
n_remap++;
|
||||||
|
}
|
||||||
|
for (i = 0; i <= bmtd.bmt_blk_idx; i++) {
|
||||||
|
char c;
|
||||||
|
|
||||||
|
switch (bbmt[i].mapped) {
|
||||||
|
case NO_MAPPED:
|
||||||
|
continue;
|
||||||
|
case NORMAL_MAPPED:
|
||||||
|
c = 'm';
|
||||||
|
if (test_bit(i, used))
|
||||||
|
c = 'M';
|
||||||
|
break;
|
||||||
|
case BMT_MAPPED:
|
||||||
|
c = 'B';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
c = 'X';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printk("[%x:%c] = 0x%x\n", i, c, bbmt[i].block);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 100:
|
||||||
|
for (i = 0; i <= bmtd.bmt_blk_idx; i++) {
|
||||||
|
if (bbmt[i].mapped != NORMAL_MAPPED)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (test_bit(i, used))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
n_remap++;
|
||||||
|
bbmt[i].mapped = NO_MAPPED;
|
||||||
|
printk("free block [%d:%x]\n", i, bbmt[i].block);
|
||||||
|
}
|
||||||
|
if (n_remap)
|
||||||
|
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree(used);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtk_bmt_init_v2(struct device_node *np)
|
||||||
|
{
|
||||||
|
u32 bmt_pool_size, bmt_table_size;
|
||||||
|
u32 bufsz, block;
|
||||||
|
u16 pmt_block;
|
||||||
|
|
||||||
|
if (of_property_read_u32(np, "mediatek,bmt-pool-size",
|
||||||
|
&bmt_pool_size) != 0)
|
||||||
|
bmt_pool_size = 80;
|
||||||
|
|
||||||
|
if (of_property_read_u8(np, "mediatek,bmt-oob-offset",
|
||||||
|
&bmtd.oob_offset) != 0)
|
||||||
|
bmtd.oob_offset = 0;
|
||||||
|
|
||||||
|
if (of_property_read_u32(np, "mediatek,bmt-table-size",
|
||||||
|
&bmt_table_size) != 0)
|
||||||
|
bmt_table_size = 0x2000U;
|
||||||
|
|
||||||
|
bmtd.table_size = bmt_table_size;
|
||||||
|
|
||||||
|
pmt_block = bmtd.total_blks - bmt_pool_size - 2;
|
||||||
|
|
||||||
|
bmtd.mtd->size = pmt_block << bmtd.blk_shift;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ---------------------------------------
|
||||||
|
* | PMT(2blks) | BMT POOL(totalblks * 2%) |
|
||||||
|
* ---------------------------------------
|
||||||
|
* ^ ^
|
||||||
|
* | |
|
||||||
|
* pmt_block pmt_block + 2blocks(pool_lba)
|
||||||
|
*
|
||||||
|
* ATTETION!!!!!!
|
||||||
|
* The blocks ahead of the boundary block are stored in bb_tbl
|
||||||
|
* and blocks behind are stored in bmt_tbl
|
||||||
|
*/
|
||||||
|
|
||||||
|
bmtd.pool_lba = (u16)(pmt_block + 2);
|
||||||
|
bmtd.bb_max = bmtd.total_blks * BBPOOL_RATIO / 100;
|
||||||
|
|
||||||
|
bufsz = round_up(sizeof(struct bbbt) +
|
||||||
|
bmt_table_size * sizeof(struct bbmt), bmtd.pg_size);
|
||||||
|
bmtd.bmt_pgs = bufsz >> bmtd.pg_shift;
|
||||||
|
|
||||||
|
bmtd.bbt_buf = kzalloc(bufsz, GFP_KERNEL);
|
||||||
|
if (!bmtd.bbt_buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
memset(bmtd.bbt_buf, 0xff, bufsz);
|
||||||
|
|
||||||
|
/* Scanning start from the first page of the last block
|
||||||
|
* of whole flash
|
||||||
|
*/
|
||||||
|
bmtd.bbt = NULL;
|
||||||
|
for (u16 block = bmtd.total_blks - 1; !bmtd.bbt && block >= bmtd.pool_lba; block--) {
|
||||||
|
u8 fdm[4];
|
||||||
|
|
||||||
|
if (bbt_nand_read(blk_pg(block), bmtd.bbt_buf, bufsz, fdm, sizeof(fdm))) {
|
||||||
|
/* Read failed, try the previous block */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_valid_bmt(bmtd.bbt_buf, fdm)) {
|
||||||
|
/* No valid BMT found, try the previous block */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bmtd.bmt_blk_idx = get_bmt_index(bmt_tbl((struct bbbt *)bmtd.bbt_buf));
|
||||||
|
if (bmtd.bmt_blk_idx == 0) {
|
||||||
|
pr_info("[BBT] FATAL ERR: bmt block index is wrong!\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_info("[BBT] BMT.v2 is found at 0x%x\n", block);
|
||||||
|
bmtd.bbt = (struct bbbt *)bmtd.bbt_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bmtd.bbt) {
|
||||||
|
/* BMT not found */
|
||||||
|
if (bmtd.total_blks > BB_TABLE_MAX + BMT_TABLE_MAX) {
|
||||||
|
pr_info("nand: FATAL: Too many blocks, can not support!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bmtd.bbt = (struct bbbt *)bmtd.bbt_buf;
|
||||||
|
memset(bmt_tbl(bmtd.bbt), BMT_TBL_DEF_VAL,
|
||||||
|
bmtd.table_size * sizeof(struct bbmt));
|
||||||
|
|
||||||
|
if (scan_bad_blocks(bmtd.bbt))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* BMT always in the last valid block in pool */
|
||||||
|
bmtd.bmt_blk_idx = upload_bmt(bmtd.bbt, bmtd.bmt_blk_idx);
|
||||||
|
block = bmt_tbl(bmtd.bbt)[bmtd.bmt_blk_idx].block;
|
||||||
|
pr_notice("[BBT] BMT.v2 is written into PBA:0x%x\n", block);
|
||||||
|
|
||||||
|
if (bmtd.bmt_blk_idx == 0)
|
||||||
|
pr_info("nand: Warning: no available block in BMT pool!\n");
|
||||||
|
else if (bmtd.bmt_blk_idx == (u16)-1)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct mtk_bmt_ops mtk_bmt_v2_ops = {
|
||||||
|
.sig = "bmt",
|
||||||
|
.sig_len = 3,
|
||||||
|
.init = mtk_bmt_init_v2,
|
||||||
|
.remap_block = remap_block_v2,
|
||||||
|
.unmap_block = unmap_block_v2,
|
||||||
|
.get_mapping_block = get_mapping_block_index_v2,
|
||||||
|
.debug = mtk_bmt_debug_v2,
|
||||||
|
};
|
|
@ -0,0 +1,365 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Parser for MikroTik RouterBoot partitions.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This parser builds from the "fixed-partitions" one (see ofpart.c), but it can
|
||||||
|
* handle dynamic partitions as found on routerboot devices.
|
||||||
|
*
|
||||||
|
* DTS nodes are defined as follows:
|
||||||
|
* For fixed partitions:
|
||||||
|
* node-name@unit-address {
|
||||||
|
* reg = <prop-encoded-array>;
|
||||||
|
* label = <string>;
|
||||||
|
* read-only;
|
||||||
|
* lock;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* reg property is mandatory; other properties are optional.
|
||||||
|
* reg format is <address length>. length can be 0 if the next partition is
|
||||||
|
* another fixed partition or a "well-known" partition as defined below: in that
|
||||||
|
* case the partition will extend up to the next one.
|
||||||
|
*
|
||||||
|
* For dynamic partitions:
|
||||||
|
* node-name {
|
||||||
|
* size = <prop-encoded-array>;
|
||||||
|
* label = <string>;
|
||||||
|
* read-only;
|
||||||
|
* lock;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* size property is normally mandatory. It can only be omitted (or set to 0) if:
|
||||||
|
* - the partition is a "well-known" one (as defined below), in which case
|
||||||
|
* the partition size will be automatically adjusted; or
|
||||||
|
* - the next partition is a fixed one or a "well-known" one, in which case
|
||||||
|
* the current partition will extend up to the next one.
|
||||||
|
* Other properties are optional.
|
||||||
|
* size format is <length>.
|
||||||
|
* By default dynamic partitions are appended after the preceding one, except
|
||||||
|
* for "well-known" ones which are automatically located on flash.
|
||||||
|
*
|
||||||
|
* Well-known partitions (matched via label or node-name):
|
||||||
|
* - "hard_config"
|
||||||
|
* - "soft_config"
|
||||||
|
* - "dtb_config"
|
||||||
|
*
|
||||||
|
* Note: this parser will happily register 0-sized partitions if misused.
|
||||||
|
*
|
||||||
|
* This parser requires the DTS to list partitions in ascending order as
|
||||||
|
* expected on the MTD device.
|
||||||
|
*
|
||||||
|
* Since only the "hard_config" and "soft_config" partitions are used in OpenWRT,
|
||||||
|
* a minimal working DTS could define only these two partitions dynamically (in
|
||||||
|
* the right order, usually hard_config then soft_config).
|
||||||
|
*
|
||||||
|
* Note: some mips RB devices encode the hard_config offset and length in two
|
||||||
|
* consecutive u32 located at offset 0x14 (for ramips) or 0x24 (for ath79) on
|
||||||
|
* the SPI NOR flash. Unfortunately this seems inconsistent across machines and
|
||||||
|
* does not apply to e.g. ipq-based ones, so we ignore that information.
|
||||||
|
*
|
||||||
|
* Note: To find well-known partitions, this parser will go through the entire
|
||||||
|
* top mtd partition parsed, _before_ the DTS nodes are processed. This works
|
||||||
|
* well in the current state of affairs, and is a simpler implementation than
|
||||||
|
* searching for known partitions in the "holes" left between fixed-partition,
|
||||||
|
* _after_ processing DTS nodes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_fdt.h>
|
||||||
|
#include <linux/libfdt_env.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
|
||||||
|
#define RB_MAGIC_HARD (('H') | ('a' << 8) | ('r' << 16) | ('d' << 24))
|
||||||
|
#define RB_MAGIC_SOFT (('S') | ('o' << 8) | ('f' << 16) | ('t' << 24))
|
||||||
|
#define RB_BLOCK_SIZE 0x1000
|
||||||
|
|
||||||
|
struct routerboot_dynpart {
|
||||||
|
const char * const name;
|
||||||
|
const u32 magic;
|
||||||
|
int (* const size_fixup)(struct mtd_info *, struct routerboot_dynpart *);
|
||||||
|
size_t offset;
|
||||||
|
size_t size;
|
||||||
|
bool found;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int routerboot_dtbsfixup(struct mtd_info *, struct routerboot_dynpart *);
|
||||||
|
|
||||||
|
static struct routerboot_dynpart rb_dynparts[] = {
|
||||||
|
{
|
||||||
|
.name = "hard_config",
|
||||||
|
.magic = RB_MAGIC_HARD, // stored in CPU-endianness on flash
|
||||||
|
.size_fixup = NULL,
|
||||||
|
.offset = 0x0,
|
||||||
|
.size = RB_BLOCK_SIZE,
|
||||||
|
.found = false,
|
||||||
|
}, {
|
||||||
|
.name = "soft_config",
|
||||||
|
.magic = RB_MAGIC_SOFT, // stored in CPU-endianness on flash
|
||||||
|
.size_fixup = NULL,
|
||||||
|
.offset = 0x0,
|
||||||
|
.size = RB_BLOCK_SIZE,
|
||||||
|
.found = false,
|
||||||
|
}, {
|
||||||
|
.name = "dtb_config",
|
||||||
|
.magic = fdt32_to_cpu(OF_DT_HEADER), // stored BE on flash
|
||||||
|
.size_fixup = routerboot_dtbsfixup,
|
||||||
|
.offset = 0x0,
|
||||||
|
.size = 0x0,
|
||||||
|
.found = false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int routerboot_dtbsfixup(struct mtd_info *master, struct routerboot_dynpart *rbdpart)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
size_t bytes_read, psize;
|
||||||
|
struct {
|
||||||
|
fdt32_t magic;
|
||||||
|
fdt32_t totalsize;
|
||||||
|
fdt32_t off_dt_struct;
|
||||||
|
fdt32_t off_dt_strings;
|
||||||
|
fdt32_t off_mem_rsvmap;
|
||||||
|
fdt32_t version;
|
||||||
|
fdt32_t last_comp_version;
|
||||||
|
fdt32_t boot_cpuid_phys;
|
||||||
|
fdt32_t size_dt_strings;
|
||||||
|
fdt32_t size_dt_struct;
|
||||||
|
} fdt_header;
|
||||||
|
|
||||||
|
err = mtd_read(master, rbdpart->offset, sizeof(fdt_header),
|
||||||
|
&bytes_read, (u8 *)&fdt_header);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (bytes_read != sizeof(fdt_header))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
psize = fdt32_to_cpu(fdt_header.totalsize);
|
||||||
|
if (!psize)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
rbdpart->size = psize;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void routerboot_find_dynparts(struct mtd_info *master)
|
||||||
|
{
|
||||||
|
size_t bytes_read, offset;
|
||||||
|
bool allfound;
|
||||||
|
int err, i;
|
||||||
|
u32 buf;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dynamic RouterBoot partitions offsets are aligned to RB_BLOCK_SIZE:
|
||||||
|
* read the whole partition at RB_BLOCK_SIZE intervals to find sigs.
|
||||||
|
* Skip partition content when possible.
|
||||||
|
*/
|
||||||
|
offset = 0;
|
||||||
|
while (offset < master->size) {
|
||||||
|
err = mtd_read(master, offset, sizeof(buf), &bytes_read, (u8 *)&buf);
|
||||||
|
if (err) {
|
||||||
|
pr_err("%s: mtd_read error while parsing (offset: 0x%zX): %d\n",
|
||||||
|
master->name, offset, err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
allfound = true;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(rb_dynparts); i++) {
|
||||||
|
if (rb_dynparts[i].found)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
allfound = false;
|
||||||
|
|
||||||
|
if (rb_dynparts[i].magic == buf) {
|
||||||
|
rb_dynparts[i].offset = offset;
|
||||||
|
|
||||||
|
if (rb_dynparts[i].size_fixup) {
|
||||||
|
err = rb_dynparts[i].size_fixup(master, &rb_dynparts[i]);
|
||||||
|
if (err) {
|
||||||
|
pr_err("%s: size fixup error while parsing \"%s\": %d\n",
|
||||||
|
master->name, rb_dynparts[i].name, err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_dynparts[i].found = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* move offset to skip the whole partition on
|
||||||
|
* next iteration if size > RB_BLOCK_SIZE.
|
||||||
|
*/
|
||||||
|
if (rb_dynparts[i].size > RB_BLOCK_SIZE)
|
||||||
|
offset += ALIGN_DOWN((rb_dynparts[i].size - RB_BLOCK_SIZE), RB_BLOCK_SIZE);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += RB_BLOCK_SIZE;
|
||||||
|
|
||||||
|
if (allfound)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int routerboot_partitions_parse(struct mtd_info *master,
|
||||||
|
const struct mtd_partition **pparts,
|
||||||
|
struct mtd_part_parser_data *data)
|
||||||
|
{
|
||||||
|
struct device_node *rbpart_node, *pp;
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
const char *partname;
|
||||||
|
size_t master_ofs;
|
||||||
|
int np;
|
||||||
|
|
||||||
|
/* Pull of_node from the master device node */
|
||||||
|
rbpart_node = mtd_get_of_node(master);
|
||||||
|
if (!rbpart_node)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* First count the subnodes */
|
||||||
|
np = 0;
|
||||||
|
for_each_child_of_node(rbpart_node, pp)
|
||||||
|
np++;
|
||||||
|
|
||||||
|
if (!np)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
parts = kcalloc(np, sizeof(*parts), GFP_KERNEL);
|
||||||
|
if (!parts)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Preemptively look for known parts in flash */
|
||||||
|
routerboot_find_dynparts(master);
|
||||||
|
|
||||||
|
np = 0;
|
||||||
|
master_ofs = 0;
|
||||||
|
for_each_child_of_node(rbpart_node, pp) {
|
||||||
|
const __be32 *reg, *sz;
|
||||||
|
size_t offset, size;
|
||||||
|
int i, len, a_cells, s_cells;
|
||||||
|
|
||||||
|
partname = of_get_property(pp, "label", &len);
|
||||||
|
/* Allow deprecated use of "name" instead of "label" */
|
||||||
|
if (!partname)
|
||||||
|
partname = of_get_property(pp, "name", &len);
|
||||||
|
/* Fallback to node name per spec if all else fails: partname is always set */
|
||||||
|
if (!partname)
|
||||||
|
partname = pp->name;
|
||||||
|
parts[np].name = partname;
|
||||||
|
|
||||||
|
reg = of_get_property(pp, "reg", &len);
|
||||||
|
if (reg) {
|
||||||
|
/* Fixed partition */
|
||||||
|
a_cells = of_n_addr_cells(pp);
|
||||||
|
s_cells = of_n_size_cells(pp);
|
||||||
|
|
||||||
|
if ((len / 4) != (a_cells + s_cells)) {
|
||||||
|
pr_debug("%s: routerboot partition %pOF (%pOF) error parsing reg property.\n",
|
||||||
|
master->name, pp, rbpart_node);
|
||||||
|
goto rbpart_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = of_read_number(reg, a_cells);
|
||||||
|
size = of_read_number(reg + a_cells, s_cells);
|
||||||
|
} else {
|
||||||
|
/* Dynamic partition */
|
||||||
|
/* Default: part starts at current offset, 0 size */
|
||||||
|
offset = master_ofs;
|
||||||
|
size = 0;
|
||||||
|
|
||||||
|
/* Check if well-known partition */
|
||||||
|
for (i = 0; i < ARRAY_SIZE(rb_dynparts); i++) {
|
||||||
|
if (!strcmp(partname, rb_dynparts[i].name) && rb_dynparts[i].found) {
|
||||||
|
offset = rb_dynparts[i].offset;
|
||||||
|
size = rb_dynparts[i].size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standalone 'size' property? Override size */
|
||||||
|
sz = of_get_property(pp, "size", &len);
|
||||||
|
if (sz) {
|
||||||
|
s_cells = of_n_size_cells(pp);
|
||||||
|
if ((len / 4) != s_cells) {
|
||||||
|
pr_debug("%s: routerboot partition %pOF (%pOF) error parsing size property.\n",
|
||||||
|
master->name, pp, rbpart_node);
|
||||||
|
goto rbpart_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = of_read_number(sz, s_cells);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (np > 0) {
|
||||||
|
/* Minor sanity check for overlaps */
|
||||||
|
if (offset < (parts[np-1].offset + parts[np-1].size)) {
|
||||||
|
pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" overlaps with previous partition \"%s\".\n",
|
||||||
|
master->name, pp, rbpart_node,
|
||||||
|
partname, parts[np-1].name);
|
||||||
|
goto rbpart_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fixup end of previous partition if necessary */
|
||||||
|
if (!parts[np-1].size)
|
||||||
|
parts[np-1].size = (offset - parts[np-1].offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((offset + size) > master->size) {
|
||||||
|
pr_err("%s: routerboot partition %pOF (%pOF) \"%s\" extends past end of segment.\n",
|
||||||
|
master->name, pp, rbpart_node, partname);
|
||||||
|
goto rbpart_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[np].offset = offset;
|
||||||
|
parts[np].size = size;
|
||||||
|
parts[np].of_node = pp;
|
||||||
|
|
||||||
|
if (of_get_property(pp, "read-only", &len))
|
||||||
|
parts[np].mask_flags |= MTD_WRITEABLE;
|
||||||
|
|
||||||
|
if (of_get_property(pp, "lock", &len))
|
||||||
|
parts[np].mask_flags |= MTD_POWERUP_LOCK;
|
||||||
|
|
||||||
|
/* Keep master offset aligned to RB_BLOCK_SIZE */
|
||||||
|
master_ofs = ALIGN(offset + size, RB_BLOCK_SIZE);
|
||||||
|
np++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pparts = parts;
|
||||||
|
return np;
|
||||||
|
|
||||||
|
rbpart_fail:
|
||||||
|
pr_err("%s: error parsing routerboot partition %pOF (%pOF)\n",
|
||||||
|
master->name, pp, rbpart_node);
|
||||||
|
of_node_put(pp);
|
||||||
|
kfree(parts);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id parse_routerbootpart_match_table[] = {
|
||||||
|
{ .compatible = "mikrotik,routerboot-partitions" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, parse_routerbootpart_match_table);
|
||||||
|
|
||||||
|
static struct mtd_part_parser routerbootpart_parser = {
|
||||||
|
.parse_fn = routerboot_partitions_parse,
|
||||||
|
.name = "routerbootpart",
|
||||||
|
.of_match_table = parse_routerbootpart_match_table,
|
||||||
|
};
|
||||||
|
module_mtd_part_parser(routerbootpart_parser);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
MODULE_DESCRIPTION("MTD partitioning for RouterBoot");
|
||||||
|
MODULE_AUTHOR("Thibaut VARENE");
|
1249
6.12/target/linux/generic/files/drivers/net/phy/adm6996.c
Normal file
1249
6.12/target/linux/generic/files/drivers/net/phy/adm6996.c
Normal file
File diff suppressed because it is too large
Load diff
186
6.12/target/linux/generic/files/drivers/net/phy/adm6996.h
Normal file
186
6.12/target/linux/generic/files/drivers/net/phy/adm6996.h
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
/*
|
||||||
|
* ADM6996 switch driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
|
||||||
|
* Copyright (c) 2010,2011 Peter Lebbing <peter@digitalbrains.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License v2 as published by the
|
||||||
|
* Free Software Foundation
|
||||||
|
*/
|
||||||
|
#ifndef __ADM6996_H
|
||||||
|
#define __ADM6996_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ADM_PHY_PORTS: Number of ports with a PHY.
|
||||||
|
* We only control ports 0 to 3, because if 4 is connected, it is most likely
|
||||||
|
* not connected to the switch but to a separate MII and MAC for the WAN port.
|
||||||
|
*/
|
||||||
|
#define ADM_PHY_PORTS 4
|
||||||
|
#define ADM_NUM_PORTS 6
|
||||||
|
#define ADM_CPU_PORT 5
|
||||||
|
|
||||||
|
#define ADM_NUM_VLANS 16
|
||||||
|
#define ADM_VLAN_MAX_ID 4094
|
||||||
|
|
||||||
|
enum admreg {
|
||||||
|
ADM_EEPROM_BASE = 0x0,
|
||||||
|
ADM_P0_CFG = ADM_EEPROM_BASE + 1,
|
||||||
|
ADM_P1_CFG = ADM_EEPROM_BASE + 3,
|
||||||
|
ADM_P2_CFG = ADM_EEPROM_BASE + 5,
|
||||||
|
ADM_P3_CFG = ADM_EEPROM_BASE + 7,
|
||||||
|
ADM_P4_CFG = ADM_EEPROM_BASE + 8,
|
||||||
|
ADM_P5_CFG = ADM_EEPROM_BASE + 9,
|
||||||
|
ADM_SYSC0 = ADM_EEPROM_BASE + 0xa,
|
||||||
|
ADM_VLAN_PRIOMAP = ADM_EEPROM_BASE + 0xe,
|
||||||
|
ADM_SYSC3 = ADM_EEPROM_BASE + 0x11,
|
||||||
|
/* Input Force No Tag Enable */
|
||||||
|
ADM_IFNTE = ADM_EEPROM_BASE + 0x20,
|
||||||
|
ADM_VID_CHECK = ADM_EEPROM_BASE + 0x26,
|
||||||
|
ADM_P0_PVID = ADM_EEPROM_BASE + 0x28,
|
||||||
|
ADM_P1_PVID = ADM_EEPROM_BASE + 0x29,
|
||||||
|
/* Output Tag Bypass Enable and P2 PVID */
|
||||||
|
ADM_OTBE_P2_PVID = ADM_EEPROM_BASE + 0x2a,
|
||||||
|
ADM_P3_P4_PVID = ADM_EEPROM_BASE + 0x2b,
|
||||||
|
ADM_P5_PVID = ADM_EEPROM_BASE + 0x2c,
|
||||||
|
ADM_EEPROM_EXT_BASE = 0x40,
|
||||||
|
#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n))
|
||||||
|
#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n))
|
||||||
|
#define ADM_VLAN_MAP(n) (ADM_EEPROM_BASE + 0x13 + n)
|
||||||
|
ADM_COUNTER_BASE = 0xa0,
|
||||||
|
ADM_SIG0 = ADM_COUNTER_BASE + 0,
|
||||||
|
ADM_SIG1 = ADM_COUNTER_BASE + 1,
|
||||||
|
ADM_PS0 = ADM_COUNTER_BASE + 2,
|
||||||
|
ADM_PS1 = ADM_COUNTER_BASE + 3,
|
||||||
|
ADM_PS2 = ADM_COUNTER_BASE + 4,
|
||||||
|
ADM_CL0 = ADM_COUNTER_BASE + 8, /* RxPacket */
|
||||||
|
ADM_CL6 = ADM_COUNTER_BASE + 0x1a, /* RxByte */
|
||||||
|
ADM_CL12 = ADM_COUNTER_BASE + 0x2c, /* TxPacket */
|
||||||
|
ADM_CL18 = ADM_COUNTER_BASE + 0x3e, /* TxByte */
|
||||||
|
ADM_CL24 = ADM_COUNTER_BASE + 0x50, /* Coll */
|
||||||
|
ADM_CL30 = ADM_COUNTER_BASE + 0x62, /* Err */
|
||||||
|
#define ADM_OFFSET_PORT(n) ((n * 4) - (n / 4) * 2 - (n / 5) * 2)
|
||||||
|
ADM_PHY_BASE = 0x200,
|
||||||
|
#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n))
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Chip identification patterns */
|
||||||
|
#define ADM_SIG0_MASK 0xffff
|
||||||
|
#define ADM_SIG0_VAL 0x1023
|
||||||
|
#define ADM_SIG1_MASK 0xffff
|
||||||
|
#define ADM_SIG1_VAL 0x0007
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ADM_PHYCFG_COLTST = (1 << 7), /* Enable collision test */
|
||||||
|
ADM_PHYCFG_DPLX = (1 << 8), /* Enable full duplex */
|
||||||
|
ADM_PHYCFG_ANEN_RST = (1 << 9), /* Restart auto negotiation (self clear) */
|
||||||
|
ADM_PHYCFG_ISO = (1 << 10), /* Isolate PHY */
|
||||||
|
ADM_PHYCFG_PDN = (1 << 11), /* Power down PHY */
|
||||||
|
ADM_PHYCFG_ANEN = (1 << 12), /* Enable auto negotiation */
|
||||||
|
ADM_PHYCFG_SPEED_100 = (1 << 13), /* Enable 100 Mbit/s */
|
||||||
|
ADM_PHYCFG_LPBK = (1 << 14), /* Enable loopback operation */
|
||||||
|
ADM_PHYCFG_RST = (1 << 15), /* Reset the port (self clear) */
|
||||||
|
ADM_PHYCFG_INIT = (
|
||||||
|
ADM_PHYCFG_RST |
|
||||||
|
ADM_PHYCFG_SPEED_100 |
|
||||||
|
ADM_PHYCFG_ANEN |
|
||||||
|
ADM_PHYCFG_ANEN_RST
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ADM_PORTCFG_FC = (1 << 0), /* Enable 802.x flow control */
|
||||||
|
ADM_PORTCFG_AN = (1 << 1), /* Enable auto-negotiation */
|
||||||
|
ADM_PORTCFG_SPEED_100 = (1 << 2), /* Enable 100 Mbit/s */
|
||||||
|
ADM_PORTCFG_DPLX = (1 << 3), /* Enable full duplex */
|
||||||
|
ADM_PORTCFG_OT = (1 << 4), /* Output tagged packets */
|
||||||
|
ADM_PORTCFG_PD = (1 << 5), /* Port disable */
|
||||||
|
ADM_PORTCFG_TV_PRIO = (1 << 6), /* 0 = VLAN based priority
|
||||||
|
* 1 = TOS based priority */
|
||||||
|
ADM_PORTCFG_PPE = (1 << 7), /* Port based priority enable */
|
||||||
|
ADM_PORTCFG_PP_S = (1 << 8), /* Port based priority, 2 bits */
|
||||||
|
ADM_PORTCFG_PVID_BASE = (1 << 10), /* Primary VLAN id, 4 bits */
|
||||||
|
ADM_PORTCFG_FSE = (1 << 14), /* Fx select enable */
|
||||||
|
ADM_PORTCFG_CAM = (1 << 15), /* Crossover Auto MDIX */
|
||||||
|
|
||||||
|
ADM_PORTCFG_INIT = (
|
||||||
|
ADM_PORTCFG_FC |
|
||||||
|
ADM_PORTCFG_AN |
|
||||||
|
ADM_PORTCFG_SPEED_100 |
|
||||||
|
ADM_PORTCFG_DPLX |
|
||||||
|
ADM_PORTCFG_CAM
|
||||||
|
),
|
||||||
|
ADM_PORTCFG_CPU = (
|
||||||
|
ADM_PORTCFG_FC |
|
||||||
|
ADM_PORTCFG_SPEED_100 |
|
||||||
|
ADM_PORTCFG_OT |
|
||||||
|
ADM_PORTCFG_DPLX
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8)
|
||||||
|
#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10)
|
||||||
|
#define ADM_PORTCFG_PVID_MASK (0xf << 10)
|
||||||
|
|
||||||
|
#define ADM_IFNTE_MASK (0x3f << 9)
|
||||||
|
#define ADM_VID_CHECK_MASK (0x3f << 6)
|
||||||
|
|
||||||
|
#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
|
||||||
|
#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
|
||||||
|
#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
|
||||||
|
#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
|
||||||
|
#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8)
|
||||||
|
#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
|
||||||
|
#define ADM_P2_PVID_MASK 0xff
|
||||||
|
|
||||||
|
#define ADM_OTBE(n) (((n) & 0x3f) << 8)
|
||||||
|
#define ADM_OTBE_MASK (0x3f << 8)
|
||||||
|
|
||||||
|
/* ADM_SYSC0 */
|
||||||
|
enum {
|
||||||
|
ADM_NTTE = (1 << 2), /* New Tag Transmit Enable */
|
||||||
|
ADM_RVID1 = (1 << 8) /* Replace VLAN ID 1 */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Tag Based VLAN in ADM_SYSC3 */
|
||||||
|
#define ADM_MAC_CLONE BIT(4)
|
||||||
|
#define ADM_TBV BIT(5)
|
||||||
|
|
||||||
|
static const u8 adm_portcfg[] = {
|
||||||
|
[0] = ADM_P0_CFG,
|
||||||
|
[1] = ADM_P1_CFG,
|
||||||
|
[2] = ADM_P2_CFG,
|
||||||
|
[3] = ADM_P3_CFG,
|
||||||
|
[4] = ADM_P4_CFG,
|
||||||
|
[5] = ADM_P5_CFG,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Fields in ADM_VLAN_FILT_L(x) */
|
||||||
|
#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12)
|
||||||
|
#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6)
|
||||||
|
#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0)
|
||||||
|
#define ADM_VLAN_FILT_MEMBER_MASK 0x3f
|
||||||
|
/* Fields in ADM_VLAN_FILT_H(x) */
|
||||||
|
#define ADM_VLAN_FILT_VALID (1 << 15)
|
||||||
|
#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0)
|
||||||
|
|
||||||
|
/* Convert ports to a form for ADM6996L VLAN map */
|
||||||
|
#define ADM_VLAN_FILT(ports) ((ports & 0x01) | ((ports & 0x02) << 1) | \
|
||||||
|
((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \
|
||||||
|
((ports & 0x10) << 3) | ((ports & 0x20) << 3))
|
||||||
|
|
||||||
|
/* Port status register */
|
||||||
|
enum {
|
||||||
|
ADM_PS_LS = (1 << 0), /* Link status */
|
||||||
|
ADM_PS_SS = (1 << 1), /* Speed status */
|
||||||
|
ADM_PS_DS = (1 << 2), /* Duplex status */
|
||||||
|
ADM_PS_FCS = (1 << 3) /* Flow control status */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split the register address in phy id and register
|
||||||
|
* it will get combined again by the mdio bus op
|
||||||
|
*/
|
||||||
|
#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f)
|
||||||
|
|
||||||
|
#endif
|
2909
6.12/target/linux/generic/files/drivers/net/phy/ar8216.c
Normal file
2909
6.12/target/linux/generic/files/drivers/net/phy/ar8216.c
Normal file
File diff suppressed because it is too large
Load diff
725
6.12/target/linux/generic/files/drivers/net/phy/ar8216.h
Normal file
725
6.12/target/linux/generic/files/drivers/net/phy/ar8216.h
Normal file
|
@ -0,0 +1,725 @@
|
||||||
|
/*
|
||||||
|
* ar8216.h: AR8216 switch driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __AR8216_H
|
||||||
|
#define __AR8216_H
|
||||||
|
|
||||||
|
#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s)
|
||||||
|
|
||||||
|
#define AR8XXX_CAP_GIGE BIT(0)
|
||||||
|
#define AR8XXX_CAP_MIB_COUNTERS BIT(1)
|
||||||
|
|
||||||
|
#define AR8XXX_NUM_PHYS 5
|
||||||
|
#define AR8216_PORT_CPU 0
|
||||||
|
#define AR8216_NUM_PORTS 6
|
||||||
|
#define AR8216_NUM_VLANS 16
|
||||||
|
#define AR7240SW_NUM_PORTS 5
|
||||||
|
#define AR8316_NUM_VLANS 4096
|
||||||
|
|
||||||
|
/* size of the vlan table */
|
||||||
|
#define AR8X16_MAX_VLANS 128
|
||||||
|
#define AR83X7_MAX_VLANS 4096
|
||||||
|
#define AR8XXX_MAX_VLANS AR83X7_MAX_VLANS
|
||||||
|
|
||||||
|
#define AR8X16_PROBE_RETRIES 10
|
||||||
|
#define AR8X16_MAX_PORTS 8
|
||||||
|
|
||||||
|
#define AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS 7
|
||||||
|
#define AR8XXX_DEFAULT_ARL_AGE_TIME 300
|
||||||
|
|
||||||
|
/* Atheros specific MII registers */
|
||||||
|
#define MII_ATH_MMD_ADDR 0x0d
|
||||||
|
#define MII_ATH_MMD_DATA 0x0e
|
||||||
|
#define MII_ATH_DBG_ADDR 0x1d
|
||||||
|
#define MII_ATH_DBG_DATA 0x1e
|
||||||
|
|
||||||
|
#define AR8216_REG_CTRL 0x0000
|
||||||
|
#define AR8216_CTRL_REVISION BITS(0, 8)
|
||||||
|
#define AR8216_CTRL_REVISION_S 0
|
||||||
|
#define AR8216_CTRL_VERSION BITS(8, 8)
|
||||||
|
#define AR8216_CTRL_VERSION_S 8
|
||||||
|
#define AR8216_CTRL_RESET BIT(31)
|
||||||
|
|
||||||
|
#define AR8216_REG_FLOOD_MASK 0x002C
|
||||||
|
#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6)
|
||||||
|
#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6)
|
||||||
|
#define AR8216_FM_CPU_BROADCAST_EN BIT(26)
|
||||||
|
#define AR8229_FLOOD_MASK_UC_DP(_p) BIT(_p)
|
||||||
|
#define AR8229_FLOOD_MASK_MC_DP(_p) BIT(16 + (_p))
|
||||||
|
#define AR8229_FLOOD_MASK_BC_DP(_p) BIT(25 + (_p))
|
||||||
|
|
||||||
|
#define AR8216_REG_GLOBAL_CTRL 0x0030
|
||||||
|
#define AR8216_GCTRL_MTU BITS(0, 11)
|
||||||
|
#define AR8236_GCTRL_MTU BITS(0, 14)
|
||||||
|
#define AR8316_GCTRL_MTU BITS(0, 14)
|
||||||
|
|
||||||
|
#define AR8216_REG_VTU 0x0040
|
||||||
|
#define AR8216_VTU_OP BITS(0, 3)
|
||||||
|
#define AR8216_VTU_OP_NOOP 0x0
|
||||||
|
#define AR8216_VTU_OP_FLUSH 0x1
|
||||||
|
#define AR8216_VTU_OP_LOAD 0x2
|
||||||
|
#define AR8216_VTU_OP_PURGE 0x3
|
||||||
|
#define AR8216_VTU_OP_REMOVE_PORT 0x4
|
||||||
|
#define AR8216_VTU_ACTIVE BIT(3)
|
||||||
|
#define AR8216_VTU_FULL BIT(4)
|
||||||
|
#define AR8216_VTU_PORT BITS(8, 4)
|
||||||
|
#define AR8216_VTU_PORT_S 8
|
||||||
|
#define AR8216_VTU_VID BITS(16, 12)
|
||||||
|
#define AR8216_VTU_VID_S 16
|
||||||
|
#define AR8216_VTU_PRIO BITS(28, 3)
|
||||||
|
#define AR8216_VTU_PRIO_S 28
|
||||||
|
#define AR8216_VTU_PRIO_EN BIT(31)
|
||||||
|
|
||||||
|
#define AR8216_REG_VTU_DATA 0x0044
|
||||||
|
#define AR8216_VTUDATA_MEMBER BITS(0, 10)
|
||||||
|
#define AR8236_VTUDATA_MEMBER BITS(0, 7)
|
||||||
|
#define AR8216_VTUDATA_VALID BIT(11)
|
||||||
|
|
||||||
|
#define AR8216_REG_ATU_FUNC0 0x0050
|
||||||
|
#define AR8216_ATU_OP BITS(0, 3)
|
||||||
|
#define AR8216_ATU_OP_NOOP 0x0
|
||||||
|
#define AR8216_ATU_OP_FLUSH 0x1
|
||||||
|
#define AR8216_ATU_OP_LOAD 0x2
|
||||||
|
#define AR8216_ATU_OP_PURGE 0x3
|
||||||
|
#define AR8216_ATU_OP_FLUSH_UNLOCKED 0x4
|
||||||
|
#define AR8216_ATU_OP_FLUSH_PORT 0x5
|
||||||
|
#define AR8216_ATU_OP_GET_NEXT 0x6
|
||||||
|
#define AR8216_ATU_ACTIVE BIT(3)
|
||||||
|
#define AR8216_ATU_PORT_NUM BITS(8, 4)
|
||||||
|
#define AR8216_ATU_PORT_NUM_S 8
|
||||||
|
#define AR8216_ATU_FULL_VIO BIT(12)
|
||||||
|
#define AR8216_ATU_ADDR5 BITS(16, 8)
|
||||||
|
#define AR8216_ATU_ADDR5_S 16
|
||||||
|
#define AR8216_ATU_ADDR4 BITS(24, 8)
|
||||||
|
#define AR8216_ATU_ADDR4_S 24
|
||||||
|
|
||||||
|
#define AR8216_REG_ATU_FUNC1 0x0054
|
||||||
|
#define AR8216_ATU_ADDR3 BITS(0, 8)
|
||||||
|
#define AR8216_ATU_ADDR3_S 0
|
||||||
|
#define AR8216_ATU_ADDR2 BITS(8, 8)
|
||||||
|
#define AR8216_ATU_ADDR2_S 8
|
||||||
|
#define AR8216_ATU_ADDR1 BITS(16, 8)
|
||||||
|
#define AR8216_ATU_ADDR1_S 16
|
||||||
|
#define AR8216_ATU_ADDR0 BITS(24, 8)
|
||||||
|
#define AR8216_ATU_ADDR0_S 24
|
||||||
|
|
||||||
|
#define AR8216_REG_ATU_FUNC2 0x0058
|
||||||
|
#define AR8216_ATU_PORTS BITS(0, 6)
|
||||||
|
#define AR8216_ATU_PORTS_S 0
|
||||||
|
#define AR8216_ATU_PORT0 BIT(0)
|
||||||
|
#define AR8216_ATU_PORT1 BIT(1)
|
||||||
|
#define AR8216_ATU_PORT2 BIT(2)
|
||||||
|
#define AR8216_ATU_PORT3 BIT(3)
|
||||||
|
#define AR8216_ATU_PORT4 BIT(4)
|
||||||
|
#define AR8216_ATU_PORT5 BIT(5)
|
||||||
|
#define AR8216_ATU_STATUS BITS(16, 4)
|
||||||
|
#define AR8216_ATU_STATUS_S 16
|
||||||
|
|
||||||
|
#define AR8216_REG_ATU_CTRL 0x005C
|
||||||
|
#define AR8216_ATU_CTRL_AGE_EN BIT(17)
|
||||||
|
#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16)
|
||||||
|
#define AR8216_ATU_CTRL_AGE_TIME_S 0
|
||||||
|
#define AR8236_ATU_CTRL_RES BIT(20)
|
||||||
|
#define AR8216_ATU_CTRL_LEARN_CHANGE BIT(18)
|
||||||
|
#define AR8216_ATU_CTRL_RESERVED BIT(19)
|
||||||
|
#define AR8216_ATU_CTRL_ARP_EN BIT(20)
|
||||||
|
|
||||||
|
#define AR8216_REG_TAG_PRIORITY 0x0070
|
||||||
|
|
||||||
|
#define AR8216_REG_SERVICE_TAG 0x0074
|
||||||
|
#define AR8216_SERVICE_TAG_M BITS(0, 16)
|
||||||
|
|
||||||
|
#define AR8216_REG_MIB_FUNC 0x0080
|
||||||
|
#define AR8216_MIB_TIMER BITS(0, 16)
|
||||||
|
#define AR8216_MIB_AT_HALF_EN BIT(16)
|
||||||
|
#define AR8216_MIB_BUSY BIT(17)
|
||||||
|
#define AR8216_MIB_FUNC BITS(24, 3)
|
||||||
|
#define AR8216_MIB_FUNC_S 24
|
||||||
|
#define AR8216_MIB_FUNC_NO_OP 0x0
|
||||||
|
#define AR8216_MIB_FUNC_FLUSH 0x1
|
||||||
|
#define AR8216_MIB_FUNC_CAPTURE 0x3
|
||||||
|
#define AR8236_MIB_EN BIT(30)
|
||||||
|
|
||||||
|
#define AR8216_REG_GLOBAL_CPUPORT 0x0078
|
||||||
|
#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT BITS(4, 4)
|
||||||
|
#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S 4
|
||||||
|
#define AR8216_GLOBAL_CPUPORT_EN BIT(8)
|
||||||
|
|
||||||
|
#define AR8216_REG_MDIO_CTRL 0x98
|
||||||
|
#define AR8216_MDIO_CTRL_DATA_M BITS(0, 16)
|
||||||
|
#define AR8216_MDIO_CTRL_REG_ADDR_S 16
|
||||||
|
#define AR8216_MDIO_CTRL_PHY_ADDR_S 21
|
||||||
|
#define AR8216_MDIO_CTRL_CMD_WRITE 0
|
||||||
|
#define AR8216_MDIO_CTRL_CMD_READ BIT(27)
|
||||||
|
#define AR8216_MDIO_CTRL_MASTER_EN BIT(30)
|
||||||
|
#define AR8216_MDIO_CTRL_BUSY BIT(31)
|
||||||
|
|
||||||
|
#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1))
|
||||||
|
#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000)
|
||||||
|
#define AR8216_PORT_STATUS_SPEED BITS(0,2)
|
||||||
|
#define AR8216_PORT_STATUS_SPEED_S 0
|
||||||
|
#define AR8216_PORT_STATUS_TXMAC BIT(2)
|
||||||
|
#define AR8216_PORT_STATUS_RXMAC BIT(3)
|
||||||
|
#define AR8216_PORT_STATUS_TXFLOW BIT(4)
|
||||||
|
#define AR8216_PORT_STATUS_RXFLOW BIT(5)
|
||||||
|
#define AR8216_PORT_STATUS_DUPLEX BIT(6)
|
||||||
|
#define AR8216_PORT_STATUS_LINK_UP BIT(8)
|
||||||
|
#define AR8216_PORT_STATUS_LINK_AUTO BIT(9)
|
||||||
|
#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10)
|
||||||
|
#define AR8216_PORT_STATUS_FLOW_CONTROL BIT(12)
|
||||||
|
|
||||||
|
#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004)
|
||||||
|
|
||||||
|
/* port forwarding state */
|
||||||
|
#define AR8216_PORT_CTRL_STATE BITS(0, 3)
|
||||||
|
#define AR8216_PORT_CTRL_STATE_S 0
|
||||||
|
|
||||||
|
#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7)
|
||||||
|
|
||||||
|
/* egress 802.1q mode */
|
||||||
|
#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2)
|
||||||
|
#define AR8216_PORT_CTRL_VLAN_MODE_S 8
|
||||||
|
|
||||||
|
#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10)
|
||||||
|
#define AR8216_PORT_CTRL_HEADER BIT(11)
|
||||||
|
#define AR8216_PORT_CTRL_MAC_LOOP BIT(12)
|
||||||
|
#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13)
|
||||||
|
#define AR8216_PORT_CTRL_LEARN BIT(14)
|
||||||
|
#define AR8216_PORT_CTRL_MIRROR_TX BIT(16)
|
||||||
|
#define AR8216_PORT_CTRL_MIRROR_RX BIT(17)
|
||||||
|
|
||||||
|
#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008)
|
||||||
|
|
||||||
|
#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12)
|
||||||
|
#define AR8216_PORT_VLAN_DEFAULT_ID_S 0
|
||||||
|
|
||||||
|
#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9)
|
||||||
|
#define AR8216_PORT_VLAN_DEST_PORTS_S 16
|
||||||
|
|
||||||
|
/* bit0 added to the priority field of egress frames */
|
||||||
|
#define AR8216_PORT_VLAN_TX_PRIO BIT(27)
|
||||||
|
|
||||||
|
/* port default priority */
|
||||||
|
#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2)
|
||||||
|
#define AR8216_PORT_VLAN_PRIORITY_S 28
|
||||||
|
|
||||||
|
/* ingress 802.1q mode */
|
||||||
|
#define AR8216_PORT_VLAN_MODE BITS(30, 2)
|
||||||
|
#define AR8216_PORT_VLAN_MODE_S 30
|
||||||
|
|
||||||
|
#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c)
|
||||||
|
#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010)
|
||||||
|
|
||||||
|
#define AR8216_STATS_RXBROAD 0x00
|
||||||
|
#define AR8216_STATS_RXPAUSE 0x04
|
||||||
|
#define AR8216_STATS_RXMULTI 0x08
|
||||||
|
#define AR8216_STATS_RXFCSERR 0x0c
|
||||||
|
#define AR8216_STATS_RXALIGNERR 0x10
|
||||||
|
#define AR8216_STATS_RXRUNT 0x14
|
||||||
|
#define AR8216_STATS_RXFRAGMENT 0x18
|
||||||
|
#define AR8216_STATS_RX64BYTE 0x1c
|
||||||
|
#define AR8216_STATS_RX128BYTE 0x20
|
||||||
|
#define AR8216_STATS_RX256BYTE 0x24
|
||||||
|
#define AR8216_STATS_RX512BYTE 0x28
|
||||||
|
#define AR8216_STATS_RX1024BYTE 0x2c
|
||||||
|
#define AR8216_STATS_RXMAXBYTE 0x30
|
||||||
|
#define AR8216_STATS_RXTOOLONG 0x34
|
||||||
|
#define AR8216_STATS_RXGOODBYTE 0x38
|
||||||
|
#define AR8216_STATS_RXBADBYTE 0x40
|
||||||
|
#define AR8216_STATS_RXOVERFLOW 0x48
|
||||||
|
#define AR8216_STATS_FILTERED 0x4c
|
||||||
|
#define AR8216_STATS_TXBROAD 0x50
|
||||||
|
#define AR8216_STATS_TXPAUSE 0x54
|
||||||
|
#define AR8216_STATS_TXMULTI 0x58
|
||||||
|
#define AR8216_STATS_TXUNDERRUN 0x5c
|
||||||
|
#define AR8216_STATS_TX64BYTE 0x60
|
||||||
|
#define AR8216_STATS_TX128BYTE 0x64
|
||||||
|
#define AR8216_STATS_TX256BYTE 0x68
|
||||||
|
#define AR8216_STATS_TX512BYTE 0x6c
|
||||||
|
#define AR8216_STATS_TX1024BYTE 0x70
|
||||||
|
#define AR8216_STATS_TXMAXBYTE 0x74
|
||||||
|
#define AR8216_STATS_TXOVERSIZE 0x78
|
||||||
|
#define AR8216_STATS_TXBYTE 0x7c
|
||||||
|
#define AR8216_STATS_TXCOLLISION 0x84
|
||||||
|
#define AR8216_STATS_TXABORTCOL 0x88
|
||||||
|
#define AR8216_STATS_TXMULTICOL 0x8c
|
||||||
|
#define AR8216_STATS_TXSINGLECOL 0x90
|
||||||
|
#define AR8216_STATS_TXEXCDEFER 0x94
|
||||||
|
#define AR8216_STATS_TXDEFER 0x98
|
||||||
|
#define AR8216_STATS_TXLATECOL 0x9c
|
||||||
|
|
||||||
|
#define AR8216_MIB_RXB_ID 14 /* RxGoodByte */
|
||||||
|
#define AR8216_MIB_TXB_ID 29 /* TxByte */
|
||||||
|
|
||||||
|
#define AR8229_REG_OPER_MODE0 0x04
|
||||||
|
#define AR8229_OPER_MODE0_MAC_GMII_EN BIT(6)
|
||||||
|
#define AR8229_OPER_MODE0_PHY_MII_EN BIT(10)
|
||||||
|
|
||||||
|
#define AR8229_REG_OPER_MODE1 0x08
|
||||||
|
#define AR8229_REG_OPER_MODE1_PHY4_MII_EN BIT(28)
|
||||||
|
|
||||||
|
#define AR8229_REG_QM_CTRL 0x3c
|
||||||
|
#define AR8229_QM_CTRL_ARP_EN BIT(15)
|
||||||
|
|
||||||
|
#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008)
|
||||||
|
#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12)
|
||||||
|
#define AR8236_PORT_VLAN_DEFAULT_ID_S 16
|
||||||
|
#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3)
|
||||||
|
#define AR8236_PORT_VLAN_PRIORITY_S 28
|
||||||
|
|
||||||
|
#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c)
|
||||||
|
#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7)
|
||||||
|
#define AR8236_PORT_VLAN2_MEMBER_S 16
|
||||||
|
#define AR8236_PORT_VLAN2_TX_PRIO BIT(23)
|
||||||
|
#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2)
|
||||||
|
#define AR8236_PORT_VLAN2_VLAN_MODE_S 30
|
||||||
|
|
||||||
|
#define AR8236_STATS_RXBROAD 0x00
|
||||||
|
#define AR8236_STATS_RXPAUSE 0x04
|
||||||
|
#define AR8236_STATS_RXMULTI 0x08
|
||||||
|
#define AR8236_STATS_RXFCSERR 0x0c
|
||||||
|
#define AR8236_STATS_RXALIGNERR 0x10
|
||||||
|
#define AR8236_STATS_RXRUNT 0x14
|
||||||
|
#define AR8236_STATS_RXFRAGMENT 0x18
|
||||||
|
#define AR8236_STATS_RX64BYTE 0x1c
|
||||||
|
#define AR8236_STATS_RX128BYTE 0x20
|
||||||
|
#define AR8236_STATS_RX256BYTE 0x24
|
||||||
|
#define AR8236_STATS_RX512BYTE 0x28
|
||||||
|
#define AR8236_STATS_RX1024BYTE 0x2c
|
||||||
|
#define AR8236_STATS_RX1518BYTE 0x30
|
||||||
|
#define AR8236_STATS_RXMAXBYTE 0x34
|
||||||
|
#define AR8236_STATS_RXTOOLONG 0x38
|
||||||
|
#define AR8236_STATS_RXGOODBYTE 0x3c
|
||||||
|
#define AR8236_STATS_RXBADBYTE 0x44
|
||||||
|
#define AR8236_STATS_RXOVERFLOW 0x4c
|
||||||
|
#define AR8236_STATS_FILTERED 0x50
|
||||||
|
#define AR8236_STATS_TXBROAD 0x54
|
||||||
|
#define AR8236_STATS_TXPAUSE 0x58
|
||||||
|
#define AR8236_STATS_TXMULTI 0x5c
|
||||||
|
#define AR8236_STATS_TXUNDERRUN 0x60
|
||||||
|
#define AR8236_STATS_TX64BYTE 0x64
|
||||||
|
#define AR8236_STATS_TX128BYTE 0x68
|
||||||
|
#define AR8236_STATS_TX256BYTE 0x6c
|
||||||
|
#define AR8236_STATS_TX512BYTE 0x70
|
||||||
|
#define AR8236_STATS_TX1024BYTE 0x74
|
||||||
|
#define AR8236_STATS_TX1518BYTE 0x78
|
||||||
|
#define AR8236_STATS_TXMAXBYTE 0x7c
|
||||||
|
#define AR8236_STATS_TXOVERSIZE 0x80
|
||||||
|
#define AR8236_STATS_TXBYTE 0x84
|
||||||
|
#define AR8236_STATS_TXCOLLISION 0x8c
|
||||||
|
#define AR8236_STATS_TXABORTCOL 0x90
|
||||||
|
#define AR8236_STATS_TXMULTICOL 0x94
|
||||||
|
#define AR8236_STATS_TXSINGLECOL 0x98
|
||||||
|
#define AR8236_STATS_TXEXCDEFER 0x9c
|
||||||
|
#define AR8236_STATS_TXDEFER 0xa0
|
||||||
|
#define AR8236_STATS_TXLATECOL 0xa4
|
||||||
|
|
||||||
|
#define AR8236_MIB_RXB_ID 15 /* RxGoodByte */
|
||||||
|
#define AR8236_MIB_TXB_ID 31 /* TxByte */
|
||||||
|
|
||||||
|
#define AR8316_REG_POSTRIP 0x0008
|
||||||
|
#define AR8316_POSTRIP_MAC0_GMII_EN BIT(0)
|
||||||
|
#define AR8316_POSTRIP_MAC0_RGMII_EN BIT(1)
|
||||||
|
#define AR8316_POSTRIP_PHY4_GMII_EN BIT(2)
|
||||||
|
#define AR8316_POSTRIP_PHY4_RGMII_EN BIT(3)
|
||||||
|
#define AR8316_POSTRIP_MAC0_MAC_MODE BIT(4)
|
||||||
|
#define AR8316_POSTRIP_RTL_MODE BIT(5)
|
||||||
|
#define AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN BIT(6)
|
||||||
|
#define AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN BIT(7)
|
||||||
|
#define AR8316_POSTRIP_SERDES_EN BIT(8)
|
||||||
|
#define AR8316_POSTRIP_SEL_ANA_RST BIT(9)
|
||||||
|
#define AR8316_POSTRIP_GATE_25M_EN BIT(10)
|
||||||
|
#define AR8316_POSTRIP_SEL_CLK25M BIT(11)
|
||||||
|
#define AR8316_POSTRIP_HIB_PULSE_HW BIT(12)
|
||||||
|
#define AR8316_POSTRIP_DBG_MODE_I BIT(13)
|
||||||
|
#define AR8316_POSTRIP_MAC5_MAC_MODE BIT(14)
|
||||||
|
#define AR8316_POSTRIP_MAC5_PHY_MODE BIT(15)
|
||||||
|
#define AR8316_POSTRIP_POWER_DOWN_HW BIT(16)
|
||||||
|
#define AR8316_POSTRIP_LPW_STATE_EN BIT(17)
|
||||||
|
#define AR8316_POSTRIP_MAN_EN BIT(18)
|
||||||
|
#define AR8316_POSTRIP_PHY_PLL_ON BIT(19)
|
||||||
|
#define AR8316_POSTRIP_LPW_EXIT BIT(20)
|
||||||
|
#define AR8316_POSTRIP_TXDELAY_S0 BIT(21)
|
||||||
|
#define AR8316_POSTRIP_TXDELAY_S1 BIT(22)
|
||||||
|
#define AR8316_POSTRIP_RXDELAY_S0 BIT(23)
|
||||||
|
#define AR8316_POSTRIP_LED_OPEN_EN BIT(24)
|
||||||
|
#define AR8316_POSTRIP_SPI_EN BIT(25)
|
||||||
|
#define AR8316_POSTRIP_RXDELAY_S1 BIT(26)
|
||||||
|
#define AR8316_POSTRIP_POWER_ON_SEL BIT(31)
|
||||||
|
|
||||||
|
/* port speed */
|
||||||
|
enum {
|
||||||
|
AR8216_PORT_SPEED_10M = 0,
|
||||||
|
AR8216_PORT_SPEED_100M = 1,
|
||||||
|
AR8216_PORT_SPEED_1000M = 2,
|
||||||
|
AR8216_PORT_SPEED_ERR = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ingress 802.1q mode */
|
||||||
|
enum {
|
||||||
|
AR8216_IN_PORT_ONLY = 0,
|
||||||
|
AR8216_IN_PORT_FALLBACK = 1,
|
||||||
|
AR8216_IN_VLAN_ONLY = 2,
|
||||||
|
AR8216_IN_SECURE = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
/* egress 802.1q mode */
|
||||||
|
enum {
|
||||||
|
AR8216_OUT_KEEP = 0,
|
||||||
|
AR8216_OUT_STRIP_VLAN = 1,
|
||||||
|
AR8216_OUT_ADD_VLAN = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
/* port forwarding state */
|
||||||
|
enum {
|
||||||
|
AR8216_PORT_STATE_DISABLED = 0,
|
||||||
|
AR8216_PORT_STATE_BLOCK = 1,
|
||||||
|
AR8216_PORT_STATE_LISTEN = 2,
|
||||||
|
AR8216_PORT_STATE_LEARN = 3,
|
||||||
|
AR8216_PORT_STATE_FORWARD = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
/* mib counter type */
|
||||||
|
enum {
|
||||||
|
AR8XXX_MIB_BASIC = 0,
|
||||||
|
AR8XXX_MIB_EXTENDED = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AR8XXX_VER_AR8216 = 0x01,
|
||||||
|
AR8XXX_VER_AR8236 = 0x03,
|
||||||
|
AR8XXX_VER_AR8316 = 0x10,
|
||||||
|
AR8XXX_VER_AR8327 = 0x12,
|
||||||
|
AR8XXX_VER_AR8337 = 0x13,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define AR8XXX_NUM_ARL_RECORDS 100
|
||||||
|
|
||||||
|
enum arl_op {
|
||||||
|
AR8XXX_ARL_INITIALIZE,
|
||||||
|
AR8XXX_ARL_GET_NEXT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct arl_entry {
|
||||||
|
u16 portmap;
|
||||||
|
u8 mac[6];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8xxx_priv;
|
||||||
|
|
||||||
|
struct ar8xxx_mib_desc {
|
||||||
|
unsigned int size;
|
||||||
|
unsigned int offset;
|
||||||
|
const char *name;
|
||||||
|
u8 type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8xxx_chip {
|
||||||
|
unsigned long caps;
|
||||||
|
bool config_at_probe;
|
||||||
|
bool mii_lo_first;
|
||||||
|
|
||||||
|
/* parameters to calculate REG_PORT_STATS_BASE */
|
||||||
|
unsigned reg_port_stats_start;
|
||||||
|
unsigned reg_port_stats_length;
|
||||||
|
|
||||||
|
unsigned reg_arl_ctrl;
|
||||||
|
|
||||||
|
int (*hw_init)(struct ar8xxx_priv *priv);
|
||||||
|
void (*cleanup)(struct ar8xxx_priv *priv);
|
||||||
|
|
||||||
|
const char *name;
|
||||||
|
int vlans;
|
||||||
|
int ports;
|
||||||
|
const struct switch_dev_ops *swops;
|
||||||
|
|
||||||
|
void (*init_globals)(struct ar8xxx_priv *priv);
|
||||||
|
void (*init_port)(struct ar8xxx_priv *priv, int port);
|
||||||
|
void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 members);
|
||||||
|
u32 (*read_port_status)(struct ar8xxx_priv *priv, int port);
|
||||||
|
u32 (*read_port_eee_status)(struct ar8xxx_priv *priv, int port);
|
||||||
|
int (*atu_flush)(struct ar8xxx_priv *priv);
|
||||||
|
int (*atu_flush_port)(struct ar8xxx_priv *priv, int port);
|
||||||
|
void (*vtu_flush)(struct ar8xxx_priv *priv);
|
||||||
|
void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask);
|
||||||
|
void (*phy_fixup)(struct ar8xxx_priv *priv, int phy);
|
||||||
|
void (*set_mirror_regs)(struct ar8xxx_priv *priv);
|
||||||
|
void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a,
|
||||||
|
u32 *status, enum arl_op op);
|
||||||
|
int (*sw_hw_apply)(struct switch_dev *dev);
|
||||||
|
void (*phy_rgmii_set)(struct ar8xxx_priv *priv, struct phy_device *phydev);
|
||||||
|
int (*phy_read)(struct ar8xxx_priv *priv, int addr, int regnum);
|
||||||
|
int (*phy_write)(struct ar8xxx_priv *priv, int addr, int regnum, u16 val);
|
||||||
|
|
||||||
|
const struct ar8xxx_mib_desc *mib_decs;
|
||||||
|
unsigned num_mibs;
|
||||||
|
unsigned mib_func;
|
||||||
|
int mib_rxb_id;
|
||||||
|
int mib_txb_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8xxx_priv {
|
||||||
|
struct switch_dev dev;
|
||||||
|
struct mii_bus *mii_bus;
|
||||||
|
struct mii_bus *sw_mii_bus;
|
||||||
|
struct phy_device *phy;
|
||||||
|
struct device *pdev;
|
||||||
|
|
||||||
|
int (*get_port_link)(unsigned port);
|
||||||
|
|
||||||
|
const struct net_device_ops *ndo_old;
|
||||||
|
struct net_device_ops ndo;
|
||||||
|
struct mutex reg_mutex;
|
||||||
|
u8 chip_ver;
|
||||||
|
u8 chip_rev;
|
||||||
|
const struct ar8xxx_chip *chip;
|
||||||
|
void *chip_data;
|
||||||
|
bool initialized;
|
||||||
|
bool port4_phy;
|
||||||
|
char buf[2048];
|
||||||
|
struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS];
|
||||||
|
char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256];
|
||||||
|
bool link_up[AR8X16_MAX_PORTS];
|
||||||
|
|
||||||
|
bool init;
|
||||||
|
|
||||||
|
struct mutex mib_lock;
|
||||||
|
struct delayed_work mib_work;
|
||||||
|
u64 *mib_stats;
|
||||||
|
u32 mib_poll_interval;
|
||||||
|
u8 mib_type;
|
||||||
|
|
||||||
|
struct list_head list;
|
||||||
|
unsigned int use_count;
|
||||||
|
|
||||||
|
/* all fields below are cleared on reset */
|
||||||
|
struct_group(ar8xxx_priv_volatile,
|
||||||
|
bool vlan;
|
||||||
|
|
||||||
|
u16 vlan_id[AR8XXX_MAX_VLANS];
|
||||||
|
u8 vlan_table[AR8XXX_MAX_VLANS];
|
||||||
|
u8 vlan_tagged;
|
||||||
|
u16 pvid[AR8X16_MAX_PORTS];
|
||||||
|
int arl_age_time;
|
||||||
|
|
||||||
|
/* mirroring */
|
||||||
|
bool mirror_rx;
|
||||||
|
bool mirror_tx;
|
||||||
|
int source_port;
|
||||||
|
int monitor_port;
|
||||||
|
u8 port_vlan_prio[AR8X16_MAX_PORTS];
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
u32
|
||||||
|
ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum);
|
||||||
|
void
|
||||||
|
ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val);
|
||||||
|
u32
|
||||||
|
ar8xxx_read(struct ar8xxx_priv *priv, int reg);
|
||||||
|
void
|
||||||
|
ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val);
|
||||||
|
u32
|
||||||
|
ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val);
|
||||||
|
|
||||||
|
void
|
||||||
|
ar8xxx_phy_dbg_read(struct ar8xxx_priv *priv, int phy_addr,
|
||||||
|
u16 dbg_addr, u16 *dbg_data);
|
||||||
|
void
|
||||||
|
ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr,
|
||||||
|
u16 dbg_addr, u16 dbg_data);
|
||||||
|
void
|
||||||
|
ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data);
|
||||||
|
u16
|
||||||
|
ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg);
|
||||||
|
void
|
||||||
|
ar8xxx_phy_init(struct ar8xxx_priv *priv);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_reset_mibs(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_mib_poll_interval(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_mib_poll_interval(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_mib_type(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_mib_type(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_hw_apply(struct switch_dev *dev);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_reset_switch(struct switch_dev *dev);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_port_link(struct switch_dev *dev, int port,
|
||||||
|
struct switch_port_link *link);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_port_mib(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_arl_age_time(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_arl_age_time(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_arl_table(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int
|
||||||
|
ar8xxx_sw_get_port_stats(struct switch_dev *dev, int port,
|
||||||
|
struct switch_port_stats *stats);
|
||||||
|
int
|
||||||
|
ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val);
|
||||||
|
|
||||||
|
static inline struct ar8xxx_priv *
|
||||||
|
swdev_to_ar8xxx(struct switch_dev *swdev)
|
||||||
|
{
|
||||||
|
return container_of(swdev, struct ar8xxx_priv, dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv)
|
||||||
|
{
|
||||||
|
return priv->chip->caps & AR8XXX_CAP_GIGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv)
|
||||||
|
{
|
||||||
|
return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool chip_is_ar8216(struct ar8xxx_priv *priv)
|
||||||
|
{
|
||||||
|
return priv->chip_ver == AR8XXX_VER_AR8216;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool chip_is_ar8236(struct ar8xxx_priv *priv)
|
||||||
|
{
|
||||||
|
return priv->chip_ver == AR8XXX_VER_AR8236;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool chip_is_ar8316(struct ar8xxx_priv *priv)
|
||||||
|
{
|
||||||
|
return priv->chip_ver == AR8XXX_VER_AR8316;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool chip_is_ar8327(struct ar8xxx_priv *priv)
|
||||||
|
{
|
||||||
|
return priv->chip_ver == AR8XXX_VER_AR8327;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool chip_is_ar8337(struct ar8xxx_priv *priv)
|
||||||
|
{
|
||||||
|
return priv->chip_ver == AR8XXX_VER_AR8337;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val)
|
||||||
|
{
|
||||||
|
ar8xxx_rmw(priv, reg, 0, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val)
|
||||||
|
{
|
||||||
|
ar8xxx_rmw(priv, reg, val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
|
||||||
|
{
|
||||||
|
regaddr >>= 1;
|
||||||
|
*r1 = regaddr & 0x1e;
|
||||||
|
|
||||||
|
regaddr >>= 5;
|
||||||
|
*r2 = regaddr & 0x7;
|
||||||
|
|
||||||
|
regaddr >>= 3;
|
||||||
|
*page = regaddr & 0x1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
wait_for_page_switch(void)
|
||||||
|
{
|
||||||
|
udelay(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
1551
6.12/target/linux/generic/files/drivers/net/phy/ar8327.c
Normal file
1551
6.12/target/linux/generic/files/drivers/net/phy/ar8327.c
Normal file
File diff suppressed because it is too large
Load diff
333
6.12/target/linux/generic/files/drivers/net/phy/ar8327.h
Normal file
333
6.12/target/linux/generic/files/drivers/net/phy/ar8327.h
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
/*
|
||||||
|
* ar8327.h: AR8216 switch driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __AR8327_H
|
||||||
|
#define __AR8327_H
|
||||||
|
|
||||||
|
#define AR8327_NUM_PORTS 7
|
||||||
|
#define AR8327_NUM_LEDS 15
|
||||||
|
#define AR8327_PORTS_ALL 0x7f
|
||||||
|
#define AR8327_NUM_LED_CTRL_REGS 4
|
||||||
|
|
||||||
|
#define AR8327_REG_MASK 0x000
|
||||||
|
|
||||||
|
#define AR8327_REG_PAD0_MODE 0x004
|
||||||
|
#define AR8327_REG_PAD5_MODE 0x008
|
||||||
|
#define AR8327_REG_PAD6_MODE 0x00c
|
||||||
|
#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0)
|
||||||
|
#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1)
|
||||||
|
#define AR8327_PAD_MAC_MII_EN BIT(2)
|
||||||
|
#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4)
|
||||||
|
#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5)
|
||||||
|
#define AR8327_PAD_MAC_GMII_EN BIT(6)
|
||||||
|
#define AR8327_PAD_SGMII_EN BIT(7)
|
||||||
|
#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8)
|
||||||
|
#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9)
|
||||||
|
#define AR8327_PAD_PHY_MII_EN BIT(10)
|
||||||
|
#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11)
|
||||||
|
#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12)
|
||||||
|
#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13)
|
||||||
|
#define AR8327_PAD_PHY_GMII_EN BIT(14)
|
||||||
|
#define AR8327_PAD_PHYX_GMII_EN BIT(16)
|
||||||
|
#define AR8327_PAD_PHYX_RGMII_EN BIT(17)
|
||||||
|
#define AR8327_PAD_PHYX_MII_EN BIT(18)
|
||||||
|
#define AR8327_PAD_SGMII_DELAY_EN BIT(19)
|
||||||
|
#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2)
|
||||||
|
#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20
|
||||||
|
#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2)
|
||||||
|
#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22
|
||||||
|
#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24)
|
||||||
|
#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25)
|
||||||
|
#define AR8327_PAD_RGMII_EN BIT(26)
|
||||||
|
|
||||||
|
#define AR8327_REG_POWER_ON_STRAP 0x010
|
||||||
|
#define AR8327_POWER_ON_STRAP_POWER_ON_SEL BIT(31)
|
||||||
|
#define AR8327_POWER_ON_STRAP_LED_OPEN_EN BIT(24)
|
||||||
|
#define AR8327_POWER_ON_STRAP_SERDES_AEN BIT(7)
|
||||||
|
|
||||||
|
#define AR8327_REG_INT_STATUS0 0x020
|
||||||
|
#define AR8327_INT0_VT_DONE BIT(20)
|
||||||
|
|
||||||
|
#define AR8327_REG_INT_STATUS1 0x024
|
||||||
|
#define AR8327_REG_INT_MASK0 0x028
|
||||||
|
#define AR8327_REG_INT_MASK1 0x02c
|
||||||
|
|
||||||
|
#define AR8327_REG_MODULE_EN 0x030
|
||||||
|
#define AR8327_MODULE_EN_MIB BIT(0)
|
||||||
|
|
||||||
|
#define AR8327_REG_MIB_FUNC 0x034
|
||||||
|
#define AR8327_MIB_CPU_KEEP BIT(20)
|
||||||
|
|
||||||
|
#define AR8327_REG_SERVICE_TAG 0x048
|
||||||
|
#define AR8327_REG_LED_CTRL(_i) (0x050 + (_i) * 4)
|
||||||
|
#define AR8327_REG_LED_CTRL0 0x050
|
||||||
|
#define AR8327_REG_LED_CTRL1 0x054
|
||||||
|
#define AR8327_REG_LED_CTRL2 0x058
|
||||||
|
#define AR8327_REG_LED_CTRL3 0x05c
|
||||||
|
#define AR8327_REG_MAC_ADDR0 0x060
|
||||||
|
#define AR8327_REG_MAC_ADDR1 0x064
|
||||||
|
|
||||||
|
#define AR8327_REG_MAX_FRAME_SIZE 0x078
|
||||||
|
#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14)
|
||||||
|
|
||||||
|
#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4)
|
||||||
|
#define AR8327_PORT_STATUS_TXFLOW_AUTO BIT(10)
|
||||||
|
#define AR8327_PORT_STATUS_RXFLOW_AUTO BIT(11)
|
||||||
|
|
||||||
|
#define AR8327_REG_HEADER_CTRL 0x098
|
||||||
|
#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4)
|
||||||
|
|
||||||
|
#define AR8327_REG_SGMII_CTRL 0x0e0
|
||||||
|
#define AR8327_SGMII_CTRL_EN_PLL BIT(1)
|
||||||
|
#define AR8327_SGMII_CTRL_EN_RX BIT(2)
|
||||||
|
#define AR8327_SGMII_CTRL_EN_TX BIT(3)
|
||||||
|
|
||||||
|
#define AR8327_REG_EEE_CTRL 0x100
|
||||||
|
#define AR8327_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2)
|
||||||
|
|
||||||
|
#define AR8327_REG_FRAME_ACK_CTRL0 0x210
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN0 BIT(0)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN0 BIT(1)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN0 BIT(2)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN0 BIT(3)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_DHCP_EN0 BIT(4)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN0 BIT(5)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN0 BIT(6)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN1 BIT(8)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN1 BIT(9)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN1 BIT(10)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN1 BIT(11)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_DHCP_EN1 BIT(12)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN1 BIT(13)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN1 BIT(14)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN2 BIT(16)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN2 BIT(17)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN2 BIT(18)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN2 BIT(19)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_DHCP_EN2 BIT(20)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN2 BIT(21)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN2 BIT(22)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN3 BIT(24)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN3 BIT(25)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN3 BIT(26)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN3 BIT(27)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_DHCP_EN3 BIT(28)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN3 BIT(29)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN3 BIT(30)
|
||||||
|
|
||||||
|
#define AR8327_REG_FRAME_ACK_CTRL1 0x214
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN4 BIT(0)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN4 BIT(1)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN4 BIT(2)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN4 BIT(3)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_DHCP_EN4 BIT(4)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN4 BIT(5)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN4 BIT(6)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN5 BIT(8)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN5 BIT(9)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN5 BIT(10)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN5 BIT(11)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_DHCP_EN5 BIT(12)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN5 BIT(13)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN5 BIT(14)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN6 BIT(16)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN6 BIT(17)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN6 BIT(18)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_EAPOL_EN6 BIT(19)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_DHCP_EN6 BIT(20)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN6 BIT(21)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN6 BIT(22)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_V3_EN BIT(24)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_PPPOE_EN BIT(25)
|
||||||
|
|
||||||
|
#define AR8327_REG_FRAME_ACK_CTRL(_i) (0x210 + ((_i) / 4) * 0x4)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_MLD BIT(0)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN BIT(1)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE BIT(2)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_EAPOL BIT(3)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_DHCP BIT(4)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_ACK BIT(5)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_ARP_REQ BIT(6)
|
||||||
|
#define AR8327_FRAME_ACK_CTRL_S(_i) (((_i) % 4) * 8)
|
||||||
|
|
||||||
|
#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8)
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_PRI_MASK BITS(0, 3)
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12)
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_SVID_S 0
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_SPRI BITS(13, 3)
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_SPRI_S 13
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12)
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_CVID_S 16
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_CPRI BITS(29, 3)
|
||||||
|
#define AR8327_PORT_VLAN0_DEF_CPRI_S 29
|
||||||
|
|
||||||
|
#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8)
|
||||||
|
#define AR8327_PORT_VLAN1_VLAN_PRI_PROP BIT(4)
|
||||||
|
#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6)
|
||||||
|
#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2)
|
||||||
|
#define AR8327_PORT_VLAN1_OUT_MODE_S 12
|
||||||
|
#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0
|
||||||
|
#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1
|
||||||
|
#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2
|
||||||
|
#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3
|
||||||
|
|
||||||
|
#define AR8327_REG_ATU_DATA0 0x600
|
||||||
|
#define AR8327_ATU_ADDR0 BITS(0, 8)
|
||||||
|
#define AR8327_ATU_ADDR0_S 0
|
||||||
|
#define AR8327_ATU_ADDR1 BITS(8, 8)
|
||||||
|
#define AR8327_ATU_ADDR1_S 8
|
||||||
|
#define AR8327_ATU_ADDR2 BITS(16, 8)
|
||||||
|
#define AR8327_ATU_ADDR2_S 16
|
||||||
|
#define AR8327_ATU_ADDR3 BITS(24, 8)
|
||||||
|
#define AR8327_ATU_ADDR3_S 24
|
||||||
|
#define AR8327_REG_ATU_DATA1 0x604
|
||||||
|
#define AR8327_ATU_ADDR4 BITS(0, 8)
|
||||||
|
#define AR8327_ATU_ADDR4_S 0
|
||||||
|
#define AR8327_ATU_ADDR5 BITS(8, 8)
|
||||||
|
#define AR8327_ATU_ADDR5_S 8
|
||||||
|
#define AR8327_ATU_PORTS BITS(16, 7)
|
||||||
|
#define AR8327_ATU_PORTS_S 16
|
||||||
|
#define AR8327_ATU_PORT0 BIT(16)
|
||||||
|
#define AR8327_ATU_PORT1 BIT(17)
|
||||||
|
#define AR8327_ATU_PORT2 BIT(18)
|
||||||
|
#define AR8327_ATU_PORT3 BIT(19)
|
||||||
|
#define AR8327_ATU_PORT4 BIT(20)
|
||||||
|
#define AR8327_ATU_PORT5 BIT(21)
|
||||||
|
#define AR8327_ATU_PORT6 BIT(22)
|
||||||
|
#define AR8327_REG_ATU_DATA2 0x608
|
||||||
|
#define AR8327_ATU_STATUS BITS(0, 4)
|
||||||
|
|
||||||
|
#define AR8327_REG_ATU_FUNC 0x60c
|
||||||
|
#define AR8327_ATU_FUNC_OP BITS(0, 4)
|
||||||
|
#define AR8327_ATU_FUNC_OP_NOOP 0x0
|
||||||
|
#define AR8327_ATU_FUNC_OP_FLUSH 0x1
|
||||||
|
#define AR8327_ATU_FUNC_OP_LOAD 0x2
|
||||||
|
#define AR8327_ATU_FUNC_OP_PURGE 0x3
|
||||||
|
#define AR8327_ATU_FUNC_OP_FLUSH_UNLOCKED 0x4
|
||||||
|
#define AR8327_ATU_FUNC_OP_FLUSH_PORT 0x5
|
||||||
|
#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6
|
||||||
|
#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7
|
||||||
|
#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8
|
||||||
|
#define AR8327_ATU_PORT_NUM BITS(8, 4)
|
||||||
|
#define AR8327_ATU_PORT_NUM_S 8
|
||||||
|
#define AR8327_ATU_FUNC_BUSY BIT(31)
|
||||||
|
|
||||||
|
#define AR8327_REG_VTU_FUNC0 0x0610
|
||||||
|
#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14)
|
||||||
|
#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2)
|
||||||
|
#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0
|
||||||
|
#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1
|
||||||
|
#define AR8327_VTU_FUNC0_EG_MODE_TAG 2
|
||||||
|
#define AR8327_VTU_FUNC0_EG_MODE_NOT 3
|
||||||
|
#define AR8327_VTU_FUNC0_IVL BIT(19)
|
||||||
|
#define AR8327_VTU_FUNC0_VALID BIT(20)
|
||||||
|
|
||||||
|
#define AR8327_REG_VTU_FUNC1 0x0614
|
||||||
|
#define AR8327_VTU_FUNC1_OP BITS(0, 3)
|
||||||
|
#define AR8327_VTU_FUNC1_OP_NOOP 0
|
||||||
|
#define AR8327_VTU_FUNC1_OP_FLUSH 1
|
||||||
|
#define AR8327_VTU_FUNC1_OP_LOAD 2
|
||||||
|
#define AR8327_VTU_FUNC1_OP_PURGE 3
|
||||||
|
#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4
|
||||||
|
#define AR8327_VTU_FUNC1_OP_GET_NEXT 5
|
||||||
|
#define AR8327_VTU_FUNC1_OP_GET_ONE 6
|
||||||
|
#define AR8327_VTU_FUNC1_FULL BIT(4)
|
||||||
|
#define AR8327_VTU_FUNC1_PORT BIT(8, 4)
|
||||||
|
#define AR8327_VTU_FUNC1_PORT_S 8
|
||||||
|
#define AR8327_VTU_FUNC1_VID BIT(16, 12)
|
||||||
|
#define AR8327_VTU_FUNC1_VID_S 16
|
||||||
|
#define AR8327_VTU_FUNC1_BUSY BIT(31)
|
||||||
|
|
||||||
|
#define AR8327_REG_ARL_CTRL 0x0618
|
||||||
|
|
||||||
|
#define AR8327_REG_FWD_CTRL0 0x620
|
||||||
|
#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10)
|
||||||
|
#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4)
|
||||||
|
#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4
|
||||||
|
|
||||||
|
#define AR8327_REG_FWD_CTRL1 0x624
|
||||||
|
#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7)
|
||||||
|
#define AR8327_FWD_CTRL1_UC_FLOOD_S 0
|
||||||
|
#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7)
|
||||||
|
#define AR8327_FWD_CTRL1_MC_FLOOD_S 8
|
||||||
|
#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7)
|
||||||
|
#define AR8327_FWD_CTRL1_BC_FLOOD_S 16
|
||||||
|
#define AR8327_FWD_CTRL1_IGMP BITS(24, 7)
|
||||||
|
#define AR8327_FWD_CTRL1_IGMP_S 24
|
||||||
|
|
||||||
|
#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc)
|
||||||
|
#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7)
|
||||||
|
#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2)
|
||||||
|
#define AR8327_PORT_LOOKUP_IN_MODE_S 8
|
||||||
|
#define AR8327_PORT_LOOKUP_STATE BITS(16, 3)
|
||||||
|
#define AR8327_PORT_LOOKUP_STATE_S 16
|
||||||
|
#define AR8327_PORT_LOOKUP_LEARN BIT(20)
|
||||||
|
#define AR8327_PORT_LOOKUP_ING_MIRROR_EN BIT(25)
|
||||||
|
|
||||||
|
#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc)
|
||||||
|
|
||||||
|
#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8)
|
||||||
|
#define AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16)
|
||||||
|
|
||||||
|
#define AR8337_PAD_MAC06_EXCHANGE_EN BIT(31)
|
||||||
|
|
||||||
|
#define AR8327_PHY_MODE_SEL 0x12
|
||||||
|
#define AR8327_PHY_MODE_SEL_RGMII BIT(3)
|
||||||
|
#define AR8327_PHY_TEST_CTRL 0x0
|
||||||
|
#define AR8327_PHY_TEST_CTRL_RGMII_RX_DELAY BIT(15)
|
||||||
|
#define AR8327_PHY_SYS_CTRL 0x5
|
||||||
|
#define AR8327_PHY_SYS_CTRL_RGMII_TX_DELAY BIT(8)
|
||||||
|
|
||||||
|
enum ar8327_led_pattern {
|
||||||
|
AR8327_LED_PATTERN_OFF = 0,
|
||||||
|
AR8327_LED_PATTERN_BLINK,
|
||||||
|
AR8327_LED_PATTERN_ON,
|
||||||
|
AR8327_LED_PATTERN_RULE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8327_led_entry {
|
||||||
|
unsigned reg;
|
||||||
|
unsigned shift;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8327_led {
|
||||||
|
struct led_classdev cdev;
|
||||||
|
struct ar8xxx_priv *sw_priv;
|
||||||
|
|
||||||
|
char *name;
|
||||||
|
bool active_low;
|
||||||
|
u8 led_num;
|
||||||
|
enum ar8327_led_mode mode;
|
||||||
|
|
||||||
|
struct mutex mutex;
|
||||||
|
spinlock_t lock;
|
||||||
|
struct work_struct led_work;
|
||||||
|
bool enable_hw_mode;
|
||||||
|
enum ar8327_led_pattern pattern;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8327_data {
|
||||||
|
u32 port0_status;
|
||||||
|
u32 port6_status;
|
||||||
|
|
||||||
|
struct ar8327_led **leds;
|
||||||
|
unsigned int num_leds;
|
||||||
|
|
||||||
|
/* all fields below are cleared on reset */
|
||||||
|
bool eee[AR8XXX_NUM_PHYS];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
37
6.12/target/linux/generic/files/drivers/net/phy/b53/Kconfig
Normal file
37
6.12/target/linux/generic/files/drivers/net/phy/b53/Kconfig
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
menuconfig SWCONFIG_B53
|
||||||
|
tristate "Broadcom bcm53xx managed switch support"
|
||||||
|
depends on SWCONFIG
|
||||||
|
help
|
||||||
|
This driver adds support for Broadcom managed switch chips. It supports
|
||||||
|
BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX
|
||||||
|
integrated switches.
|
||||||
|
|
||||||
|
config SWCONFIG_B53_SPI_DRIVER
|
||||||
|
tristate "B53 SPI connected switch driver"
|
||||||
|
depends on SWCONFIG_B53 && SPI
|
||||||
|
help
|
||||||
|
Select to enable support for registering switches configured through SPI.
|
||||||
|
|
||||||
|
config SWCONFIG_B53_PHY_DRIVER
|
||||||
|
tristate "B53 MDIO connected switch driver"
|
||||||
|
depends on SWCONFIG_B53
|
||||||
|
select SWCONFIG_B53_PHY_FIXUP
|
||||||
|
help
|
||||||
|
Select to enable support for registering switches configured through MDIO.
|
||||||
|
|
||||||
|
config SWCONFIG_B53_MMAP_DRIVER
|
||||||
|
tristate "B53 MMAP connected switch driver"
|
||||||
|
depends on SWCONFIG_B53
|
||||||
|
help
|
||||||
|
Select to enable support for memory-mapped switches like the BCM63XX
|
||||||
|
integrated switches.
|
||||||
|
|
||||||
|
config SWCONFIG_B53_SRAB_DRIVER
|
||||||
|
tristate "B53 SRAB connected switch driver"
|
||||||
|
depends on SWCONFIG_B53
|
||||||
|
help
|
||||||
|
Select to enable support for memory-mapped Switch Register Access
|
||||||
|
Bridge Registers (SRAB) like it is found on the BCM53010
|
||||||
|
|
||||||
|
config SWCONFIG_B53_PHY_FIXUP
|
||||||
|
bool
|
10
6.12/target/linux/generic/files/drivers/net/phy/b53/Makefile
Normal file
10
6.12/target/linux/generic/files/drivers/net/phy/b53/Makefile
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
obj-$(CONFIG_SWCONFIG_B53) += b53_common.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_SWCONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_SWCONFIG_B53_MMAP_DRIVER) += b53_mmap.o
|
||||||
|
obj-$(CONFIG_SWCONFIG_B53_SRAB_DRIVER) += b53_srab.o
|
||||||
|
obj-$(CONFIG_SWCONFIG_B53_PHY_DRIVER) += b53_mdio.o
|
||||||
|
obj-$(CONFIG_SWCONFIG_B53_SPI_DRIVER) += b53_spi.o
|
||||||
|
|
||||||
|
ccflags-y += -Werror
|
1724
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_common.c
Normal file
1724
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_common.c
Normal file
File diff suppressed because it is too large
Load diff
436
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_mdio.c
Normal file
436
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_mdio.c
Normal file
|
@ -0,0 +1,436 @@
|
||||||
|
/*
|
||||||
|
* B53 register access through MII registers
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/phy.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
|
||||||
|
#include "b53_priv.h"
|
||||||
|
|
||||||
|
#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */
|
||||||
|
|
||||||
|
/* MII registers */
|
||||||
|
#define REG_MII_PAGE 0x10 /* MII Page register */
|
||||||
|
#define REG_MII_ADDR 0x11 /* MII Address register */
|
||||||
|
#define REG_MII_DATA0 0x18 /* MII Data register 0 */
|
||||||
|
#define REG_MII_DATA1 0x19 /* MII Data register 1 */
|
||||||
|
#define REG_MII_DATA2 0x1a /* MII Data register 2 */
|
||||||
|
#define REG_MII_DATA3 0x1b /* MII Data register 3 */
|
||||||
|
|
||||||
|
#define REG_MII_PAGE_ENABLE BIT(0)
|
||||||
|
#define REG_MII_ADDR_WRITE BIT(0)
|
||||||
|
#define REG_MII_ADDR_READ BIT(1)
|
||||||
|
|
||||||
|
static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u16 v;
|
||||||
|
int ret;
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
|
||||||
|
if (dev->current_page != page) {
|
||||||
|
/* set page number */
|
||||||
|
v = (page << 8) | REG_MII_PAGE_ENABLE;
|
||||||
|
ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
dev->current_page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set register address */
|
||||||
|
v = (reg << 8) | op;
|
||||||
|
ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* check if operation completed */
|
||||||
|
for (i = 0; i < 5; ++i) {
|
||||||
|
v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR);
|
||||||
|
if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ)))
|
||||||
|
break;
|
||||||
|
usleep_range(10, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WARN_ON(i == 5))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0);
|
||||||
|
*val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
u64 temp = 0;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
for (i = 2; i >= 0; i--) {
|
||||||
|
temp <<= 16;
|
||||||
|
temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
*val = temp;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
u64 temp = 0;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
for (i = 3; i >= 0; i--) {
|
||||||
|
temp <<= 16;
|
||||||
|
temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
*val = temp;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u16 value)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u32 value)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
unsigned int i;
|
||||||
|
u32 temp = value;
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
|
||||||
|
temp & 0xffff);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
temp >>= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u64 value)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
unsigned i;
|
||||||
|
u64 temp = value;
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
|
||||||
|
temp & 0xffff);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
temp >>= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u64 value)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
unsigned i;
|
||||||
|
u64 temp = value;
|
||||||
|
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i,
|
||||||
|
temp & 0xffff);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
temp >>= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_phy_read16(struct b53_device *dev, int addr, u8 reg,
|
||||||
|
u16 *value)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
|
||||||
|
*value = mdiobus_read(bus, addr, reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mdio_phy_write16(struct b53_device *dev, int addr, u8 reg,
|
||||||
|
u16 value)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->priv;
|
||||||
|
|
||||||
|
return mdiobus_write(bus, addr, reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct b53_io_ops b53_mdio_ops = {
|
||||||
|
.read8 = b53_mdio_read8,
|
||||||
|
.read16 = b53_mdio_read16,
|
||||||
|
.read32 = b53_mdio_read32,
|
||||||
|
.read48 = b53_mdio_read48,
|
||||||
|
.read64 = b53_mdio_read64,
|
||||||
|
.write8 = b53_mdio_write8,
|
||||||
|
.write16 = b53_mdio_write16,
|
||||||
|
.write32 = b53_mdio_write32,
|
||||||
|
.write48 = b53_mdio_write48,
|
||||||
|
.write64 = b53_mdio_write64,
|
||||||
|
.phy_read16 = b53_mdio_phy_read16,
|
||||||
|
.phy_write16 = b53_mdio_phy_write16,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int b53_phy_probe(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
struct b53_device *dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* allow the generic phy driver to take over */
|
||||||
|
if (phydev->mdio.addr != B53_PSEUDO_PHY && phydev->mdio.addr != 0)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
dev = b53_swconfig_switch_alloc(&phydev->mdio.dev, &b53_mdio_ops, phydev->mdio.bus);
|
||||||
|
if (!dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
dev->current_page = 0xff;
|
||||||
|
dev->priv = phydev->mdio.bus;
|
||||||
|
dev->ops = &b53_mdio_ops;
|
||||||
|
dev->pdata = NULL;
|
||||||
|
mutex_init(&dev->reg_mutex);
|
||||||
|
|
||||||
|
ret = b53_swconfig_switch_detect(dev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
linkmode_zero(phydev->supported);
|
||||||
|
if (is5325(dev) || is5365(dev))
|
||||||
|
linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported);
|
||||||
|
else
|
||||||
|
linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported);
|
||||||
|
|
||||||
|
linkmode_copy(phydev->advertising, phydev->supported);
|
||||||
|
|
||||||
|
ret = b53_swconfig_switch_register(dev);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev->dev, "failed to register switch: %i\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
phydev->priv = dev;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_phy_config_init(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
struct b53_device *dev = phydev->priv;
|
||||||
|
|
||||||
|
/* we don't use page 0xff, so force a page set */
|
||||||
|
dev->current_page = 0xff;
|
||||||
|
/* force the ethX as alias */
|
||||||
|
dev->sw_dev.alias = phydev->attached_dev->name;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void b53_phy_remove(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
struct b53_device *priv = phydev->priv;
|
||||||
|
|
||||||
|
if (!priv)
|
||||||
|
return;
|
||||||
|
|
||||||
|
b53_switch_remove(priv);
|
||||||
|
|
||||||
|
phydev->priv = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_phy_config_aneg(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_phy_read_status(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
struct b53_device *priv = phydev->priv;
|
||||||
|
|
||||||
|
if (is5325(priv) || is5365(priv))
|
||||||
|
phydev->speed = 100;
|
||||||
|
else
|
||||||
|
phydev->speed = 1000;
|
||||||
|
|
||||||
|
phydev->duplex = DUPLEX_FULL;
|
||||||
|
phydev->link = 1;
|
||||||
|
phydev->state = PHY_RUNNING;
|
||||||
|
|
||||||
|
netif_carrier_on(phydev->attached_dev);
|
||||||
|
phydev->adjust_link(phydev->attached_dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BCM5325, BCM539x */
|
||||||
|
static struct phy_driver b53_phy_driver_id1 = {
|
||||||
|
.phy_id = 0x0143bc00,
|
||||||
|
.name = "Broadcom B53 (1)",
|
||||||
|
.phy_id_mask = 0x1ffffc00,
|
||||||
|
.features = 0,
|
||||||
|
.probe = b53_phy_probe,
|
||||||
|
.remove = b53_phy_remove,
|
||||||
|
.config_aneg = b53_phy_config_aneg,
|
||||||
|
.config_init = b53_phy_config_init,
|
||||||
|
.read_status = b53_phy_read_status,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* BCM53125, BCM53128 */
|
||||||
|
static struct phy_driver b53_phy_driver_id2 = {
|
||||||
|
.phy_id = 0x03625c00,
|
||||||
|
.name = "Broadcom B53 (2)",
|
||||||
|
.phy_id_mask = 0x1ffffc00,
|
||||||
|
.features = 0,
|
||||||
|
.probe = b53_phy_probe,
|
||||||
|
.remove = b53_phy_remove,
|
||||||
|
.config_aneg = b53_phy_config_aneg,
|
||||||
|
.config_init = b53_phy_config_init,
|
||||||
|
.read_status = b53_phy_read_status,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* BCM5365 */
|
||||||
|
static struct phy_driver b53_phy_driver_id3 = {
|
||||||
|
.phy_id = 0x00406300,
|
||||||
|
.name = "Broadcom B53 (3)",
|
||||||
|
.phy_id_mask = 0x1fffff00,
|
||||||
|
.features = 0,
|
||||||
|
.probe = b53_phy_probe,
|
||||||
|
.remove = b53_phy_remove,
|
||||||
|
.config_aneg = b53_phy_config_aneg,
|
||||||
|
.config_init = b53_phy_config_init,
|
||||||
|
.read_status = b53_phy_read_status,
|
||||||
|
};
|
||||||
|
|
||||||
|
int __init b53_phy_driver_register(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = phy_driver_register(&b53_phy_driver_id1, THIS_MODULE);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = phy_driver_register(&b53_phy_driver_id2, THIS_MODULE);
|
||||||
|
if (ret)
|
||||||
|
goto err1;
|
||||||
|
|
||||||
|
ret = phy_driver_register(&b53_phy_driver_id3, THIS_MODULE);
|
||||||
|
if (!ret)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
phy_driver_unregister(&b53_phy_driver_id2);
|
||||||
|
err1:
|
||||||
|
phy_driver_unregister(&b53_phy_driver_id1);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __exit b53_phy_driver_unregister(void)
|
||||||
|
{
|
||||||
|
phy_driver_unregister(&b53_phy_driver_id3);
|
||||||
|
phy_driver_unregister(&b53_phy_driver_id2);
|
||||||
|
phy_driver_unregister(&b53_phy_driver_id1);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(b53_phy_driver_register);
|
||||||
|
module_exit(b53_phy_driver_unregister);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("B53 MDIO access driver");
|
||||||
|
MODULE_LICENSE("Dual BSD/GPL");
|
248
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_mmap.c
Normal file
248
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_mmap.c
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
/*
|
||||||
|
* B53 register access through memory mapped registers
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012-2013 Jonas Gorski <jogo@openwrt.org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/platform_data/b53.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
|
||||||
|
#include "b53_priv.h"
|
||||||
|
|
||||||
|
static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
|
||||||
|
*val = readb(regs + (page << 8) + reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
|
||||||
|
if (WARN_ON(reg % 2))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (dev->pdata && dev->pdata->big_endian)
|
||||||
|
*val = readw_be(regs + (page << 8) + reg);
|
||||||
|
else
|
||||||
|
*val = readw(regs + (page << 8) + reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
|
||||||
|
if (WARN_ON(reg % 4))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (dev->pdata && dev->pdata->big_endian)
|
||||||
|
*val = readl_be(regs + (page << 8) + reg);
|
||||||
|
else
|
||||||
|
*val = readl(regs + (page << 8) + reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
if (WARN_ON(reg % 2))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (reg % 4) {
|
||||||
|
u16 lo;
|
||||||
|
u32 hi;
|
||||||
|
|
||||||
|
b53_mmap_read16(dev, page, reg, &lo);
|
||||||
|
b53_mmap_read32(dev, page, reg + 2, &hi);
|
||||||
|
|
||||||
|
*val = ((u64)hi << 16) | lo;
|
||||||
|
} else {
|
||||||
|
u32 lo;
|
||||||
|
u16 hi;
|
||||||
|
|
||||||
|
b53_mmap_read32(dev, page, reg, &lo);
|
||||||
|
b53_mmap_read16(dev, page, reg + 4, &hi);
|
||||||
|
|
||||||
|
*val = ((u64)hi << 32) | lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
u32 hi, lo;
|
||||||
|
|
||||||
|
if (WARN_ON(reg % 4))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
b53_mmap_read32(dev, page, reg, &lo);
|
||||||
|
b53_mmap_read32(dev, page, reg + 4, &hi);
|
||||||
|
|
||||||
|
*val = ((u64)hi << 32) | lo;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
|
||||||
|
writeb(value, regs + (page << 8) + reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u16 value)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
|
||||||
|
if (WARN_ON(reg % 2))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (dev->pdata && dev->pdata->big_endian)
|
||||||
|
writew_be(value, regs + (page << 8) + reg);
|
||||||
|
else
|
||||||
|
writew(value, regs + (page << 8) + reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u32 value)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
|
||||||
|
if (WARN_ON(reg % 4))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (dev->pdata && dev->pdata->big_endian)
|
||||||
|
writel_be(value, regs + (page << 8) + reg);
|
||||||
|
else
|
||||||
|
writel(value, regs + (page << 8) + reg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u64 value)
|
||||||
|
{
|
||||||
|
if (WARN_ON(reg % 2))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (reg % 4) {
|
||||||
|
u32 hi = (u32)(value >> 16);
|
||||||
|
u16 lo = (u16)value;
|
||||||
|
|
||||||
|
b53_mmap_write16(dev, page, reg, lo);
|
||||||
|
b53_mmap_write32(dev, page, reg + 2, hi);
|
||||||
|
} else {
|
||||||
|
u16 hi = (u16)(value >> 32);
|
||||||
|
u32 lo = (u32)value;
|
||||||
|
|
||||||
|
b53_mmap_write32(dev, page, reg, lo);
|
||||||
|
b53_mmap_write16(dev, page, reg + 4, hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u64 value)
|
||||||
|
{
|
||||||
|
u32 hi, lo;
|
||||||
|
|
||||||
|
hi = (u32)(value >> 32);
|
||||||
|
lo = (u32)value;
|
||||||
|
|
||||||
|
if (WARN_ON(reg % 4))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
b53_mmap_write32(dev, page, reg, lo);
|
||||||
|
b53_mmap_write32(dev, page, reg + 4, hi);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct b53_io_ops b53_mmap_ops = {
|
||||||
|
.read8 = b53_mmap_read8,
|
||||||
|
.read16 = b53_mmap_read16,
|
||||||
|
.read32 = b53_mmap_read32,
|
||||||
|
.read48 = b53_mmap_read48,
|
||||||
|
.read64 = b53_mmap_read64,
|
||||||
|
.write8 = b53_mmap_write8,
|
||||||
|
.write16 = b53_mmap_write16,
|
||||||
|
.write32 = b53_mmap_write32,
|
||||||
|
.write48 = b53_mmap_write48,
|
||||||
|
.write64 = b53_mmap_write64,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int b53_mmap_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct b53_platform_data *pdata = pdev->dev.platform_data;
|
||||||
|
struct b53_device *dev;
|
||||||
|
|
||||||
|
if (!pdata)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
dev = b53_swconfig_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs);
|
||||||
|
if (!dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (pdata)
|
||||||
|
dev->pdata = pdata;
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, dev);
|
||||||
|
|
||||||
|
return b53_swconfig_switch_register(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
|
||||||
|
static int b53_mmap_remove(struct platform_device *pdev)
|
||||||
|
#else
|
||||||
|
static void b53_mmap_remove(struct platform_device *pdev)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
struct b53_device *dev = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
if (dev)
|
||||||
|
b53_switch_remove(dev);
|
||||||
|
|
||||||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver b53_mmap_driver = {
|
||||||
|
.probe = b53_mmap_probe,
|
||||||
|
.remove = b53_mmap_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "b53-switch",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(b53_mmap_driver);
|
||||||
|
MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
|
||||||
|
MODULE_DESCRIPTION("B53 MMAP access driver");
|
||||||
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* B53 PHY Fixup call
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/phy.h>
|
||||||
|
|
||||||
|
#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */
|
||||||
|
|
||||||
|
#define B53_BRCM_OUI_1 0x0143bc00
|
||||||
|
#define B53_BRCM_OUI_2 0x03625c00
|
||||||
|
#define B53_BRCM_OUI_3 0x00406300
|
||||||
|
|
||||||
|
static int b53_phy_fixup(struct phy_device *dev)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->mdio.bus;
|
||||||
|
u32 phy_id;
|
||||||
|
|
||||||
|
if (dev->mdio.addr != B53_PSEUDO_PHY)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* read the first port's id */
|
||||||
|
phy_id = mdiobus_read(bus, 0, 2) << 16;
|
||||||
|
phy_id |= mdiobus_read(bus, 0, 3);
|
||||||
|
|
||||||
|
if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 ||
|
||||||
|
(phy_id & 0xfffffc00) == B53_BRCM_OUI_2 ||
|
||||||
|
(phy_id & 0xffffff00) == B53_BRCM_OUI_3) {
|
||||||
|
dev->phy_id = phy_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int __init b53_phy_fixup_register(void)
|
||||||
|
{
|
||||||
|
return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup);
|
||||||
|
}
|
||||||
|
|
||||||
|
subsys_initcall(b53_phy_fixup_register);
|
336
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_priv.h
Normal file
336
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_priv.h
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
/*
|
||||||
|
* B53 common definitions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __B53_PRIV_H
|
||||||
|
#define __B53_PRIV_H
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/switch.h>
|
||||||
|
|
||||||
|
struct b53_device;
|
||||||
|
|
||||||
|
struct b53_io_ops {
|
||||||
|
int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value);
|
||||||
|
int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value);
|
||||||
|
int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value);
|
||||||
|
int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value);
|
||||||
|
int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value);
|
||||||
|
int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value);
|
||||||
|
int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value);
|
||||||
|
int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value);
|
||||||
|
int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value);
|
||||||
|
int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value);
|
||||||
|
int (*phy_read16)(struct b53_device *dev, int addr, u8 reg, u16 *value);
|
||||||
|
int (*phy_write16)(struct b53_device *dev, int addr, u8 reg, u16 value);
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
BCM5325_DEVICE_ID = 0x25,
|
||||||
|
BCM5365_DEVICE_ID = 0x65,
|
||||||
|
BCM5395_DEVICE_ID = 0x95,
|
||||||
|
BCM5397_DEVICE_ID = 0x97,
|
||||||
|
BCM5398_DEVICE_ID = 0x98,
|
||||||
|
BCM53115_DEVICE_ID = 0x53115,
|
||||||
|
BCM53125_DEVICE_ID = 0x53125,
|
||||||
|
BCM53128_DEVICE_ID = 0x53128,
|
||||||
|
BCM63XX_DEVICE_ID = 0x6300,
|
||||||
|
BCM53010_DEVICE_ID = 0x53010,
|
||||||
|
BCM53011_DEVICE_ID = 0x53011,
|
||||||
|
BCM53012_DEVICE_ID = 0x53012,
|
||||||
|
BCM53018_DEVICE_ID = 0x53018,
|
||||||
|
BCM53019_DEVICE_ID = 0x53019,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define B53_N_PORTS 9
|
||||||
|
#define B53_N_PORTS_25 6
|
||||||
|
|
||||||
|
struct b53_vlan {
|
||||||
|
unsigned int members:B53_N_PORTS;
|
||||||
|
unsigned int untag:B53_N_PORTS;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct b53_port {
|
||||||
|
unsigned int pvid:12;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct b53_device {
|
||||||
|
struct switch_dev sw_dev;
|
||||||
|
struct b53_platform_data *pdata;
|
||||||
|
|
||||||
|
struct mutex reg_mutex;
|
||||||
|
const struct b53_io_ops *ops;
|
||||||
|
|
||||||
|
/* chip specific data */
|
||||||
|
u32 chip_id;
|
||||||
|
u8 core_rev;
|
||||||
|
u8 vta_regs[3];
|
||||||
|
u8 duplex_reg;
|
||||||
|
u8 jumbo_pm_reg;
|
||||||
|
u8 jumbo_size_reg;
|
||||||
|
int reset_gpio;
|
||||||
|
|
||||||
|
/* used ports mask */
|
||||||
|
u16 enabled_ports;
|
||||||
|
|
||||||
|
/* connect specific data */
|
||||||
|
u8 current_page;
|
||||||
|
struct device *dev;
|
||||||
|
void *priv;
|
||||||
|
|
||||||
|
/* run time configuration */
|
||||||
|
unsigned enable_vlan:1;
|
||||||
|
unsigned enable_jumbo:1;
|
||||||
|
unsigned allow_vid_4095:1;
|
||||||
|
|
||||||
|
struct b53_port *ports;
|
||||||
|
struct b53_vlan *vlans;
|
||||||
|
|
||||||
|
char *buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define b53_for_each_port(dev, i) \
|
||||||
|
for (i = 0; i < B53_N_PORTS; i++) \
|
||||||
|
if (dev->enabled_ports & BIT(i))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static inline int is5325(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
return dev->chip_id == BCM5325_DEVICE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int is5365(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_BCM47XX
|
||||||
|
return dev->chip_id == BCM5365_DEVICE_ID;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int is5397_98(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
return dev->chip_id == BCM5397_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM5398_DEVICE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int is539x(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
return dev->chip_id == BCM5395_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM5397_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM5398_DEVICE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int is531x5(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
return dev->chip_id == BCM53115_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM53125_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM53128_DEVICE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int is63xx(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_BCM63XX
|
||||||
|
return dev->chip_id == BCM63XX_DEVICE_ID;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int is5301x(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
return dev->chip_id == BCM53010_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM53011_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM53012_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM53018_DEVICE_ID ||
|
||||||
|
dev->chip_id == BCM53019_DEVICE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define B53_CPU_PORT_25 5
|
||||||
|
#define B53_CPU_PORT 8
|
||||||
|
|
||||||
|
static inline int is_cpu_port(struct b53_device *dev, int port)
|
||||||
|
{
|
||||||
|
return dev->sw_dev.cpu_port == port;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int is_imp_port(struct b53_device *dev, int port)
|
||||||
|
{
|
||||||
|
if (is5325(dev) || is5365(dev))
|
||||||
|
return port == B53_CPU_PORT_25;
|
||||||
|
else
|
||||||
|
return port == B53_CPU_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct b53_device *sw_to_b53(struct switch_dev *sw)
|
||||||
|
{
|
||||||
|
return container_of(sw, struct b53_device, sw_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct b53_device *b53_swconfig_switch_alloc(struct device *base, struct b53_io_ops *ops,
|
||||||
|
void *priv);
|
||||||
|
|
||||||
|
int b53_swconfig_switch_detect(struct b53_device *dev);
|
||||||
|
|
||||||
|
int b53_swconfig_switch_register(struct b53_device *dev);
|
||||||
|
|
||||||
|
static inline void b53_switch_remove(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
unregister_switch(&dev->sw_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->read8(dev, page, reg, val);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->read16(dev, page, reg, val);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->read32(dev, page, reg, val);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->read48(dev, page, reg, val);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->read64(dev, page, reg, val);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->write8(dev, page, reg, value);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u16 value)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->write16(dev, page, reg, value);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u32 value)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->write32(dev, page, reg, value);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u64 value)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->write48(dev, page, reg, value);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u64 value)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&dev->reg_mutex);
|
||||||
|
ret = dev->ops->write64(dev, page, reg, value);
|
||||||
|
mutex_unlock(&dev->reg_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_BCM47XX
|
||||||
|
#include <bcm47xx_board.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <linux/version.h>
|
||||||
|
#include <linux/bcm47xx_nvram.h>
|
||||||
|
|
||||||
|
static inline int b53_switch_get_reset_gpio(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_BCM47XX
|
||||||
|
enum bcm47xx_board board = bcm47xx_board_get();
|
||||||
|
|
||||||
|
switch (board) {
|
||||||
|
case BCM47XX_BOARD_LINKSYS_WRT300NV11:
|
||||||
|
case BCM47XX_BOARD_LINKSYS_WRT310NV1:
|
||||||
|
return 8;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return bcm47xx_nvram_gpio_pin("robo_reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
348
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_regs.h
Normal file
348
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_regs.h
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
/*
|
||||||
|
* B53 register definitions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2004 Broadcom Corporation
|
||||||
|
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __B53_REGS_H
|
||||||
|
#define __B53_REGS_H
|
||||||
|
|
||||||
|
/* Management Port (SMP) Page offsets */
|
||||||
|
#define B53_CTRL_PAGE 0x00 /* Control */
|
||||||
|
#define B53_STAT_PAGE 0x01 /* Status */
|
||||||
|
#define B53_MGMT_PAGE 0x02 /* Management Mode */
|
||||||
|
#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */
|
||||||
|
#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */
|
||||||
|
#define B53_ARLIO_PAGE 0x05 /* ARL Access */
|
||||||
|
#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */
|
||||||
|
#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */
|
||||||
|
|
||||||
|
/* PHY Registers */
|
||||||
|
#define B53_PORT_MII_PAGE(i) (0x10 + (i)) /* Port i MII Registers */
|
||||||
|
#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */
|
||||||
|
#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */
|
||||||
|
|
||||||
|
/* MIB registers */
|
||||||
|
#define B53_MIB_PAGE(i) (0x20 + (i))
|
||||||
|
|
||||||
|
/* Quality of Service (QoS) Registers */
|
||||||
|
#define B53_QOS_PAGE 0x30
|
||||||
|
|
||||||
|
/* Port VLAN Page */
|
||||||
|
#define B53_PVLAN_PAGE 0x31
|
||||||
|
|
||||||
|
/* VLAN Registers */
|
||||||
|
#define B53_VLAN_PAGE 0x34
|
||||||
|
|
||||||
|
/* Jumbo Frame Registers */
|
||||||
|
#define B53_JUMBO_PAGE 0x40
|
||||||
|
|
||||||
|
/* CFP Configuration Registers Page */
|
||||||
|
#define B53_CFP_PAGE 0xa1
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* Control Page registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* Port Control Register (8 bit) */
|
||||||
|
#define B53_PORT_CTRL(i) (0x00 + (i))
|
||||||
|
#define PORT_CTRL_RX_DISABLE BIT(0)
|
||||||
|
#define PORT_CTRL_TX_DISABLE BIT(1)
|
||||||
|
#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */
|
||||||
|
#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */
|
||||||
|
#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */
|
||||||
|
#define PORT_CTRL_STP_STATE_S 5
|
||||||
|
#define PORT_CTRL_STP_STATE_MASK (0x7 << PORT_CTRL_STP_STATE_S)
|
||||||
|
|
||||||
|
/* SMP Control Register (8 bit) */
|
||||||
|
#define B53_SMP_CTRL 0x0a
|
||||||
|
|
||||||
|
/* Switch Mode Control Register (8 bit) */
|
||||||
|
#define B53_SWITCH_MODE 0x0b
|
||||||
|
#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */
|
||||||
|
#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */
|
||||||
|
|
||||||
|
/* IMP Port state override register (8 bit) */
|
||||||
|
#define B53_PORT_OVERRIDE_CTRL 0x0e
|
||||||
|
#define PORT_OVERRIDE_LINK BIT(0)
|
||||||
|
#define PORT_OVERRIDE_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */
|
||||||
|
#define PORT_OVERRIDE_SPEED_S 2
|
||||||
|
#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S)
|
||||||
|
#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S)
|
||||||
|
#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S)
|
||||||
|
#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */
|
||||||
|
#define PORT_OVERRIDE_RX_FLOW BIT(4)
|
||||||
|
#define PORT_OVERRIDE_TX_FLOW BIT(5)
|
||||||
|
#define PORT_OVERRIDE_SPEED_2000M BIT(6) /* BCM5301X only, requires setting 1000M */
|
||||||
|
#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */
|
||||||
|
|
||||||
|
/* Power-down mode control */
|
||||||
|
#define B53_PD_MODE_CTRL_25 0x0f
|
||||||
|
|
||||||
|
/* IP Multicast control (8 bit) */
|
||||||
|
#define B53_IP_MULTICAST_CTRL 0x21
|
||||||
|
#define B53_IPMC_FWD_EN BIT(1)
|
||||||
|
#define B53_UC_FWD_EN BIT(6)
|
||||||
|
#define B53_MC_FWD_EN BIT(7)
|
||||||
|
|
||||||
|
/* (16 bit) */
|
||||||
|
#define B53_UC_FLOOD_MASK 0x32
|
||||||
|
#define B53_MC_FLOOD_MASK 0x34
|
||||||
|
#define B53_IPMC_FLOOD_MASK 0x36
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Override Ports 0-7 State on devices with xMII interfaces (8 bit)
|
||||||
|
*
|
||||||
|
* For port 8 still use B53_PORT_OVERRIDE_CTRL
|
||||||
|
* Please note that not all ports are available on every hardware, e.g. BCM5301X
|
||||||
|
* don't include overriding port 6, BCM63xx also have some limitations.
|
||||||
|
*/
|
||||||
|
#define B53_GMII_PORT_OVERRIDE_CTRL(i) (0x58 + (i))
|
||||||
|
#define GMII_PO_LINK BIT(0)
|
||||||
|
#define GMII_PO_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */
|
||||||
|
#define GMII_PO_SPEED_S 2
|
||||||
|
#define GMII_PO_SPEED_10M (0 << GMII_PO_SPEED_S)
|
||||||
|
#define GMII_PO_SPEED_100M (1 << GMII_PO_SPEED_S)
|
||||||
|
#define GMII_PO_SPEED_1000M (2 << GMII_PO_SPEED_S)
|
||||||
|
#define GMII_PO_RX_FLOW BIT(4)
|
||||||
|
#define GMII_PO_TX_FLOW BIT(5)
|
||||||
|
#define GMII_PO_EN BIT(6) /* Use the register contents */
|
||||||
|
#define GMII_PO_SPEED_2000M BIT(7) /* BCM5301X only, requires setting 1000M */
|
||||||
|
|
||||||
|
/* Software reset register (8 bit) */
|
||||||
|
#define B53_SOFTRESET 0x79
|
||||||
|
|
||||||
|
/* Fast Aging Control register (8 bit) */
|
||||||
|
#define B53_FAST_AGE_CTRL 0x88
|
||||||
|
#define FAST_AGE_STATIC BIT(0)
|
||||||
|
#define FAST_AGE_DYNAMIC BIT(1)
|
||||||
|
#define FAST_AGE_PORT BIT(2)
|
||||||
|
#define FAST_AGE_VLAN BIT(3)
|
||||||
|
#define FAST_AGE_STP BIT(4)
|
||||||
|
#define FAST_AGE_MC BIT(5)
|
||||||
|
#define FAST_AGE_DONE BIT(7)
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* Status Page registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* Link Status Summary Register (16bit) */
|
||||||
|
#define B53_LINK_STAT 0x00
|
||||||
|
|
||||||
|
/* Link Status Change Register (16 bit) */
|
||||||
|
#define B53_LINK_STAT_CHANGE 0x02
|
||||||
|
|
||||||
|
/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */
|
||||||
|
#define B53_SPEED_STAT 0x04
|
||||||
|
#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1)
|
||||||
|
#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3)
|
||||||
|
#define SPEED_STAT_10M 0
|
||||||
|
#define SPEED_STAT_100M 1
|
||||||
|
#define SPEED_STAT_1000M 2
|
||||||
|
|
||||||
|
/* Duplex Status Summary (16 bit) */
|
||||||
|
#define B53_DUPLEX_STAT_FE 0x06
|
||||||
|
#define B53_DUPLEX_STAT_GE 0x08
|
||||||
|
#define B53_DUPLEX_STAT_63XX 0x0c
|
||||||
|
|
||||||
|
/* Revision ID register for BCM5325 */
|
||||||
|
#define B53_REV_ID_25 0x50
|
||||||
|
|
||||||
|
/* Strap Value (48 bit) */
|
||||||
|
#define B53_STRAP_VALUE 0x70
|
||||||
|
#define SV_GMII_CTRL_115 BIT(27)
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* Management Mode Page Registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* Global Management Config Register (8 bit) */
|
||||||
|
#define B53_GLOBAL_CONFIG 0x00
|
||||||
|
#define GC_RESET_MIB 0x01
|
||||||
|
#define GC_RX_BPDU_EN 0x02
|
||||||
|
#define GC_MIB_AC_HDR_EN 0x10
|
||||||
|
#define GC_MIB_AC_EN 0x20
|
||||||
|
#define GC_FRM_MGMT_PORT_M 0xC0
|
||||||
|
#define GC_FRM_MGMT_PORT_04 0x00
|
||||||
|
#define GC_FRM_MGMT_PORT_MII 0x80
|
||||||
|
|
||||||
|
/* Broadcom Header control register (8 bit) */
|
||||||
|
#define B53_BRCM_HDR 0x03
|
||||||
|
#define BRCM_HDR_P8_EN BIT(0) /* Enable tagging on port 8 */
|
||||||
|
#define BRCM_HDR_P5_EN BIT(1) /* Enable tagging on port 5 */
|
||||||
|
|
||||||
|
/* Device ID register (8 or 32 bit) */
|
||||||
|
#define B53_DEVICE_ID 0x30
|
||||||
|
|
||||||
|
/* Revision ID register (8 bit) */
|
||||||
|
#define B53_REV_ID 0x40
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* ARL Access Page Registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* VLAN Table Access Register (8 bit) */
|
||||||
|
#define B53_VT_ACCESS 0x80
|
||||||
|
#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */
|
||||||
|
#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */
|
||||||
|
#define VTA_CMD_WRITE 0
|
||||||
|
#define VTA_CMD_READ 1
|
||||||
|
#define VTA_CMD_CLEAR 2
|
||||||
|
#define VTA_START_CMD BIT(7)
|
||||||
|
|
||||||
|
/* VLAN Table Index Register (16 bit) */
|
||||||
|
#define B53_VT_INDEX 0x81
|
||||||
|
#define B53_VT_INDEX_9798 0x61
|
||||||
|
#define B53_VT_INDEX_63XX 0x62
|
||||||
|
|
||||||
|
/* VLAN Table Entry Register (32 bit) */
|
||||||
|
#define B53_VT_ENTRY 0x83
|
||||||
|
#define B53_VT_ENTRY_9798 0x63
|
||||||
|
#define B53_VT_ENTRY_63XX 0x64
|
||||||
|
#define VTE_MEMBERS 0x1ff
|
||||||
|
#define VTE_UNTAG_S 9
|
||||||
|
#define VTE_UNTAG (0x1ff << 9)
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* Port VLAN Registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */
|
||||||
|
#define B53_PVLAN_PORT_MASK(i) ((i) * 2)
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* 802.1Q Page Registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* Global QoS Control (8 bit) */
|
||||||
|
#define B53_QOS_GLOBAL_CTL 0x00
|
||||||
|
|
||||||
|
/* Enable 802.1Q for individual Ports (16 bit) */
|
||||||
|
#define B53_802_1P_EN 0x04
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* VLAN Page Registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* VLAN Control 0 (8 bit) */
|
||||||
|
#define B53_VLAN_CTRL0 0x00
|
||||||
|
#define VC0_8021PF_CTRL_MASK 0x3
|
||||||
|
#define VC0_8021PF_CTRL_NONE 0x0
|
||||||
|
#define VC0_8021PF_CTRL_CHANGE_PRI 0x1
|
||||||
|
#define VC0_8021PF_CTRL_CHANGE_VID 0x2
|
||||||
|
#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3
|
||||||
|
#define VC0_8021QF_CTRL_MASK 0xc
|
||||||
|
#define VC0_8021QF_CTRL_CHANGE_PRI 0x1
|
||||||
|
#define VC0_8021QF_CTRL_CHANGE_VID 0x2
|
||||||
|
#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3
|
||||||
|
#define VC0_RESERVED_1 BIT(1)
|
||||||
|
#define VC0_DROP_VID_MISS BIT(4)
|
||||||
|
#define VC0_VID_HASH_VID BIT(5)
|
||||||
|
#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */
|
||||||
|
#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */
|
||||||
|
|
||||||
|
/* VLAN Control 1 (8 bit) */
|
||||||
|
#define B53_VLAN_CTRL1 0x01
|
||||||
|
#define VC1_RX_MCST_TAG_EN BIT(1)
|
||||||
|
#define VC1_RX_MCST_FWD_EN BIT(2)
|
||||||
|
#define VC1_RX_MCST_UNTAG_EN BIT(3)
|
||||||
|
|
||||||
|
/* VLAN Control 2 (8 bit) */
|
||||||
|
#define B53_VLAN_CTRL2 0x02
|
||||||
|
|
||||||
|
/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */
|
||||||
|
#define B53_VLAN_CTRL3 0x03
|
||||||
|
#define B53_VLAN_CTRL3_63XX 0x04
|
||||||
|
#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */
|
||||||
|
#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */
|
||||||
|
|
||||||
|
/* VLAN Control 4 (8 bit) */
|
||||||
|
#define B53_VLAN_CTRL4 0x05
|
||||||
|
#define B53_VLAN_CTRL4_25 0x04
|
||||||
|
#define B53_VLAN_CTRL4_63XX 0x06
|
||||||
|
#define VC4_ING_VID_CHECK_S 6
|
||||||
|
#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S)
|
||||||
|
#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */
|
||||||
|
#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */
|
||||||
|
#define VC4_NO_ING_VID_CHK 2 /* do not check */
|
||||||
|
#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */
|
||||||
|
|
||||||
|
/* VLAN Control 5 (8 bit) */
|
||||||
|
#define B53_VLAN_CTRL5 0x06
|
||||||
|
#define B53_VLAN_CTRL5_25 0x05
|
||||||
|
#define B53_VLAN_CTRL5_63XX 0x07
|
||||||
|
#define VC5_VID_FFF_EN BIT(2)
|
||||||
|
#define VC5_DROP_VTABLE_MISS BIT(3)
|
||||||
|
|
||||||
|
/* VLAN Control 6 (8 bit) */
|
||||||
|
#define B53_VLAN_CTRL6 0x07
|
||||||
|
#define B53_VLAN_CTRL6_63XX 0x08
|
||||||
|
|
||||||
|
/* VLAN Table Access Register (16 bit) */
|
||||||
|
#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */
|
||||||
|
#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */
|
||||||
|
#define VTA_VID_LOW_MASK_25 0xf
|
||||||
|
#define VTA_VID_LOW_MASK_65 0xff
|
||||||
|
#define VTA_VID_HIGH_S_25 4
|
||||||
|
#define VTA_VID_HIGH_S_65 8
|
||||||
|
#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E)
|
||||||
|
#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65)
|
||||||
|
#define VTA_RW_STATE BIT(12)
|
||||||
|
#define VTA_RW_STATE_RD 0
|
||||||
|
#define VTA_RW_STATE_WR BIT(12)
|
||||||
|
#define VTA_RW_OP_EN BIT(13)
|
||||||
|
|
||||||
|
/* VLAN Read/Write Registers for (16/32 bit) */
|
||||||
|
#define B53_VLAN_WRITE_25 0x08
|
||||||
|
#define B53_VLAN_WRITE_65 0x0a
|
||||||
|
#define B53_VLAN_READ 0x0c
|
||||||
|
#define VA_MEMBER_MASK 0x3f
|
||||||
|
#define VA_UNTAG_S_25 6
|
||||||
|
#define VA_UNTAG_MASK_25 0x3f
|
||||||
|
#define VA_UNTAG_S_65 7
|
||||||
|
#define VA_UNTAG_MASK_65 0x1f
|
||||||
|
#define VA_VID_HIGH_S 12
|
||||||
|
#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S)
|
||||||
|
#define VA_VALID_25 BIT(20)
|
||||||
|
#define VA_VALID_25_R4 BIT(24)
|
||||||
|
#define VA_VALID_65 BIT(14)
|
||||||
|
|
||||||
|
/* VLAN Port Default Tag (16 bit) */
|
||||||
|
#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i))
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* Jumbo Frame Page Registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */
|
||||||
|
#define B53_JUMBO_PORT_MASK 0x01
|
||||||
|
#define B53_JUMBO_PORT_MASK_63XX 0x04
|
||||||
|
#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */
|
||||||
|
|
||||||
|
/* Good Frame Max Size without 802.1Q TAG (16 bit) */
|
||||||
|
#define B53_JUMBO_MAX_SIZE 0x05
|
||||||
|
#define B53_JUMBO_MAX_SIZE_63XX 0x08
|
||||||
|
#define JMS_MIN_SIZE 1518
|
||||||
|
#define JMS_MAX_SIZE 9724
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
* CFP Configuration Page Registers
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
/* CFP Control Register with ports map (8 bit) */
|
||||||
|
#define B53_CFP_CTRL 0x00
|
||||||
|
|
||||||
|
#endif /* !__B53_REGS_H */
|
344
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_spi.c
Normal file
344
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_spi.c
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
/*
|
||||||
|
* B53 register access through SPI
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <asm/unaligned.h>
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/spi/spi.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/platform_data/b53.h>
|
||||||
|
|
||||||
|
#include "b53_priv.h"
|
||||||
|
|
||||||
|
#define B53_SPI_DATA 0xf0
|
||||||
|
|
||||||
|
#define B53_SPI_STATUS 0xfe
|
||||||
|
#define B53_SPI_CMD_SPIF BIT(7)
|
||||||
|
#define B53_SPI_CMD_RACK BIT(5)
|
||||||
|
|
||||||
|
#define B53_SPI_CMD_READ 0x00
|
||||||
|
#define B53_SPI_CMD_WRITE 0x01
|
||||||
|
#define B53_SPI_CMD_NORMAL 0x60
|
||||||
|
#define B53_SPI_CMD_FAST 0x10
|
||||||
|
|
||||||
|
#define B53_SPI_PAGE_SELECT 0xff
|
||||||
|
|
||||||
|
static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val,
|
||||||
|
unsigned len)
|
||||||
|
{
|
||||||
|
u8 txbuf[2];
|
||||||
|
|
||||||
|
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ;
|
||||||
|
txbuf[1] = reg;
|
||||||
|
|
||||||
|
return spi_write_then_read(spi, txbuf, 2, val, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_spi_clear_status(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
u8 rxbuf;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
for (i = 0; i < 10; i++) {
|
||||||
|
ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (!(rxbuf & B53_SPI_CMD_SPIF))
|
||||||
|
break;
|
||||||
|
|
||||||
|
mdelay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 10)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_spi_set_page(struct spi_device *spi, u8 page)
|
||||||
|
{
|
||||||
|
u8 txbuf[3];
|
||||||
|
|
||||||
|
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
||||||
|
txbuf[1] = B53_SPI_PAGE_SELECT;
|
||||||
|
txbuf[2] = page;
|
||||||
|
|
||||||
|
return spi_write(spi, txbuf, sizeof(txbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page)
|
||||||
|
{
|
||||||
|
int ret = b53_spi_clear_status(spi);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return b53_spi_set_page(spi, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg)
|
||||||
|
{
|
||||||
|
u8 rxbuf;
|
||||||
|
int retry_count;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = b53_spi_read_reg(spi, reg, &rxbuf, 1);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
for (retry_count = 0; retry_count < 10; retry_count++) {
|
||||||
|
ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (rxbuf & B53_SPI_CMD_RACK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
mdelay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retry_count == 10)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data,
|
||||||
|
unsigned len)
|
||||||
|
{
|
||||||
|
struct spi_device *spi = dev->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = b53_prepare_reg_access(spi, page);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = b53_spi_prepare_reg_read(spi, reg);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return b53_spi_read_reg(spi, B53_SPI_DATA, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
||||||
|
{
|
||||||
|
return b53_spi_read(dev, page, reg, val, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
||||||
|
{
|
||||||
|
int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
*val = le16_to_cpu(*val);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
||||||
|
{
|
||||||
|
int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
*val = le32_to_cpu(*val);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
*val = 0;
|
||||||
|
ret = b53_spi_read(dev, page, reg, (u8 *)val, 6);
|
||||||
|
if (!ret)
|
||||||
|
*val = le64_to_cpu(*val);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
*val = le64_to_cpu(*val);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
||||||
|
{
|
||||||
|
struct spi_device *spi = dev->priv;
|
||||||
|
int ret;
|
||||||
|
u8 txbuf[3];
|
||||||
|
|
||||||
|
ret = b53_prepare_reg_access(spi, page);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
||||||
|
txbuf[1] = reg;
|
||||||
|
txbuf[2] = value;
|
||||||
|
|
||||||
|
return spi_write(spi, txbuf, sizeof(txbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value)
|
||||||
|
{
|
||||||
|
struct spi_device *spi = dev->priv;
|
||||||
|
int ret;
|
||||||
|
u8 txbuf[4];
|
||||||
|
|
||||||
|
ret = b53_prepare_reg_access(spi, page);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
||||||
|
txbuf[1] = reg;
|
||||||
|
put_unaligned_le16(value, &txbuf[2]);
|
||||||
|
|
||||||
|
return spi_write(spi, txbuf, sizeof(txbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value)
|
||||||
|
{
|
||||||
|
struct spi_device *spi = dev->priv;
|
||||||
|
int ret;
|
||||||
|
u8 txbuf[6];
|
||||||
|
|
||||||
|
ret = b53_prepare_reg_access(spi, page);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
||||||
|
txbuf[1] = reg;
|
||||||
|
put_unaligned_le32(value, &txbuf[2]);
|
||||||
|
|
||||||
|
return spi_write(spi, txbuf, sizeof(txbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value)
|
||||||
|
{
|
||||||
|
struct spi_device *spi = dev->priv;
|
||||||
|
int ret;
|
||||||
|
u8 txbuf[10];
|
||||||
|
|
||||||
|
ret = b53_prepare_reg_access(spi, page);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
||||||
|
txbuf[1] = reg;
|
||||||
|
put_unaligned_le64(value, &txbuf[2]);
|
||||||
|
|
||||||
|
return spi_write(spi, txbuf, sizeof(txbuf) - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value)
|
||||||
|
{
|
||||||
|
struct spi_device *spi = dev->priv;
|
||||||
|
int ret;
|
||||||
|
u8 txbuf[10];
|
||||||
|
|
||||||
|
ret = b53_prepare_reg_access(spi, page);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE;
|
||||||
|
txbuf[1] = reg;
|
||||||
|
put_unaligned_le64(value, &txbuf[2]);
|
||||||
|
|
||||||
|
return spi_write(spi, txbuf, sizeof(txbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct b53_io_ops b53_spi_ops = {
|
||||||
|
.read8 = b53_spi_read8,
|
||||||
|
.read16 = b53_spi_read16,
|
||||||
|
.read32 = b53_spi_read32,
|
||||||
|
.read48 = b53_spi_read48,
|
||||||
|
.read64 = b53_spi_read64,
|
||||||
|
.write8 = b53_spi_write8,
|
||||||
|
.write16 = b53_spi_write16,
|
||||||
|
.write32 = b53_spi_write32,
|
||||||
|
.write48 = b53_spi_write48,
|
||||||
|
.write64 = b53_spi_write64,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int b53_spi_probe(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
struct b53_device *dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev = b53_swconfig_switch_alloc(&spi->dev, &b53_spi_ops, spi);
|
||||||
|
if (!dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (spi->dev.platform_data)
|
||||||
|
dev->pdata = spi->dev.platform_data;
|
||||||
|
|
||||||
|
ret = b53_swconfig_switch_register(dev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
spi_set_drvdata(spi, dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_spi_remove(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
struct b53_device *dev = spi_get_drvdata(spi);
|
||||||
|
|
||||||
|
if (dev)
|
||||||
|
b53_switch_remove(dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id b53_of_match[] = {
|
||||||
|
{ .compatible = "brcm,bcm5325" },
|
||||||
|
{ .compatible = "brcm,bcm53115" },
|
||||||
|
{ .compatible = "brcm,bcm53125" },
|
||||||
|
{ .compatible = "brcm,bcm53128" },
|
||||||
|
{ .compatible = "brcm,bcm5365" },
|
||||||
|
{ .compatible = "brcm,bcm5395" },
|
||||||
|
{ .compatible = "brcm,bcm5397" },
|
||||||
|
{ .compatible = "brcm,bcm5398" },
|
||||||
|
{ /* sentinel */ },
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct spi_driver b53_spi_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "b53-switch",
|
||||||
|
.bus = &spi_bus_type,
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.of_match_table = b53_of_match,
|
||||||
|
},
|
||||||
|
.probe = b53_spi_probe,
|
||||||
|
.remove = b53_spi_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_spi_driver(b53_spi_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
|
||||||
|
MODULE_DESCRIPTION("B53 SPI access driver");
|
||||||
|
MODULE_LICENSE("Dual BSD/GPL");
|
385
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_srab.c
Normal file
385
6.12/target/linux/generic/files/drivers/net/phy/b53/b53_srab.c
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
/*
|
||||||
|
* B53 register access through Switch Register Access Bridge Registers
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/platform_data/b53.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
|
||||||
|
#include "b53_priv.h"
|
||||||
|
|
||||||
|
/* command and status register of the SRAB */
|
||||||
|
#define B53_SRAB_CMDSTAT 0x2c
|
||||||
|
#define B53_SRAB_CMDSTAT_RST BIT(2)
|
||||||
|
#define B53_SRAB_CMDSTAT_WRITE BIT(1)
|
||||||
|
#define B53_SRAB_CMDSTAT_GORDYN BIT(0)
|
||||||
|
#define B53_SRAB_CMDSTAT_PAGE 24
|
||||||
|
#define B53_SRAB_CMDSTAT_REG 16
|
||||||
|
|
||||||
|
/* high order word of write data to switch registe */
|
||||||
|
#define B53_SRAB_WD_H 0x30
|
||||||
|
|
||||||
|
/* low order word of write data to switch registe */
|
||||||
|
#define B53_SRAB_WD_L 0x34
|
||||||
|
|
||||||
|
/* high order word of read data from switch register */
|
||||||
|
#define B53_SRAB_RD_H 0x38
|
||||||
|
|
||||||
|
/* low order word of read data from switch register */
|
||||||
|
#define B53_SRAB_RD_L 0x3c
|
||||||
|
|
||||||
|
/* command and status register of the SRAB */
|
||||||
|
#define B53_SRAB_CTRLS 0x40
|
||||||
|
#define B53_SRAB_CTRLS_RCAREQ BIT(3)
|
||||||
|
#define B53_SRAB_CTRLS_RCAGNT BIT(4)
|
||||||
|
#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6)
|
||||||
|
|
||||||
|
/* the register captures interrupt pulses from the switch */
|
||||||
|
#define B53_SRAB_INTR 0x44
|
||||||
|
|
||||||
|
static int b53_srab_request_grant(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
u32 ctrls;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
ctrls = readl(regs + B53_SRAB_CTRLS);
|
||||||
|
ctrls |= B53_SRAB_CTRLS_RCAREQ;
|
||||||
|
writel(ctrls, regs + B53_SRAB_CTRLS);
|
||||||
|
|
||||||
|
for (i = 0; i < 20; i++) {
|
||||||
|
ctrls = readl(regs + B53_SRAB_CTRLS);
|
||||||
|
if (ctrls & B53_SRAB_CTRLS_RCAGNT)
|
||||||
|
break;
|
||||||
|
usleep_range(10, 100);
|
||||||
|
}
|
||||||
|
if (WARN_ON(i == 5))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void b53_srab_release_grant(struct b53_device *dev)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
u32 ctrls;
|
||||||
|
|
||||||
|
ctrls = readl(regs + B53_SRAB_CTRLS);
|
||||||
|
ctrls &= ~B53_SRAB_CTRLS_RCAREQ;
|
||||||
|
writel(ctrls, regs + B53_SRAB_CTRLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u32 cmdstat;
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
|
||||||
|
/* set register address */
|
||||||
|
cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) |
|
||||||
|
(reg << B53_SRAB_CMDSTAT_REG) |
|
||||||
|
B53_SRAB_CMDSTAT_GORDYN |
|
||||||
|
op;
|
||||||
|
writel(cmdstat, regs + B53_SRAB_CMDSTAT);
|
||||||
|
|
||||||
|
/* check if operation completed */
|
||||||
|
for (i = 0; i < 5; ++i) {
|
||||||
|
cmdstat = readl(regs + B53_SRAB_CMDSTAT);
|
||||||
|
if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN))
|
||||||
|
break;
|
||||||
|
usleep_range(10, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WARN_ON(i == 5))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, 0);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
*val = readl(regs + B53_SRAB_RD_L) & 0xff;
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, 0);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
*val = readl(regs + B53_SRAB_RD_L) & 0xffff;
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, 0);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
*val = readl(regs + B53_SRAB_RD_L);
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, 0);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
*val = readl(regs + B53_SRAB_RD_L);
|
||||||
|
*val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32;
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, 0);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
*val = readl(regs + B53_SRAB_RD_L);
|
||||||
|
*val += (u64)readl(regs + B53_SRAB_RD_H) << 32;
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
writel(value, regs + B53_SRAB_WD_L);
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u16 value)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
writel(value, regs + B53_SRAB_WD_L);
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u32 value)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
writel(value, regs + B53_SRAB_WD_L);
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u64 value)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
writel((u32)value, regs + B53_SRAB_WD_L);
|
||||||
|
writel((u16)(value >> 32), regs + B53_SRAB_WD_H);
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg,
|
||||||
|
u64 value)
|
||||||
|
{
|
||||||
|
u8 __iomem *regs = dev->priv;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
ret = b53_srab_request_grant(dev);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
writel((u32)value, regs + B53_SRAB_WD_L);
|
||||||
|
writel((u32)(value >> 32), regs + B53_SRAB_WD_H);
|
||||||
|
|
||||||
|
ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE);
|
||||||
|
|
||||||
|
err:
|
||||||
|
b53_srab_release_grant(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct b53_io_ops b53_srab_ops = {
|
||||||
|
.read8 = b53_srab_read8,
|
||||||
|
.read16 = b53_srab_read16,
|
||||||
|
.read32 = b53_srab_read32,
|
||||||
|
.read48 = b53_srab_read48,
|
||||||
|
.read64 = b53_srab_read64,
|
||||||
|
.write8 = b53_srab_write8,
|
||||||
|
.write16 = b53_srab_write16,
|
||||||
|
.write32 = b53_srab_write32,
|
||||||
|
.write48 = b53_srab_write48,
|
||||||
|
.write64 = b53_srab_write64,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int b53_srab_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct b53_platform_data *pdata = pdev->dev.platform_data;
|
||||||
|
struct b53_device *dev;
|
||||||
|
|
||||||
|
if (!pdata)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
dev = b53_swconfig_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs);
|
||||||
|
if (!dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (pdata)
|
||||||
|
dev->pdata = pdata;
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, dev);
|
||||||
|
|
||||||
|
return b53_swconfig_switch_register(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
|
||||||
|
static int b53_srab_remove(struct platform_device *pdev)
|
||||||
|
#else
|
||||||
|
static void b53_srab_remove(struct platform_device *pdev)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
struct b53_device *dev = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
if (dev)
|
||||||
|
b53_switch_remove(dev);
|
||||||
|
|
||||||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver b53_srab_driver = {
|
||||||
|
.probe = b53_srab_probe,
|
||||||
|
.remove = b53_srab_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "b53-srab-switch",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(b53_srab_driver);
|
||||||
|
MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
|
||||||
|
MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver");
|
||||||
|
MODULE_LICENSE("Dual BSD/GPL");
|
1370
6.12/target/linux/generic/files/drivers/net/phy/ip17xx.c
Normal file
1370
6.12/target/linux/generic/files/drivers/net/phy/ip17xx.c
Normal file
File diff suppressed because it is too large
Load diff
443
6.12/target/linux/generic/files/drivers/net/phy/psb6970.c
Normal file
443
6.12/target/linux/generic/files/drivers/net/phy/psb6970.c
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
/*
|
||||||
|
* Lantiq PSB6970 (Tantos) Switch driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009,2010 Team Embedded.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License v2 as published by the
|
||||||
|
* Free Software Foundation.
|
||||||
|
*
|
||||||
|
* The switch programming done in this driver follows the
|
||||||
|
* "Ethernet Traffic Separation using VLAN" Application Note as
|
||||||
|
* published by Lantiq.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/netdevice.h>
|
||||||
|
#include <linux/switch.h>
|
||||||
|
#include <linux/phy.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
|
||||||
|
#define PSB6970_MAX_VLANS 16
|
||||||
|
#define PSB6970_NUM_PORTS 7
|
||||||
|
#define PSB6970_DEFAULT_PORT_CPU 6
|
||||||
|
#define PSB6970_IS_CPU_PORT(x) ((x) > 4)
|
||||||
|
|
||||||
|
#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f)
|
||||||
|
|
||||||
|
/* --- Identification --- */
|
||||||
|
#define PSB6970_CI0 0x0100
|
||||||
|
#define PSB6970_CI0_MASK 0x000f
|
||||||
|
#define PSB6970_CI1 0x0101
|
||||||
|
#define PSB6970_CI1_VAL 0x2599
|
||||||
|
#define PSB6970_CI1_MASK 0xffff
|
||||||
|
|
||||||
|
/* --- VLAN filter table --- */
|
||||||
|
#define PSB6970_VFxL(i) ((i)*2+0x10) /* VLAN Filter Low */
|
||||||
|
#define PSB6970_VFxL_VV (1 << 15) /* VLAN_Valid */
|
||||||
|
|
||||||
|
#define PSB6970_VFxH(i) ((i)*2+0x11) /* VLAN Filter High */
|
||||||
|
#define PSB6970_VFxH_TM_SHIFT 7 /* Tagged Member */
|
||||||
|
|
||||||
|
/* --- Port registers --- */
|
||||||
|
#define PSB6970_EC(p) ((p)*0x20+2) /* Extended Control */
|
||||||
|
#define PSB6970_EC_IFNTE (1 << 1) /* Input Force No Tag Enable */
|
||||||
|
|
||||||
|
#define PSB6970_PBVM(p) ((p)*0x20+3) /* Port Base VLAN Map */
|
||||||
|
#define PSB6970_PBVM_VMCE (1 << 8)
|
||||||
|
#define PSB6970_PBVM_AOVTP (1 << 9)
|
||||||
|
#define PSB6970_PBVM_VSD (1 << 10)
|
||||||
|
#define PSB6970_PBVM_VC (1 << 11) /* VID Check with VID table */
|
||||||
|
#define PSB6970_PBVM_TBVE (1 << 13) /* Tag-Based VLAN enable */
|
||||||
|
|
||||||
|
#define PSB6970_DVID(p) ((p)*0x20+4) /* Default VLAN ID & Priority */
|
||||||
|
|
||||||
|
struct psb6970_priv {
|
||||||
|
struct switch_dev dev;
|
||||||
|
struct phy_device *phy;
|
||||||
|
u16 (*read) (struct phy_device* phydev, int reg);
|
||||||
|
void (*write) (struct phy_device* phydev, int reg, u16 val);
|
||||||
|
struct mutex reg_mutex;
|
||||||
|
|
||||||
|
/* all fields below are cleared on reset */
|
||||||
|
struct_group(psb6970_priv_volatile,
|
||||||
|
bool vlan;
|
||||||
|
u16 vlan_id[PSB6970_MAX_VLANS];
|
||||||
|
u8 vlan_table[PSB6970_MAX_VLANS];
|
||||||
|
u8 vlan_tagged;
|
||||||
|
u16 pvid[PSB6970_NUM_PORTS];
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev)
|
||||||
|
|
||||||
|
static u16 psb6970_mii_read(struct phy_device *phydev, int reg)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = phydev->mdio.bus;
|
||||||
|
|
||||||
|
return bus->read(bus, PHYADDR(reg));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = phydev->mdio.bus;
|
||||||
|
|
||||||
|
bus->write(bus, PHYADDR(reg), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
|
||||||
|
struct switch_val *val)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
priv->vlan = !!val->value.i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
|
||||||
|
struct switch_val *val)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
val->value.i = priv->vlan;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
|
||||||
|
/* make sure no invalid PVIDs get set */
|
||||||
|
if (vlan >= dev->vlans)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
priv->pvid[port] = vlan;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
*vlan = priv->pvid[port];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
|
||||||
|
struct switch_val *val)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
priv->vlan_id[val->port_vlan] = val->value.i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
|
||||||
|
struct switch_val *val)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
val->value.i = priv->vlan_id[val->port_vlan];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct switch_attr psb6970_globals[] = {
|
||||||
|
{
|
||||||
|
.type = SWITCH_TYPE_INT,
|
||||||
|
.name = "enable_vlan",
|
||||||
|
.description = "Enable VLAN mode",
|
||||||
|
.set = psb6970_set_vlan,
|
||||||
|
.get = psb6970_get_vlan,
|
||||||
|
.max = 1},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct switch_attr psb6970_port[] = {
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct switch_attr psb6970_vlan[] = {
|
||||||
|
{
|
||||||
|
.type = SWITCH_TYPE_INT,
|
||||||
|
.name = "vid",
|
||||||
|
.description = "VLAN ID (0-4094)",
|
||||||
|
.set = psb6970_set_vid,
|
||||||
|
.get = psb6970_get_vid,
|
||||||
|
.max = 4094,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
u8 ports = priv->vlan_table[val->port_vlan];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
val->len = 0;
|
||||||
|
for (i = 0; i < PSB6970_NUM_PORTS; i++) {
|
||||||
|
struct switch_port *p;
|
||||||
|
|
||||||
|
if (!(ports & (1 << i)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
p = &val->value.ports[val->len++];
|
||||||
|
p->id = i;
|
||||||
|
if (priv->vlan_tagged & (1 << i))
|
||||||
|
p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
|
||||||
|
else
|
||||||
|
p->flags = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
u8 *vt = &priv->vlan_table[val->port_vlan];
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
*vt = 0;
|
||||||
|
for (i = 0; i < val->len; i++) {
|
||||||
|
struct switch_port *p = &val->value.ports[i];
|
||||||
|
|
||||||
|
if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
|
||||||
|
priv->vlan_tagged |= (1 << p->id);
|
||||||
|
else {
|
||||||
|
priv->vlan_tagged &= ~(1 << p->id);
|
||||||
|
priv->pvid[p->id] = val->port_vlan;
|
||||||
|
|
||||||
|
/* make sure that an untagged port does not
|
||||||
|
* appear in other vlans */
|
||||||
|
for (j = 0; j < PSB6970_MAX_VLANS; j++) {
|
||||||
|
if (j == val->port_vlan)
|
||||||
|
continue;
|
||||||
|
priv->vlan_table[j] &= ~(1 << p->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*vt |= 1 << p->id;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_hw_apply(struct switch_dev *dev)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
mutex_lock(&priv->reg_mutex);
|
||||||
|
|
||||||
|
if (priv->vlan) {
|
||||||
|
/* into the vlan translation unit */
|
||||||
|
for (j = 0; j < PSB6970_MAX_VLANS; j++) {
|
||||||
|
u8 vp = priv->vlan_table[j];
|
||||||
|
|
||||||
|
if (vp) {
|
||||||
|
priv->write(priv->phy, PSB6970_VFxL(j),
|
||||||
|
PSB6970_VFxL_VV | priv->vlan_id[j]);
|
||||||
|
priv->write(priv->phy, PSB6970_VFxH(j),
|
||||||
|
((vp & priv->
|
||||||
|
vlan_tagged) <<
|
||||||
|
PSB6970_VFxH_TM_SHIFT) | vp);
|
||||||
|
} else /* clear VLAN Valid flag for unused vlans */
|
||||||
|
priv->write(priv->phy, PSB6970_VFxL(j), 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update the port destination mask registers and tag settings */
|
||||||
|
for (i = 0; i < PSB6970_NUM_PORTS; i++) {
|
||||||
|
int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0;
|
||||||
|
|
||||||
|
if (priv->vlan) {
|
||||||
|
ec = PSB6970_EC_IFNTE;
|
||||||
|
dvid = priv->vlan_id[priv->pvid[i]];
|
||||||
|
pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE;
|
||||||
|
|
||||||
|
if ((i << 1) & priv->vlan_tagged)
|
||||||
|
pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->write(priv->phy, PSB6970_PBVM(i), pbvm);
|
||||||
|
|
||||||
|
if (!PSB6970_IS_CPU_PORT(i)) {
|
||||||
|
priv->write(priv->phy, PSB6970_EC(i), ec);
|
||||||
|
priv->write(priv->phy, PSB6970_DVID(i), dvid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&priv->reg_mutex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_reset_switch(struct switch_dev *dev)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = to_psb6970(dev);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
mutex_lock(&priv->reg_mutex);
|
||||||
|
|
||||||
|
memset(&priv->psb6970_priv_volatile, 0,
|
||||||
|
sizeof(priv->psb6970_priv_volatile));
|
||||||
|
|
||||||
|
for (i = 0; i < PSB6970_MAX_VLANS; i++)
|
||||||
|
priv->vlan_id[i] = i;
|
||||||
|
|
||||||
|
mutex_unlock(&priv->reg_mutex);
|
||||||
|
|
||||||
|
return psb6970_hw_apply(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct switch_dev_ops psb6970_ops = {
|
||||||
|
.attr_global = {
|
||||||
|
.attr = psb6970_globals,
|
||||||
|
.n_attr = ARRAY_SIZE(psb6970_globals),
|
||||||
|
},
|
||||||
|
.attr_port = {
|
||||||
|
.attr = psb6970_port,
|
||||||
|
.n_attr = ARRAY_SIZE(psb6970_port),
|
||||||
|
},
|
||||||
|
.attr_vlan = {
|
||||||
|
.attr = psb6970_vlan,
|
||||||
|
.n_attr = ARRAY_SIZE(psb6970_vlan),
|
||||||
|
},
|
||||||
|
.get_port_pvid = psb6970_get_pvid,
|
||||||
|
.set_port_pvid = psb6970_set_pvid,
|
||||||
|
.get_vlan_ports = psb6970_get_ports,
|
||||||
|
.set_vlan_ports = psb6970_set_ports,
|
||||||
|
.apply_config = psb6970_hw_apply,
|
||||||
|
.reset_switch = psb6970_reset_switch,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int psb6970_config_init(struct phy_device *pdev)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv;
|
||||||
|
struct switch_dev *swdev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL);
|
||||||
|
if (priv == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
priv->phy = pdev;
|
||||||
|
|
||||||
|
if (pdev->mdio.addr == 0)
|
||||||
|
printk(KERN_INFO "%s: psb6970 switch driver attached.\n",
|
||||||
|
pdev->attached_dev->name);
|
||||||
|
|
||||||
|
if (pdev->mdio.addr != 0) {
|
||||||
|
kfree(priv);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
linkmode_zero(pdev->supported);
|
||||||
|
linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, pdev->supported);
|
||||||
|
linkmode_copy(pdev->advertising, pdev->supported);
|
||||||
|
|
||||||
|
mutex_init(&priv->reg_mutex);
|
||||||
|
priv->read = psb6970_mii_read;
|
||||||
|
priv->write = psb6970_mii_write;
|
||||||
|
|
||||||
|
pdev->priv = priv;
|
||||||
|
|
||||||
|
swdev = &priv->dev;
|
||||||
|
swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU;
|
||||||
|
swdev->ops = &psb6970_ops;
|
||||||
|
|
||||||
|
swdev->name = "Lantiq PSB6970";
|
||||||
|
swdev->vlans = PSB6970_MAX_VLANS;
|
||||||
|
swdev->ports = PSB6970_NUM_PORTS;
|
||||||
|
|
||||||
|
if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) {
|
||||||
|
kfree(priv);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = psb6970_reset_switch(&priv->dev);
|
||||||
|
if (ret) {
|
||||||
|
kfree(priv);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_read_status(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
phydev->speed = SPEED_100;
|
||||||
|
phydev->duplex = DUPLEX_FULL;
|
||||||
|
phydev->link = 1;
|
||||||
|
|
||||||
|
phydev->state = PHY_RUNNING;
|
||||||
|
netif_carrier_on(phydev->attached_dev);
|
||||||
|
phydev->adjust_link(phydev->attached_dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_config_aneg(struct phy_device *phydev)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_probe(struct phy_device *pdev)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void psb6970_remove(struct phy_device *pdev)
|
||||||
|
{
|
||||||
|
struct psb6970_priv *priv = pdev->priv;
|
||||||
|
|
||||||
|
if (!priv)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (pdev->mdio.addr == 0)
|
||||||
|
unregister_switch(&priv->dev);
|
||||||
|
kfree(priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int psb6970_fixup(struct phy_device *dev)
|
||||||
|
{
|
||||||
|
struct mii_bus *bus = dev->mdio.bus;
|
||||||
|
u16 reg;
|
||||||
|
|
||||||
|
/* look for the switch on the bus */
|
||||||
|
reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK;
|
||||||
|
if (reg != PSB6970_CI1_VAL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
dev->phy_id = (reg << 16);
|
||||||
|
dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct phy_driver psb6970_driver = {
|
||||||
|
.name = "Lantiq PSB6970",
|
||||||
|
.phy_id = PSB6970_CI1_VAL << 16,
|
||||||
|
.phy_id_mask = 0xffff0000,
|
||||||
|
.features = PHY_BASIC_FEATURES,
|
||||||
|
.probe = psb6970_probe,
|
||||||
|
.remove = psb6970_remove,
|
||||||
|
.config_init = &psb6970_config_init,
|
||||||
|
.config_aneg = &psb6970_config_aneg,
|
||||||
|
.read_status = &psb6970_read_status,
|
||||||
|
};
|
||||||
|
|
||||||
|
int __init psb6970_init(void)
|
||||||
|
{
|
||||||
|
phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup);
|
||||||
|
return phy_driver_register(&psb6970_driver, THIS_MODULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(psb6970_init);
|
||||||
|
|
||||||
|
void __exit psb6970_exit(void)
|
||||||
|
{
|
||||||
|
phy_driver_unregister(&psb6970_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_exit(psb6970_exit);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Lantiq PSB6970 Switch");
|
||||||
|
MODULE_AUTHOR("Ithamar R. Adema <ithamar.adema@team-embedded.nl>");
|
||||||
|
MODULE_LICENSE("GPL");
|
1063
6.12/target/linux/generic/files/drivers/net/phy/rtl8306.c
Normal file
1063
6.12/target/linux/generic/files/drivers/net/phy/rtl8306.c
Normal file
File diff suppressed because it is too large
Load diff
1625
6.12/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c
Normal file
1625
6.12/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c
Normal file
File diff suppressed because it is too large
Load diff
171
6.12/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h
Normal file
171
6.12/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* Realtek RTL8366 SMI interface driver defines
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _RTL8366_SMI_H
|
||||||
|
#define _RTL8366_SMI_H
|
||||||
|
|
||||||
|
#include <linux/phy.h>
|
||||||
|
#include <linux/switch.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/reset.h>
|
||||||
|
|
||||||
|
struct rtl8366_smi_ops;
|
||||||
|
struct rtl8366_vlan_ops;
|
||||||
|
struct mii_bus;
|
||||||
|
struct dentry;
|
||||||
|
struct inode;
|
||||||
|
struct file;
|
||||||
|
|
||||||
|
typedef enum rtl8367b_chip_e {
|
||||||
|
RTL8367B_CHIP_UNKNOWN,
|
||||||
|
/* Family B */
|
||||||
|
RTL8367B_CHIP_RTL8367RB,
|
||||||
|
RTL8367B_CHIP_RTL8367R_VB, /* chip with exception in extif assignment */
|
||||||
|
/* Family C */
|
||||||
|
RTL8367B_CHIP_RTL8367RB_VB,
|
||||||
|
RTL8367B_CHIP_RTL8367S,
|
||||||
|
/* Family D */
|
||||||
|
RTL8367B_CHIP_RTL8367S_VB /* chip with exception in extif assignment */
|
||||||
|
} rtl8367b_chip_t;
|
||||||
|
|
||||||
|
struct rtl8366_mib_counter {
|
||||||
|
unsigned base;
|
||||||
|
unsigned offset;
|
||||||
|
unsigned length;
|
||||||
|
const char *name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rtl8366_smi {
|
||||||
|
struct device *parent;
|
||||||
|
unsigned int gpio_sda;
|
||||||
|
unsigned int gpio_sck;
|
||||||
|
void (*hw_reset)(struct rtl8366_smi *smi, bool active);
|
||||||
|
unsigned int clk_delay; /* ns */
|
||||||
|
u8 cmd_read;
|
||||||
|
u8 cmd_write;
|
||||||
|
spinlock_t lock;
|
||||||
|
struct mii_bus *mii_bus;
|
||||||
|
int mii_irq[PHY_MAX_ADDR];
|
||||||
|
struct switch_dev sw_dev;
|
||||||
|
|
||||||
|
unsigned int cpu_port;
|
||||||
|
unsigned int num_ports;
|
||||||
|
unsigned int num_vlan_mc;
|
||||||
|
unsigned int num_mib_counters;
|
||||||
|
struct rtl8366_mib_counter *mib_counters;
|
||||||
|
|
||||||
|
struct rtl8366_smi_ops *ops;
|
||||||
|
|
||||||
|
int vlan_enabled;
|
||||||
|
int vlan4k_enabled;
|
||||||
|
|
||||||
|
char buf[4096];
|
||||||
|
|
||||||
|
struct reset_control *reset;
|
||||||
|
|
||||||
|
#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
|
||||||
|
struct dentry *debugfs_root;
|
||||||
|
u16 dbg_reg;
|
||||||
|
u8 dbg_vlan_4k_page;
|
||||||
|
#endif
|
||||||
|
u32 phy_id;
|
||||||
|
rtl8367b_chip_t rtl8367b_chip;
|
||||||
|
struct mii_bus *ext_mbus;
|
||||||
|
struct rtl8366_vlan_mc *emu_vlanmc;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rtl8366_vlan_mc {
|
||||||
|
u16 vid;
|
||||||
|
u16 untag;
|
||||||
|
u16 member;
|
||||||
|
u8 fid;
|
||||||
|
u8 priority;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rtl8366_vlan_4k {
|
||||||
|
u16 vid;
|
||||||
|
u16 untag;
|
||||||
|
u16 member;
|
||||||
|
u8 fid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rtl8366_smi_ops {
|
||||||
|
int (*detect)(struct rtl8366_smi *smi);
|
||||||
|
int (*reset_chip)(struct rtl8366_smi *smi);
|
||||||
|
int (*setup)(struct rtl8366_smi *smi);
|
||||||
|
|
||||||
|
int (*mii_read)(struct mii_bus *bus, int addr, int reg);
|
||||||
|
int (*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val);
|
||||||
|
|
||||||
|
int (*get_vlan_mc)(struct rtl8366_smi *smi, u32 index,
|
||||||
|
struct rtl8366_vlan_mc *vlanmc);
|
||||||
|
int (*set_vlan_mc)(struct rtl8366_smi *smi, u32 index,
|
||||||
|
const struct rtl8366_vlan_mc *vlanmc);
|
||||||
|
int (*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid,
|
||||||
|
struct rtl8366_vlan_4k *vlan4k);
|
||||||
|
int (*set_vlan_4k)(struct rtl8366_smi *smi,
|
||||||
|
const struct rtl8366_vlan_4k *vlan4k);
|
||||||
|
int (*get_mc_index)(struct rtl8366_smi *smi, int port, int *val);
|
||||||
|
int (*set_mc_index)(struct rtl8366_smi *smi, int port, int index);
|
||||||
|
int (*get_mib_counter)(struct rtl8366_smi *smi, int counter,
|
||||||
|
int port, unsigned long long *val);
|
||||||
|
int (*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan);
|
||||||
|
int (*enable_vlan)(struct rtl8366_smi *smi, int enable);
|
||||||
|
int (*enable_vlan4k)(struct rtl8366_smi *smi, int enable);
|
||||||
|
int (*enable_port)(struct rtl8366_smi *smi, int port, int enable);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent);
|
||||||
|
int rtl8366_smi_init(struct rtl8366_smi *smi);
|
||||||
|
void rtl8366_smi_cleanup(struct rtl8366_smi *smi);
|
||||||
|
int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data);
|
||||||
|
int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data);
|
||||||
|
int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data);
|
||||||
|
int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data);
|
||||||
|
|
||||||
|
#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
|
||||||
|
int rtl8366_debugfs_open(struct inode *inode, struct file *file);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw)
|
||||||
|
{
|
||||||
|
return container_of(sw, struct rtl8366_smi, sw_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rtl8366_sw_reset_switch(struct switch_dev *dev);
|
||||||
|
int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val);
|
||||||
|
int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val);
|
||||||
|
int rtl8366_sw_get_port_mib(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int rtl8366_sw_get_vlan_info(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int rtl8366_sw_get_vlan_fid(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int rtl8366_sw_set_vlan_fid(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val);
|
||||||
|
int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val);
|
||||||
|
int rtl8366_sw_get_vlan_enable(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int rtl8366_sw_set_vlan_enable(struct switch_dev *dev,
|
||||||
|
const struct switch_attr *attr,
|
||||||
|
struct switch_val *val);
|
||||||
|
int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port,
|
||||||
|
struct switch_port_stats *stats,
|
||||||
|
int txb_id, int rxb_id);
|
||||||
|
|
||||||
|
struct rtl8366_smi* rtl8366_smi_probe(struct platform_device *pdev);
|
||||||
|
|
||||||
|
#endif /* _RTL8366_SMI_H */
|
1538
6.12/target/linux/generic/files/drivers/net/phy/rtl8366rb.c
Normal file
1538
6.12/target/linux/generic/files/drivers/net/phy/rtl8366rb.c
Normal file
File diff suppressed because it is too large
Load diff
1326
6.12/target/linux/generic/files/drivers/net/phy/rtl8366s.c
Normal file
1326
6.12/target/linux/generic/files/drivers/net/phy/rtl8366s.c
Normal file
File diff suppressed because it is too large
Load diff
1868
6.12/target/linux/generic/files/drivers/net/phy/rtl8367.c
Normal file
1868
6.12/target/linux/generic/files/drivers/net/phy/rtl8367.c
Normal file
File diff suppressed because it is too large
Load diff
1658
6.12/target/linux/generic/files/drivers/net/phy/rtl8367b.c
Normal file
1658
6.12/target/linux/generic/files/drivers/net/phy/rtl8367b.c
Normal file
File diff suppressed because it is too large
Load diff
1242
6.12/target/linux/generic/files/drivers/net/phy/swconfig.c
Normal file
1242
6.12/target/linux/generic/files/drivers/net/phy/swconfig.c
Normal file
File diff suppressed because it is too large
Load diff
555
6.12/target/linux/generic/files/drivers/net/phy/swconfig_leds.c
Normal file
555
6.12/target/linux/generic/files/drivers/net/phy/swconfig_leds.c
Normal file
|
@ -0,0 +1,555 @@
|
||||||
|
/*
|
||||||
|
* swconfig_led.c: LED trigger support for the switch configuration API
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef CONFIG_SWCONFIG_LEDS
|
||||||
|
|
||||||
|
#include <linux/leds.h>
|
||||||
|
#include <linux/ctype.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
|
#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10)
|
||||||
|
#define SWCONFIG_LED_NUM_PORTS 32
|
||||||
|
|
||||||
|
#define SWCONFIG_LED_PORT_SPEED_NA 0x01 /* unknown speed */
|
||||||
|
#define SWCONFIG_LED_PORT_SPEED_10 0x02 /* 10 Mbps */
|
||||||
|
#define SWCONFIG_LED_PORT_SPEED_100 0x04 /* 100 Mbps */
|
||||||
|
#define SWCONFIG_LED_PORT_SPEED_1000 0x08 /* 1000 Mbps */
|
||||||
|
#define SWCONFIG_LED_PORT_SPEED_ALL (SWCONFIG_LED_PORT_SPEED_NA | \
|
||||||
|
SWCONFIG_LED_PORT_SPEED_10 | \
|
||||||
|
SWCONFIG_LED_PORT_SPEED_100 | \
|
||||||
|
SWCONFIG_LED_PORT_SPEED_1000)
|
||||||
|
|
||||||
|
#define SWCONFIG_LED_MODE_LINK 0x01
|
||||||
|
#define SWCONFIG_LED_MODE_TX 0x02
|
||||||
|
#define SWCONFIG_LED_MODE_RX 0x04
|
||||||
|
#define SWCONFIG_LED_MODE_TXRX (SWCONFIG_LED_MODE_TX | \
|
||||||
|
SWCONFIG_LED_MODE_RX)
|
||||||
|
#define SWCONFIG_LED_MODE_ALL (SWCONFIG_LED_MODE_LINK | \
|
||||||
|
SWCONFIG_LED_MODE_TX | \
|
||||||
|
SWCONFIG_LED_MODE_RX)
|
||||||
|
|
||||||
|
struct switch_led_trigger {
|
||||||
|
struct led_trigger trig;
|
||||||
|
struct switch_dev *swdev;
|
||||||
|
|
||||||
|
struct delayed_work sw_led_work;
|
||||||
|
u32 port_mask;
|
||||||
|
u32 port_link;
|
||||||
|
unsigned long long port_tx_traffic[SWCONFIG_LED_NUM_PORTS];
|
||||||
|
unsigned long long port_rx_traffic[SWCONFIG_LED_NUM_PORTS];
|
||||||
|
u8 link_speed[SWCONFIG_LED_NUM_PORTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct swconfig_trig_data {
|
||||||
|
struct led_classdev *led_cdev;
|
||||||
|
struct switch_dev *swdev;
|
||||||
|
|
||||||
|
rwlock_t lock;
|
||||||
|
u32 port_mask;
|
||||||
|
|
||||||
|
bool prev_link;
|
||||||
|
unsigned long prev_traffic;
|
||||||
|
enum led_brightness prev_brightness;
|
||||||
|
u8 mode;
|
||||||
|
u8 speed_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data,
|
||||||
|
enum led_brightness brightness)
|
||||||
|
{
|
||||||
|
led_set_brightness(trig_data->led_cdev, brightness);
|
||||||
|
trig_data->prev_brightness = brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
swconfig_trig_update_port_mask(struct led_trigger *trigger)
|
||||||
|
{
|
||||||
|
struct list_head *entry;
|
||||||
|
struct switch_led_trigger *sw_trig;
|
||||||
|
u32 port_mask;
|
||||||
|
|
||||||
|
if (!trigger)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sw_trig = (void *) trigger;
|
||||||
|
|
||||||
|
port_mask = 0;
|
||||||
|
spin_lock(&trigger->leddev_list_lock);
|
||||||
|
list_for_each(entry, &trigger->led_cdevs) {
|
||||||
|
struct led_classdev *led_cdev;
|
||||||
|
struct swconfig_trig_data *trig_data;
|
||||||
|
|
||||||
|
led_cdev = list_entry(entry, struct led_classdev, trig_list);
|
||||||
|
trig_data = led_cdev->trigger_data;
|
||||||
|
if (trig_data) {
|
||||||
|
read_lock(&trig_data->lock);
|
||||||
|
port_mask |= trig_data->port_mask;
|
||||||
|
read_unlock(&trig_data->lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock(&trigger->leddev_list_lock);
|
||||||
|
|
||||||
|
sw_trig->port_mask = port_mask;
|
||||||
|
|
||||||
|
if (port_mask)
|
||||||
|
schedule_delayed_work(&sw_trig->sw_led_work,
|
||||||
|
SWCONFIG_LED_TIMER_INTERVAL);
|
||||||
|
else
|
||||||
|
cancel_delayed_work_sync(&sw_trig->sw_led_work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t size)
|
||||||
|
{
|
||||||
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||||
|
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
|
||||||
|
unsigned long port_mask;
|
||||||
|
int ret;
|
||||||
|
bool changed;
|
||||||
|
|
||||||
|
ret = kstrtoul(buf, 0, &port_mask);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
write_lock(&trig_data->lock);
|
||||||
|
changed = (trig_data->port_mask != port_mask);
|
||||||
|
trig_data->port_mask = port_mask;
|
||||||
|
write_unlock(&trig_data->lock);
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
if (port_mask == 0)
|
||||||
|
swconfig_trig_set_brightness(trig_data, LED_OFF);
|
||||||
|
|
||||||
|
swconfig_trig_update_port_mask(led_cdev->trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||||
|
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
|
||||||
|
u32 port_mask;
|
||||||
|
|
||||||
|
read_lock(&trig_data->lock);
|
||||||
|
port_mask = trig_data->port_mask;
|
||||||
|
read_unlock(&trig_data->lock);
|
||||||
|
|
||||||
|
sprintf(buf, "%#x\n", port_mask);
|
||||||
|
|
||||||
|
return strlen(buf) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show,
|
||||||
|
swconfig_trig_port_mask_store);
|
||||||
|
|
||||||
|
/* speed_mask file handler - display value */
|
||||||
|
static ssize_t swconfig_trig_speed_mask_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||||
|
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
|
||||||
|
u8 speed_mask;
|
||||||
|
|
||||||
|
read_lock(&trig_data->lock);
|
||||||
|
speed_mask = trig_data->speed_mask;
|
||||||
|
read_unlock(&trig_data->lock);
|
||||||
|
|
||||||
|
sprintf(buf, "%#x\n", speed_mask);
|
||||||
|
|
||||||
|
return strlen(buf) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* speed_mask file handler - store value */
|
||||||
|
static ssize_t swconfig_trig_speed_mask_store(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf, size_t size)
|
||||||
|
{
|
||||||
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||||
|
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
|
||||||
|
u8 speed_mask;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = kstrtou8(buf, 0, &speed_mask);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
write_lock(&trig_data->lock);
|
||||||
|
trig_data->speed_mask = speed_mask & SWCONFIG_LED_PORT_SPEED_ALL;
|
||||||
|
write_unlock(&trig_data->lock);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* speed_mask special file */
|
||||||
|
static DEVICE_ATTR(speed_mask, 0644, swconfig_trig_speed_mask_show,
|
||||||
|
swconfig_trig_speed_mask_store);
|
||||||
|
|
||||||
|
static ssize_t swconfig_trig_mode_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||||
|
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
|
||||||
|
u8 mode;
|
||||||
|
|
||||||
|
read_lock(&trig_data->lock);
|
||||||
|
mode = trig_data->mode;
|
||||||
|
read_unlock(&trig_data->lock);
|
||||||
|
|
||||||
|
if (mode == 0) {
|
||||||
|
strcpy(buf, "none\n");
|
||||||
|
} else {
|
||||||
|
if (mode & SWCONFIG_LED_MODE_LINK)
|
||||||
|
strcat(buf, "link ");
|
||||||
|
if (mode & SWCONFIG_LED_MODE_TX)
|
||||||
|
strcat(buf, "tx ");
|
||||||
|
if (mode & SWCONFIG_LED_MODE_RX)
|
||||||
|
strcat(buf, "rx ");
|
||||||
|
strcat(buf, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen(buf)+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t swconfig_trig_mode_store(struct device *dev,
|
||||||
|
struct device_attribute *attr, const char *buf, size_t size)
|
||||||
|
{
|
||||||
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||||
|
struct swconfig_trig_data *trig_data = led_cdev->trigger_data;
|
||||||
|
char copybuf[128];
|
||||||
|
int new_mode = -1;
|
||||||
|
char *p, *token;
|
||||||
|
|
||||||
|
/* take a copy since we don't want to trash the inbound buffer when using strsep */
|
||||||
|
strncpy(copybuf, buf, sizeof(copybuf));
|
||||||
|
copybuf[sizeof(copybuf) - 1] = 0;
|
||||||
|
p = copybuf;
|
||||||
|
|
||||||
|
while ((token = strsep(&p, " \t\n")) != NULL) {
|
||||||
|
if (!*token)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (new_mode < 0)
|
||||||
|
new_mode = 0;
|
||||||
|
|
||||||
|
if (!strcmp(token, "none"))
|
||||||
|
new_mode = 0;
|
||||||
|
else if (!strcmp(token, "tx"))
|
||||||
|
new_mode |= SWCONFIG_LED_MODE_TX;
|
||||||
|
else if (!strcmp(token, "rx"))
|
||||||
|
new_mode |= SWCONFIG_LED_MODE_RX;
|
||||||
|
else if (!strcmp(token, "link"))
|
||||||
|
new_mode |= SWCONFIG_LED_MODE_LINK;
|
||||||
|
else
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_mode < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
write_lock(&trig_data->lock);
|
||||||
|
trig_data->mode = (u8)new_mode;
|
||||||
|
write_unlock(&trig_data->lock);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mode special file */
|
||||||
|
static DEVICE_ATTR(mode, 0644, swconfig_trig_mode_show,
|
||||||
|
swconfig_trig_mode_store);
|
||||||
|
|
||||||
|
static int
|
||||||
|
swconfig_trig_activate(struct led_classdev *led_cdev)
|
||||||
|
{
|
||||||
|
struct switch_led_trigger *sw_trig;
|
||||||
|
struct swconfig_trig_data *trig_data;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL);
|
||||||
|
if (!trig_data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
sw_trig = (void *) led_cdev->trigger;
|
||||||
|
|
||||||
|
rwlock_init(&trig_data->lock);
|
||||||
|
trig_data->led_cdev = led_cdev;
|
||||||
|
trig_data->swdev = sw_trig->swdev;
|
||||||
|
trig_data->speed_mask = SWCONFIG_LED_PORT_SPEED_ALL;
|
||||||
|
trig_data->mode = SWCONFIG_LED_MODE_ALL;
|
||||||
|
led_cdev->trigger_data = trig_data;
|
||||||
|
|
||||||
|
err = device_create_file(led_cdev->dev, &dev_attr_port_mask);
|
||||||
|
if (err)
|
||||||
|
goto err_free;
|
||||||
|
|
||||||
|
err = device_create_file(led_cdev->dev, &dev_attr_speed_mask);
|
||||||
|
if (err)
|
||||||
|
goto err_dev_free;
|
||||||
|
|
||||||
|
err = device_create_file(led_cdev->dev, &dev_attr_mode);
|
||||||
|
if (err)
|
||||||
|
goto err_mode_free;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_mode_free:
|
||||||
|
device_remove_file(led_cdev->dev, &dev_attr_speed_mask);
|
||||||
|
|
||||||
|
err_dev_free:
|
||||||
|
device_remove_file(led_cdev->dev, &dev_attr_port_mask);
|
||||||
|
|
||||||
|
err_free:
|
||||||
|
led_cdev->trigger_data = NULL;
|
||||||
|
kfree(trig_data);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
swconfig_trig_deactivate(struct led_classdev *led_cdev)
|
||||||
|
{
|
||||||
|
struct swconfig_trig_data *trig_data;
|
||||||
|
|
||||||
|
swconfig_trig_update_port_mask(led_cdev->trigger);
|
||||||
|
|
||||||
|
trig_data = (void *) led_cdev->trigger_data;
|
||||||
|
if (trig_data) {
|
||||||
|
device_remove_file(led_cdev->dev, &dev_attr_port_mask);
|
||||||
|
device_remove_file(led_cdev->dev, &dev_attr_speed_mask);
|
||||||
|
device_remove_file(led_cdev->dev, &dev_attr_mode);
|
||||||
|
kfree(trig_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* link off -> led off (can't be any other reason to turn it on)
|
||||||
|
* link on:
|
||||||
|
* mode link: led on by default only if speed matches, else off
|
||||||
|
* mode txrx: blink only if speed matches, else off
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
swconfig_trig_led_event(struct switch_led_trigger *sw_trig,
|
||||||
|
struct led_classdev *led_cdev)
|
||||||
|
{
|
||||||
|
struct swconfig_trig_data *trig_data;
|
||||||
|
u32 port_mask;
|
||||||
|
bool link;
|
||||||
|
u8 speed_mask, mode;
|
||||||
|
enum led_brightness led_base, led_blink;
|
||||||
|
|
||||||
|
trig_data = led_cdev->trigger_data;
|
||||||
|
if (!trig_data)
|
||||||
|
return;
|
||||||
|
|
||||||
|
read_lock(&trig_data->lock);
|
||||||
|
port_mask = trig_data->port_mask;
|
||||||
|
speed_mask = trig_data->speed_mask;
|
||||||
|
mode = trig_data->mode;
|
||||||
|
read_unlock(&trig_data->lock);
|
||||||
|
|
||||||
|
link = !!(sw_trig->port_link & port_mask);
|
||||||
|
if (!link) {
|
||||||
|
if (trig_data->prev_brightness != LED_OFF)
|
||||||
|
swconfig_trig_set_brightness(trig_data, LED_OFF); /* and stop */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unsigned long traffic;
|
||||||
|
int speedok; /* link speed flag */
|
||||||
|
int i;
|
||||||
|
|
||||||
|
led_base = LED_FULL;
|
||||||
|
led_blink = LED_OFF;
|
||||||
|
traffic = 0;
|
||||||
|
speedok = 0;
|
||||||
|
for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) {
|
||||||
|
if (port_mask & (1 << i)) {
|
||||||
|
if (sw_trig->link_speed[i] & speed_mask) {
|
||||||
|
traffic += ((mode & SWCONFIG_LED_MODE_TX) ?
|
||||||
|
sw_trig->port_tx_traffic[i] : 0) +
|
||||||
|
((mode & SWCONFIG_LED_MODE_RX) ?
|
||||||
|
sw_trig->port_rx_traffic[i] : 0);
|
||||||
|
speedok = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (speedok) {
|
||||||
|
/* At least one port speed matches speed_mask */
|
||||||
|
if (!(mode & SWCONFIG_LED_MODE_LINK)) {
|
||||||
|
led_base = LED_OFF;
|
||||||
|
led_blink = LED_FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trig_data->prev_brightness != led_base)
|
||||||
|
swconfig_trig_set_brightness(trig_data,
|
||||||
|
led_base);
|
||||||
|
else if (traffic != trig_data->prev_traffic)
|
||||||
|
swconfig_trig_set_brightness(trig_data,
|
||||||
|
led_blink);
|
||||||
|
} else if (trig_data->prev_brightness != LED_OFF)
|
||||||
|
swconfig_trig_set_brightness(trig_data, LED_OFF);
|
||||||
|
|
||||||
|
trig_data->prev_traffic = traffic;
|
||||||
|
}
|
||||||
|
|
||||||
|
trig_data->prev_link = link;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
swconfig_trig_update_leds(struct switch_led_trigger *sw_trig)
|
||||||
|
{
|
||||||
|
struct list_head *entry;
|
||||||
|
struct led_trigger *trigger;
|
||||||
|
|
||||||
|
trigger = &sw_trig->trig;
|
||||||
|
spin_lock(&trigger->leddev_list_lock);
|
||||||
|
list_for_each(entry, &trigger->led_cdevs) {
|
||||||
|
struct led_classdev *led_cdev;
|
||||||
|
|
||||||
|
led_cdev = list_entry(entry, struct led_classdev, trig_list);
|
||||||
|
swconfig_trig_led_event(sw_trig, led_cdev);
|
||||||
|
}
|
||||||
|
spin_unlock(&trigger->leddev_list_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
swconfig_led_work_func(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct switch_led_trigger *sw_trig;
|
||||||
|
struct switch_dev *swdev;
|
||||||
|
u32 port_mask;
|
||||||
|
u32 link;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
sw_trig = container_of(work, struct switch_led_trigger,
|
||||||
|
sw_led_work.work);
|
||||||
|
|
||||||
|
port_mask = sw_trig->port_mask;
|
||||||
|
swdev = sw_trig->swdev;
|
||||||
|
|
||||||
|
link = 0;
|
||||||
|
for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) {
|
||||||
|
u32 port_bit;
|
||||||
|
|
||||||
|
sw_trig->link_speed[i] = 0;
|
||||||
|
|
||||||
|
port_bit = BIT(i);
|
||||||
|
if ((port_mask & port_bit) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (swdev->ops->get_port_link) {
|
||||||
|
struct switch_port_link port_link;
|
||||||
|
|
||||||
|
memset(&port_link, '\0', sizeof(port_link));
|
||||||
|
swdev->ops->get_port_link(swdev, i, &port_link);
|
||||||
|
|
||||||
|
if (port_link.link) {
|
||||||
|
link |= port_bit;
|
||||||
|
switch (port_link.speed) {
|
||||||
|
case SWITCH_PORT_SPEED_UNKNOWN:
|
||||||
|
sw_trig->link_speed[i] =
|
||||||
|
SWCONFIG_LED_PORT_SPEED_NA;
|
||||||
|
break;
|
||||||
|
case SWITCH_PORT_SPEED_10:
|
||||||
|
sw_trig->link_speed[i] =
|
||||||
|
SWCONFIG_LED_PORT_SPEED_10;
|
||||||
|
break;
|
||||||
|
case SWITCH_PORT_SPEED_100:
|
||||||
|
sw_trig->link_speed[i] =
|
||||||
|
SWCONFIG_LED_PORT_SPEED_100;
|
||||||
|
break;
|
||||||
|
case SWITCH_PORT_SPEED_1000:
|
||||||
|
sw_trig->link_speed[i] =
|
||||||
|
SWCONFIG_LED_PORT_SPEED_1000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swdev->ops->get_port_stats) {
|
||||||
|
struct switch_port_stats port_stats;
|
||||||
|
|
||||||
|
memset(&port_stats, '\0', sizeof(port_stats));
|
||||||
|
swdev->ops->get_port_stats(swdev, i, &port_stats);
|
||||||
|
sw_trig->port_tx_traffic[i] = port_stats.tx_bytes;
|
||||||
|
sw_trig->port_rx_traffic[i] = port_stats.rx_bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sw_trig->port_link = link;
|
||||||
|
|
||||||
|
swconfig_trig_update_leds(sw_trig);
|
||||||
|
|
||||||
|
schedule_delayed_work(&sw_trig->sw_led_work,
|
||||||
|
SWCONFIG_LED_TIMER_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
swconfig_create_led_trigger(struct switch_dev *swdev)
|
||||||
|
{
|
||||||
|
struct switch_led_trigger *sw_trig;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!swdev->ops->get_port_link)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL);
|
||||||
|
if (!sw_trig)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
sw_trig->swdev = swdev;
|
||||||
|
sw_trig->trig.name = swdev->devname;
|
||||||
|
sw_trig->trig.activate = swconfig_trig_activate;
|
||||||
|
sw_trig->trig.deactivate = swconfig_trig_deactivate;
|
||||||
|
|
||||||
|
INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func);
|
||||||
|
|
||||||
|
err = led_trigger_register(&sw_trig->trig);
|
||||||
|
if (err)
|
||||||
|
goto err_free;
|
||||||
|
|
||||||
|
swdev->led_trigger = sw_trig;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_free:
|
||||||
|
kfree(sw_trig);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
swconfig_destroy_led_trigger(struct switch_dev *swdev)
|
||||||
|
{
|
||||||
|
struct switch_led_trigger *sw_trig;
|
||||||
|
|
||||||
|
sw_trig = swdev->led_trigger;
|
||||||
|
if (sw_trig) {
|
||||||
|
cancel_delayed_work_sync(&sw_trig->sw_led_work);
|
||||||
|
led_trigger_unregister(&sw_trig->trig);
|
||||||
|
kfree(sw_trig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* SWCONFIG_LEDS */
|
||||||
|
static inline int
|
||||||
|
swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; }
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
swconfig_destroy_led_trigger(struct switch_dev *swdev) { }
|
||||||
|
#endif /* CONFIG_SWCONFIG_LEDS */
|
|
@ -0,0 +1,31 @@
|
||||||
|
menuconfig MIKROTIK
|
||||||
|
bool "Platform support for MikroTik RouterBoard virtual devices"
|
||||||
|
help
|
||||||
|
Say Y here to get to see options for the MikroTik RouterBoard platform.
|
||||||
|
This option alone does not add any kernel code.
|
||||||
|
|
||||||
|
|
||||||
|
if MIKROTIK
|
||||||
|
|
||||||
|
config MIKROTIK_RB_SYSFS
|
||||||
|
tristate "RouterBoot sysfs support"
|
||||||
|
depends on MTD
|
||||||
|
select LZO_DECOMPRESS
|
||||||
|
select CRC32
|
||||||
|
help
|
||||||
|
This driver exposes RouterBoot configuration in sysfs.
|
||||||
|
|
||||||
|
config NVMEM_LAYOUT_MIKROTIK
|
||||||
|
tristate "RouterBoot NVMEM layout support"
|
||||||
|
depends on NVMEM_LAYOUTS
|
||||||
|
help
|
||||||
|
This driver exposes MikroTik hard_config via NVMEM layout.
|
||||||
|
|
||||||
|
config MIKROTIK_WLAN_DECOMPRESS_LZ77
|
||||||
|
tristate "Mikrotik factory Wi-Fi caldata LZ77 decompression support"
|
||||||
|
depends on MIKROTIK_RB_SYSFS
|
||||||
|
help
|
||||||
|
Allow Mikrotik LZ77 factory flashed Wi-Fi calibration data to be
|
||||||
|
decompressed
|
||||||
|
|
||||||
|
endif # MIKROTIK
|
|
@ -0,0 +1,6 @@
|
||||||
|
#
|
||||||
|
# Makefile for MikroTik RouterBoard platform specific drivers
|
||||||
|
#
|
||||||
|
obj-$(CONFIG_MIKROTIK_RB_SYSFS) += routerboot.o rb_hardconfig.o rb_softconfig.o
|
||||||
|
obj-$(CONFIG_NVMEM_LAYOUT_MIKROTIK) += rb_nvmem.o
|
||||||
|
obj-$(CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77) += rb_lz77.o
|
|
@ -0,0 +1,844 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Driver for MikroTik RouterBoot hard config.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This driver exposes the data encoded in the "hard_config" flash segment of
|
||||||
|
* MikroTik RouterBOARDs devices. It presents the data in a sysfs folder
|
||||||
|
* named "hard_config". The WLAN calibration data is available on demand via
|
||||||
|
* the 'wlan_data' sysfs file in that folder.
|
||||||
|
*
|
||||||
|
* This driver permanently allocates a chunk of RAM as large as the hard_config
|
||||||
|
* MTD partition, although it is technically possible to operate entirely from
|
||||||
|
* the MTD device without using a local buffer (except when requesting WLAN
|
||||||
|
* calibration data), at the cost of a performance penalty.
|
||||||
|
*
|
||||||
|
* Note: PAGE_SIZE is assumed to be >= 4K, hence the device attribute show
|
||||||
|
* routines need not check for output overflow.
|
||||||
|
*
|
||||||
|
* Some constant defines extracted from routerboot.{c,h} by Gabor Juhos
|
||||||
|
* <juhosg@openwrt.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/errno.h>
|
||||||
|
#include <linux/kobject.h>
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/sysfs.h>
|
||||||
|
#include <linux/lzo.h>
|
||||||
|
|
||||||
|
#include "rb_hardconfig.h"
|
||||||
|
#include "routerboot.h"
|
||||||
|
#include "rb_lz77.h"
|
||||||
|
|
||||||
|
#define RB_HARDCONFIG_VER "0.08"
|
||||||
|
#define RB_HC_PR_PFX "[rb_hardconfig] "
|
||||||
|
|
||||||
|
/* Bit definitions for hardware options */
|
||||||
|
#define RB_HW_OPT_NO_UART BIT(0)
|
||||||
|
#define RB_HW_OPT_HAS_VOLTAGE BIT(1)
|
||||||
|
#define RB_HW_OPT_HAS_USB BIT(2)
|
||||||
|
#define RB_HW_OPT_HAS_ATTINY BIT(3)
|
||||||
|
#define RB_HW_OPT_PULSE_DUTY_CYCLE BIT(9)
|
||||||
|
#define RB_HW_OPT_NO_NAND BIT(14)
|
||||||
|
#define RB_HW_OPT_HAS_LCD BIT(15)
|
||||||
|
#define RB_HW_OPT_HAS_POE_OUT BIT(16)
|
||||||
|
#define RB_HW_OPT_HAS_uSD BIT(17)
|
||||||
|
#define RB_HW_OPT_HAS_SIM BIT(18)
|
||||||
|
#define RB_HW_OPT_HAS_SFP BIT(20)
|
||||||
|
#define RB_HW_OPT_HAS_WIFI BIT(21)
|
||||||
|
#define RB_HW_OPT_HAS_TS_FOR_ADC BIT(22)
|
||||||
|
#define RB_HW_OPT_HAS_PLC BIT(29)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tag ID values for ERD data.
|
||||||
|
* Mikrotik used to pack all calibration data under a single tag id 0x1, but
|
||||||
|
* recently switched to a new scheme where each radio calibration gets a
|
||||||
|
* separate tag. The new scheme has tag id bit 15 always set and seems to be
|
||||||
|
* mutually exclusive with the old scheme.
|
||||||
|
*/
|
||||||
|
#define RB_WLAN_ERD_ID_SOLO 0x0001
|
||||||
|
#define RB_WLAN_ERD_ID_MULTI_8001 0x8001
|
||||||
|
#define RB_WLAN_ERD_ID_MULTI_8201 0x8201
|
||||||
|
|
||||||
|
static struct kobject *hc_kobj;
|
||||||
|
static u8 *hc_buf; // ro buffer after init(): no locking required
|
||||||
|
static size_t hc_buflen;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For LZOR style WLAN data unpacking.
|
||||||
|
* This binary blob is prepended to the data encoded on some devices as
|
||||||
|
* RB_ID_WLAN_DATA, the result is then first decompressed with LZO, and then
|
||||||
|
* finally RLE-decoded.
|
||||||
|
* This binary blob has been extracted from RouterOS by
|
||||||
|
* https://forum.openwrt.org/u/ius
|
||||||
|
*/
|
||||||
|
static const u8 hc_lzor_prefix[] = {
|
||||||
|
0x00, 0x05, 0x4c, 0x4c, 0x44, 0x00, 0x34, 0xfe,
|
||||||
|
0xfe, 0x34, 0x11, 0x3c, 0x1e, 0x3c, 0x2e, 0x3c,
|
||||||
|
0x4c, 0x34, 0x00, 0x52, 0x62, 0x92, 0xa2, 0xb2,
|
||||||
|
0xc3, 0x2a, 0x14, 0x00, 0x00, 0x05, 0xfe, 0x6a,
|
||||||
|
0x3c, 0x16, 0x32, 0x16, 0x11, 0x1e, 0x12, 0x46,
|
||||||
|
0x32, 0x46, 0x11, 0x4e, 0x12, 0x36, 0x32, 0x36,
|
||||||
|
0x11, 0x3e, 0x12, 0x5a, 0x9a, 0x64, 0x00, 0x04,
|
||||||
|
0xfe, 0x10, 0x3c, 0x00, 0x01, 0x00, 0x00, 0x28,
|
||||||
|
0x0c, 0x00, 0x0f, 0xfe, 0x14, 0x00, 0x24, 0x24,
|
||||||
|
0x23, 0x24, 0x24, 0x23, 0x25, 0x22, 0x21, 0x21,
|
||||||
|
0x23, 0x22, 0x21, 0x22, 0x21, 0x2d, 0x38, 0x00,
|
||||||
|
0x0c, 0x25, 0x25, 0x24, 0x25, 0x25, 0x24, 0x23,
|
||||||
|
0x22, 0x21, 0x20, 0x23, 0x21, 0x21, 0x22, 0x21,
|
||||||
|
0x2d, 0x38, 0x00, 0x28, 0xb0, 0x00, 0x00, 0x22,
|
||||||
|
0x00, 0x00, 0xc0, 0xfe, 0x03, 0x00, 0xc0, 0x00,
|
||||||
|
0x62, 0xff, 0x62, 0xff, 0xfe, 0x06, 0x00, 0xbb,
|
||||||
|
0xff, 0xba, 0xff, 0xfe, 0x08, 0x00, 0x9e, 0xff,
|
||||||
|
0xfe, 0x0a, 0x00, 0x53, 0xff, 0xfe, 0x02, 0x00,
|
||||||
|
0x20, 0xff, 0xb1, 0xfe, 0xfe, 0xb2, 0xfe, 0xfe,
|
||||||
|
0xed, 0xfe, 0xfe, 0xfe, 0x04, 0x00, 0x3a, 0xff,
|
||||||
|
0x3a, 0xff, 0xde, 0xfd, 0x5f, 0x04, 0x33, 0xff,
|
||||||
|
0x4c, 0x74, 0x03, 0x05, 0x05, 0xff, 0x6d, 0xfe,
|
||||||
|
0xfe, 0x6d, 0xfe, 0xfe, 0xaf, 0x08, 0x63, 0xff,
|
||||||
|
0x64, 0x6f, 0x08, 0xac, 0xff, 0xbf, 0x6d, 0x08,
|
||||||
|
0x7a, 0x6d, 0x08, 0x96, 0x74, 0x04, 0x00, 0x08,
|
||||||
|
0x79, 0xff, 0xda, 0xfe, 0xfe, 0xdb, 0xfe, 0xfe,
|
||||||
|
0x56, 0xff, 0xfe, 0x04, 0x00, 0x5e, 0xff, 0x5e,
|
||||||
|
0xff, 0x6c, 0xfe, 0xfe, 0xfe, 0x06, 0x00, 0x41,
|
||||||
|
0xff, 0x7f, 0x74, 0x03, 0x00, 0x11, 0x44, 0xff,
|
||||||
|
0xa9, 0xfe, 0xfe, 0xa9, 0xfe, 0xfe, 0xa5, 0x8f,
|
||||||
|
0x01, 0x00, 0x08, 0x01, 0x01, 0x02, 0x04, 0x08,
|
||||||
|
0x02, 0x04, 0x08, 0x08, 0x01, 0x01, 0xfe, 0x22,
|
||||||
|
0x00, 0x4c, 0x60, 0x64, 0x8c, 0x90, 0xd0, 0xd4,
|
||||||
|
0xd8, 0x5c, 0x10, 0x09, 0xd8, 0xff, 0xb0, 0xff,
|
||||||
|
0x00, 0x00, 0xba, 0xff, 0x14, 0x00, 0xba, 0xff,
|
||||||
|
0x64, 0x00, 0x00, 0x08, 0xfe, 0x06, 0x00, 0x74,
|
||||||
|
0xff, 0x42, 0xff, 0xce, 0xff, 0x60, 0xff, 0x0a,
|
||||||
|
0x00, 0xb4, 0x00, 0xa0, 0x00, 0xa0, 0xfe, 0x07,
|
||||||
|
0x00, 0x0a, 0x00, 0xb0, 0xff, 0x96, 0x4d, 0x00,
|
||||||
|
0x56, 0x57, 0x18, 0xa6, 0xff, 0x92, 0x70, 0x11,
|
||||||
|
0x00, 0x12, 0x90, 0x90, 0x76, 0x5a, 0x54, 0x54,
|
||||||
|
0x4c, 0x46, 0x38, 0x00, 0x10, 0x10, 0x08, 0xfe,
|
||||||
|
0x05, 0x00, 0x38, 0x29, 0x25, 0x23, 0x22, 0x22,
|
||||||
|
0x1f, 0x00, 0x00, 0x00, 0xf6, 0xe1, 0xdd, 0xf8,
|
||||||
|
0xfe, 0x00, 0xfe, 0x15, 0x00, 0x00, 0xd0, 0x02,
|
||||||
|
0x74, 0x02, 0x08, 0xf8, 0xe5, 0xde, 0x02, 0x04,
|
||||||
|
0x04, 0xfd, 0x00, 0x00, 0x00, 0x07, 0x50, 0x2d,
|
||||||
|
0x01, 0x90, 0x90, 0x76, 0x60, 0xb0, 0x07, 0x07,
|
||||||
|
0x0c, 0x0c, 0x04, 0xfe, 0x05, 0x00, 0x66, 0x66,
|
||||||
|
0x5a, 0x56, 0xbc, 0x01, 0x06, 0xfc, 0xfc, 0xf1,
|
||||||
|
0xfe, 0x07, 0x00, 0x24, 0x95, 0x70, 0x64, 0x18,
|
||||||
|
0x06, 0x2c, 0xff, 0xb5, 0xfe, 0xfe, 0xb5, 0xfe,
|
||||||
|
0xfe, 0xe2, 0x8c, 0x24, 0x02, 0x2f, 0xff, 0x2f,
|
||||||
|
0xff, 0xb4, 0x78, 0x02, 0x05, 0x73, 0xff, 0xed,
|
||||||
|
0xfe, 0xfe, 0x4f, 0xff, 0x36, 0x74, 0x1e, 0x09,
|
||||||
|
0x4f, 0xff, 0x50, 0xff, 0xfe, 0x16, 0x00, 0x70,
|
||||||
|
0xac, 0x70, 0x8e, 0xac, 0x40, 0x0e, 0x01, 0x70,
|
||||||
|
0x7f, 0x8e, 0xac, 0x6c, 0x00, 0x0b, 0xfe, 0x02,
|
||||||
|
0x00, 0xfe, 0x0a, 0x2c, 0x2a, 0x2a, 0x28, 0x26,
|
||||||
|
0x1e, 0x1e, 0xfe, 0x02, 0x20, 0x65, 0x20, 0x00,
|
||||||
|
0x00, 0x05, 0x12, 0x00, 0x11, 0x1e, 0x11, 0x11,
|
||||||
|
0x41, 0x1e, 0x41, 0x11, 0x31, 0x1e, 0x31, 0x11,
|
||||||
|
0x70, 0x75, 0x7a, 0x7f, 0x84, 0x89, 0x8e, 0x93,
|
||||||
|
0x98, 0x30, 0x20, 0x00, 0x02, 0x00, 0xfe, 0x06,
|
||||||
|
0x3c, 0xbc, 0x32, 0x0c, 0x00, 0x00, 0x2a, 0x12,
|
||||||
|
0x1e, 0x12, 0x2e, 0x12, 0xcc, 0x12, 0x11, 0x1a,
|
||||||
|
0x1e, 0x1a, 0x2e, 0x1a, 0x4c, 0x10, 0x1e, 0x10,
|
||||||
|
0x11, 0x18, 0x1e, 0x42, 0x1e, 0x42, 0x2e, 0x42,
|
||||||
|
0xcc, 0x42, 0x11, 0x4a, 0x1e, 0x4a, 0x2e, 0x4a,
|
||||||
|
0x4c, 0x40, 0x1e, 0x40, 0x11, 0x48, 0x1e, 0x32,
|
||||||
|
0x1e, 0x32, 0x2e, 0x32, 0xcc, 0x32, 0x11, 0x3a,
|
||||||
|
0x1e, 0x3a, 0x2e, 0x3a, 0x4c, 0x30, 0x1e, 0x30,
|
||||||
|
0x11, 0x38, 0x1e, 0x27, 0x9a, 0x01, 0x9d, 0xa2,
|
||||||
|
0x2f, 0x28, 0x00, 0x00, 0x46, 0xde, 0xc4, 0xbf,
|
||||||
|
0xa6, 0x9d, 0x81, 0x7b, 0x5c, 0x61, 0x40, 0xc7,
|
||||||
|
0xc0, 0xae, 0xa9, 0x8c, 0x83, 0x6a, 0x62, 0x50,
|
||||||
|
0x3e, 0xce, 0xc2, 0xae, 0xa3, 0x8c, 0x7b, 0x6a,
|
||||||
|
0x5a, 0x50, 0x35, 0xd7, 0xc2, 0xb7, 0xa4, 0x95,
|
||||||
|
0x7e, 0x72, 0x5a, 0x59, 0x37, 0xfe, 0x02, 0xf8,
|
||||||
|
0x8c, 0x95, 0x90, 0x8f, 0x00, 0xd7, 0xc0, 0xb7,
|
||||||
|
0xa2, 0x95, 0x7b, 0x72, 0x56, 0x59, 0x32, 0xc7,
|
||||||
|
0xc3, 0xae, 0xad, 0x8c, 0x85, 0x6a, 0x63, 0x50,
|
||||||
|
0x3e, 0xce, 0xc3, 0xae, 0xa4, 0x8c, 0x7c, 0x6a,
|
||||||
|
0x59, 0x50, 0x34, 0xd7, 0xc2, 0xb7, 0xa5, 0x95,
|
||||||
|
0x7e, 0x72, 0x59, 0x59, 0x36, 0xfc, 0x05, 0x00,
|
||||||
|
0x02, 0xce, 0xc5, 0xae, 0xa5, 0x95, 0x83, 0x72,
|
||||||
|
0x5c, 0x59, 0x36, 0xbf, 0xc6, 0xa5, 0xab, 0x8c,
|
||||||
|
0x8c, 0x6a, 0x67, 0x50, 0x41, 0x64, 0x07, 0x00,
|
||||||
|
0x02, 0x95, 0x8c, 0x72, 0x65, 0x59, 0x3f, 0xce,
|
||||||
|
0xc7, 0xae, 0xa8, 0x95, 0x86, 0x72, 0x5f, 0x59,
|
||||||
|
0x39, 0xfe, 0x02, 0xf8, 0x8b, 0x7c, 0x0b, 0x09,
|
||||||
|
0xb7, 0xc2, 0x9d, 0xa4, 0x83, 0x85, 0x6a, 0x6b,
|
||||||
|
0x50, 0x44, 0xb7, 0xc1, 0x64, 0x01, 0x00, 0x06,
|
||||||
|
0x61, 0x5d, 0x48, 0x3d, 0xae, 0xc4, 0x9d, 0xad,
|
||||||
|
0x7b, 0x85, 0x61, 0x66, 0x48, 0x46, 0xae, 0xc3,
|
||||||
|
0x95, 0xa3, 0x72, 0x7c, 0x59, 0x56, 0x38, 0x31,
|
||||||
|
0x7c, 0x0b, 0x00, 0x0c, 0x96, 0x91, 0x8f, 0x00,
|
||||||
|
0xb7, 0xc0, 0xa5, 0xab, 0x8c, 0x8a, 0x6a, 0x64,
|
||||||
|
0x50, 0x3c, 0xb7, 0xc0, 0x9d, 0xa0, 0x83, 0x80,
|
||||||
|
0x6a, 0x64, 0x50, 0x3d, 0xb7, 0xc5, 0x9d, 0xa5,
|
||||||
|
0x83, 0x87, 0x6c, 0x08, 0x07, 0xae, 0xc0, 0x9d,
|
||||||
|
0xa8, 0x83, 0x88, 0x6a, 0x6d, 0x50, 0x46, 0xfc,
|
||||||
|
0x05, 0x00, 0x16, 0xbf, 0xc0, 0xa5, 0xa2, 0x8c,
|
||||||
|
0x7f, 0x6a, 0x57, 0x50, 0x2f, 0xb7, 0xc7, 0xa5,
|
||||||
|
0xb1, 0x8c, 0x8e, 0x72, 0x6d, 0x59, 0x45, 0xbf,
|
||||||
|
0xc6, 0xa5, 0xa8, 0x8c, 0x87, 0x6a, 0x5f, 0x50,
|
||||||
|
0x37, 0xbf, 0xc2, 0xa5, 0xa4, 0x8c, 0x83, 0x6a,
|
||||||
|
0x5c, 0x50, 0x34, 0xbc, 0x05, 0x00, 0x0e, 0x90,
|
||||||
|
0x00, 0xc7, 0xc2, 0xae, 0xaa, 0x95, 0x82, 0x7b,
|
||||||
|
0x60, 0x61, 0x3f, 0xb7, 0xc6, 0xa5, 0xb1, 0x8c,
|
||||||
|
0x8d, 0x72, 0x6b, 0x61, 0x51, 0xbf, 0xc4, 0xa5,
|
||||||
|
0xa5, 0x8c, 0x82, 0x72, 0x61, 0x59, 0x39, 0x6c,
|
||||||
|
0x26, 0x03, 0x95, 0x82, 0x7b, 0x61, 0x61, 0x40,
|
||||||
|
0xfc, 0x05, 0x00, 0x00, 0x7e, 0xd7, 0xc3, 0xb7,
|
||||||
|
0xa8, 0x9d, 0x80, 0x83, 0x5d, 0x6a, 0x3f, 0xbf,
|
||||||
|
0xc7, 0xa5, 0xa8, 0x8c, 0x84, 0x72, 0x60, 0x61,
|
||||||
|
0x46, 0xbf, 0xc2, 0xae, 0xb0, 0x9d, 0x92, 0x83,
|
||||||
|
0x6f, 0x6a, 0x50, 0xd7, 0xc3, 0xb7, 0xa7, 0x9d,
|
||||||
|
0x80, 0x83, 0x5e, 0x6a, 0x40, 0xfe, 0x02, 0xf8,
|
||||||
|
0x8d, 0x96, 0x90, 0x90, 0xfe, 0x05, 0x00, 0x8a,
|
||||||
|
0xc4, 0x63, 0xb8, 0x3c, 0xa6, 0x29, 0x97, 0x16,
|
||||||
|
0x81, 0x84, 0xb7, 0x5b, 0xa9, 0x33, 0x94, 0x1e,
|
||||||
|
0x83, 0x11, 0x70, 0xb8, 0xc2, 0x70, 0xb1, 0x4d,
|
||||||
|
0xa3, 0x2a, 0x8d, 0x1b, 0x7b, 0xa8, 0xbc, 0x68,
|
||||||
|
0xab, 0x47, 0x9d, 0x27, 0x87, 0x18, 0x75, 0xae,
|
||||||
|
0xc6, 0x7d, 0xbb, 0x4d, 0xaa, 0x1c, 0x84, 0x11,
|
||||||
|
0x72, 0xa3, 0xbb, 0x6e, 0xad, 0x3c, 0x97, 0x24,
|
||||||
|
0x85, 0x16, 0x71, 0x80, 0xb2, 0x57, 0xa4, 0x30,
|
||||||
|
0x8e, 0x1c, 0x7c, 0x10, 0x68, 0xbb, 0xbd, 0x75,
|
||||||
|
0xac, 0x4f, 0x9e, 0x2b, 0x87, 0x1a, 0x76, 0x96,
|
||||||
|
0xc5, 0x5e, 0xb5, 0x3e, 0xa5, 0x1f, 0x8c, 0x12,
|
||||||
|
0x7a, 0xc1, 0xc6, 0x42, 0x9f, 0x27, 0x8c, 0x16,
|
||||||
|
0x77, 0x0f, 0x67, 0x9d, 0xbc, 0x68, 0xad, 0x36,
|
||||||
|
0x95, 0x20, 0x83, 0x11, 0x6d, 0x9b, 0xb8, 0x67,
|
||||||
|
0xa8, 0x34, 0x90, 0x1f, 0x7c, 0x10, 0x67, 0x9e,
|
||||||
|
0xc9, 0x6a, 0xbb, 0x37, 0xa4, 0x20, 0x90, 0x11,
|
||||||
|
0x7b, 0xc6, 0xc8, 0x47, 0xa4, 0x2a, 0x90, 0x18,
|
||||||
|
0x7b, 0x10, 0x6c, 0xae, 0xc4, 0x5d, 0xad, 0x37,
|
||||||
|
0x9a, 0x1f, 0x85, 0x13, 0x75, 0x70, 0xad, 0x42,
|
||||||
|
0x99, 0x25, 0x84, 0x17, 0x74, 0x0b, 0x56, 0x87,
|
||||||
|
0xc8, 0x57, 0xb8, 0x2b, 0x9e, 0x19, 0x8a, 0x0d,
|
||||||
|
0x74, 0xa7, 0xc8, 0x6e, 0xb9, 0x36, 0xa0, 0x1f,
|
||||||
|
0x8b, 0x11, 0x75, 0x94, 0xbe, 0x4b, 0xa5, 0x2a,
|
||||||
|
0x92, 0x18, 0x7c, 0x0f, 0x6b, 0xaf, 0xc0, 0x58,
|
||||||
|
0xa8, 0x34, 0x94, 0x1d, 0x7d, 0x12, 0x6d, 0x82,
|
||||||
|
0xc0, 0x52, 0xb0, 0x25, 0x94, 0x14, 0x7f, 0x0c,
|
||||||
|
0x68, 0x84, 0xbf, 0x3e, 0xa4, 0x22, 0x8e, 0x10,
|
||||||
|
0x76, 0x0b, 0x65, 0x88, 0xb6, 0x42, 0x9b, 0x26,
|
||||||
|
0x87, 0x14, 0x70, 0x0c, 0x5f, 0xc5, 0xc2, 0x3e,
|
||||||
|
0x97, 0x23, 0x83, 0x13, 0x6c, 0x0c, 0x5c, 0xb1,
|
||||||
|
0xc9, 0x76, 0xbc, 0x4a, 0xaa, 0x20, 0x8d, 0x12,
|
||||||
|
0x78, 0x93, 0xbf, 0x46, 0xa3, 0x26, 0x8d, 0x14,
|
||||||
|
0x74, 0x0c, 0x62, 0xc8, 0xc4, 0x3b, 0x97, 0x21,
|
||||||
|
0x82, 0x11, 0x6a, 0x0a, 0x59, 0xa3, 0xb9, 0x68,
|
||||||
|
0xa9, 0x30, 0x8d, 0x1a, 0x78, 0x0f, 0x61, 0xa0,
|
||||||
|
0xc9, 0x73, 0xbe, 0x50, 0xb1, 0x30, 0x9f, 0x14,
|
||||||
|
0x80, 0x83, 0xb7, 0x3c, 0x9a, 0x20, 0x84, 0x0e,
|
||||||
|
0x6a, 0x0a, 0x57, 0xac, 0xc2, 0x68, 0xb0, 0x2e,
|
||||||
|
0x92, 0x19, 0x7c, 0x0d, 0x63, 0x93, 0xbe, 0x62,
|
||||||
|
0xb0, 0x3c, 0x9e, 0x1a, 0x80, 0x0e, 0x6b, 0xbb,
|
||||||
|
0x02, 0xa0, 0x02, 0xa0, 0x02, 0x6f, 0x00, 0x75,
|
||||||
|
0x00, 0x75, 0x00, 0x00, 0x00, 0xad, 0x02, 0xb3,
|
||||||
|
0x02, 0x6f, 0x00, 0x87, 0x00, 0x85, 0xfe, 0x03,
|
||||||
|
0x00, 0xc2, 0x02, 0x82, 0x4d, 0x92, 0x6e, 0x4d,
|
||||||
|
0xb1, 0xa8, 0x84, 0x01, 0x00, 0x07, 0x7e, 0x00,
|
||||||
|
0xa8, 0x02, 0xa4, 0x02, 0xa4, 0x02, 0xa2, 0x00,
|
||||||
|
0xa6, 0x00, 0xa6, 0x00, 0x00, 0x00, 0xb4, 0x02,
|
||||||
|
0xb4, 0x02, 0x92, 0x00, 0x96, 0x00, 0x96, 0x46,
|
||||||
|
0x04, 0xb0, 0x02, 0x64, 0x02, 0x0a, 0x8c, 0x00,
|
||||||
|
0x90, 0x02, 0x98, 0x02, 0x98, 0x02, 0x0e, 0x01,
|
||||||
|
0x11, 0x01, 0x11, 0x50, 0xc3, 0x08, 0x88, 0x02,
|
||||||
|
0x88, 0x02, 0x19, 0x01, 0x02, 0x01, 0x02, 0x01,
|
||||||
|
0xf3, 0x2d, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Array of known hw_options bits with human-friendly parsing */
|
||||||
|
static struct hc_hwopt {
|
||||||
|
const u32 bit;
|
||||||
|
const char *str;
|
||||||
|
} const hc_hwopts[] = {
|
||||||
|
{
|
||||||
|
.bit = RB_HW_OPT_NO_UART,
|
||||||
|
.str = "no UART\t\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_VOLTAGE,
|
||||||
|
.str = "has Vreg\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_USB,
|
||||||
|
.str = "has usb\t\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_ATTINY,
|
||||||
|
.str = "has ATtiny\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_NO_NAND,
|
||||||
|
.str = "no NAND\t\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_LCD,
|
||||||
|
.str = "has LCD\t\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_POE_OUT,
|
||||||
|
.str = "has POE out\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_uSD,
|
||||||
|
.str = "has MicroSD\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_SIM,
|
||||||
|
.str = "has SIM\t\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_SFP,
|
||||||
|
.str = "has SFP\t\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_WIFI,
|
||||||
|
.str = "has WiFi\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_TS_FOR_ADC,
|
||||||
|
.str = "has TS ADC\t",
|
||||||
|
}, {
|
||||||
|
.bit = RB_HW_OPT_HAS_PLC,
|
||||||
|
.str = "has PLC\t\t",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The MAC is stored network-endian on all devices, in 2 32-bit segments:
|
||||||
|
* <XX:XX:XX:XX> <XX:XX:00:00>. Kernel print has us covered.
|
||||||
|
*/
|
||||||
|
static ssize_t hc_tag_show_mac(const u8 *pld, u16 pld_len, char *buf)
|
||||||
|
{
|
||||||
|
if (8 != pld_len)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return sprintf(buf, "%pM\n", pld);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print HW options in a human readable way:
|
||||||
|
* The raw number and in decoded form
|
||||||
|
*/
|
||||||
|
static ssize_t hc_tag_show_hwoptions(const u8 *pld, u16 pld_len, char *buf)
|
||||||
|
{
|
||||||
|
char *out = buf;
|
||||||
|
u32 data; // cpu-endian
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (sizeof(data) != pld_len)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
data = *(u32 *)pld;
|
||||||
|
out += sprintf(out, "raw\t\t: 0x%08x\n\n", data);
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(hc_hwopts); i++)
|
||||||
|
out += sprintf(out, "%s: %s\n", hc_hwopts[i].str,
|
||||||
|
(data & hc_hwopts[i].bit) ? "true" : "false");
|
||||||
|
|
||||||
|
return out - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj,
|
||||||
|
struct bin_attribute *attr, char *buf,
|
||||||
|
loff_t off, size_t count);
|
||||||
|
|
||||||
|
static struct hc_wlan_attr {
|
||||||
|
const u16 erd_tag_id;
|
||||||
|
struct bin_attribute battr;
|
||||||
|
u16 pld_ofs;
|
||||||
|
u16 pld_len;
|
||||||
|
} hc_wd_multi_battrs[] = {
|
||||||
|
{
|
||||||
|
.erd_tag_id = RB_WLAN_ERD_ID_MULTI_8001,
|
||||||
|
.battr = __BIN_ATTR(data_0, S_IRUSR, hc_wlan_data_bin_read, NULL, 0),
|
||||||
|
}, {
|
||||||
|
.erd_tag_id = RB_WLAN_ERD_ID_MULTI_8201,
|
||||||
|
.battr = __BIN_ATTR(data_2, S_IRUSR, hc_wlan_data_bin_read, NULL, 0),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct hc_wlan_attr hc_wd_solo_battr = {
|
||||||
|
.erd_tag_id = RB_WLAN_ERD_ID_SOLO,
|
||||||
|
.battr = __BIN_ATTR(wlan_data, S_IRUSR, hc_wlan_data_bin_read, NULL, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t hc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||||
|
char *buf);
|
||||||
|
|
||||||
|
/* Array of known tags to publish in sysfs */
|
||||||
|
static struct hc_attr {
|
||||||
|
const u16 tag_id;
|
||||||
|
ssize_t (* const tshow)(const u8 *pld, u16 pld_len, char *buf);
|
||||||
|
struct kobj_attribute kattr;
|
||||||
|
u16 pld_ofs;
|
||||||
|
u16 pld_len;
|
||||||
|
} hc_attrs[] = {
|
||||||
|
{
|
||||||
|
.tag_id = RB_ID_FLASH_INFO,
|
||||||
|
.tshow = routerboot_tag_show_u32s,
|
||||||
|
.kattr = __ATTR(flash_info, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_MAC_ADDRESS_PACK,
|
||||||
|
.tshow = hc_tag_show_mac,
|
||||||
|
.kattr = __ATTR(mac_base, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_BOARD_PRODUCT_CODE,
|
||||||
|
.tshow = routerboot_tag_show_string,
|
||||||
|
.kattr = __ATTR(board_product_code, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_BIOS_VERSION,
|
||||||
|
.tshow = routerboot_tag_show_string,
|
||||||
|
.kattr = __ATTR(booter_version, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_SERIAL_NUMBER,
|
||||||
|
.tshow = routerboot_tag_show_string,
|
||||||
|
.kattr = __ATTR(board_serial, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_MEMORY_SIZE,
|
||||||
|
.tshow = routerboot_tag_show_u32s,
|
||||||
|
.kattr = __ATTR(mem_size, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_MAC_ADDRESS_COUNT,
|
||||||
|
.tshow = routerboot_tag_show_u32s,
|
||||||
|
.kattr = __ATTR(mac_count, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_HW_OPTIONS,
|
||||||
|
.tshow = hc_tag_show_hwoptions,
|
||||||
|
.kattr = __ATTR(hw_options, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_WLAN_DATA,
|
||||||
|
.tshow = NULL,
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_BOARD_IDENTIFIER,
|
||||||
|
.tshow = routerboot_tag_show_string,
|
||||||
|
.kattr = __ATTR(board_identifier, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_PRODUCT_NAME,
|
||||||
|
.tshow = routerboot_tag_show_string,
|
||||||
|
.kattr = __ATTR(product_name, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_DEFCONF,
|
||||||
|
.tshow = routerboot_tag_show_string,
|
||||||
|
.kattr = __ATTR(defconf, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_ID_BOARD_REVISION,
|
||||||
|
.tshow = routerboot_tag_show_string,
|
||||||
|
.kattr = __ATTR(board_revision, S_IRUSR, hc_attr_show, NULL),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the RB_ID_WLAN_DATA payload starts with RB_MAGIC_ERD, then past
|
||||||
|
* that magic number the payload itself contains a routerboot tag node
|
||||||
|
* locating the LZO-compressed calibration data. So far this scheme is only
|
||||||
|
* known to use a single tag at id 0x1.
|
||||||
|
*/
|
||||||
|
static int hc_wlan_data_unpack_erd(const u16 tag_id, const u8 *inbuf, size_t inlen,
|
||||||
|
void *outbuf, size_t *outlen)
|
||||||
|
{
|
||||||
|
u16 lzo_ofs, lzo_len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Find embedded tag */
|
||||||
|
ret = routerboot_tag_find(inbuf, inlen, tag_id, &lzo_ofs, &lzo_len);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug(RB_HC_PR_PFX "no ERD data for id 0x%04x\n", tag_id);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lzo_len > inlen) {
|
||||||
|
pr_debug(RB_HC_PR_PFX "Invalid ERD data length\n");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = lzo1x_decompress_safe(inbuf+lzo_ofs, lzo_len, outbuf, outlen);
|
||||||
|
if (ret)
|
||||||
|
pr_debug(RB_HC_PR_PFX "LZO decompression error (%d)\n", ret);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the RB_ID_WLAN_DATA payload starts with RB_MAGIC_LZOR, then past
|
||||||
|
* that magic number is a payload that must be appended to the hc_lzor_prefix,
|
||||||
|
* the resulting blob is LZO-compressed.
|
||||||
|
* If payload starts with RB_MAGIC_LZ77, a separate (bit level LZ77)
|
||||||
|
* decompression function needs to be used. In the decompressed result,
|
||||||
|
* the RB_MAGIC_ERD magic number (aligned) must be located. Following that
|
||||||
|
* magic, there is one or more routerboot tag node(s) locating the RLE-encoded
|
||||||
|
* calibration data payload.
|
||||||
|
*/
|
||||||
|
static int hc_wlan_data_unpack_lzor_lz77(const u16 tag_id, const u8 *inbuf, size_t inlen,
|
||||||
|
void *outbuf, size_t *outlen, u32 magic)
|
||||||
|
{
|
||||||
|
u16 rle_ofs, rle_len;
|
||||||
|
const u32 *needle;
|
||||||
|
u8 *tempbuf;
|
||||||
|
size_t templen, lzo_len;
|
||||||
|
int ret;
|
||||||
|
const char lzor[] = "LZOR";
|
||||||
|
const char lz77[] = "LZ77";
|
||||||
|
const char *lz_type;
|
||||||
|
|
||||||
|
/* Temporary buffer same size as the outbuf */
|
||||||
|
templen = *outlen;
|
||||||
|
tempbuf = kmalloc(templen, GFP_KERNEL);
|
||||||
|
if (!tempbuf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
lzo_len = inlen;
|
||||||
|
if (magic == RB_MAGIC_LZOR)
|
||||||
|
lzo_len += sizeof(hc_lzor_prefix);
|
||||||
|
if (lzo_len > *outlen)
|
||||||
|
return -EFBIG;
|
||||||
|
|
||||||
|
switch (magic) {
|
||||||
|
case RB_MAGIC_LZOR:
|
||||||
|
lz_type = lzor;
|
||||||
|
|
||||||
|
/* Concatenate into the outbuf */
|
||||||
|
memcpy(outbuf, hc_lzor_prefix, sizeof(hc_lzor_prefix));
|
||||||
|
memcpy(outbuf + sizeof(hc_lzor_prefix), inbuf, inlen);
|
||||||
|
|
||||||
|
/* LZO-decompress lzo_len bytes of outbuf into the tempbuf */
|
||||||
|
ret = lzo1x_decompress_safe(outbuf, lzo_len, tempbuf, &templen);
|
||||||
|
if (ret) {
|
||||||
|
if (LZO_E_INPUT_NOT_CONSUMED == ret) {
|
||||||
|
/*
|
||||||
|
* The tag length is always aligned thus the LZO payload may be padded,
|
||||||
|
* which can trigger a spurious error which we ignore here.
|
||||||
|
*/
|
||||||
|
pr_debug(RB_HC_PR_PFX "LZOR: LZO EOF before buffer end - this may be harmless\n");
|
||||||
|
} else {
|
||||||
|
pr_debug(RB_HC_PR_PFX "LZOR: LZO decompression error (%d)\n", ret);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RB_MAGIC_LZ77:
|
||||||
|
lz_type = lz77;
|
||||||
|
/* LZO-decompress lzo_len bytes of inbuf into the tempbuf */
|
||||||
|
ret = rb_lz77_decompress(inbuf, inlen, tempbuf, &templen);
|
||||||
|
if (ret) {
|
||||||
|
pr_err(RB_HC_PR_PFX "LZ77: LZ77 decompress error %d\n", ret);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_debug(RB_HC_PR_PFX "LZ77: decompressed from %zu to %zu\n",
|
||||||
|
inlen, templen);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Post decompression we have a blob (possibly byproduct of the lzo
|
||||||
|
* dictionary). We need to find RB_MAGIC_ERD. The magic number seems to
|
||||||
|
* be 32bit-aligned in the decompression output.
|
||||||
|
*/
|
||||||
|
needle = (const u32 *)tempbuf;
|
||||||
|
while (RB_MAGIC_ERD != *needle++) {
|
||||||
|
if ((u8 *)needle >= tempbuf+templen) {
|
||||||
|
pr_warn(RB_HC_PR_PFX "%s: ERD magic not found. Decompressed first word: 0x%08x\n", lz_type, *(u32 *)tempbuf);
|
||||||
|
ret = -ENODATA;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
templen -= (u8 *)needle - tempbuf;
|
||||||
|
|
||||||
|
/* Past magic. Look for tag node */
|
||||||
|
ret = routerboot_tag_find((u8 *)needle, templen, tag_id, &rle_ofs, &rle_len);
|
||||||
|
if (ret) {
|
||||||
|
pr_debug(RB_HC_PR_PFX "%s: no RLE data for id 0x%04x\n", lz_type, tag_id);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rle_len > templen) {
|
||||||
|
pr_debug(RB_HC_PR_PFX "%s: Invalid RLE data length\n", lz_type);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RLE-decode tempbuf from needle back into the outbuf */
|
||||||
|
ret = routerboot_rle_decode((u8 *)needle+rle_ofs, rle_len, outbuf, outlen);
|
||||||
|
if (ret)
|
||||||
|
pr_debug(RB_HC_PR_PFX "%s: RLE decoding error (%d)\n", lz_type, ret);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
kfree(tempbuf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hc_wlan_data_unpack(const u16 tag_id, const size_t tofs, size_t tlen,
|
||||||
|
void *outbuf, size_t *outlen)
|
||||||
|
{
|
||||||
|
const u8 *lbuf;
|
||||||
|
u32 magic;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Caller ensure tlen > 0. tofs is aligned */
|
||||||
|
if ((tofs + tlen) > hc_buflen)
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
lbuf = hc_buf + tofs;
|
||||||
|
magic = *(u32 *)lbuf;
|
||||||
|
|
||||||
|
ret = -ENODATA;
|
||||||
|
switch (magic) {
|
||||||
|
case RB_MAGIC_LZ77:
|
||||||
|
/* no known instances of lz77 without 8001/8201 data, skip SOLO */
|
||||||
|
if (tag_id == RB_WLAN_ERD_ID_SOLO) {
|
||||||
|
pr_debug(RB_HC_PR_PFX "skipped LZ77 decompress in search for SOLO tag\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fallthrough;
|
||||||
|
case RB_MAGIC_LZOR:
|
||||||
|
/* Skip magic */
|
||||||
|
lbuf += sizeof(magic);
|
||||||
|
tlen -= sizeof(magic);
|
||||||
|
ret = hc_wlan_data_unpack_lzor_lz77(tag_id, lbuf, tlen, outbuf, outlen, magic);
|
||||||
|
break;
|
||||||
|
case RB_MAGIC_ERD:
|
||||||
|
/* Skip magic */
|
||||||
|
lbuf += sizeof(magic);
|
||||||
|
tlen -= sizeof(magic);
|
||||||
|
ret = hc_wlan_data_unpack_erd(tag_id, lbuf, tlen, outbuf, outlen);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/*
|
||||||
|
* If the RB_ID_WLAN_DATA payload doesn't start with a
|
||||||
|
* magic number, the payload itself is the raw RLE-encoded
|
||||||
|
* calibration data. Only RB_WLAN_ERD_ID_SOLO makes sense here.
|
||||||
|
*/
|
||||||
|
if (RB_WLAN_ERD_ID_SOLO == tag_id) {
|
||||||
|
ret = routerboot_rle_decode(lbuf, tlen, outbuf, outlen);
|
||||||
|
if (ret)
|
||||||
|
pr_debug(RB_HC_PR_PFX "RLE decoding error (%d)\n", ret);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t hc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
const struct hc_attr *hc_attr;
|
||||||
|
const u8 *pld;
|
||||||
|
u16 pld_len;
|
||||||
|
|
||||||
|
hc_attr = container_of(attr, typeof(*hc_attr), kattr);
|
||||||
|
|
||||||
|
if (!hc_attr->pld_len)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
pld = hc_buf + hc_attr->pld_ofs;
|
||||||
|
pld_len = hc_attr->pld_len;
|
||||||
|
|
||||||
|
return hc_attr->tshow(pld, pld_len, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function will allocate and free memory every time it is called. This
|
||||||
|
* is not the fastest way to do this, but since the data is rarely read (mainly
|
||||||
|
* at boot time to load wlan caldata), this makes it possible to save memory for
|
||||||
|
* the system.
|
||||||
|
*/
|
||||||
|
static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj,
|
||||||
|
struct bin_attribute *attr, char *buf,
|
||||||
|
loff_t off, size_t count)
|
||||||
|
{
|
||||||
|
struct hc_wlan_attr *hc_wattr;
|
||||||
|
size_t outlen;
|
||||||
|
void *outbuf;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
hc_wattr = container_of(attr, typeof(*hc_wattr), battr);
|
||||||
|
|
||||||
|
if (!hc_wattr->pld_len)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
outlen = RB_ART_SIZE;
|
||||||
|
|
||||||
|
/* Don't bother unpacking if the source is already too large */
|
||||||
|
if (hc_wattr->pld_len > outlen)
|
||||||
|
return -EFBIG;
|
||||||
|
|
||||||
|
outbuf = kmalloc(outlen, GFP_KERNEL);
|
||||||
|
if (!outbuf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = hc_wlan_data_unpack(hc_wattr->erd_tag_id, hc_wattr->pld_ofs, hc_wattr->pld_len, outbuf, &outlen);
|
||||||
|
if (ret) {
|
||||||
|
kfree(outbuf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (off >= outlen) {
|
||||||
|
kfree(outbuf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (off + count > outlen)
|
||||||
|
count = outlen - off;
|
||||||
|
|
||||||
|
memcpy(buf, outbuf + off, count);
|
||||||
|
|
||||||
|
kfree(outbuf);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb_hardconfig_init(struct kobject *rb_kobj, struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
struct kobject *hc_wlan_kobj;
|
||||||
|
size_t bytes_read, buflen, outlen;
|
||||||
|
const u8 *buf;
|
||||||
|
void *outbuf;
|
||||||
|
int i, j, ret;
|
||||||
|
u32 magic;
|
||||||
|
|
||||||
|
hc_buf = NULL;
|
||||||
|
hc_kobj = NULL;
|
||||||
|
hc_wlan_kobj = NULL;
|
||||||
|
|
||||||
|
ret = __get_mtd_device(mtd);
|
||||||
|
if (ret)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
hc_buflen = mtd->size;
|
||||||
|
hc_buf = kmalloc(hc_buflen, GFP_KERNEL);
|
||||||
|
if (!hc_buf) {
|
||||||
|
__put_mtd_device(mtd);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = mtd_read(mtd, 0, hc_buflen, &bytes_read, hc_buf);
|
||||||
|
__put_mtd_device(mtd);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (bytes_read != hc_buflen) {
|
||||||
|
ret = -EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check we have what we expect */
|
||||||
|
magic = *(const u32 *)hc_buf;
|
||||||
|
if (RB_MAGIC_HARD != magic) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip magic */
|
||||||
|
buf = hc_buf + sizeof(magic);
|
||||||
|
buflen = hc_buflen - sizeof(magic);
|
||||||
|
|
||||||
|
/* Populate sysfs */
|
||||||
|
ret = -ENOMEM;
|
||||||
|
hc_kobj = kobject_create_and_add(RB_MTD_HARD_CONFIG, rb_kobj);
|
||||||
|
if (!hc_kobj)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
/* Locate and publish all known tags */
|
||||||
|
for (i = 0; i < ARRAY_SIZE(hc_attrs); i++) {
|
||||||
|
ret = routerboot_tag_find(buf, buflen, hc_attrs[i].tag_id,
|
||||||
|
&hc_attrs[i].pld_ofs, &hc_attrs[i].pld_len);
|
||||||
|
if (ret) {
|
||||||
|
hc_attrs[i].pld_ofs = hc_attrs[i].pld_len = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Account for skipped magic */
|
||||||
|
hc_attrs[i].pld_ofs += sizeof(magic);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Special case RB_ID_WLAN_DATA to prep and create the binary attribute.
|
||||||
|
* We first check if the data is "old style" within a single tag (or no tag at all):
|
||||||
|
* If it is we publish this single blob as a binary attribute child of hc_kobj to
|
||||||
|
* preserve backward compatibility.
|
||||||
|
* If it isn't and instead uses multiple ERD tags, we create a subfolder and
|
||||||
|
* publish the known ones there.
|
||||||
|
*/
|
||||||
|
if ((RB_ID_WLAN_DATA == hc_attrs[i].tag_id) && hc_attrs[i].pld_len) {
|
||||||
|
outlen = RB_ART_SIZE;
|
||||||
|
outbuf = kmalloc(outlen, GFP_KERNEL);
|
||||||
|
if (!outbuf) {
|
||||||
|
pr_warn(RB_HC_PR_PFX "Out of memory parsing WLAN tag\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test ID_SOLO first, if found: done */
|
||||||
|
ret = hc_wlan_data_unpack(RB_WLAN_ERD_ID_SOLO, hc_attrs[i].pld_ofs, hc_attrs[i].pld_len, outbuf, &outlen);
|
||||||
|
if (!ret) {
|
||||||
|
hc_wd_solo_battr.pld_ofs = hc_attrs[i].pld_ofs;
|
||||||
|
hc_wd_solo_battr.pld_len = hc_attrs[i].pld_len;
|
||||||
|
|
||||||
|
ret = sysfs_create_bin_file(hc_kobj, &hc_wd_solo_battr.battr);
|
||||||
|
if (ret)
|
||||||
|
pr_warn(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n",
|
||||||
|
hc_wd_solo_battr.battr.attr.name, ret);
|
||||||
|
}
|
||||||
|
/* Otherwise, create "wlan_data" subtree and publish known data */
|
||||||
|
else {
|
||||||
|
hc_wlan_kobj = kobject_create_and_add("wlan_data", hc_kobj);
|
||||||
|
if (!hc_wlan_kobj) {
|
||||||
|
kfree(outbuf);
|
||||||
|
pr_warn(RB_HC_PR_PFX "Could not create wlan_data sysfs folder\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < ARRAY_SIZE(hc_wd_multi_battrs); j++) {
|
||||||
|
outlen = RB_ART_SIZE;
|
||||||
|
ret = hc_wlan_data_unpack(hc_wd_multi_battrs[j].erd_tag_id,
|
||||||
|
hc_attrs[i].pld_ofs, hc_attrs[i].pld_len, outbuf, &outlen);
|
||||||
|
if (ret) {
|
||||||
|
hc_wd_multi_battrs[j].pld_ofs = hc_wd_multi_battrs[j].pld_len = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
hc_wd_multi_battrs[j].pld_ofs = hc_attrs[i].pld_ofs;
|
||||||
|
hc_wd_multi_battrs[j].pld_len = hc_attrs[i].pld_len;
|
||||||
|
|
||||||
|
ret = sysfs_create_bin_file(hc_wlan_kobj, &hc_wd_multi_battrs[j].battr);
|
||||||
|
if (ret)
|
||||||
|
pr_warn(RB_HC_PR_PFX "Could not create wlan_data/%s sysfs entry (%d)\n",
|
||||||
|
hc_wd_multi_battrs[j].battr.attr.name, ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree(outbuf);
|
||||||
|
}
|
||||||
|
/* All other tags are published via standard attributes */
|
||||||
|
else {
|
||||||
|
ret = sysfs_create_file(hc_kobj, &hc_attrs[i].kattr.attr);
|
||||||
|
if (ret)
|
||||||
|
pr_warn(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n",
|
||||||
|
hc_attrs[i].kattr.attr.name, ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_info("MikroTik RouterBOARD hardware configuration sysfs driver v" RB_HARDCONFIG_VER "\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
kfree(hc_buf);
|
||||||
|
hc_buf = NULL;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rb_hardconfig_exit(void)
|
||||||
|
{
|
||||||
|
kobject_put(hc_kobj);
|
||||||
|
hc_kobj = NULL;
|
||||||
|
kfree(hc_buf);
|
||||||
|
hc_buf = NULL;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Common definitions for MikroTik RouterBoot hard config data.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
|
||||||
|
*
|
||||||
|
* Some constant defines extracted from routerboot.{c,h} by Gabor Juhos
|
||||||
|
* <juhosg@openwrt.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ROUTERBOOT_HARD_CONFIG_H_
|
||||||
|
#define _ROUTERBOOT_HARD_CONFIG_H_
|
||||||
|
|
||||||
|
/* ID values for hardware settings */
|
||||||
|
#define RB_ID_FLASH_INFO 0x03
|
||||||
|
#define RB_ID_MAC_ADDRESS_PACK 0x04
|
||||||
|
#define RB_ID_BOARD_PRODUCT_CODE 0x05
|
||||||
|
#define RB_ID_BIOS_VERSION 0x06
|
||||||
|
#define RB_ID_SDRAM_TIMINGS 0x08
|
||||||
|
#define RB_ID_DEVICE_TIMINGS 0x09
|
||||||
|
#define RB_ID_SOFTWARE_ID 0x0A
|
||||||
|
#define RB_ID_SERIAL_NUMBER 0x0B
|
||||||
|
#define RB_ID_MEMORY_SIZE 0x0D
|
||||||
|
#define RB_ID_MAC_ADDRESS_COUNT 0x0E
|
||||||
|
#define RB_ID_HW_OPTIONS 0x15
|
||||||
|
#define RB_ID_WLAN_DATA 0x16
|
||||||
|
#define RB_ID_BOARD_IDENTIFIER 0x17
|
||||||
|
#define RB_ID_PRODUCT_NAME 0x21
|
||||||
|
#define RB_ID_DEFCONF 0x26
|
||||||
|
#define RB_ID_BOARD_REVISION 0x27
|
||||||
|
|
||||||
|
#endif /* _ROUTERBOOT_HARD_CONFIG_H_ */
|
|
@ -0,0 +1,446 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 John Thomson
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
#include <linux/errno.h>
|
||||||
|
#include <linux/minmax.h>
|
||||||
|
|
||||||
|
#include "rb_lz77.h"
|
||||||
|
|
||||||
|
#define MIKRO_LZ77 "[rb lz77] "
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The maximum number of bits used in a counter.
|
||||||
|
* For the look behind window, long instruction match offsets
|
||||||
|
* up to 6449 have been seen in provided compressed caldata blobs
|
||||||
|
* (that would need 21 counter bits: 4 to 12 + 11 to 0).
|
||||||
|
* conservative value here: 27 provides offset up to 0x8000 bytes
|
||||||
|
* uses a u8 in this code
|
||||||
|
*/
|
||||||
|
#define MIKRO_LZ77_MAX_COUNT_BIT_LEN 27
|
||||||
|
|
||||||
|
enum rb_lz77_instruction {
|
||||||
|
INSTR_ERROR = -1,
|
||||||
|
INSTR_LITERAL_BYTE = 0,
|
||||||
|
/* a (non aligned) byte follows this instruction,
|
||||||
|
* which is directly copied into output
|
||||||
|
*/
|
||||||
|
INSTR_PREVIOUS_OFFSET = 1,
|
||||||
|
/* this group is a match, with a bytes length defined by
|
||||||
|
* following counter bits, starting at bitshift 0,
|
||||||
|
* less the built-in count of 1
|
||||||
|
* using the previous offset as source
|
||||||
|
*/
|
||||||
|
INSTR_LONG = 2
|
||||||
|
/* this group has two counters,
|
||||||
|
* the first counter starts at bitshift 4,
|
||||||
|
* if this counter == 0, this is a non-matching group
|
||||||
|
* the second counter (bytes length) starts at bitshift 4,
|
||||||
|
* less the built-in count of 11+1.
|
||||||
|
* The final match group has this count 0,
|
||||||
|
* and following bits which pad to byte-alignment.
|
||||||
|
*
|
||||||
|
* if this counter > 0, this is a matching group
|
||||||
|
* this first count is the match offset (in bytes)
|
||||||
|
* the second count is the match length (in bytes),
|
||||||
|
* less the built-in count of 2
|
||||||
|
* these groups can source bytes that are part of this group
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rb_lz77_instr_opcodes {
|
||||||
|
/* group instruction */
|
||||||
|
enum rb_lz77_instruction instruction;
|
||||||
|
/* if >0, a match group,
|
||||||
|
* which starts at byte output_position - 1*offset
|
||||||
|
*/
|
||||||
|
size_t offset;
|
||||||
|
/* how long the match group is,
|
||||||
|
* or how long the (following counter) non-match group is
|
||||||
|
*/
|
||||||
|
size_t length;
|
||||||
|
/* how many bits were used for this instruction + op code(s) */
|
||||||
|
size_t bits_used;
|
||||||
|
/* input char */
|
||||||
|
u8 *in;
|
||||||
|
/* offset where this instruction started */
|
||||||
|
size_t in_pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rb_lz77_get_bit
|
||||||
|
*
|
||||||
|
* @in: compressed data ptr
|
||||||
|
* @in_offset_bit: bit offset to extract
|
||||||
|
*
|
||||||
|
* convert the bit offset to byte offset,
|
||||||
|
* shift to modulo of bits per bytes, so that wanted bit is lsb
|
||||||
|
* and to extract only that bit.
|
||||||
|
* Caller is responsible for ensuring that in_offset_bit/8
|
||||||
|
* does not exceed input length
|
||||||
|
*/
|
||||||
|
static inline u8 rb_lz77_get_bit(const u8 *in, const size_t in_offset_bit)
|
||||||
|
{
|
||||||
|
return ((in[in_offset_bit / BITS_PER_BYTE] >>
|
||||||
|
(in_offset_bit % BITS_PER_BYTE)) &
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rb_lz77_get_byte
|
||||||
|
*
|
||||||
|
* @in: compressed data
|
||||||
|
* @in_offset_bit: bit offset to extract byte
|
||||||
|
*/
|
||||||
|
static inline u8 rb_lz77_get_byte(const u8 *in, const size_t in_offset_bit)
|
||||||
|
{
|
||||||
|
u8 buf = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* built a reversed byte from (likely) unaligned bits */
|
||||||
|
for (i = 0; i <= 7; ++i)
|
||||||
|
buf += rb_lz77_get_bit(in, in_offset_bit + i) << (7 - i);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rb_lz77_decode_count - decode bits at given offset as a count
|
||||||
|
*
|
||||||
|
* @in: compressed data
|
||||||
|
* @in_len: length of compressed data
|
||||||
|
* @in_offset_bit: bit offset where count starts
|
||||||
|
* @shift: left shift operand value of first count bit
|
||||||
|
* @count: initial count
|
||||||
|
* @bits_used: how many bits were consumed by this count
|
||||||
|
* @max_bits: maximum bit count for this counter
|
||||||
|
*
|
||||||
|
* Returns the decoded count
|
||||||
|
*/
|
||||||
|
static int rb_lz77_decode_count(const u8 *in, const size_t in_len,
|
||||||
|
const size_t in_offset_bit, u8 shift,
|
||||||
|
size_t count, u8 *bits_used, const u8 max_bits)
|
||||||
|
{
|
||||||
|
size_t pos = in_offset_bit;
|
||||||
|
const size_t max_pos = min(pos + max_bits, in_len * BITS_PER_BYTE);
|
||||||
|
bool up = true;
|
||||||
|
|
||||||
|
*bits_used = 0;
|
||||||
|
pr_debug(MIKRO_LZ77
|
||||||
|
"decode_count inbit: %zu, start shift:%u, initial count:%zu\n",
|
||||||
|
in_offset_bit, shift, count);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
/* check the input offset bit does not overflow the minimum of
|
||||||
|
* a reasonable length for this encoded count, and
|
||||||
|
* the end of the input */
|
||||||
|
if (unlikely(pos >= max_pos)) {
|
||||||
|
pr_err(MIKRO_LZ77
|
||||||
|
"max bit index reached before count completed\n");
|
||||||
|
return -EFBIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if the bit value at offset is set */
|
||||||
|
if (rb_lz77_get_bit(in, pos))
|
||||||
|
count += (1 << shift);
|
||||||
|
|
||||||
|
/* shift increases until we find an unsed bit */
|
||||||
|
else if (up)
|
||||||
|
up = false;
|
||||||
|
|
||||||
|
if (up)
|
||||||
|
++shift;
|
||||||
|
else {
|
||||||
|
if (!shift) {
|
||||||
|
*bits_used = pos - in_offset_bit + 1;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
--shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rb_lz77_decode_instruction
|
||||||
|
*
|
||||||
|
* @in: compressed data
|
||||||
|
* @in_offset_bit: bit offset where instruction starts
|
||||||
|
* @bits_used: how many bits were consumed by this count
|
||||||
|
*
|
||||||
|
* Returns the decoded instruction
|
||||||
|
*/
|
||||||
|
static enum rb_lz77_instruction
|
||||||
|
rb_lz77_decode_instruction(const u8 *in, size_t in_offset_bit, u8 *bits_used)
|
||||||
|
{
|
||||||
|
if (rb_lz77_get_bit(in, in_offset_bit)) {
|
||||||
|
*bits_used = 2;
|
||||||
|
if (rb_lz77_get_bit(in, ++in_offset_bit))
|
||||||
|
return INSTR_LONG;
|
||||||
|
else
|
||||||
|
return INSTR_PREVIOUS_OFFSET;
|
||||||
|
} else {
|
||||||
|
*bits_used = 1;
|
||||||
|
return INSTR_LITERAL_BYTE;
|
||||||
|
}
|
||||||
|
return INSTR_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rb_lz77_decode_instruction_operators
|
||||||
|
*
|
||||||
|
* @in: compressed data
|
||||||
|
* @in_len: length of compressed data
|
||||||
|
* @in_offset_bit: bit offset where instruction starts
|
||||||
|
* @previous_offset: last used match offset
|
||||||
|
* @opcode: struct to hold instruction & operators
|
||||||
|
*
|
||||||
|
* Returns error code
|
||||||
|
*/
|
||||||
|
static int rb_lz77_decode_instruction_operators(
|
||||||
|
const u8 *in, const size_t in_len, const size_t in_offset_bit,
|
||||||
|
const size_t previous_offset, struct rb_lz77_instr_opcodes *opcode)
|
||||||
|
{
|
||||||
|
enum rb_lz77_instruction instruction;
|
||||||
|
u8 bit_count = 0;
|
||||||
|
u8 bits_used = 0;
|
||||||
|
int offset = 0;
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
instruction = rb_lz77_decode_instruction(in, in_offset_bit, &bit_count);
|
||||||
|
|
||||||
|
/* skip bits used by instruction */
|
||||||
|
bits_used += bit_count;
|
||||||
|
|
||||||
|
switch (instruction) {
|
||||||
|
case INSTR_LITERAL_BYTE:
|
||||||
|
/* non-matching char */
|
||||||
|
offset = 0;
|
||||||
|
length = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INSTR_PREVIOUS_OFFSET:
|
||||||
|
/* matching group uses previous offset */
|
||||||
|
offset = previous_offset;
|
||||||
|
|
||||||
|
length = rb_lz77_decode_count(in, in_len,
|
||||||
|
in_offset_bit + bits_used, 0, 1,
|
||||||
|
&bit_count,
|
||||||
|
MIKRO_LZ77_MAX_COUNT_BIT_LEN);
|
||||||
|
if (unlikely(length < 0))
|
||||||
|
return length;
|
||||||
|
/* skip bits used by count */
|
||||||
|
bits_used += bit_count;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INSTR_LONG:
|
||||||
|
offset = rb_lz77_decode_count(in, in_len,
|
||||||
|
in_offset_bit + bits_used, 4, 0,
|
||||||
|
&bit_count,
|
||||||
|
MIKRO_LZ77_MAX_COUNT_BIT_LEN);
|
||||||
|
if (unlikely(offset < 0))
|
||||||
|
return offset;
|
||||||
|
|
||||||
|
/* skip bits used by offset count */
|
||||||
|
bits_used += bit_count;
|
||||||
|
|
||||||
|
if (offset == 0) {
|
||||||
|
/* non-matching long group */
|
||||||
|
length = rb_lz77_decode_count(
|
||||||
|
in, in_len, in_offset_bit + bits_used, 4, 12,
|
||||||
|
&bit_count, MIKRO_LZ77_MAX_COUNT_BIT_LEN);
|
||||||
|
if (unlikely(length < 0))
|
||||||
|
return length;
|
||||||
|
/* skip bits used by length count */
|
||||||
|
bits_used += bit_count;
|
||||||
|
} else {
|
||||||
|
/* matching group */
|
||||||
|
length = rb_lz77_decode_count(
|
||||||
|
in, in_len, in_offset_bit + bits_used, 0, 2,
|
||||||
|
&bit_count, MIKRO_LZ77_MAX_COUNT_BIT_LEN);
|
||||||
|
if (unlikely(length < 0))
|
||||||
|
return length;
|
||||||
|
/* skip bits used by length count */
|
||||||
|
bits_used += bit_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INSTR_ERROR:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
opcode->instruction = instruction;
|
||||||
|
opcode->offset = offset;
|
||||||
|
opcode->length = length;
|
||||||
|
opcode->bits_used = bits_used;
|
||||||
|
opcode->in = (u8 *)in;
|
||||||
|
opcode->in_pos = in_offset_bit;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rb_lz77_decompress
|
||||||
|
*
|
||||||
|
* @in: compressed data ptr
|
||||||
|
* @in_len: length of compressed data
|
||||||
|
* @out: buffer ptr to decompress into
|
||||||
|
* @out_len: length of decompressed buffer in input,
|
||||||
|
* length of decompressed data in success
|
||||||
|
*
|
||||||
|
* Returns 0 on success, or negative error
|
||||||
|
*/
|
||||||
|
int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out,
|
||||||
|
size_t *out_len)
|
||||||
|
{
|
||||||
|
u8 *output_ptr;
|
||||||
|
size_t input_bit = 0;
|
||||||
|
const u8 *output_end = out + *out_len;
|
||||||
|
struct rb_lz77_instr_opcodes *opcode;
|
||||||
|
size_t match_offset = 0;
|
||||||
|
int rc = 0;
|
||||||
|
size_t match_length, partial_count, i;
|
||||||
|
|
||||||
|
output_ptr = out;
|
||||||
|
|
||||||
|
if (unlikely((in_len * BITS_PER_BYTE) > SIZE_MAX)) {
|
||||||
|
pr_err(MIKRO_LZ77 "input longer than expected\n");
|
||||||
|
return -EFBIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
opcode = kmalloc(sizeof(*opcode), GFP_KERNEL);
|
||||||
|
if (!opcode)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (unlikely(output_ptr > output_end)) {
|
||||||
|
pr_err(MIKRO_LZ77 "output overrun\n");
|
||||||
|
rc = -EOVERFLOW;
|
||||||
|
goto free_lz77_struct;
|
||||||
|
}
|
||||||
|
if (unlikely(input_bit > in_len * BITS_PER_BYTE)) {
|
||||||
|
pr_err(MIKRO_LZ77 "input overrun\n");
|
||||||
|
rc = -ENODATA;
|
||||||
|
goto free_lz77_struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = rb_lz77_decode_instruction_operators(in, in_len, input_bit,
|
||||||
|
match_offset, opcode);
|
||||||
|
if (unlikely(rc < 0)) {
|
||||||
|
pr_err(MIKRO_LZ77
|
||||||
|
"instruction operands decode error\n");
|
||||||
|
goto free_lz77_struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_debug(MIKRO_LZ77 "inbit:0x%zx->outbyte:0x%zx", input_bit,
|
||||||
|
output_ptr - out);
|
||||||
|
|
||||||
|
input_bit += opcode->bits_used;
|
||||||
|
switch (opcode->instruction) {
|
||||||
|
case INSTR_LITERAL_BYTE:
|
||||||
|
pr_debug(" short");
|
||||||
|
fallthrough;
|
||||||
|
case INSTR_LONG:
|
||||||
|
if (opcode->offset == 0) {
|
||||||
|
/* this is a non-matching group */
|
||||||
|
pr_debug(" non-match, len: 0x%zx\n",
|
||||||
|
opcode->length);
|
||||||
|
/* test end marker */
|
||||||
|
if (opcode->length == 0xc &&
|
||||||
|
((input_bit +
|
||||||
|
opcode->length * BITS_PER_BYTE) >
|
||||||
|
in_len)) {
|
||||||
|
*out_len = output_ptr - out;
|
||||||
|
pr_debug(
|
||||||
|
MIKRO_LZ77
|
||||||
|
"lz77 decompressed from %zu to %zu\n",
|
||||||
|
in_len, *out_len);
|
||||||
|
rc = 0;
|
||||||
|
goto free_lz77_struct;
|
||||||
|
}
|
||||||
|
for (i = opcode->length; i > 0; --i) {
|
||||||
|
*output_ptr =
|
||||||
|
rb_lz77_get_byte(in, input_bit);
|
||||||
|
++output_ptr;
|
||||||
|
input_bit += BITS_PER_BYTE;
|
||||||
|
}
|
||||||
|
/* do no fallthrough if a non-match group */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match_offset = opcode->offset;
|
||||||
|
fallthrough;
|
||||||
|
case INSTR_PREVIOUS_OFFSET:
|
||||||
|
match_length = opcode->length;
|
||||||
|
partial_count = 0;
|
||||||
|
|
||||||
|
pr_debug(" match, offset: 0x%zx, len: 0x%zx",
|
||||||
|
opcode->offset, match_length);
|
||||||
|
|
||||||
|
if (unlikely(opcode->offset == 0)) {
|
||||||
|
pr_err(MIKRO_LZ77
|
||||||
|
"match group missing opcode->offset\n");
|
||||||
|
rc = -EBADMSG;
|
||||||
|
goto free_lz77_struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* overflow */
|
||||||
|
if (unlikely((output_ptr + match_length) >
|
||||||
|
output_end)) {
|
||||||
|
pr_err(MIKRO_LZ77
|
||||||
|
"match group output overflow\n");
|
||||||
|
rc = -ENOBUFS;
|
||||||
|
goto free_lz77_struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* underflow */
|
||||||
|
if (unlikely((output_ptr - opcode->offset) < out)) {
|
||||||
|
pr_err(MIKRO_LZ77
|
||||||
|
"match group offset underflow\n");
|
||||||
|
rc = -ESPIPE;
|
||||||
|
goto free_lz77_struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* there are cases where the match (length) includes
|
||||||
|
* data that is a part of the same match
|
||||||
|
*/
|
||||||
|
while (opcode->offset < match_length) {
|
||||||
|
++partial_count;
|
||||||
|
memcpy(output_ptr, output_ptr - opcode->offset,
|
||||||
|
opcode->offset);
|
||||||
|
output_ptr += opcode->offset;
|
||||||
|
match_length -= opcode->offset;
|
||||||
|
}
|
||||||
|
memcpy(output_ptr, output_ptr - opcode->offset,
|
||||||
|
match_length);
|
||||||
|
output_ptr += match_length;
|
||||||
|
if (partial_count)
|
||||||
|
pr_debug(" (%zu partial memcpy)",
|
||||||
|
partial_count);
|
||||||
|
pr_debug("\n");
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INSTR_ERROR:
|
||||||
|
rc = -EINVAL;
|
||||||
|
goto free_lz77_struct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_err(MIKRO_LZ77 "decode loop broken\n");
|
||||||
|
rc = -EINVAL;
|
||||||
|
|
||||||
|
free_lz77_struct:
|
||||||
|
kfree(opcode);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(rb_lz77_decompress);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_DESCRIPTION("Mikrotik Wi-Fi caldata LZ77 decompressor");
|
||||||
|
MODULE_AUTHOR("John Thomson");
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 John Thomson
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __MIKROTIK_WLAN_LZ77_H__
|
||||||
|
#define __MIKROTIK_WLAN_LZ77_H__
|
||||||
|
|
||||||
|
#include <linux/errno.h>
|
||||||
|
|
||||||
|
#ifdef CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77
|
||||||
|
/**
|
||||||
|
* rb_lz77_decompress
|
||||||
|
*
|
||||||
|
* @in: compressed data ptr
|
||||||
|
* @in_len: length of compressed data
|
||||||
|
* @out: buffer ptr to decompress into
|
||||||
|
* @out_len: length of decompressed buffer in input,
|
||||||
|
* length of decompressed data in success
|
||||||
|
*
|
||||||
|
* Returns 0 on success, or negative error
|
||||||
|
*/
|
||||||
|
int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out,
|
||||||
|
size_t *out_len);
|
||||||
|
|
||||||
|
#else /* CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 */
|
||||||
|
|
||||||
|
static inline int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out,
|
||||||
|
size_t *out_len)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 */
|
||||||
|
#endif /* __MIKROTIK_WLAN_LZ77_H__ */
|
|
@ -0,0 +1,230 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* NVMEM layout driver for MikroTik Routerboard hard config cells
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Robert Marko <robimarko@gmail.com>
|
||||||
|
* Based on the sysfs hard config driver by Thibaut VARÈNE <hacks+kernel@slashdirt.org>
|
||||||
|
* Comments documenting the format carried over from routerboot.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/etherdevice.h>
|
||||||
|
#include <linux/mod_devicetable.h>
|
||||||
|
#include <linux/nvmem-consumer.h>
|
||||||
|
#include <linux/nvmem-provider.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#include "rb_hardconfig.h"
|
||||||
|
#include "routerboot.h"
|
||||||
|
|
||||||
|
#define TLV_TAG_MASK GENMASK(15, 0)
|
||||||
|
#define TLV_LEN_MASK GENMASK(31, 16)
|
||||||
|
|
||||||
|
static const char *rb_tlv_cell_name(u16 tag)
|
||||||
|
{
|
||||||
|
switch (tag) {
|
||||||
|
case RB_ID_FLASH_INFO:
|
||||||
|
return "flash-info";
|
||||||
|
case RB_ID_MAC_ADDRESS_PACK:
|
||||||
|
return "base-mac-address";
|
||||||
|
case RB_ID_BOARD_PRODUCT_CODE:
|
||||||
|
return "board-product-code";
|
||||||
|
case RB_ID_BIOS_VERSION:
|
||||||
|
return "booter-version";
|
||||||
|
case RB_ID_SERIAL_NUMBER:
|
||||||
|
return "board-serial";
|
||||||
|
case RB_ID_MEMORY_SIZE:
|
||||||
|
return "mem-size";
|
||||||
|
case RB_ID_MAC_ADDRESS_COUNT:
|
||||||
|
return "mac-count";
|
||||||
|
case RB_ID_HW_OPTIONS:
|
||||||
|
return "hw-options";
|
||||||
|
case RB_ID_WLAN_DATA:
|
||||||
|
return "wlan-data";
|
||||||
|
case RB_ID_BOARD_IDENTIFIER:
|
||||||
|
return "board-identifier";
|
||||||
|
case RB_ID_PRODUCT_NAME:
|
||||||
|
return "product-name";
|
||||||
|
case RB_ID_DEFCONF:
|
||||||
|
return "defconf";
|
||||||
|
case RB_ID_BOARD_REVISION:
|
||||||
|
return "board-revision";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rb_tlv_mac_read_cb(void *priv, const char *id, int index,
|
||||||
|
unsigned int offset, void *buf,
|
||||||
|
size_t bytes)
|
||||||
|
{
|
||||||
|
if (index < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (!is_valid_ether_addr(buf))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
eth_addr_add(buf, index);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static nvmem_cell_post_process_t rb_tlv_read_cb(u16 tag)
|
||||||
|
{
|
||||||
|
switch (tag) {
|
||||||
|
case RB_ID_MAC_ADDRESS_PACK:
|
||||||
|
return &rb_tlv_mac_read_cb;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rb_add_cells(struct device *dev, struct nvmem_device *nvmem,
|
||||||
|
const size_t data_len, u8 *data)
|
||||||
|
{
|
||||||
|
u32 node, offset = sizeof(RB_MAGIC_HARD);
|
||||||
|
struct nvmem_cell_info cell = {};
|
||||||
|
struct device_node *layout;
|
||||||
|
u16 tlv_tag, tlv_len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
layout = of_nvmem_layout_get_container(nvmem);
|
||||||
|
if (!layout)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Routerboot tag nodes are u32 values:
|
||||||
|
* - The low nibble is the tag identification number,
|
||||||
|
* - The high nibble is the tag payload length (node excluded) in bytes.
|
||||||
|
* Tag nodes are CPU-endian.
|
||||||
|
* Tag nodes are 32bit-aligned.
|
||||||
|
*
|
||||||
|
* The payload immediately follows the tag node.
|
||||||
|
* Payload offset will always be aligned. while length may not end on 32bit
|
||||||
|
* boundary (the only known case is when parsing ERD data).
|
||||||
|
* The payload is CPU-endian when applicable.
|
||||||
|
* Tag nodes are not ordered (by ID) on flash.
|
||||||
|
*/
|
||||||
|
while ((offset + sizeof(node)) <= data_len) {
|
||||||
|
node = *((const u32 *) (data + offset));
|
||||||
|
/* Tag list ends with null node */
|
||||||
|
if (!node)
|
||||||
|
break;
|
||||||
|
|
||||||
|
tlv_tag = FIELD_GET(TLV_TAG_MASK, node);
|
||||||
|
tlv_len = FIELD_GET(TLV_LEN_MASK, node);
|
||||||
|
|
||||||
|
offset += sizeof(node);
|
||||||
|
if (offset + tlv_len > data_len) {
|
||||||
|
dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n",
|
||||||
|
tlv_len, offset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.name = rb_tlv_cell_name(tlv_tag);
|
||||||
|
if (!cell.name)
|
||||||
|
goto skip;
|
||||||
|
|
||||||
|
cell.offset = offset;
|
||||||
|
/*
|
||||||
|
* MikroTik stores MAC-s with length of 8 bytes,
|
||||||
|
* but kernel expects it to be ETH_ALEN (6 bytes),
|
||||||
|
* so we need to make sure that is the case.
|
||||||
|
*/
|
||||||
|
if (tlv_tag == RB_ID_MAC_ADDRESS_PACK)
|
||||||
|
cell.bytes = ETH_ALEN;
|
||||||
|
else
|
||||||
|
cell.bytes = tlv_len;
|
||||||
|
cell.np = of_get_child_by_name(layout, cell.name);
|
||||||
|
cell.read_post_process = rb_tlv_read_cb(tlv_tag);
|
||||||
|
|
||||||
|
ret = nvmem_add_one_cell(nvmem, &cell);
|
||||||
|
if (ret) {
|
||||||
|
of_node_put(layout);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The only known situation where len may not end on 32bit
|
||||||
|
* boundary is within ERD data. Since we're only extracting
|
||||||
|
* one tag (the first and only one) from that data, we should
|
||||||
|
* never need to forcefully ALIGN(). Do it anyway, this is not a
|
||||||
|
* performance path.
|
||||||
|
*/
|
||||||
|
skip:
|
||||||
|
offset += ALIGN(tlv_len, sizeof(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
of_node_put(layout);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rb_parse_table(struct nvmem_layout *layout)
|
||||||
|
{
|
||||||
|
struct nvmem_device *nvmem = layout->nvmem;
|
||||||
|
struct device *dev = &layout->dev;
|
||||||
|
size_t mtd_size;
|
||||||
|
u8 *data;
|
||||||
|
u32 hdr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (hdr != RB_MAGIC_HARD) {
|
||||||
|
dev_err(dev, "Invalid header\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtd_size = nvmem_dev_size(nvmem);
|
||||||
|
|
||||||
|
data = devm_kmalloc(dev, mtd_size, GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = nvmem_device_read(nvmem, 0, mtd_size, data);
|
||||||
|
if (ret != mtd_size)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return rb_add_cells(dev, nvmem, mtd_size, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rb_nvmem_probe(struct nvmem_layout *layout)
|
||||||
|
{
|
||||||
|
layout->add_cells = rb_parse_table;
|
||||||
|
|
||||||
|
return nvmem_layout_register(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rb_nvmem_remove(struct nvmem_layout *layout)
|
||||||
|
{
|
||||||
|
nvmem_layout_unregister(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id rb_nvmem_of_match_table[] = {
|
||||||
|
{ .compatible = "mikrotik,routerboot-nvmem", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, rb_nvmem_of_match_table);
|
||||||
|
|
||||||
|
static struct nvmem_layout_driver rb_nvmem_layout = {
|
||||||
|
.probe = rb_nvmem_probe,
|
||||||
|
.remove = rb_nvmem_remove,
|
||||||
|
.driver = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "rb_nvmem",
|
||||||
|
.of_match_table = rb_nvmem_of_match_table,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
module_nvmem_layout_driver(rb_nvmem_layout);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Robert Marko <robimarko@gmail.com>");
|
||||||
|
MODULE_DESCRIPTION("NVMEM layout driver for MikroTik Routerboard hard config cells");
|
|
@ -0,0 +1,795 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Driver for MikroTik RouterBoot soft config.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This driver exposes the data encoded in the "soft_config" flash segment of
|
||||||
|
* MikroTik RouterBOARDs devices. It presents the data in a sysfs folder
|
||||||
|
* named "soft_config". The data is presented in a user/machine-friendly way
|
||||||
|
* with just as much parsing as can be generalized across mikrotik platforms
|
||||||
|
* (as inferred from reverse-engineering).
|
||||||
|
*
|
||||||
|
* The known soft_config tags are presented in the "soft_config" sysfs folder,
|
||||||
|
* with the addition of one specific file named "commit", which is only
|
||||||
|
* available if the driver supports writes to the mtd device: no modifications
|
||||||
|
* made to any of the other attributes are actually written back to flash media
|
||||||
|
* until a true value is input into this file (e.g. [Yy1]). This is to avoid
|
||||||
|
* unnecessary flash wear, and to permit to revert all changes by issuing a
|
||||||
|
* false value ([Nn0]). Reading the content of this file shows the current
|
||||||
|
* status of the driver: if the data in sysfs matches the content of the
|
||||||
|
* soft_config partition, the file will read "clean". Otherwise, it will read
|
||||||
|
* "dirty".
|
||||||
|
*
|
||||||
|
* The writeable sysfs files presented by this driver will accept only inputs
|
||||||
|
* which are in a valid range for the given tag. As a design choice, the driver
|
||||||
|
* will not assess whether the inputs are identical to the existing data.
|
||||||
|
*
|
||||||
|
* Note: PAGE_SIZE is assumed to be >= 4K, hence the device attribute show
|
||||||
|
* routines need not check for output overflow.
|
||||||
|
*
|
||||||
|
* Some constant defines extracted from rbcfg.h by Gabor Juhos
|
||||||
|
* <juhosg@openwrt.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/errno.h>
|
||||||
|
#include <linux/kobject.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/sysfs.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
#include <linux/capability.h>
|
||||||
|
#include <linux/spinlock.h>
|
||||||
|
#include <linux/crc32.h>
|
||||||
|
|
||||||
|
#ifdef CONFIG_ATH79
|
||||||
|
#include <asm/mach-ath79/ath79.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "routerboot.h"
|
||||||
|
|
||||||
|
#define RB_SOFTCONFIG_VER "0.05"
|
||||||
|
#define RB_SC_PR_PFX "[rb_softconfig] "
|
||||||
|
|
||||||
|
#define RB_SC_HAS_WRITE_SUPPORT true
|
||||||
|
#define RB_SC_WMODE S_IWUSR
|
||||||
|
#define RB_SC_RMODE S_IRUSR
|
||||||
|
|
||||||
|
/* ID values for software settings */
|
||||||
|
#define RB_SCID_UART_SPEED 0x01 // u32*1
|
||||||
|
#define RB_SCID_BOOT_DELAY 0x02 // u32*1
|
||||||
|
#define RB_SCID_BOOT_DEVICE 0x03 // u32*1
|
||||||
|
#define RB_SCID_BOOT_KEY 0x04 // u32*1
|
||||||
|
#define RB_SCID_CPU_MODE 0x05 // u32*1
|
||||||
|
#define RB_SCID_BIOS_VERSION 0x06 // str
|
||||||
|
#define RB_SCID_BOOT_PROTOCOL 0x09 // u32*1
|
||||||
|
#define RB_SCID_CPU_FREQ_IDX 0x0C // u32*1
|
||||||
|
#define RB_SCID_BOOTER 0x0D // u32*1
|
||||||
|
#define RB_SCID_SILENT_BOOT 0x0F // u32*1
|
||||||
|
/*
|
||||||
|
* protected_routerboot seems to use tag 0x1F. It only works in combination with
|
||||||
|
* RouterOS, resulting in a wiped board otherwise, so it's not implemented here.
|
||||||
|
* The tag values are as follows:
|
||||||
|
* - off: 0x0
|
||||||
|
* - on: the lower halfword encodes the max value in s for the reset feature,
|
||||||
|
* the higher halfword encodes the min value in s for the reset feature.
|
||||||
|
* Default value when on: 0x00140258: 0x14 = 20s / 0x258= 600s
|
||||||
|
* See details here: https://wiki.mikrotik.com/wiki/Manual:RouterBOARD_settings#Protected_bootloader
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Tag values */
|
||||||
|
|
||||||
|
#define RB_UART_SPEED_115200 0
|
||||||
|
#define RB_UART_SPEED_57600 1
|
||||||
|
#define RB_UART_SPEED_38400 2
|
||||||
|
#define RB_UART_SPEED_19200 3
|
||||||
|
#define RB_UART_SPEED_9600 4
|
||||||
|
#define RB_UART_SPEED_4800 5
|
||||||
|
#define RB_UART_SPEED_2400 6
|
||||||
|
#define RB_UART_SPEED_1200 7
|
||||||
|
#define RB_UART_SPEED_OFF 8
|
||||||
|
|
||||||
|
/* valid boot delay: 1 - 9s in 1s increment */
|
||||||
|
#define RB_BOOT_DELAY_MIN 1
|
||||||
|
#define RB_BOOT_DELAY_MAX 9
|
||||||
|
|
||||||
|
#define RB_BOOT_DEVICE_ETHER 0 // "boot over Ethernet"
|
||||||
|
#define RB_BOOT_DEVICE_NANDETH 1 // "boot from NAND, if fail then Ethernet"
|
||||||
|
#define RB_BOOT_DEVICE_CFCARD 2 // (not available in rbcfg)
|
||||||
|
#define RB_BOOT_DEVICE_ETHONCE 3 // "boot Ethernet once, then NAND"
|
||||||
|
#define RB_BOOT_DEVICE_NANDONLY 5 // "boot from NAND only"
|
||||||
|
#define RB_BOOT_DEVICE_FLASHCFG 7 // "boot in flash configuration mode"
|
||||||
|
#define RB_BOOT_DEVICE_FLSHONCE 8 // "boot in flash configuration mode once, then NAND"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ATH79 9xxx CPU frequency indices.
|
||||||
|
* It is unknown if they apply to all ATH79 RBs, and some do not seem to feature
|
||||||
|
* the upper levels (QCA955x), while F is presumably AR9344-only.
|
||||||
|
*/
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_A (0 << 3)
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_B (1 << 3) // 0x8
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_C (2 << 3) // 0x10 - factory freq for many devices
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_D (3 << 3) // 0x18
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_E (4 << 3) // 0x20
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_F (5 << 3) // 0x28
|
||||||
|
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_MIN 0 // all devices support lowest setting
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_AR9334_MAX 5 // stops at F
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA953X_MAX 4 // stops at E
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA9556_MAX 2 // stops at C
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA9558_MAX 3 // stops at D
|
||||||
|
|
||||||
|
/* ATH79 7xxx CPU frequency indices. */
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_A ((0 * 9) << 4)
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_B ((1 * 9) << 4)
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_C ((2 * 9) << 4)
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_D ((3 * 9) << 4)
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_E ((4 * 9) << 4)
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_F ((5 * 9) << 4)
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_G ((6 * 9) << 4)
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_H ((7 * 9) << 4)
|
||||||
|
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_MIN 0 // all devices support lowest setting
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_AR724X_MAX 3 // stops at D
|
||||||
|
#define RB_CPU_FREQ_IDX_ATH79_7X_AR7161_MAX 7 // stops at H - check if applies to all AR71xx devices
|
||||||
|
|
||||||
|
#define RB_SC_CRC32_OFFSET 4 // located right after magic
|
||||||
|
|
||||||
|
static struct kobject *sc_kobj;
|
||||||
|
static u8 *sc_buf;
|
||||||
|
static size_t sc_buflen;
|
||||||
|
static rwlock_t sc_bufrwl; // rw lock to sc_buf
|
||||||
|
|
||||||
|
/* MUST be used with lock held */
|
||||||
|
#define RB_SC_CLRCRC() *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET) = 0
|
||||||
|
#define RB_SC_GETCRC() *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET)
|
||||||
|
#define RB_SC_SETCRC(_crc) *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET) = (_crc)
|
||||||
|
|
||||||
|
struct sc_u32tvs {
|
||||||
|
const u32 val;
|
||||||
|
const char *str;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define RB_SC_TVS(_val, _str) { \
|
||||||
|
.val = (_val), \
|
||||||
|
.str = (_str), \
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t sc_tag_show_u32tvs(const u8 *pld, u16 pld_len, char *buf,
|
||||||
|
const struct sc_u32tvs tvs[], const int tvselmts)
|
||||||
|
{
|
||||||
|
const char *fmt;
|
||||||
|
char *out = buf;
|
||||||
|
u32 data; // cpu-endian
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// fallback to raw hex output if we can't handle the input
|
||||||
|
if (tvselmts < 0)
|
||||||
|
return routerboot_tag_show_u32s(pld, pld_len, buf);
|
||||||
|
|
||||||
|
if (sizeof(data) != pld_len)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
read_lock(&sc_bufrwl);
|
||||||
|
data = *(u32 *)pld; // pld aliases sc_buf
|
||||||
|
read_unlock(&sc_bufrwl);
|
||||||
|
|
||||||
|
for (i = 0; i < tvselmts; i++) {
|
||||||
|
fmt = (tvs[i].val == data) ? "[%s] " : "%s ";
|
||||||
|
out += sprintf(out, fmt, tvs[i].str);
|
||||||
|
}
|
||||||
|
|
||||||
|
out += sprintf(out, "\n");
|
||||||
|
return out - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t sc_tag_store_u32tvs(const u8 *pld, u16 pld_len, const char *buf, size_t count,
|
||||||
|
const struct sc_u32tvs tvs[], const int tvselmts)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (tvselmts < 0)
|
||||||
|
return tvselmts;
|
||||||
|
|
||||||
|
if (sizeof(u32) != pld_len)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
for (i = 0; i < tvselmts; i++) {
|
||||||
|
if (sysfs_streq(buf, tvs[i].str)) {
|
||||||
|
write_lock(&sc_bufrwl);
|
||||||
|
*(u32 *)pld = tvs[i].val; // pld aliases sc_buf
|
||||||
|
RB_SC_CLRCRC();
|
||||||
|
write_unlock(&sc_bufrwl);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_boolts {
|
||||||
|
const char *strfalse;
|
||||||
|
const char *strtrue;
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t sc_tag_show_boolts(const u8 *pld, u16 pld_len, char *buf,
|
||||||
|
const struct sc_boolts *bts)
|
||||||
|
{
|
||||||
|
const char *fmt;
|
||||||
|
char *out = buf;
|
||||||
|
u32 data; // cpu-endian
|
||||||
|
|
||||||
|
if (sizeof(data) != pld_len)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
read_lock(&sc_bufrwl);
|
||||||
|
data = *(u32 *)pld; // pld aliases sc_buf
|
||||||
|
read_unlock(&sc_bufrwl);
|
||||||
|
|
||||||
|
fmt = (data) ? "%s [%s]\n" : "[%s] %s\n";
|
||||||
|
out += sprintf(out, fmt, bts->strfalse, bts->strtrue);
|
||||||
|
|
||||||
|
return out - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t sc_tag_store_boolts(const u8 *pld, u16 pld_len, const char *buf, size_t count,
|
||||||
|
const struct sc_boolts *bts)
|
||||||
|
{
|
||||||
|
u32 data; // cpu-endian
|
||||||
|
|
||||||
|
if (sizeof(data) != pld_len)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (sysfs_streq(buf, bts->strfalse))
|
||||||
|
data = 0;
|
||||||
|
else if (sysfs_streq(buf, bts->strtrue))
|
||||||
|
data = 1;
|
||||||
|
else
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
write_lock(&sc_bufrwl);
|
||||||
|
*(u32 *)pld = data; // pld aliases sc_buf
|
||||||
|
RB_SC_CLRCRC();
|
||||||
|
write_unlock(&sc_bufrwl);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
static struct sc_u32tvs const sc_uartspeeds[] = {
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_OFF, "off"),
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_1200, "1200"),
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_2400, "2400"),
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_4800, "4800"),
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_9600, "9600"),
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_19200, "19200"),
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_38400, "38400"),
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_57600, "57600"),
|
||||||
|
RB_SC_TVS(RB_UART_SPEED_115200, "115200"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* While the defines are carried over from rbcfg, use strings that more clearly
|
||||||
|
* show the actual setting purpose (especially since the NAND* settings apply
|
||||||
|
* to both nand- and nor-based devices). "cfcard" was disabled in rbcfg: disable
|
||||||
|
* it here too.
|
||||||
|
*/
|
||||||
|
static struct sc_u32tvs const sc_bootdevices[] = {
|
||||||
|
RB_SC_TVS(RB_BOOT_DEVICE_ETHER, "eth"),
|
||||||
|
RB_SC_TVS(RB_BOOT_DEVICE_NANDETH, "flasheth"),
|
||||||
|
//RB_SC_TVS(RB_BOOT_DEVICE_CFCARD, "cfcard"),
|
||||||
|
RB_SC_TVS(RB_BOOT_DEVICE_ETHONCE, "ethonce"),
|
||||||
|
RB_SC_TVS(RB_BOOT_DEVICE_NANDONLY, "flash"),
|
||||||
|
RB_SC_TVS(RB_BOOT_DEVICE_FLASHCFG, "cfg"),
|
||||||
|
RB_SC_TVS(RB_BOOT_DEVICE_FLSHONCE, "cfgonce"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sc_boolts const sc_bootkey = {
|
||||||
|
.strfalse = "any",
|
||||||
|
.strtrue = "del",
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sc_boolts const sc_cpumode = {
|
||||||
|
.strfalse = "powersave",
|
||||||
|
.strtrue = "regular",
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sc_boolts const sc_bootproto = {
|
||||||
|
.strfalse = "bootp",
|
||||||
|
.strtrue = "dhcp",
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sc_boolts const sc_booter = {
|
||||||
|
.strfalse = "regular",
|
||||||
|
.strtrue = "backup",
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sc_boolts const sc_silent_boot = {
|
||||||
|
.strfalse = "off",
|
||||||
|
.strtrue = "on",
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SC_TAG_SHOW_STORE_U32TVS_FUNCS(_name) \
|
||||||
|
static ssize_t sc_tag_show_##_name(const u8 *pld, u16 pld_len, char *buf) \
|
||||||
|
{ \
|
||||||
|
return sc_tag_show_u32tvs(pld, pld_len, buf, sc_##_name, ARRAY_SIZE(sc_##_name)); \
|
||||||
|
} \
|
||||||
|
static ssize_t sc_tag_store_##_name(const u8 *pld, u16 pld_len, const char *buf, size_t count) \
|
||||||
|
{ \
|
||||||
|
return sc_tag_store_u32tvs(pld, pld_len, buf, count, sc_##_name, ARRAY_SIZE(sc_##_name)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SC_TAG_SHOW_STORE_BOOLTS_FUNCS(_name) \
|
||||||
|
static ssize_t sc_tag_show_##_name(const u8 *pld, u16 pld_len, char *buf) \
|
||||||
|
{ \
|
||||||
|
return sc_tag_show_boolts(pld, pld_len, buf, &sc_##_name); \
|
||||||
|
} \
|
||||||
|
static ssize_t sc_tag_store_##_name(const u8 *pld, u16 pld_len, const char *buf, size_t count) \
|
||||||
|
{ \
|
||||||
|
return sc_tag_store_boolts(pld, pld_len, buf, count, &sc_##_name); \
|
||||||
|
}
|
||||||
|
|
||||||
|
SC_TAG_SHOW_STORE_U32TVS_FUNCS(uartspeeds)
|
||||||
|
SC_TAG_SHOW_STORE_U32TVS_FUNCS(bootdevices)
|
||||||
|
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(bootkey)
|
||||||
|
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(cpumode)
|
||||||
|
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(bootproto)
|
||||||
|
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(booter)
|
||||||
|
SC_TAG_SHOW_STORE_BOOLTS_FUNCS(silent_boot)
|
||||||
|
|
||||||
|
static ssize_t sc_tag_show_bootdelays(const u8 *pld, u16 pld_len, char *buf)
|
||||||
|
{
|
||||||
|
const char *fmt;
|
||||||
|
char *out = buf;
|
||||||
|
u32 data; // cpu-endian
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (sizeof(data) != pld_len)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
read_lock(&sc_bufrwl);
|
||||||
|
data = *(u32 *)pld; // pld aliases sc_buf
|
||||||
|
read_unlock(&sc_bufrwl);
|
||||||
|
|
||||||
|
for (i = RB_BOOT_DELAY_MIN; i <= RB_BOOT_DELAY_MAX; i++) {
|
||||||
|
fmt = (i == data) ? "[%d] " : "%d ";
|
||||||
|
out += sprintf(out, fmt, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
out += sprintf(out, "\n");
|
||||||
|
return out - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t sc_tag_store_bootdelays(const u8 *pld, u16 pld_len, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
u32 data; // cpu-endian
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (sizeof(data) != pld_len)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = kstrtou32(buf, 10, &data);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if ((data < RB_BOOT_DELAY_MIN) || (RB_BOOT_DELAY_MAX < data))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
write_lock(&sc_bufrwl);
|
||||||
|
*(u32 *)pld = data; // pld aliases sc_buf
|
||||||
|
RB_SC_CLRCRC();
|
||||||
|
write_unlock(&sc_bufrwl);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Support CPU frequency accessors only when the tag format has been asserted */
|
||||||
|
#if defined(CONFIG_ATH79)
|
||||||
|
/* Use the same letter-based nomenclature as RouterBOOT */
|
||||||
|
static struct sc_u32tvs const sc_cpufreq_indexes_ath79_9x[] = {
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_A, "a"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_B, "b"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_C, "c"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_D, "d"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_E, "e"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_F, "f"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sc_u32tvs const sc_cpufreq_indexes_ath79_7x[] = {
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_A, "a"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_B, "b"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_C, "c"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_D, "d"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_E, "e"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_F, "f"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_G, "g"),
|
||||||
|
RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_H, "h"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static int sc_tag_cpufreq_ath79_arraysize(void)
|
||||||
|
{
|
||||||
|
int idx_max;
|
||||||
|
|
||||||
|
if (ATH79_SOC_AR7161 == ath79_soc)
|
||||||
|
idx_max = RB_CPU_FREQ_IDX_ATH79_7X_AR7161_MAX+1;
|
||||||
|
else if (soc_is_ar724x())
|
||||||
|
idx_max = RB_CPU_FREQ_IDX_ATH79_7X_AR724X_MAX+1;
|
||||||
|
else if (soc_is_ar9344())
|
||||||
|
idx_max = RB_CPU_FREQ_IDX_ATH79_9X_AR9334_MAX+1;
|
||||||
|
else if (soc_is_qca953x())
|
||||||
|
idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA953X_MAX+1;
|
||||||
|
else if (soc_is_qca9556())
|
||||||
|
idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA9556_MAX+1;
|
||||||
|
else if (soc_is_qca9558())
|
||||||
|
idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA9558_MAX+1;
|
||||||
|
else
|
||||||
|
idx_max = -EOPNOTSUPP;
|
||||||
|
|
||||||
|
return idx_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t sc_tag_show_cpufreq_indexes(const u8 *pld, u16 pld_len, char *buf)
|
||||||
|
{
|
||||||
|
const struct sc_u32tvs *tvs;
|
||||||
|
|
||||||
|
if (soc_is_ar71xx() || soc_is_ar724x())
|
||||||
|
tvs = sc_cpufreq_indexes_ath79_7x;
|
||||||
|
else
|
||||||
|
tvs = sc_cpufreq_indexes_ath79_9x;
|
||||||
|
|
||||||
|
return sc_tag_show_u32tvs(pld, pld_len, buf, tvs, sc_tag_cpufreq_ath79_arraysize());
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t sc_tag_store_cpufreq_indexes(const u8 *pld, u16 pld_len, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
const struct sc_u32tvs *tvs;
|
||||||
|
|
||||||
|
if (soc_is_ar71xx() || soc_is_ar724x())
|
||||||
|
tvs = sc_cpufreq_indexes_ath79_7x;
|
||||||
|
else
|
||||||
|
tvs = sc_cpufreq_indexes_ath79_9x;
|
||||||
|
|
||||||
|
return sc_tag_store_u32tvs(pld, pld_len, buf, count, tvs, sc_tag_cpufreq_ath79_arraysize());
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/* By default we only show the raw value to help with reverse-engineering */
|
||||||
|
#define sc_tag_show_cpufreq_indexes routerboot_tag_show_u32s
|
||||||
|
#define sc_tag_store_cpufreq_indexes NULL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static ssize_t sc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||||
|
char *buf);
|
||||||
|
static ssize_t sc_attr_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||||
|
const char *buf, size_t count);
|
||||||
|
|
||||||
|
/* Array of known tags to publish in sysfs */
|
||||||
|
static struct sc_attr {
|
||||||
|
const u16 tag_id;
|
||||||
|
/* sysfs tag show attribute. Must lock sc_buf when dereferencing pld */
|
||||||
|
ssize_t (* const tshow)(const u8 *pld, u16 pld_len, char *buf);
|
||||||
|
/* sysfs tag store attribute. Must lock sc_buf when dereferencing pld */
|
||||||
|
ssize_t (* const tstore)(const u8 *pld, u16 pld_len, const char *buf, size_t count);
|
||||||
|
struct kobj_attribute kattr;
|
||||||
|
u16 pld_ofs;
|
||||||
|
u16 pld_len;
|
||||||
|
} sc_attrs[] = {
|
||||||
|
{
|
||||||
|
.tag_id = RB_SCID_UART_SPEED,
|
||||||
|
.tshow = sc_tag_show_uartspeeds,
|
||||||
|
.tstore = sc_tag_store_uartspeeds,
|
||||||
|
.kattr = __ATTR(uart_speed, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_BOOT_DELAY,
|
||||||
|
.tshow = sc_tag_show_bootdelays,
|
||||||
|
.tstore = sc_tag_store_bootdelays,
|
||||||
|
.kattr = __ATTR(boot_delay, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_BOOT_DEVICE,
|
||||||
|
.tshow = sc_tag_show_bootdevices,
|
||||||
|
.tstore = sc_tag_store_bootdevices,
|
||||||
|
.kattr = __ATTR(boot_device, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_BOOT_KEY,
|
||||||
|
.tshow = sc_tag_show_bootkey,
|
||||||
|
.tstore = sc_tag_store_bootkey,
|
||||||
|
.kattr = __ATTR(boot_key, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_CPU_MODE,
|
||||||
|
.tshow = sc_tag_show_cpumode,
|
||||||
|
.tstore = sc_tag_store_cpumode,
|
||||||
|
.kattr = __ATTR(cpu_mode, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_BIOS_VERSION,
|
||||||
|
.tshow = routerboot_tag_show_string,
|
||||||
|
.tstore = NULL,
|
||||||
|
.kattr = __ATTR(bios_version, RB_SC_RMODE, sc_attr_show, NULL),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_BOOT_PROTOCOL,
|
||||||
|
.tshow = sc_tag_show_bootproto,
|
||||||
|
.tstore = sc_tag_store_bootproto,
|
||||||
|
.kattr = __ATTR(boot_proto, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_CPU_FREQ_IDX,
|
||||||
|
.tshow = sc_tag_show_cpufreq_indexes,
|
||||||
|
.tstore = sc_tag_store_cpufreq_indexes,
|
||||||
|
.kattr = __ATTR(cpufreq_index, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_BOOTER,
|
||||||
|
.tshow = sc_tag_show_booter,
|
||||||
|
.tstore = sc_tag_store_booter,
|
||||||
|
.kattr = __ATTR(booter, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
}, {
|
||||||
|
.tag_id = RB_SCID_SILENT_BOOT,
|
||||||
|
.tshow = sc_tag_show_silent_boot,
|
||||||
|
.tstore = sc_tag_store_silent_boot,
|
||||||
|
.kattr = __ATTR(silent_boot, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t sc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
const struct sc_attr *sc_attr;
|
||||||
|
const u8 *pld;
|
||||||
|
u16 pld_len;
|
||||||
|
|
||||||
|
sc_attr = container_of(attr, typeof(*sc_attr), kattr);
|
||||||
|
|
||||||
|
if (!sc_attr->pld_len)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
pld = sc_buf + sc_attr->pld_ofs; // pld aliases sc_buf -> lock!
|
||||||
|
pld_len = sc_attr->pld_len;
|
||||||
|
|
||||||
|
return sc_attr->tshow(pld, pld_len, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t sc_attr_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
const struct sc_attr *sc_attr;
|
||||||
|
const u8 *pld;
|
||||||
|
u16 pld_len;
|
||||||
|
|
||||||
|
if (!RB_SC_HAS_WRITE_SUPPORT)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
if (!capable(CAP_SYS_ADMIN))
|
||||||
|
return -EACCES;
|
||||||
|
|
||||||
|
sc_attr = container_of(attr, typeof(*sc_attr), kattr);
|
||||||
|
|
||||||
|
if (!sc_attr->tstore)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
if (!sc_attr->pld_len)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
pld = sc_buf + sc_attr->pld_ofs; // pld aliases sc_buf -> lock!
|
||||||
|
pld_len = sc_attr->pld_len;
|
||||||
|
|
||||||
|
return sc_attr->tstore(pld, pld_len, buf, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shows the current buffer status:
|
||||||
|
* "clean": the buffer is in sync with the mtd data
|
||||||
|
* "dirty": the buffer is out of sync with the mtd data
|
||||||
|
*/
|
||||||
|
static ssize_t sc_commit_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
const char *str;
|
||||||
|
char *out = buf;
|
||||||
|
u32 crc;
|
||||||
|
|
||||||
|
read_lock(&sc_bufrwl);
|
||||||
|
crc = RB_SC_GETCRC();
|
||||||
|
read_unlock(&sc_bufrwl);
|
||||||
|
|
||||||
|
str = (crc) ? "clean" : "dirty";
|
||||||
|
out += sprintf(out, "%s\n", str);
|
||||||
|
|
||||||
|
return out - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Performs buffer flushing:
|
||||||
|
* This routine expects an input compatible with kstrtobool().
|
||||||
|
* - a "false" input discards the current changes and reads data back from mtd.
|
||||||
|
* - a "true" input commits the current changes to mtd.
|
||||||
|
* If there is no pending changes, this routine is a no-op.
|
||||||
|
* Handling failures is left as an exercise to userspace.
|
||||||
|
*/
|
||||||
|
static ssize_t sc_commit_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd;
|
||||||
|
struct erase_info ei;
|
||||||
|
size_t bytes_rw, ret = count;
|
||||||
|
bool flush;
|
||||||
|
u32 crc;
|
||||||
|
|
||||||
|
if (!RB_SC_HAS_WRITE_SUPPORT)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
read_lock(&sc_bufrwl);
|
||||||
|
crc = RB_SC_GETCRC();
|
||||||
|
read_unlock(&sc_bufrwl);
|
||||||
|
|
||||||
|
if (crc)
|
||||||
|
return count; // NO-OP
|
||||||
|
|
||||||
|
ret = kstrtobool(buf, &flush);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
mtd = get_mtd_device_nm(RB_MTD_SOFT_CONFIG); // TODO allow override
|
||||||
|
if (IS_ERR(mtd))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
write_lock(&sc_bufrwl);
|
||||||
|
if (!flush) // reread
|
||||||
|
ret = mtd_read(mtd, 0, mtd->size, &bytes_rw, sc_buf);
|
||||||
|
else { // crc32 + commit
|
||||||
|
/*
|
||||||
|
* CRC32 is computed on the entire buffer, excluding the CRC
|
||||||
|
* value itself. CRC is already null when we reach this point,
|
||||||
|
* so we can compute the CRC32 on the buffer as is.
|
||||||
|
* The expected CRC32 is Ethernet FCS style, meaning the seed is
|
||||||
|
* ~0 and the final result is also bitflipped.
|
||||||
|
*/
|
||||||
|
|
||||||
|
crc = ~crc32(~0, sc_buf, sc_buflen);
|
||||||
|
RB_SC_SETCRC(crc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The soft_config partition is assumed to be entirely contained
|
||||||
|
* in a single eraseblock.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ei.addr = 0;
|
||||||
|
ei.len = mtd->size;
|
||||||
|
ret = mtd_erase(mtd, &ei);
|
||||||
|
if (!ret)
|
||||||
|
ret = mtd_write(mtd, 0, mtd->size, &bytes_rw, sc_buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handling mtd_write() failure here is a tricky situation. The
|
||||||
|
* proposed approach is to let userspace deal with retrying,
|
||||||
|
* with the caveat that it must try to flush the buffer again as
|
||||||
|
* rereading the mtd contents could potentially read garbage.
|
||||||
|
* The rationale is: even if we keep a shadow buffer of the
|
||||||
|
* original content, there is no guarantee that we will ever be
|
||||||
|
* able to write it anyway.
|
||||||
|
* Regardless, it appears that RouterBOOT will ignore an invalid
|
||||||
|
* soft_config (including a completely wiped segment) and will
|
||||||
|
* write back factory defaults when it happens.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
write_unlock(&sc_bufrwl);
|
||||||
|
|
||||||
|
put_mtd_device(mtd);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
goto mtdfail;
|
||||||
|
|
||||||
|
if (bytes_rw != sc_buflen) {
|
||||||
|
ret = -EIO;
|
||||||
|
goto mtdfail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
|
||||||
|
mtdfail:
|
||||||
|
RB_SC_CLRCRC(); // mark buffer content as dirty/invalid
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct kobj_attribute sc_kattrcommit = __ATTR(commit, RB_SC_RMODE|RB_SC_WMODE, sc_commit_show, sc_commit_store);
|
||||||
|
|
||||||
|
int rb_softconfig_init(struct kobject *rb_kobj, struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
size_t bytes_read, buflen;
|
||||||
|
const u8 *buf;
|
||||||
|
int i, ret;
|
||||||
|
u32 magic;
|
||||||
|
|
||||||
|
sc_buf = NULL;
|
||||||
|
sc_kobj = NULL;
|
||||||
|
|
||||||
|
ret = __get_mtd_device(mtd);
|
||||||
|
if (ret)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
sc_buflen = mtd->size;
|
||||||
|
sc_buf = kmalloc(sc_buflen, GFP_KERNEL);
|
||||||
|
if (!sc_buf) {
|
||||||
|
__put_mtd_device(mtd);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = mtd_read(mtd, 0, sc_buflen, &bytes_read, sc_buf);
|
||||||
|
__put_mtd_device(mtd);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (bytes_read != sc_buflen) {
|
||||||
|
ret = -EIO;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check we have what we expect */
|
||||||
|
magic = *(const u32 *)sc_buf;
|
||||||
|
if (RB_MAGIC_SOFT != magic) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip magic and 32bit CRC located immediately after */
|
||||||
|
buf = sc_buf + (sizeof(magic) + sizeof(u32));
|
||||||
|
buflen = sc_buflen - (sizeof(magic) + sizeof(u32));
|
||||||
|
|
||||||
|
/* Populate sysfs */
|
||||||
|
ret = -ENOMEM;
|
||||||
|
sc_kobj = kobject_create_and_add(RB_MTD_SOFT_CONFIG, rb_kobj);
|
||||||
|
if (!sc_kobj)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
rwlock_init(&sc_bufrwl);
|
||||||
|
|
||||||
|
/* Locate and publish all known tags */
|
||||||
|
for (i = 0; i < ARRAY_SIZE(sc_attrs); i++) {
|
||||||
|
ret = routerboot_tag_find(buf, buflen, sc_attrs[i].tag_id,
|
||||||
|
&sc_attrs[i].pld_ofs, &sc_attrs[i].pld_len);
|
||||||
|
if (ret) {
|
||||||
|
sc_attrs[i].pld_ofs = sc_attrs[i].pld_len = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Account for skipped magic and crc32 */
|
||||||
|
sc_attrs[i].pld_ofs += sizeof(magic) + sizeof(u32);
|
||||||
|
|
||||||
|
ret = sysfs_create_file(sc_kobj, &sc_attrs[i].kattr.attr);
|
||||||
|
if (ret)
|
||||||
|
pr_warn(RB_SC_PR_PFX "Could not create %s sysfs entry (%d)\n",
|
||||||
|
sc_attrs[i].kattr.attr.name, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finally add the 'commit' attribute */
|
||||||
|
if (RB_SC_HAS_WRITE_SUPPORT) {
|
||||||
|
ret = sysfs_create_file(sc_kobj, &sc_kattrcommit.attr);
|
||||||
|
if (ret) {
|
||||||
|
pr_err(RB_SC_PR_PFX "Could not create %s sysfs entry (%d), aborting!\n",
|
||||||
|
sc_kattrcommit.attr.name, ret);
|
||||||
|
goto sysfsfail; // required attribute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_info("MikroTik RouterBOARD software configuration sysfs driver v" RB_SOFTCONFIG_VER "\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
sysfsfail:
|
||||||
|
kobject_put(sc_kobj);
|
||||||
|
sc_kobj = NULL;
|
||||||
|
fail:
|
||||||
|
kfree(sc_buf);
|
||||||
|
sc_buf = NULL;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rb_softconfig_exit(void)
|
||||||
|
{
|
||||||
|
kobject_put(sc_kobj);
|
||||||
|
sc_kobj = NULL;
|
||||||
|
kfree(sc_buf);
|
||||||
|
sc_buf = NULL;
|
||||||
|
}
|
|
@ -0,0 +1,251 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Driver for MikroTik RouterBoot flash data. Common routines.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/sysfs.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
|
||||||
|
#include "routerboot.h"
|
||||||
|
|
||||||
|
static struct kobject *rb_kobj;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* routerboot_tag_find() - Locate a given tag in routerboot config data.
|
||||||
|
* @bufhead: the buffer to look into. Must start with a tag node.
|
||||||
|
* @buflen: size of bufhead
|
||||||
|
* @tag_id: the tag identifier to look for
|
||||||
|
* @pld_ofs: will be updated with tag payload offset in bufhead, if tag found
|
||||||
|
* @pld_len: will be updated with tag payload size, if tag found
|
||||||
|
*
|
||||||
|
* This incarnation of tag_find() does only that: it finds a specific routerboot
|
||||||
|
* tag node in the input buffer. Routerboot tag nodes are u32 values:
|
||||||
|
* - The low nibble is the tag identification number,
|
||||||
|
* - The high nibble is the tag payload length (node excluded) in bytes.
|
||||||
|
* The payload immediately follows the tag node. Tag nodes are 32bit-aligned.
|
||||||
|
* The returned pld_ofs will always be aligned. pld_len may not end on 32bit
|
||||||
|
* boundary (the only known case is when parsing ERD data).
|
||||||
|
* The nodes are cpu-endian on the flash media. The payload is cpu-endian when
|
||||||
|
* applicable. Tag nodes are not ordered (by ID) on flash.
|
||||||
|
*
|
||||||
|
* Return: 0 on success (tag found) or errno
|
||||||
|
*/
|
||||||
|
int routerboot_tag_find(const u8 *bufhead, const size_t buflen, const u16 tag_id,
|
||||||
|
u16 *pld_ofs, u16 *pld_len)
|
||||||
|
{
|
||||||
|
const u32 *datum, *bufend;
|
||||||
|
u32 node;
|
||||||
|
u16 id, len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!bufhead || !tag_id)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = -ENOENT;
|
||||||
|
datum = (const u32 *)bufhead;
|
||||||
|
bufend = (const u32 *)(bufhead + buflen);
|
||||||
|
|
||||||
|
while (datum < bufend) {
|
||||||
|
node = *datum++;
|
||||||
|
|
||||||
|
/* Tag list ends with null node */
|
||||||
|
if (!node)
|
||||||
|
break;
|
||||||
|
|
||||||
|
id = node & 0xFFFF;
|
||||||
|
len = node >> 16;
|
||||||
|
|
||||||
|
if (tag_id == id) {
|
||||||
|
if (datum >= bufend)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (pld_ofs)
|
||||||
|
*pld_ofs = (u16)((u8 *)datum - bufhead);
|
||||||
|
if (pld_len)
|
||||||
|
*pld_len = len;
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The only known situation where len may not end on 32bit
|
||||||
|
* boundary is within ERD data. Since we're only extracting
|
||||||
|
* one tag (the first and only one) from that data, we should
|
||||||
|
* never need to forcefully ALIGN(). Do it anyway, this is not a
|
||||||
|
* performance path.
|
||||||
|
*/
|
||||||
|
len = ALIGN(len, sizeof(*datum));
|
||||||
|
datum += len / sizeof(*datum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* routerboot_rle_decode() - Simple RLE (MikroTik variant) decoding routine.
|
||||||
|
* @in: input buffer to decode
|
||||||
|
* @inlen: size of in
|
||||||
|
* @out: output buffer to write decoded data to
|
||||||
|
* @outlen: pointer to out size when function is called, will be updated with
|
||||||
|
* size of decoded output on return
|
||||||
|
*
|
||||||
|
* MikroTik's variant of RLE operates as follows, considering a signed run byte:
|
||||||
|
* - positive run => classic RLE
|
||||||
|
* - negative run => the next -<run> bytes must be copied verbatim
|
||||||
|
* The API is matched to the lzo1x routines for convenience.
|
||||||
|
*
|
||||||
|
* NB: The output buffer cannot overlap with the input buffer.
|
||||||
|
*
|
||||||
|
* Return: 0 on success or errno
|
||||||
|
*/
|
||||||
|
int routerboot_rle_decode(const u8 *in, size_t inlen, u8 *out, size_t *outlen)
|
||||||
|
{
|
||||||
|
int ret, run, nbytes; // use native types for speed
|
||||||
|
u8 byte;
|
||||||
|
|
||||||
|
if (!in || (inlen < 2) || !out)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = -ENOSPC;
|
||||||
|
nbytes = 0;
|
||||||
|
while (inlen >= 2) {
|
||||||
|
run = *in++;
|
||||||
|
inlen--;
|
||||||
|
|
||||||
|
/* Verbatim copies */
|
||||||
|
if (run & 0x80) {
|
||||||
|
/* Invert run byte sign */
|
||||||
|
run = ~run & 0xFF;
|
||||||
|
run++;
|
||||||
|
|
||||||
|
if (run > inlen)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
inlen -= run;
|
||||||
|
|
||||||
|
nbytes += run;
|
||||||
|
if (nbytes > *outlen)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
/* Basic memcpy */
|
||||||
|
while (run-- > 0)
|
||||||
|
*out++ = *in++;
|
||||||
|
}
|
||||||
|
/* Stream of half-words RLE: <run><byte>. run == 0 is ignored */
|
||||||
|
else {
|
||||||
|
byte = *in++;
|
||||||
|
inlen--;
|
||||||
|
|
||||||
|
nbytes += run;
|
||||||
|
if (nbytes > *outlen)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
while (run-- > 0)
|
||||||
|
*out++ = byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
fail:
|
||||||
|
*outlen = nbytes;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void routerboot_mtd_notifier_add(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
/* Currently routerboot is only known to live on NOR flash */
|
||||||
|
if (mtd->type != MTD_NORFLASH)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We ignore the following return values and always register.
|
||||||
|
* These init() routines are designed so that their failed state is
|
||||||
|
* always manageable by the corresponding exit() calls.
|
||||||
|
* Notifier is called with MTD mutex held: use __get/__put variants.
|
||||||
|
* TODO: allow partition names override
|
||||||
|
*/
|
||||||
|
if (!strcmp(mtd->name, RB_MTD_HARD_CONFIG))
|
||||||
|
rb_hardconfig_init(rb_kobj, mtd);
|
||||||
|
else if (!strcmp(mtd->name, RB_MTD_SOFT_CONFIG))
|
||||||
|
rb_softconfig_init(rb_kobj, mtd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void routerboot_mtd_notifier_remove(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
if (mtd->type != MTD_NORFLASH)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!strcmp(mtd->name, RB_MTD_HARD_CONFIG))
|
||||||
|
rb_hardconfig_exit();
|
||||||
|
else if (!strcmp(mtd->name, RB_MTD_SOFT_CONFIG))
|
||||||
|
rb_softconfig_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note: using a notifier prevents qualifying init()/exit() functions with __init/__exit */
|
||||||
|
static struct mtd_notifier routerboot_mtd_notifier = {
|
||||||
|
.add = routerboot_mtd_notifier_add,
|
||||||
|
.remove = routerboot_mtd_notifier_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init routerboot_init(void)
|
||||||
|
{
|
||||||
|
rb_kobj = kobject_create_and_add("mikrotik", firmware_kobj);
|
||||||
|
if (!rb_kobj)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
register_mtd_user(&routerboot_mtd_notifier);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit routerboot_exit(void)
|
||||||
|
{
|
||||||
|
unregister_mtd_user(&routerboot_mtd_notifier);
|
||||||
|
/* Exit routines are idempotent */
|
||||||
|
rb_softconfig_exit();
|
||||||
|
rb_hardconfig_exit();
|
||||||
|
kobject_put(rb_kobj); // recursive afaict
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Common routines */
|
||||||
|
|
||||||
|
ssize_t routerboot_tag_show_string(const u8 *pld, u16 pld_len, char *buf)
|
||||||
|
{
|
||||||
|
return scnprintf(buf, pld_len+1, "%s\n", pld);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t routerboot_tag_show_u32s(const u8 *pld, u16 pld_len, char *buf)
|
||||||
|
{
|
||||||
|
char *out = buf;
|
||||||
|
u32 *data; // cpu-endian
|
||||||
|
|
||||||
|
/* Caller ensures pld_len > 0 */
|
||||||
|
if (pld_len % sizeof(*data))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
data = (u32 *)pld;
|
||||||
|
|
||||||
|
do {
|
||||||
|
out += sprintf(out, "0x%08x\n", *data);
|
||||||
|
data++;
|
||||||
|
} while ((pld_len -= sizeof(*data)));
|
||||||
|
|
||||||
|
return out - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(routerboot_init);
|
||||||
|
module_exit(routerboot_exit);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
MODULE_DESCRIPTION("MikroTik RouterBoot sysfs support");
|
||||||
|
MODULE_AUTHOR("Thibaut VARENE");
|
|
@ -0,0 +1,38 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Common definitions for MikroTik RouterBoot data.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _ROUTERBOOT_H_
|
||||||
|
#define _ROUTERBOOT_H_
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
// these magic values are stored in cpu-endianness on flash
|
||||||
|
#define RB_MAGIC_HARD (('H') | ('a' << 8) | ('r' << 16) | ('d' << 24))
|
||||||
|
#define RB_MAGIC_SOFT (('S') | ('o' << 8) | ('f' << 16) | ('t' << 24))
|
||||||
|
#define RB_MAGIC_LZOR (('L') | ('Z' << 8) | ('O' << 16) | ('R' << 24))
|
||||||
|
#define RB_MAGIC_LZ77 (('L' << 24) | ('Z' << 16) | ('7' << 8) | ('7'))
|
||||||
|
#define RB_MAGIC_ERD (('E' << 16) | ('R' << 8) | ('D'))
|
||||||
|
|
||||||
|
#define RB_ART_SIZE 0x10000
|
||||||
|
|
||||||
|
#define RB_MTD_HARD_CONFIG "hard_config"
|
||||||
|
#define RB_MTD_SOFT_CONFIG "soft_config"
|
||||||
|
|
||||||
|
int routerboot_tag_find(const u8 *bufhead, const size_t buflen, const u16 tag_id, u16 *pld_ofs, u16 *pld_len);
|
||||||
|
int routerboot_rle_decode(const u8 *in, size_t inlen, u8 *out, size_t *outlen);
|
||||||
|
|
||||||
|
int rb_hardconfig_init(struct kobject *rb_kobj, struct mtd_info *mtd);
|
||||||
|
void rb_hardconfig_exit(void);
|
||||||
|
|
||||||
|
int rb_softconfig_init(struct kobject *rb_kobj, struct mtd_info *mtd);
|
||||||
|
void rb_softconfig_exit(void);
|
||||||
|
|
||||||
|
ssize_t routerboot_tag_show_string(const u8 *pld, u16 pld_len, char *buf);
|
||||||
|
ssize_t routerboot_tag_show_u32s(const u8 *pld, u16 pld_len, char *buf);
|
||||||
|
|
||||||
|
#endif /* _ROUTERBOOT_H_ */
|
745
6.12/target/linux/generic/files/drivers/ssb/fallback-sprom.c
Normal file
745
6.12/target/linux/generic/files/drivers/ssb/fallback-sprom.c
Normal file
|
@ -0,0 +1,745 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* SSB Fallback SPROM Driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
|
||||||
|
* Copyright (C) 2014 Jonas Gorski <jonas.gorski@gmail.com>
|
||||||
|
* Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
|
||||||
|
* Copyright (C) 2008 Florian Fainelli <f.fainelli@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/etherdevice.h>
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/of_net.h>
|
||||||
|
#include <linux/of_platform.h>
|
||||||
|
#include <linux/ssb/ssb.h>
|
||||||
|
#include "fallback-sprom.h"
|
||||||
|
|
||||||
|
#define SSB_FBS_MAX_SIZE 440
|
||||||
|
|
||||||
|
/* Get the word-offset for a SSB_SPROM_XXX define. */
|
||||||
|
#define SPOFF(offset) ((offset) / sizeof(u16))
|
||||||
|
/* Helper to extract some _offset, which is one of the SSB_SPROM_XXX defines. */
|
||||||
|
#define SPEX16(_outvar, _offset, _mask, _shift) \
|
||||||
|
out->_outvar = ((in[SPOFF(_offset)] & (_mask)) >> (_shift))
|
||||||
|
#define SPEX32(_outvar, _offset, _mask, _shift) \
|
||||||
|
out->_outvar = ((((u32)in[SPOFF((_offset)+2)] << 16 | \
|
||||||
|
in[SPOFF(_offset)]) & (_mask)) >> (_shift))
|
||||||
|
#define SPEX(_outvar, _offset, _mask, _shift) \
|
||||||
|
SPEX16(_outvar, _offset, _mask, _shift)
|
||||||
|
|
||||||
|
#define SPEX_ARRAY8(_field, _offset, _mask, _shift) \
|
||||||
|
do { \
|
||||||
|
SPEX(_field[0], _offset + 0, _mask, _shift); \
|
||||||
|
SPEX(_field[1], _offset + 2, _mask, _shift); \
|
||||||
|
SPEX(_field[2], _offset + 4, _mask, _shift); \
|
||||||
|
SPEX(_field[3], _offset + 6, _mask, _shift); \
|
||||||
|
SPEX(_field[4], _offset + 8, _mask, _shift); \
|
||||||
|
SPEX(_field[5], _offset + 10, _mask, _shift); \
|
||||||
|
SPEX(_field[6], _offset + 12, _mask, _shift); \
|
||||||
|
SPEX(_field[7], _offset + 14, _mask, _shift); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
struct ssb_fbs {
|
||||||
|
struct device *dev;
|
||||||
|
struct list_head list;
|
||||||
|
struct ssb_sprom sprom;
|
||||||
|
u32 pci_bus;
|
||||||
|
u32 pci_dev;
|
||||||
|
bool devid_override;
|
||||||
|
};
|
||||||
|
|
||||||
|
static DEFINE_SPINLOCK(ssb_fbs_lock);
|
||||||
|
static struct list_head ssb_fbs_list = LIST_HEAD_INIT(ssb_fbs_list);
|
||||||
|
|
||||||
|
int ssb_get_fallback_sprom(struct ssb_bus *bus, struct ssb_sprom *out)
|
||||||
|
{
|
||||||
|
struct ssb_fbs *pos;
|
||||||
|
u32 pci_bus, pci_dev;
|
||||||
|
|
||||||
|
if (bus->bustype != SSB_BUSTYPE_PCI)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
pci_bus = bus->host_pci->bus->number;
|
||||||
|
pci_dev = PCI_SLOT(bus->host_pci->devfn);
|
||||||
|
|
||||||
|
list_for_each_entry(pos, &ssb_fbs_list, list) {
|
||||||
|
if (pos->pci_bus != pci_bus ||
|
||||||
|
pos->pci_dev != pci_dev)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pos->devid_override)
|
||||||
|
bus->host_pci->device = pos->sprom.dev_id;
|
||||||
|
|
||||||
|
memcpy(out, &pos->sprom, sizeof(struct ssb_sprom));
|
||||||
|
dev_info(pos->dev, "requested by [%x:%x]",
|
||||||
|
pos->pci_bus, pos->pci_dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_err("unable to fill SPROM for [%x:%x]\n", pci_bus, pci_dev);
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static s8 sprom_extract_antgain(u8 sprom_revision, const u16 *in, u16 offset,
|
||||||
|
u16 mask, u16 shift)
|
||||||
|
{
|
||||||
|
u16 v;
|
||||||
|
u8 gain;
|
||||||
|
|
||||||
|
v = in[SPOFF(offset)];
|
||||||
|
gain = (v & mask) >> shift;
|
||||||
|
if (gain == 0xFF)
|
||||||
|
gain = 2; /* If unset use 2dBm */
|
||||||
|
if (sprom_revision == 1) {
|
||||||
|
/* Convert to Q5.2 */
|
||||||
|
gain <<= 2;
|
||||||
|
} else {
|
||||||
|
/* Q5.2 Fractional part is stored in 0xC0 */
|
||||||
|
gain = ((gain & 0xC0) >> 6) | ((gain & 0x3F) << 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (s8)gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sprom_extract_r23(struct ssb_sprom *out, const u16 *in)
|
||||||
|
{
|
||||||
|
SPEX(boardflags_hi, SSB_SPROM2_BFLHI, 0xFFFF, 0);
|
||||||
|
SPEX(opo, SSB_SPROM2_OPO, SSB_SPROM2_OPO_VALUE, 0);
|
||||||
|
SPEX(pa1lob0, SSB_SPROM2_PA1LOB0, 0xFFFF, 0);
|
||||||
|
SPEX(pa1lob1, SSB_SPROM2_PA1LOB1, 0xFFFF, 0);
|
||||||
|
SPEX(pa1lob2, SSB_SPROM2_PA1LOB2, 0xFFFF, 0);
|
||||||
|
SPEX(pa1hib0, SSB_SPROM2_PA1HIB0, 0xFFFF, 0);
|
||||||
|
SPEX(pa1hib1, SSB_SPROM2_PA1HIB1, 0xFFFF, 0);
|
||||||
|
SPEX(pa1hib2, SSB_SPROM2_PA1HIB2, 0xFFFF, 0);
|
||||||
|
SPEX(maxpwr_ah, SSB_SPROM2_MAXP_A, SSB_SPROM2_MAXP_A_HI, 0);
|
||||||
|
SPEX(maxpwr_al, SSB_SPROM2_MAXP_A, SSB_SPROM2_MAXP_A_LO,
|
||||||
|
SSB_SPROM2_MAXP_A_LO_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sprom_extract_r123(struct ssb_sprom *out, const u16 *in)
|
||||||
|
{
|
||||||
|
SPEX(et0phyaddr, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET0A, 0);
|
||||||
|
SPEX(et1phyaddr, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET1A,
|
||||||
|
SSB_SPROM1_ETHPHY_ET1A_SHIFT);
|
||||||
|
SPEX(et0mdcport, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET0M, 14);
|
||||||
|
SPEX(et1mdcport, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET1M, 15);
|
||||||
|
SPEX(board_rev, SSB_SPROM1_BINF, SSB_SPROM1_BINF_BREV, 0);
|
||||||
|
SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0);
|
||||||
|
if (out->revision == 1)
|
||||||
|
SPEX(country_code, SSB_SPROM1_BINF, SSB_SPROM1_BINF_CCODE,
|
||||||
|
SSB_SPROM1_BINF_CCODE_SHIFT);
|
||||||
|
SPEX(ant_available_a, SSB_SPROM1_BINF, SSB_SPROM1_BINF_ANTA,
|
||||||
|
SSB_SPROM1_BINF_ANTA_SHIFT);
|
||||||
|
SPEX(ant_available_bg, SSB_SPROM1_BINF, SSB_SPROM1_BINF_ANTBG,
|
||||||
|
SSB_SPROM1_BINF_ANTBG_SHIFT);
|
||||||
|
SPEX(pa0b0, SSB_SPROM1_PA0B0, 0xFFFF, 0);
|
||||||
|
SPEX(pa0b1, SSB_SPROM1_PA0B1, 0xFFFF, 0);
|
||||||
|
SPEX(pa0b2, SSB_SPROM1_PA0B2, 0xFFFF, 0);
|
||||||
|
SPEX(pa1b0, SSB_SPROM1_PA1B0, 0xFFFF, 0);
|
||||||
|
SPEX(pa1b1, SSB_SPROM1_PA1B1, 0xFFFF, 0);
|
||||||
|
SPEX(pa1b2, SSB_SPROM1_PA1B2, 0xFFFF, 0);
|
||||||
|
SPEX(gpio0, SSB_SPROM1_GPIOA, SSB_SPROM1_GPIOA_P0, 0);
|
||||||
|
SPEX(gpio1, SSB_SPROM1_GPIOA, SSB_SPROM1_GPIOA_P1,
|
||||||
|
SSB_SPROM1_GPIOA_P1_SHIFT);
|
||||||
|
SPEX(gpio2, SSB_SPROM1_GPIOB, SSB_SPROM1_GPIOB_P2, 0);
|
||||||
|
SPEX(gpio3, SSB_SPROM1_GPIOB, SSB_SPROM1_GPIOB_P3,
|
||||||
|
SSB_SPROM1_GPIOB_P3_SHIFT);
|
||||||
|
SPEX(maxpwr_a, SSB_SPROM1_MAXPWR, SSB_SPROM1_MAXPWR_A,
|
||||||
|
SSB_SPROM1_MAXPWR_A_SHIFT);
|
||||||
|
SPEX(maxpwr_bg, SSB_SPROM1_MAXPWR, SSB_SPROM1_MAXPWR_BG, 0);
|
||||||
|
SPEX(itssi_a, SSB_SPROM1_ITSSI, SSB_SPROM1_ITSSI_A,
|
||||||
|
SSB_SPROM1_ITSSI_A_SHIFT);
|
||||||
|
SPEX(itssi_bg, SSB_SPROM1_ITSSI, SSB_SPROM1_ITSSI_BG, 0);
|
||||||
|
SPEX(boardflags_lo, SSB_SPROM1_BFLLO, 0xFFFF, 0);
|
||||||
|
|
||||||
|
SPEX(alpha2[0], SSB_SPROM1_CCODE, 0xff00, 8);
|
||||||
|
SPEX(alpha2[1], SSB_SPROM1_CCODE, 0x00ff, 0);
|
||||||
|
|
||||||
|
/* Extract the antenna gain values. */
|
||||||
|
out->antenna_gain.a0 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM1_AGAIN,
|
||||||
|
SSB_SPROM1_AGAIN_BG,
|
||||||
|
SSB_SPROM1_AGAIN_BG_SHIFT);
|
||||||
|
out->antenna_gain.a1 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM1_AGAIN,
|
||||||
|
SSB_SPROM1_AGAIN_A,
|
||||||
|
SSB_SPROM1_AGAIN_A_SHIFT);
|
||||||
|
if (out->revision >= 2)
|
||||||
|
sprom_extract_r23(out, in);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Revs 4 5 and 8 have partially shared layout */
|
||||||
|
static void sprom_extract_r458(struct ssb_sprom *out, const u16 *in)
|
||||||
|
{
|
||||||
|
SPEX(txpid2g[0], SSB_SPROM4_TXPID2G01,
|
||||||
|
SSB_SPROM4_TXPID2G0, SSB_SPROM4_TXPID2G0_SHIFT);
|
||||||
|
SPEX(txpid2g[1], SSB_SPROM4_TXPID2G01,
|
||||||
|
SSB_SPROM4_TXPID2G1, SSB_SPROM4_TXPID2G1_SHIFT);
|
||||||
|
SPEX(txpid2g[2], SSB_SPROM4_TXPID2G23,
|
||||||
|
SSB_SPROM4_TXPID2G2, SSB_SPROM4_TXPID2G2_SHIFT);
|
||||||
|
SPEX(txpid2g[3], SSB_SPROM4_TXPID2G23,
|
||||||
|
SSB_SPROM4_TXPID2G3, SSB_SPROM4_TXPID2G3_SHIFT);
|
||||||
|
|
||||||
|
SPEX(txpid5gl[0], SSB_SPROM4_TXPID5GL01,
|
||||||
|
SSB_SPROM4_TXPID5GL0, SSB_SPROM4_TXPID5GL0_SHIFT);
|
||||||
|
SPEX(txpid5gl[1], SSB_SPROM4_TXPID5GL01,
|
||||||
|
SSB_SPROM4_TXPID5GL1, SSB_SPROM4_TXPID5GL1_SHIFT);
|
||||||
|
SPEX(txpid5gl[2], SSB_SPROM4_TXPID5GL23,
|
||||||
|
SSB_SPROM4_TXPID5GL2, SSB_SPROM4_TXPID5GL2_SHIFT);
|
||||||
|
SPEX(txpid5gl[3], SSB_SPROM4_TXPID5GL23,
|
||||||
|
SSB_SPROM4_TXPID5GL3, SSB_SPROM4_TXPID5GL3_SHIFT);
|
||||||
|
|
||||||
|
SPEX(txpid5g[0], SSB_SPROM4_TXPID5G01,
|
||||||
|
SSB_SPROM4_TXPID5G0, SSB_SPROM4_TXPID5G0_SHIFT);
|
||||||
|
SPEX(txpid5g[1], SSB_SPROM4_TXPID5G01,
|
||||||
|
SSB_SPROM4_TXPID5G1, SSB_SPROM4_TXPID5G1_SHIFT);
|
||||||
|
SPEX(txpid5g[2], SSB_SPROM4_TXPID5G23,
|
||||||
|
SSB_SPROM4_TXPID5G2, SSB_SPROM4_TXPID5G2_SHIFT);
|
||||||
|
SPEX(txpid5g[3], SSB_SPROM4_TXPID5G23,
|
||||||
|
SSB_SPROM4_TXPID5G3, SSB_SPROM4_TXPID5G3_SHIFT);
|
||||||
|
|
||||||
|
SPEX(txpid5gh[0], SSB_SPROM4_TXPID5GH01,
|
||||||
|
SSB_SPROM4_TXPID5GH0, SSB_SPROM4_TXPID5GH0_SHIFT);
|
||||||
|
SPEX(txpid5gh[1], SSB_SPROM4_TXPID5GH01,
|
||||||
|
SSB_SPROM4_TXPID5GH1, SSB_SPROM4_TXPID5GH1_SHIFT);
|
||||||
|
SPEX(txpid5gh[2], SSB_SPROM4_TXPID5GH23,
|
||||||
|
SSB_SPROM4_TXPID5GH2, SSB_SPROM4_TXPID5GH2_SHIFT);
|
||||||
|
SPEX(txpid5gh[3], SSB_SPROM4_TXPID5GH23,
|
||||||
|
SSB_SPROM4_TXPID5GH3, SSB_SPROM4_TXPID5GH3_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sprom_extract_r45(struct ssb_sprom *out, const u16 *in)
|
||||||
|
{
|
||||||
|
static const u16 pwr_info_offset[] = {
|
||||||
|
SSB_SPROM4_PWR_INFO_CORE0, SSB_SPROM4_PWR_INFO_CORE1,
|
||||||
|
SSB_SPROM4_PWR_INFO_CORE2, SSB_SPROM4_PWR_INFO_CORE3
|
||||||
|
};
|
||||||
|
int i;
|
||||||
|
|
||||||
|
BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) !=
|
||||||
|
ARRAY_SIZE(out->core_pwr_info));
|
||||||
|
|
||||||
|
SPEX(et0phyaddr, SSB_SPROM4_ETHPHY, SSB_SPROM4_ETHPHY_ET0A, 0);
|
||||||
|
SPEX(et1phyaddr, SSB_SPROM4_ETHPHY, SSB_SPROM4_ETHPHY_ET1A,
|
||||||
|
SSB_SPROM4_ETHPHY_ET1A_SHIFT);
|
||||||
|
SPEX(board_rev, SSB_SPROM4_BOARDREV, 0xFFFF, 0);
|
||||||
|
SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0);
|
||||||
|
if (out->revision == 4) {
|
||||||
|
SPEX(alpha2[0], SSB_SPROM4_CCODE, 0xff00, 8);
|
||||||
|
SPEX(alpha2[1], SSB_SPROM4_CCODE, 0x00ff, 0);
|
||||||
|
SPEX(boardflags_lo, SSB_SPROM4_BFLLO, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags_hi, SSB_SPROM4_BFLHI, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags2_lo, SSB_SPROM4_BFL2LO, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags2_hi, SSB_SPROM4_BFL2HI, 0xFFFF, 0);
|
||||||
|
} else {
|
||||||
|
SPEX(alpha2[0], SSB_SPROM5_CCODE, 0xff00, 8);
|
||||||
|
SPEX(alpha2[1], SSB_SPROM5_CCODE, 0x00ff, 0);
|
||||||
|
SPEX(boardflags_lo, SSB_SPROM5_BFLLO, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags_hi, SSB_SPROM5_BFLHI, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags2_lo, SSB_SPROM5_BFL2LO, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags2_hi, SSB_SPROM5_BFL2HI, 0xFFFF, 0);
|
||||||
|
}
|
||||||
|
SPEX(ant_available_a, SSB_SPROM4_ANTAVAIL, SSB_SPROM4_ANTAVAIL_A,
|
||||||
|
SSB_SPROM4_ANTAVAIL_A_SHIFT);
|
||||||
|
SPEX(ant_available_bg, SSB_SPROM4_ANTAVAIL, SSB_SPROM4_ANTAVAIL_BG,
|
||||||
|
SSB_SPROM4_ANTAVAIL_BG_SHIFT);
|
||||||
|
SPEX(maxpwr_bg, SSB_SPROM4_MAXP_BG, SSB_SPROM4_MAXP_BG_MASK, 0);
|
||||||
|
SPEX(itssi_bg, SSB_SPROM4_MAXP_BG, SSB_SPROM4_ITSSI_BG,
|
||||||
|
SSB_SPROM4_ITSSI_BG_SHIFT);
|
||||||
|
SPEX(maxpwr_a, SSB_SPROM4_MAXP_A, SSB_SPROM4_MAXP_A_MASK, 0);
|
||||||
|
SPEX(itssi_a, SSB_SPROM4_MAXP_A, SSB_SPROM4_ITSSI_A,
|
||||||
|
SSB_SPROM4_ITSSI_A_SHIFT);
|
||||||
|
if (out->revision == 4) {
|
||||||
|
SPEX(gpio0, SSB_SPROM4_GPIOA, SSB_SPROM4_GPIOA_P0, 0);
|
||||||
|
SPEX(gpio1, SSB_SPROM4_GPIOA, SSB_SPROM4_GPIOA_P1,
|
||||||
|
SSB_SPROM4_GPIOA_P1_SHIFT);
|
||||||
|
SPEX(gpio2, SSB_SPROM4_GPIOB, SSB_SPROM4_GPIOB_P2, 0);
|
||||||
|
SPEX(gpio3, SSB_SPROM4_GPIOB, SSB_SPROM4_GPIOB_P3,
|
||||||
|
SSB_SPROM4_GPIOB_P3_SHIFT);
|
||||||
|
} else {
|
||||||
|
SPEX(gpio0, SSB_SPROM5_GPIOA, SSB_SPROM5_GPIOA_P0, 0);
|
||||||
|
SPEX(gpio1, SSB_SPROM5_GPIOA, SSB_SPROM5_GPIOA_P1,
|
||||||
|
SSB_SPROM5_GPIOA_P1_SHIFT);
|
||||||
|
SPEX(gpio2, SSB_SPROM5_GPIOB, SSB_SPROM5_GPIOB_P2, 0);
|
||||||
|
SPEX(gpio3, SSB_SPROM5_GPIOB, SSB_SPROM5_GPIOB_P3,
|
||||||
|
SSB_SPROM5_GPIOB_P3_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract the antenna gain values. */
|
||||||
|
out->antenna_gain.a0 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM4_AGAIN01,
|
||||||
|
SSB_SPROM4_AGAIN0,
|
||||||
|
SSB_SPROM4_AGAIN0_SHIFT);
|
||||||
|
out->antenna_gain.a1 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM4_AGAIN01,
|
||||||
|
SSB_SPROM4_AGAIN1,
|
||||||
|
SSB_SPROM4_AGAIN1_SHIFT);
|
||||||
|
out->antenna_gain.a2 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM4_AGAIN23,
|
||||||
|
SSB_SPROM4_AGAIN2,
|
||||||
|
SSB_SPROM4_AGAIN2_SHIFT);
|
||||||
|
out->antenna_gain.a3 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM4_AGAIN23,
|
||||||
|
SSB_SPROM4_AGAIN3,
|
||||||
|
SSB_SPROM4_AGAIN3_SHIFT);
|
||||||
|
|
||||||
|
/* Extract cores power info info */
|
||||||
|
for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) {
|
||||||
|
u16 o = pwr_info_offset[i];
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].itssi_2g, o + SSB_SPROM4_2G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM4_2G_ITSSI, SSB_SPROM4_2G_ITSSI_SHIFT);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SPROM4_2G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM4_2G_MAXP, 0);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SPROM4_2G_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SPROM4_2G_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SPROM4_2G_PA_2, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[3], o + SSB_SPROM4_2G_PA_3, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].itssi_5g, o + SSB_SPROM4_5G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM4_5G_ITSSI, SSB_SPROM4_5G_ITSSI_SHIFT);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SPROM4_5G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM4_5G_MAXP, 0);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM4_5GHL_MAXP,
|
||||||
|
SSB_SPROM4_5GH_MAXP, 0);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM4_5GHL_MAXP,
|
||||||
|
SSB_SPROM4_5GL_MAXP, SSB_SPROM4_5GL_MAXP_SHIFT);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SPROM4_5GL_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SPROM4_5GL_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SPROM4_5GL_PA_2, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[3], o + SSB_SPROM4_5GL_PA_3, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SPROM4_5G_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SPROM4_5G_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SPROM4_5G_PA_2, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[3], o + SSB_SPROM4_5G_PA_3, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SPROM4_5GH_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SPROM4_5GH_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SPROM4_5GH_PA_2, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[3], o + SSB_SPROM4_5GH_PA_3, ~0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sprom_extract_r458(out, in);
|
||||||
|
|
||||||
|
/* TODO - get remaining rev 4 stuff needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sprom_extract_r8(struct ssb_sprom *out, const u16 *in)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
u16 o;
|
||||||
|
static const u16 pwr_info_offset[] = {
|
||||||
|
SSB_SROM8_PWR_INFO_CORE0, SSB_SROM8_PWR_INFO_CORE1,
|
||||||
|
SSB_SROM8_PWR_INFO_CORE2, SSB_SROM8_PWR_INFO_CORE3
|
||||||
|
};
|
||||||
|
BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) !=
|
||||||
|
ARRAY_SIZE(out->core_pwr_info));
|
||||||
|
|
||||||
|
SPEX(board_rev, SSB_SPROM8_BOARDREV, 0xFFFF, 0);
|
||||||
|
SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0);
|
||||||
|
SPEX(alpha2[0], SSB_SPROM8_CCODE, 0xff00, 8);
|
||||||
|
SPEX(alpha2[1], SSB_SPROM8_CCODE, 0x00ff, 0);
|
||||||
|
SPEX(boardflags_lo, SSB_SPROM8_BFLLO, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags_hi, SSB_SPROM8_BFLHI, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags2_lo, SSB_SPROM8_BFL2LO, 0xFFFF, 0);
|
||||||
|
SPEX(boardflags2_hi, SSB_SPROM8_BFL2HI, 0xFFFF, 0);
|
||||||
|
SPEX(ant_available_a, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_A,
|
||||||
|
SSB_SPROM8_ANTAVAIL_A_SHIFT);
|
||||||
|
SPEX(ant_available_bg, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_BG,
|
||||||
|
SSB_SPROM8_ANTAVAIL_BG_SHIFT);
|
||||||
|
SPEX(maxpwr_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_MAXP_BG_MASK, 0);
|
||||||
|
SPEX(itssi_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_ITSSI_BG,
|
||||||
|
SSB_SPROM8_ITSSI_BG_SHIFT);
|
||||||
|
SPEX(maxpwr_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_MAXP_A_MASK, 0);
|
||||||
|
SPEX(itssi_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_ITSSI_A,
|
||||||
|
SSB_SPROM8_ITSSI_A_SHIFT);
|
||||||
|
SPEX(maxpwr_ah, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AH_MASK, 0);
|
||||||
|
SPEX(maxpwr_al, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AL_MASK,
|
||||||
|
SSB_SPROM8_MAXP_AL_SHIFT);
|
||||||
|
SPEX(gpio0, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P0, 0);
|
||||||
|
SPEX(gpio1, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P1,
|
||||||
|
SSB_SPROM8_GPIOA_P1_SHIFT);
|
||||||
|
SPEX(gpio2, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P2, 0);
|
||||||
|
SPEX(gpio3, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P3,
|
||||||
|
SSB_SPROM8_GPIOB_P3_SHIFT);
|
||||||
|
SPEX(tri2g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI2G, 0);
|
||||||
|
SPEX(tri5g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI5G,
|
||||||
|
SSB_SPROM8_TRI5G_SHIFT);
|
||||||
|
SPEX(tri5gl, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GL, 0);
|
||||||
|
SPEX(tri5gh, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GH,
|
||||||
|
SSB_SPROM8_TRI5GH_SHIFT);
|
||||||
|
SPEX(rxpo2g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO2G, 0);
|
||||||
|
SPEX(rxpo5g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO5G,
|
||||||
|
SSB_SPROM8_RXPO5G_SHIFT);
|
||||||
|
SPEX(rssismf2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMF2G, 0);
|
||||||
|
SPEX(rssismc2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMC2G,
|
||||||
|
SSB_SPROM8_RSSISMC2G_SHIFT);
|
||||||
|
SPEX(rssisav2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISAV2G,
|
||||||
|
SSB_SPROM8_RSSISAV2G_SHIFT);
|
||||||
|
SPEX(bxa2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_BXA2G,
|
||||||
|
SSB_SPROM8_BXA2G_SHIFT);
|
||||||
|
SPEX(rssismf5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMF5G, 0);
|
||||||
|
SPEX(rssismc5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMC5G,
|
||||||
|
SSB_SPROM8_RSSISMC5G_SHIFT);
|
||||||
|
SPEX(rssisav5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISAV5G,
|
||||||
|
SSB_SPROM8_RSSISAV5G_SHIFT);
|
||||||
|
SPEX(bxa5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_BXA5G,
|
||||||
|
SSB_SPROM8_BXA5G_SHIFT);
|
||||||
|
SPEX(pa0b0, SSB_SPROM8_PA0B0, 0xFFFF, 0);
|
||||||
|
SPEX(pa0b1, SSB_SPROM8_PA0B1, 0xFFFF, 0);
|
||||||
|
SPEX(pa0b2, SSB_SPROM8_PA0B2, 0xFFFF, 0);
|
||||||
|
SPEX(pa1b0, SSB_SPROM8_PA1B0, 0xFFFF, 0);
|
||||||
|
SPEX(pa1b1, SSB_SPROM8_PA1B1, 0xFFFF, 0);
|
||||||
|
SPEX(pa1b2, SSB_SPROM8_PA1B2, 0xFFFF, 0);
|
||||||
|
SPEX(pa1lob0, SSB_SPROM8_PA1LOB0, 0xFFFF, 0);
|
||||||
|
SPEX(pa1lob1, SSB_SPROM8_PA1LOB1, 0xFFFF, 0);
|
||||||
|
SPEX(pa1lob2, SSB_SPROM8_PA1LOB2, 0xFFFF, 0);
|
||||||
|
SPEX(pa1hib0, SSB_SPROM8_PA1HIB0, 0xFFFF, 0);
|
||||||
|
SPEX(pa1hib1, SSB_SPROM8_PA1HIB1, 0xFFFF, 0);
|
||||||
|
SPEX(pa1hib2, SSB_SPROM8_PA1HIB2, 0xFFFF, 0);
|
||||||
|
SPEX(cck2gpo, SSB_SPROM8_CCK2GPO, 0xFFFF, 0);
|
||||||
|
SPEX32(ofdm2gpo, SSB_SPROM8_OFDM2GPO, 0xFFFFFFFF, 0);
|
||||||
|
SPEX32(ofdm5glpo, SSB_SPROM8_OFDM5GLPO, 0xFFFFFFFF, 0);
|
||||||
|
SPEX32(ofdm5gpo, SSB_SPROM8_OFDM5GPO, 0xFFFFFFFF, 0);
|
||||||
|
SPEX32(ofdm5ghpo, SSB_SPROM8_OFDM5GHPO, 0xFFFFFFFF, 0);
|
||||||
|
|
||||||
|
/* Extract the antenna gain values. */
|
||||||
|
out->antenna_gain.a0 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM8_AGAIN01,
|
||||||
|
SSB_SPROM8_AGAIN0,
|
||||||
|
SSB_SPROM8_AGAIN0_SHIFT);
|
||||||
|
out->antenna_gain.a1 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM8_AGAIN01,
|
||||||
|
SSB_SPROM8_AGAIN1,
|
||||||
|
SSB_SPROM8_AGAIN1_SHIFT);
|
||||||
|
out->antenna_gain.a2 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM8_AGAIN23,
|
||||||
|
SSB_SPROM8_AGAIN2,
|
||||||
|
SSB_SPROM8_AGAIN2_SHIFT);
|
||||||
|
out->antenna_gain.a3 = sprom_extract_antgain(out->revision, in,
|
||||||
|
SSB_SPROM8_AGAIN23,
|
||||||
|
SSB_SPROM8_AGAIN3,
|
||||||
|
SSB_SPROM8_AGAIN3_SHIFT);
|
||||||
|
|
||||||
|
/* Extract cores power info info */
|
||||||
|
for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) {
|
||||||
|
o = pwr_info_offset[i];
|
||||||
|
SPEX(core_pwr_info[i].itssi_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM8_2G_ITSSI, SSB_SPROM8_2G_ITSSI_SHIFT);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM8_2G_MAXP, 0);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SROM8_2G_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SROM8_2G_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SROM8_2G_PA_2, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].itssi_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM8_5G_ITSSI, SSB_SPROM8_5G_ITSSI_SHIFT);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
|
||||||
|
SSB_SPROM8_5G_MAXP, 0);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM8_5GHL_MAXP,
|
||||||
|
SSB_SPROM8_5GH_MAXP, 0);
|
||||||
|
SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM8_5GHL_MAXP,
|
||||||
|
SSB_SPROM8_5GL_MAXP, SSB_SPROM8_5GL_MAXP_SHIFT);
|
||||||
|
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SROM8_5GL_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SROM8_5GL_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SROM8_5GL_PA_2, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SROM8_5G_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SROM8_5G_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SROM8_5G_PA_2, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SROM8_5GH_PA_0, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SROM8_5GH_PA_1, ~0, 0);
|
||||||
|
SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SROM8_5GH_PA_2, ~0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract FEM info */
|
||||||
|
SPEX(fem.ghz2.tssipos, SSB_SPROM8_FEM2G,
|
||||||
|
SSB_SROM8_FEM_TSSIPOS, SSB_SROM8_FEM_TSSIPOS_SHIFT);
|
||||||
|
SPEX(fem.ghz2.extpa_gain, SSB_SPROM8_FEM2G,
|
||||||
|
SSB_SROM8_FEM_EXTPA_GAIN, SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
|
||||||
|
SPEX(fem.ghz2.pdet_range, SSB_SPROM8_FEM2G,
|
||||||
|
SSB_SROM8_FEM_PDET_RANGE, SSB_SROM8_FEM_PDET_RANGE_SHIFT);
|
||||||
|
SPEX(fem.ghz2.tr_iso, SSB_SPROM8_FEM2G,
|
||||||
|
SSB_SROM8_FEM_TR_ISO, SSB_SROM8_FEM_TR_ISO_SHIFT);
|
||||||
|
SPEX(fem.ghz2.antswlut, SSB_SPROM8_FEM2G,
|
||||||
|
SSB_SROM8_FEM_ANTSWLUT, SSB_SROM8_FEM_ANTSWLUT_SHIFT);
|
||||||
|
|
||||||
|
SPEX(fem.ghz5.tssipos, SSB_SPROM8_FEM5G,
|
||||||
|
SSB_SROM8_FEM_TSSIPOS, SSB_SROM8_FEM_TSSIPOS_SHIFT);
|
||||||
|
SPEX(fem.ghz5.extpa_gain, SSB_SPROM8_FEM5G,
|
||||||
|
SSB_SROM8_FEM_EXTPA_GAIN, SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
|
||||||
|
SPEX(fem.ghz5.pdet_range, SSB_SPROM8_FEM5G,
|
||||||
|
SSB_SROM8_FEM_PDET_RANGE, SSB_SROM8_FEM_PDET_RANGE_SHIFT);
|
||||||
|
SPEX(fem.ghz5.tr_iso, SSB_SPROM8_FEM5G,
|
||||||
|
SSB_SROM8_FEM_TR_ISO, SSB_SROM8_FEM_TR_ISO_SHIFT);
|
||||||
|
SPEX(fem.ghz5.antswlut, SSB_SPROM8_FEM5G,
|
||||||
|
SSB_SROM8_FEM_ANTSWLUT, SSB_SROM8_FEM_ANTSWLUT_SHIFT);
|
||||||
|
|
||||||
|
SPEX(leddc_on_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_ON,
|
||||||
|
SSB_SPROM8_LEDDC_ON_SHIFT);
|
||||||
|
SPEX(leddc_off_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_OFF,
|
||||||
|
SSB_SPROM8_LEDDC_OFF_SHIFT);
|
||||||
|
|
||||||
|
SPEX(txchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_TXCHAIN,
|
||||||
|
SSB_SPROM8_TXRXC_TXCHAIN_SHIFT);
|
||||||
|
SPEX(rxchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_RXCHAIN,
|
||||||
|
SSB_SPROM8_TXRXC_RXCHAIN_SHIFT);
|
||||||
|
SPEX(antswitch, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_SWITCH,
|
||||||
|
SSB_SPROM8_TXRXC_SWITCH_SHIFT);
|
||||||
|
|
||||||
|
SPEX(opo, SSB_SPROM8_OFDM2GPO, 0x00ff, 0);
|
||||||
|
|
||||||
|
SPEX_ARRAY8(mcs2gpo, SSB_SPROM8_2G_MCSPO, ~0, 0);
|
||||||
|
SPEX_ARRAY8(mcs5gpo, SSB_SPROM8_5G_MCSPO, ~0, 0);
|
||||||
|
SPEX_ARRAY8(mcs5glpo, SSB_SPROM8_5GL_MCSPO, ~0, 0);
|
||||||
|
SPEX_ARRAY8(mcs5ghpo, SSB_SPROM8_5GH_MCSPO, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(rawtempsense, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_RAWTEMP,
|
||||||
|
SSB_SPROM8_RAWTS_RAWTEMP_SHIFT);
|
||||||
|
SPEX(measpower, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_MEASPOWER,
|
||||||
|
SSB_SPROM8_RAWTS_MEASPOWER_SHIFT);
|
||||||
|
SPEX(tempsense_slope, SSB_SPROM8_OPT_CORRX,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMP_SLOPE,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMP_SLOPE_SHIFT);
|
||||||
|
SPEX(tempcorrx, SSB_SPROM8_OPT_CORRX, SSB_SPROM8_OPT_CORRX_TEMPCORRX,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMPCORRX_SHIFT);
|
||||||
|
SPEX(tempsense_option, SSB_SPROM8_OPT_CORRX,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMP_OPTION,
|
||||||
|
SSB_SPROM8_OPT_CORRX_TEMP_OPTION_SHIFT);
|
||||||
|
SPEX(freqoffset_corr, SSB_SPROM8_HWIQ_IQSWP,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR_SHIFT);
|
||||||
|
SPEX(iqcal_swp_dis, SSB_SPROM8_HWIQ_IQSWP,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP_SHIFT);
|
||||||
|
SPEX(hw_iqcal_en, SSB_SPROM8_HWIQ_IQSWP, SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL,
|
||||||
|
SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL_SHIFT);
|
||||||
|
|
||||||
|
SPEX(bw40po, SSB_SPROM8_BW40PO, ~0, 0);
|
||||||
|
SPEX(cddpo, SSB_SPROM8_CDDPO, ~0, 0);
|
||||||
|
SPEX(stbcpo, SSB_SPROM8_STBCPO, ~0, 0);
|
||||||
|
SPEX(bwduppo, SSB_SPROM8_BWDUPPO, ~0, 0);
|
||||||
|
|
||||||
|
SPEX(tempthresh, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_TRESH,
|
||||||
|
SSB_SPROM8_THERMAL_TRESH_SHIFT);
|
||||||
|
SPEX(tempoffset, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_OFFSET,
|
||||||
|
SSB_SPROM8_THERMAL_OFFSET_SHIFT);
|
||||||
|
SPEX(phycal_tempdelta, SSB_SPROM8_TEMPDELTA,
|
||||||
|
SSB_SPROM8_TEMPDELTA_PHYCAL,
|
||||||
|
SSB_SPROM8_TEMPDELTA_PHYCAL_SHIFT);
|
||||||
|
SPEX(temps_period, SSB_SPROM8_TEMPDELTA, SSB_SPROM8_TEMPDELTA_PERIOD,
|
||||||
|
SSB_SPROM8_TEMPDELTA_PERIOD_SHIFT);
|
||||||
|
SPEX(temps_hysteresis, SSB_SPROM8_TEMPDELTA,
|
||||||
|
SSB_SPROM8_TEMPDELTA_HYSTERESIS,
|
||||||
|
SSB_SPROM8_TEMPDELTA_HYSTERESIS_SHIFT);
|
||||||
|
sprom_extract_r458(out, in);
|
||||||
|
|
||||||
|
/* TODO - get remaining rev 8 stuff needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sprom_extract(struct ssb_fbs *priv, const u16 *in, u16 size)
|
||||||
|
{
|
||||||
|
struct ssb_sprom *out = &priv->sprom;
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(*out));
|
||||||
|
|
||||||
|
out->revision = in[size - 1] & 0x00FF;
|
||||||
|
|
||||||
|
switch (out->revision) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
sprom_extract_r123(out, in);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
sprom_extract_r45(out, in);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
sprom_extract_r8(out, in);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dev_warn(priv->dev,
|
||||||
|
"Unsupported SPROM revision %d detected."
|
||||||
|
" Will extract v1\n",
|
||||||
|
out->revision);
|
||||||
|
out->revision = 1;
|
||||||
|
sprom_extract_r123(out, in);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out->boardflags_lo == 0xFFFF)
|
||||||
|
out->boardflags_lo = 0; /* per specs */
|
||||||
|
if (out->boardflags_hi == 0xFFFF)
|
||||||
|
out->boardflags_hi = 0; /* per specs */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ssb_fbs_fixup(struct ssb_fbs *priv, u16 *sprom)
|
||||||
|
{
|
||||||
|
struct device_node *node = priv->dev->of_node;
|
||||||
|
u32 fixups, off, val;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
if (!of_get_property(node, "brcm,sprom-fixups", &fixups))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixups /= sizeof(u32);
|
||||||
|
|
||||||
|
dev_info(priv->dev, "patching SPROM with %u fixups...\n", fixups >> 1);
|
||||||
|
|
||||||
|
while (i < fixups) {
|
||||||
|
if (of_property_read_u32_index(node, "brcm,sprom-fixups",
|
||||||
|
i++, &off)) {
|
||||||
|
dev_err(priv->dev, "error reading fixup[%u] offset\n",
|
||||||
|
i - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (of_property_read_u32_index(node, "brcm,sprom-fixups",
|
||||||
|
i++, &val)) {
|
||||||
|
dev_err(priv->dev, "error reading fixup[%u] value\n",
|
||||||
|
i - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_dbg(priv->dev, "fixup[%d]=0x%04x\n", off, val);
|
||||||
|
|
||||||
|
sprom[off] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sprom_override_devid(struct ssb_fbs *priv, struct ssb_sprom *out,
|
||||||
|
const u16 *in)
|
||||||
|
{
|
||||||
|
SPEX(dev_id, SSB_SPROM1_PID, 0xFFFF, 0);
|
||||||
|
return !!out->dev_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ssb_fbs_set(struct ssb_fbs *priv, struct device_node *node)
|
||||||
|
{
|
||||||
|
struct ssb_sprom *sprom = &priv->sprom;
|
||||||
|
const struct firmware *fw;
|
||||||
|
const char *sprom_name;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (of_property_read_string(node, "brcm,sprom", &sprom_name))
|
||||||
|
sprom_name = NULL;
|
||||||
|
|
||||||
|
if (sprom_name) {
|
||||||
|
err = request_firmware_direct(&fw, sprom_name, priv->dev);
|
||||||
|
if (err)
|
||||||
|
dev_err(priv->dev, "%s load error\n", sprom_name);
|
||||||
|
} else {
|
||||||
|
err = -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
sprom->revision = 0x02;
|
||||||
|
sprom->board_rev = 0x0017;
|
||||||
|
sprom->country_code = 0x00;
|
||||||
|
sprom->ant_available_bg = 0x03;
|
||||||
|
sprom->pa0b0 = 0x15ae;
|
||||||
|
sprom->pa0b1 = 0xfa85;
|
||||||
|
sprom->pa0b2 = 0xfe8d;
|
||||||
|
sprom->pa1b0 = 0xffff;
|
||||||
|
sprom->pa1b1 = 0xffff;
|
||||||
|
sprom->pa1b2 = 0xffff;
|
||||||
|
sprom->gpio0 = 0xff;
|
||||||
|
sprom->gpio1 = 0xff;
|
||||||
|
sprom->gpio2 = 0xff;
|
||||||
|
sprom->gpio3 = 0xff;
|
||||||
|
sprom->maxpwr_bg = 0x4c;
|
||||||
|
sprom->itssi_bg = 0x00;
|
||||||
|
sprom->boardflags_lo = 0x2848;
|
||||||
|
sprom->boardflags_hi = 0x0000;
|
||||||
|
priv->devid_override = false;
|
||||||
|
|
||||||
|
dev_warn(priv->dev, "using basic SPROM\n");
|
||||||
|
} else {
|
||||||
|
size_t size = min(fw->size, (size_t) SSB_FBS_MAX_SIZE);
|
||||||
|
u16 tmp_sprom[SSB_FBS_MAX_SIZE >> 1];
|
||||||
|
u32 i, j;
|
||||||
|
|
||||||
|
for (i = 0, j = 0; i < size; i += 2, j++)
|
||||||
|
tmp_sprom[j] = (fw->data[i] << 8) | fw->data[i + 1];
|
||||||
|
|
||||||
|
release_firmware(fw);
|
||||||
|
ssb_fbs_fixup(priv, tmp_sprom);
|
||||||
|
sprom_extract(priv, tmp_sprom, size >> 1);
|
||||||
|
|
||||||
|
priv->devid_override = sprom_override_devid(priv, sprom,
|
||||||
|
tmp_sprom);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ssb_fbs_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct device_node *node = dev->of_node;
|
||||||
|
struct ssb_fbs *priv;
|
||||||
|
unsigned long flags;
|
||||||
|
u8 mac[ETH_ALEN];
|
||||||
|
|
||||||
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||||
|
if (!priv)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
priv->dev = dev;
|
||||||
|
|
||||||
|
ssb_fbs_set(priv, node);
|
||||||
|
|
||||||
|
of_property_read_u32(node, "pci-bus", &priv->pci_bus);
|
||||||
|
of_property_read_u32(node, "pci-dev", &priv->pci_dev);
|
||||||
|
|
||||||
|
of_get_mac_address(node, mac);
|
||||||
|
if (is_valid_ether_addr(mac)) {
|
||||||
|
dev_info(dev, "mtd mac %pM\n", mac);
|
||||||
|
} else {
|
||||||
|
eth_random_addr(mac);
|
||||||
|
dev_info(dev, "random mac %pM\n", mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(priv->sprom.il0mac, mac, ETH_ALEN);
|
||||||
|
memcpy(priv->sprom.et0mac, mac, ETH_ALEN);
|
||||||
|
memcpy(priv->sprom.et1mac, mac, ETH_ALEN);
|
||||||
|
memcpy(priv->sprom.et2mac, mac, ETH_ALEN);
|
||||||
|
|
||||||
|
spin_lock_irqsave(&ssb_fbs_lock, flags);
|
||||||
|
list_add(&priv->list, &ssb_fbs_list);
|
||||||
|
spin_unlock_irqrestore(&ssb_fbs_lock, flags);
|
||||||
|
|
||||||
|
dev_info(dev, "registered SPROM for [%x:%x]\n",
|
||||||
|
priv->pci_bus, priv->pci_dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id ssb_fbs_of_match[] = {
|
||||||
|
{ .compatible = "brcm,ssb-sprom", },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, ssb_fbs_of_match);
|
||||||
|
|
||||||
|
static struct platform_driver ssb_fbs_driver = {
|
||||||
|
.probe = ssb_fbs_probe,
|
||||||
|
.driver = {
|
||||||
|
.name = "ssb-sprom",
|
||||||
|
.of_match_table = ssb_fbs_of_match,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
int __init ssb_fbs_register(void)
|
||||||
|
{
|
||||||
|
return platform_driver_register(&ssb_fbs_driver);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
#ifndef _FALLBACK_SPROM_H
|
||||||
|
#define _FALLBACK_SPROM_H
|
||||||
|
|
||||||
|
int __init ssb_fbs_register(void);
|
||||||
|
int ssb_get_fallback_sprom(struct ssb_bus *dev, struct ssb_sprom *out);
|
||||||
|
|
||||||
|
#endif /* _FALLBACK_SPROM_H */
|
|
@ -0,0 +1,198 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* *** IMPORTANT ***
|
||||||
|
* This file is not only included from C-code but also from devicetree source
|
||||||
|
* files. As such this file MUST only contain comments and defines.
|
||||||
|
*
|
||||||
|
* Based on image.h from U-Boot which is
|
||||||
|
* (C) Copyright 2008 Semihalf
|
||||||
|
* (C) Copyright 2000-2005 Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UIMAGE_H__
|
||||||
|
#define __UIMAGE_H__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Operating System Codes
|
||||||
|
*
|
||||||
|
* The following are exposed to uImage header.
|
||||||
|
* New IDs *MUST* be appended at the end of the list and *NEVER*
|
||||||
|
* inserted for backward compatibility.
|
||||||
|
*/
|
||||||
|
#define IH_OS_INVALID 0 /* Invalid OS */
|
||||||
|
#define IH_OS_OPENBSD 1 /* OpenBSD */
|
||||||
|
#define IH_OS_NETBSD 2 /* NetBSD */
|
||||||
|
#define IH_OS_FREEBSD 3 /* FreeBSD */
|
||||||
|
#define IH_OS_4_4BSD 4 /* 4.4BSD */
|
||||||
|
#define IH_OS_LINUX 5 /* Linux */
|
||||||
|
#define IH_OS_SVR4 6 /* SVR4 */
|
||||||
|
#define IH_OS_ESIX 7 /* Esix */
|
||||||
|
#define IH_OS_SOLARIS 8 /* Solaris */
|
||||||
|
#define IH_OS_IRIX 9 /* Irix */
|
||||||
|
#define IH_OS_SCO 10 /* SCO */
|
||||||
|
#define IH_OS_DELL 11 /* Dell */
|
||||||
|
#define IH_OS_NCR 12 /* NCR */
|
||||||
|
#define IH_OS_LYNXOS 13 /* LynxOS */
|
||||||
|
#define IH_OS_VXWORKS 14 /* VxWorks */
|
||||||
|
#define IH_OS_PSOS 15 /* pSOS */
|
||||||
|
#define IH_OS_QNX 16 /* QNX */
|
||||||
|
#define IH_OS_U_BOOT 17 /* Firmware */
|
||||||
|
#define IH_OS_RTEMS 18 /* RTEMS */
|
||||||
|
#define IH_OS_ARTOS 19 /* ARTOS */
|
||||||
|
#define IH_OS_UNITY 20 /* Unity OS */
|
||||||
|
#define IH_OS_INTEGRITY 21 /* INTEGRITY */
|
||||||
|
#define IH_OS_OSE 22 /* OSE */
|
||||||
|
#define IH_OS_PLAN9 23 /* Plan 9 */
|
||||||
|
#define IH_OS_OPENRTOS 24 /* OpenRTOS */
|
||||||
|
#define IH_OS_ARM_TRUSTED_FIRMWARE 25 /* ARM Trusted Firmware */
|
||||||
|
#define IH_OS_TEE 26 /* Trusted Execution Environment */
|
||||||
|
#define IH_OS_OPENSBI 27 /* RISC-V OpenSBI */
|
||||||
|
#define IH_OS_EFI 28 /* EFI Firmware (e.g. GRUB2) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CPU Architecture Codes (supported by Linux)
|
||||||
|
*
|
||||||
|
* The following are exposed to uImage header.
|
||||||
|
* New IDs *MUST* be appended at the end of the list and *NEVER*
|
||||||
|
* inserted for backward compatibility.
|
||||||
|
*/
|
||||||
|
#define IH_ARCH_INVALID 0 /* Invalid CPU */
|
||||||
|
#define IH_ARCH_ALPHA 1 /* Alpha */
|
||||||
|
#define IH_ARCH_ARM 2 /* ARM */
|
||||||
|
#define IH_ARCH_I386 3 /* Intel x86 */
|
||||||
|
#define IH_ARCH_IA64 4 /* IA64 */
|
||||||
|
#define IH_ARCH_MIPS 5 /* MIPS */
|
||||||
|
#define IH_ARCH_MIPS64 6 /* MIPS 64 Bit */
|
||||||
|
#define IH_ARCH_PPC 7 /* PowerPC */
|
||||||
|
#define IH_ARCH_S390 8 /* IBM S390 */
|
||||||
|
#define IH_ARCH_SH 9 /* SuperH */
|
||||||
|
#define IH_ARCH_SPARC 10 /* Sparc */
|
||||||
|
#define IH_ARCH_SPARC64 11 /* Sparc 64 Bit */
|
||||||
|
#define IH_ARCH_M68K 12 /* M68K */
|
||||||
|
#define IH_ARCH_NIOS 13 /* Nios-32 */
|
||||||
|
#define IH_ARCH_MICROBLAZE 14 /* MicroBlaze */
|
||||||
|
#define IH_ARCH_NIOS2 15 /* Nios-II */
|
||||||
|
#define IH_ARCH_BLACKFIN 16 /* Blackfin */
|
||||||
|
#define IH_ARCH_AVR32 17 /* AVR32 */
|
||||||
|
#define IH_ARCH_ST200 18 /* STMicroelectronics ST200 */
|
||||||
|
#define IH_ARCH_SANDBOX 19 /* Sandbox architecture (test only) */
|
||||||
|
#define IH_ARCH_NDS32 20 /* ANDES Technology - NDS32 */
|
||||||
|
#define IH_ARCH_OPENRISC 21 /* OpenRISC 1000 */
|
||||||
|
#define IH_ARCH_ARM64 22 /* ARM64 */
|
||||||
|
#define IH_ARCH_ARC 23 /* Synopsys DesignWare ARC */
|
||||||
|
#define IH_ARCH_X86_64 24 /* AMD x86_64, Intel and Via */
|
||||||
|
#define IH_ARCH_XTENSA 25 /* Xtensa */
|
||||||
|
#define IH_ARCH_RISCV 26 /* RISC-V */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Image Types
|
||||||
|
*
|
||||||
|
* "Standalone Programs" are directly runnable in the environment
|
||||||
|
* provided by U-Boot; it is expected that (if they behave
|
||||||
|
* well) you can continue to work in U-Boot after return from
|
||||||
|
* the Standalone Program.
|
||||||
|
* "OS Kernel Images" are usually images of some Embedded OS which
|
||||||
|
* will take over control completely. Usually these programs
|
||||||
|
* will install their own set of exception handlers, device
|
||||||
|
* drivers, set up the MMU, etc. - this means, that you cannot
|
||||||
|
* expect to re-enter U-Boot except by resetting the CPU.
|
||||||
|
* "RAMDisk Images" are more or less just data blocks, and their
|
||||||
|
* parameters (address, size) are passed to an OS kernel that is
|
||||||
|
* being started.
|
||||||
|
* "Multi-File Images" contain several images, typically an OS
|
||||||
|
* (Linux) kernel image and one or more data images like
|
||||||
|
* RAMDisks. This construct is useful for instance when you want
|
||||||
|
* to boot over the network using BOOTP etc., where the boot
|
||||||
|
* server provides just a single image file, but you want to get
|
||||||
|
* for instance an OS kernel and a RAMDisk image.
|
||||||
|
*
|
||||||
|
* "Multi-File Images" start with a list of image sizes, each
|
||||||
|
* image size (in bytes) specified by an "uint32_t" in network
|
||||||
|
* byte order. This list is terminated by an "(uint32_t)0".
|
||||||
|
* Immediately after the terminating 0 follow the images, one by
|
||||||
|
* one, all aligned on "uint32_t" boundaries (size rounded up to
|
||||||
|
* a multiple of 4 bytes - except for the last file).
|
||||||
|
*
|
||||||
|
* "Firmware Images" are binary images containing firmware (like
|
||||||
|
* U-Boot or FPGA images) which usually will be programmed to
|
||||||
|
* flash memory.
|
||||||
|
*
|
||||||
|
* "Script files" are command sequences that will be executed by
|
||||||
|
* U-Boot's command interpreter; this feature is especially
|
||||||
|
* useful when you configure U-Boot to use a real shell (hush)
|
||||||
|
* as command interpreter (=> Shell Scripts).
|
||||||
|
*
|
||||||
|
* The following are exposed to uImage header.
|
||||||
|
* New IDs *MUST* be appended at the end of the list and *NEVER*
|
||||||
|
* inserted for backward compatibility.
|
||||||
|
*/
|
||||||
|
#define IH_TYPE_INVALID 0 /* Invalid Image */
|
||||||
|
#define IH_TYPE_STANDALONE 1 /* Standalone Program */
|
||||||
|
#define IH_TYPE_KERNEL 2 /* OS Kernel Image */
|
||||||
|
#define IH_TYPE_RAMDISK 3 /* RAMDisk Image */
|
||||||
|
#define IH_TYPE_MULTI 4 /* Multi-File Image */
|
||||||
|
#define IH_TYPE_FIRMWARE 5 /* Firmware Image */
|
||||||
|
#define IH_TYPE_SCRIPT 6 /* Script file */
|
||||||
|
#define IH_TYPE_FILESYSTEM 7 /* Filesystem Image (any type) */
|
||||||
|
#define IH_TYPE_FLATDT 8 /* Binary Flat Device Tree Blob */
|
||||||
|
#define IH_TYPE_KWBIMAGE 9 /* Kirkwood Boot Image */
|
||||||
|
#define IH_TYPE_IMXIMAGE 10 /* Freescale IMXBoot Image */
|
||||||
|
#define IH_TYPE_UBLIMAGE 11 /* Davinci UBL Image */
|
||||||
|
#define IH_TYPE_OMAPIMAGE 12 /* TI OMAP Config Header Image */
|
||||||
|
#define IH_TYPE_AISIMAGE 13 /* TI Davinci AIS Image */
|
||||||
|
/* OS Kernel Image, can run from any load address */
|
||||||
|
#define IH_TYPE_KERNEL_NOLOAD 14
|
||||||
|
#define IH_TYPE_PBLIMAGE 15 /* Freescale PBL Boot Image */
|
||||||
|
#define IH_TYPE_MXSIMAGE 16 /* Freescale MXSBoot Image */
|
||||||
|
#define IH_TYPE_GPIMAGE 17 /* TI Keystone GPHeader Image */
|
||||||
|
#define IH_TYPE_ATMELIMAGE 18 /* ATMEL ROM bootable Image */
|
||||||
|
#define IH_TYPE_SOCFPGAIMAGE 19 /* Altera SOCFPGA CV/AV Preloader */
|
||||||
|
#define IH_TYPE_X86_SETUP 20 /* x86 setup.bin Image */
|
||||||
|
#define IH_TYPE_LPC32XXIMAGE 21 /* x86 setup.bin Image */
|
||||||
|
#define IH_TYPE_LOADABLE 22 /* A list of typeless images */
|
||||||
|
#define IH_TYPE_RKIMAGE 23 /* Rockchip Boot Image */
|
||||||
|
#define IH_TYPE_RKSD 24 /* Rockchip SD card */
|
||||||
|
#define IH_TYPE_RKSPI 25 /* Rockchip SPI image */
|
||||||
|
#define IH_TYPE_ZYNQIMAGE 26 /* Xilinx Zynq Boot Image */
|
||||||
|
#define IH_TYPE_ZYNQMPIMAGE 27 /* Xilinx ZynqMP Boot Image */
|
||||||
|
#define IH_TYPE_ZYNQMPBIF 28 /* Xilinx ZynqMP Boot Image (bif) */
|
||||||
|
#define IH_TYPE_FPGA 29 /* FPGA Image */
|
||||||
|
#define IH_TYPE_VYBRIDIMAGE 30 /* VYBRID .vyb Image */
|
||||||
|
#define IH_TYPE_TEE 31 /* Trusted Execution Environment OS Image */
|
||||||
|
#define IH_TYPE_FIRMWARE_IVT 32 /* Firmware Image with HABv4 IVT */
|
||||||
|
#define IH_TYPE_PMMC 33 /* TI Power Management Micro-Controller Firmware */
|
||||||
|
#define IH_TYPE_STM32IMAGE 34 /* STMicroelectronics STM32 Image */
|
||||||
|
#define IH_TYPE_SOCFPGAIMAGE_V1 35 /* Altera SOCFPGA A10 Preloader */
|
||||||
|
#define IH_TYPE_MTKIMAGE 36 /* MediaTek BootROM loadable Image */
|
||||||
|
#define IH_TYPE_IMX8MIMAGE 37 /* Freescale IMX8MBoot Image */
|
||||||
|
#define IH_TYPE_IMX8IMAGE 38 /* Freescale IMX8Boot Image */
|
||||||
|
#define IH_TYPE_COPRO 39 /* Coprocessor Image for remoteproc*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compression Types
|
||||||
|
*
|
||||||
|
* The following are exposed to uImage header.
|
||||||
|
* New IDs *MUST* be appended at the end of the list and *NEVER*
|
||||||
|
* inserted for backward compatibility.
|
||||||
|
*/
|
||||||
|
#define IH_COMP_NONE 0 /* No Compression Used */
|
||||||
|
#define IH_COMP_GZIP 1 /* gzip Compression Used */
|
||||||
|
#define IH_COMP_BZIP2 2 /* bzip2 Compression Used */
|
||||||
|
#define IH_COMP_LZMA 3 /* lzma Compression Used */
|
||||||
|
#define IH_COMP_LZO 4 /* lzo Compression Used */
|
||||||
|
#define IH_COMP_LZ4 5 /* lz4 Compression Used */
|
||||||
|
|
||||||
|
|
||||||
|
#define LZ4F_MAGIC 0x184D2204 /* LZ4 Magic Number */
|
||||||
|
#define IH_MAGIC 0x27051956 /* Image Magic Number */
|
||||||
|
#define IH_NMLEN 32 /* Image Name Length */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Magic values specific to "openwrt,uimage" partitions
|
||||||
|
*/
|
||||||
|
#define IH_MAGIC_OKLI 0x4f4b4c49 /* 'OKLI' */
|
||||||
|
#define FW_EDIMAX_OFFSET 20 /* Edimax Firmware Offset */
|
||||||
|
#define FW_MAGIC_EDIMAX 0x43535953 /* Edimax Firmware Magic Number */
|
||||||
|
|
||||||
|
#endif /* __UIMAGE_H__ */
|
133
6.12/target/linux/generic/files/include/linux/ar8216_platform.h
Normal file
133
6.12/target/linux/generic/files/include/linux/ar8216_platform.h
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
* AR8216 switch driver platform data
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef AR8216_PLATFORM_H
|
||||||
|
#define AR8216_PLATFORM_H
|
||||||
|
|
||||||
|
enum ar8327_pad_mode {
|
||||||
|
AR8327_PAD_NC = 0,
|
||||||
|
AR8327_PAD_MAC2MAC_MII,
|
||||||
|
AR8327_PAD_MAC2MAC_GMII,
|
||||||
|
AR8327_PAD_MAC_SGMII,
|
||||||
|
AR8327_PAD_MAC2PHY_MII,
|
||||||
|
AR8327_PAD_MAC2PHY_GMII,
|
||||||
|
AR8327_PAD_MAC_RGMII,
|
||||||
|
AR8327_PAD_PHY_GMII,
|
||||||
|
AR8327_PAD_PHY_RGMII,
|
||||||
|
AR8327_PAD_PHY_MII,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ar8327_clk_delay_sel {
|
||||||
|
AR8327_CLK_DELAY_SEL0 = 0,
|
||||||
|
AR8327_CLK_DELAY_SEL1,
|
||||||
|
AR8327_CLK_DELAY_SEL2,
|
||||||
|
AR8327_CLK_DELAY_SEL3,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8327_pad_cfg {
|
||||||
|
enum ar8327_pad_mode mode;
|
||||||
|
bool rxclk_sel;
|
||||||
|
bool txclk_sel;
|
||||||
|
bool pipe_rxclk_sel;
|
||||||
|
bool txclk_delay_en;
|
||||||
|
bool rxclk_delay_en;
|
||||||
|
bool sgmii_delay_en;
|
||||||
|
enum ar8327_clk_delay_sel txclk_delay_sel;
|
||||||
|
enum ar8327_clk_delay_sel rxclk_delay_sel;
|
||||||
|
bool mac06_exchange_dis;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ar8327_port_speed {
|
||||||
|
AR8327_PORT_SPEED_10 = 0,
|
||||||
|
AR8327_PORT_SPEED_100,
|
||||||
|
AR8327_PORT_SPEED_1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8327_port_cfg {
|
||||||
|
int force_link:1;
|
||||||
|
enum ar8327_port_speed speed;
|
||||||
|
int txpause:1;
|
||||||
|
int rxpause:1;
|
||||||
|
int duplex:1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8327_sgmii_cfg {
|
||||||
|
u32 sgmii_ctrl;
|
||||||
|
bool serdes_aen;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8327_led_cfg {
|
||||||
|
u32 led_ctrl0;
|
||||||
|
u32 led_ctrl1;
|
||||||
|
u32 led_ctrl2;
|
||||||
|
u32 led_ctrl3;
|
||||||
|
bool open_drain;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ar8327_led_num {
|
||||||
|
AR8327_LED_PHY0_0 = 0,
|
||||||
|
AR8327_LED_PHY0_1,
|
||||||
|
AR8327_LED_PHY0_2,
|
||||||
|
AR8327_LED_PHY1_0,
|
||||||
|
AR8327_LED_PHY1_1,
|
||||||
|
AR8327_LED_PHY1_2,
|
||||||
|
AR8327_LED_PHY2_0,
|
||||||
|
AR8327_LED_PHY2_1,
|
||||||
|
AR8327_LED_PHY2_2,
|
||||||
|
AR8327_LED_PHY3_0,
|
||||||
|
AR8327_LED_PHY3_1,
|
||||||
|
AR8327_LED_PHY3_2,
|
||||||
|
AR8327_LED_PHY4_0,
|
||||||
|
AR8327_LED_PHY4_1,
|
||||||
|
AR8327_LED_PHY4_2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ar8327_led_mode {
|
||||||
|
AR8327_LED_MODE_HW = 0,
|
||||||
|
AR8327_LED_MODE_SW,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ar8327_led_info {
|
||||||
|
const char *name;
|
||||||
|
const char *default_trigger;
|
||||||
|
bool active_low;
|
||||||
|
enum ar8327_led_num led_num;
|
||||||
|
enum ar8327_led_mode mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define AR8327_LED_INFO(_led, _mode, _name) { \
|
||||||
|
.name = (_name), \
|
||||||
|
.led_num = AR8327_LED_ ## _led, \
|
||||||
|
.mode = AR8327_LED_MODE_ ## _mode \
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ar8327_platform_data {
|
||||||
|
struct ar8327_pad_cfg *pad0_cfg;
|
||||||
|
struct ar8327_pad_cfg *pad5_cfg;
|
||||||
|
struct ar8327_pad_cfg *pad6_cfg;
|
||||||
|
struct ar8327_sgmii_cfg *sgmii_cfg;
|
||||||
|
struct ar8327_port_cfg port0_cfg;
|
||||||
|
struct ar8327_port_cfg port6_cfg;
|
||||||
|
struct ar8327_led_cfg *led_cfg;
|
||||||
|
|
||||||
|
int (*get_port_link)(unsigned port);
|
||||||
|
|
||||||
|
unsigned num_leds;
|
||||||
|
const struct ar8327_led_info *leds;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* AR8216_PLATFORM_H */
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2008 Atheros Communications Inc.
|
||||||
|
* Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
* Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
|
||||||
|
* Copyright (c) 2010 Daniel Golle <daniel.golle@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_ATH5K_PLATFORM_H
|
||||||
|
#define _LINUX_ATH5K_PLATFORM_H
|
||||||
|
|
||||||
|
#define ATH5K_PLAT_EEP_MAX_WORDS 2048
|
||||||
|
|
||||||
|
struct ath5k_platform_data {
|
||||||
|
u16 *eeprom_data;
|
||||||
|
u8 *macaddr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _LINUX_ATH5K_PLATFORM_H */
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2008 Atheros Communications Inc.
|
||||||
|
* Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
* Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_ATH9K_PLATFORM_H
|
||||||
|
#define _LINUX_ATH9K_PLATFORM_H
|
||||||
|
|
||||||
|
#define ATH9K_PLAT_EEP_MAX_WORDS 2048
|
||||||
|
|
||||||
|
struct ath9k_platform_data {
|
||||||
|
const char *eeprom_name;
|
||||||
|
|
||||||
|
u16 eeprom_data[ATH9K_PLAT_EEP_MAX_WORDS];
|
||||||
|
u8 *macaddr;
|
||||||
|
|
||||||
|
int led_pin;
|
||||||
|
u32 gpio_mask;
|
||||||
|
u32 gpio_val;
|
||||||
|
|
||||||
|
u32 bt_active_pin;
|
||||||
|
u32 bt_priority_pin;
|
||||||
|
u32 wlan_active_pin;
|
||||||
|
|
||||||
|
bool endian_check;
|
||||||
|
bool is_clk_25mhz;
|
||||||
|
bool tx_gain_buffalo;
|
||||||
|
bool disable_2ghz;
|
||||||
|
bool disable_5ghz;
|
||||||
|
bool led_active_high;
|
||||||
|
|
||||||
|
int (*get_mac_revision)(void);
|
||||||
|
int (*external_reset)(void);
|
||||||
|
|
||||||
|
bool use_eeprom;
|
||||||
|
|
||||||
|
int num_leds;
|
||||||
|
const struct gpio_led *leds;
|
||||||
|
|
||||||
|
unsigned num_btns;
|
||||||
|
const struct gpio_keys_button *btns;
|
||||||
|
unsigned btn_poll_interval;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _LINUX_ATH9K_PLATFORM_H */
|
18
6.12/target/linux/generic/files/include/linux/mtd/mtk_bmt.h
Normal file
18
6.12/target/linux/generic/files/include/linux/mtd/mtk_bmt.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef __MTK_BMT_H
|
||||||
|
#define __MTK_BMT_H
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_NAND_MTK_BMT
|
||||||
|
int mtk_bmt_attach(struct mtd_info *mtd);
|
||||||
|
void mtk_bmt_detach(struct mtd_info *mtd);
|
||||||
|
#else
|
||||||
|
static inline int mtk_bmt_attach(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void mtk_bmt_detach(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
121
6.12/target/linux/generic/files/include/linux/myloader.h
Normal file
121
6.12/target/linux/generic/files/include/linux/myloader.h
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Compex's MyLoader specific definitions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006-2008 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _MYLOADER_H_
|
||||||
|
#define _MYLOADER_H_
|
||||||
|
|
||||||
|
/* Myloader specific magic numbers */
|
||||||
|
#define MYLO_MAGIC_SYS_PARAMS 0x20021107
|
||||||
|
#define MYLO_MAGIC_PARTITIONS 0x20021103
|
||||||
|
#define MYLO_MAGIC_BOARD_PARAMS 0x20021103
|
||||||
|
|
||||||
|
/* Vendor ID's (seems to be same as the PCI vendor ID's) */
|
||||||
|
#define VENID_COMPEX 0x11F6
|
||||||
|
|
||||||
|
/* Devices based on the ADM5120 */
|
||||||
|
#define DEVID_COMPEX_NP27G 0x0078
|
||||||
|
#define DEVID_COMPEX_NP28G 0x044C
|
||||||
|
#define DEVID_COMPEX_NP28GHS 0x044E
|
||||||
|
#define DEVID_COMPEX_WP54Gv1C 0x0514
|
||||||
|
#define DEVID_COMPEX_WP54G 0x0515
|
||||||
|
#define DEVID_COMPEX_WP54AG 0x0546
|
||||||
|
#define DEVID_COMPEX_WPP54AG 0x0550
|
||||||
|
#define DEVID_COMPEX_WPP54G 0x0555
|
||||||
|
|
||||||
|
/* Devices based on the Atheros AR2317 */
|
||||||
|
#define DEVID_COMPEX_NP25G 0x05E6
|
||||||
|
#define DEVID_COMPEX_WPE53G 0x05DC
|
||||||
|
|
||||||
|
/* Devices based on the Atheros AR71xx */
|
||||||
|
#define DEVID_COMPEX_WP543 0x0640
|
||||||
|
#define DEVID_COMPEX_WPE72 0x0672
|
||||||
|
|
||||||
|
/* Devices based on the IXP422 */
|
||||||
|
#define DEVID_COMPEX_WP18 0x047E
|
||||||
|
#define DEVID_COMPEX_NP18A 0x0489
|
||||||
|
|
||||||
|
/* Other devices */
|
||||||
|
#define DEVID_COMPEX_NP26G8M 0x03E8
|
||||||
|
#define DEVID_COMPEX_NP26G16M 0x03E9
|
||||||
|
|
||||||
|
struct mylo_partition {
|
||||||
|
uint16_t flags; /* partition flags */
|
||||||
|
uint16_t type; /* type of the partition */
|
||||||
|
uint32_t addr; /* relative address of the partition from the
|
||||||
|
flash start */
|
||||||
|
uint32_t size; /* size of the partition in bytes */
|
||||||
|
uint32_t param; /* if this is the active partition, the
|
||||||
|
MyLoader load code to this address */
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PARTITION_FLAG_ACTIVE 0x8000 /* this is the active partition,
|
||||||
|
* MyLoader loads firmware from here */
|
||||||
|
#define PARTITION_FLAG_ISRAM 0x2000 /* FIXME: this is a RAM partition? */
|
||||||
|
#define PARTIIION_FLAG_RAMLOAD 0x1000 /* FIXME: load this partition into the RAM? */
|
||||||
|
#define PARTITION_FLAG_PRELOAD 0x0800 /* the partition data preloaded to RAM
|
||||||
|
* before decompression */
|
||||||
|
#define PARTITION_FLAG_LZMA 0x0100 /* partition data compressed by LZMA */
|
||||||
|
#define PARTITION_FLAG_HAVEHDR 0x0002 /* the partition data have a header */
|
||||||
|
|
||||||
|
#define PARTITION_TYPE_FREE 0
|
||||||
|
#define PARTITION_TYPE_USED 1
|
||||||
|
|
||||||
|
#define MYLO_MAX_PARTITIONS 8 /* maximum number of partitions in the
|
||||||
|
partition table */
|
||||||
|
|
||||||
|
struct mylo_partition_table {
|
||||||
|
uint32_t magic; /* must be MYLO_MAGIC_PARTITIONS */
|
||||||
|
uint32_t res0; /* unknown/unused */
|
||||||
|
uint32_t res1; /* unknown/unused */
|
||||||
|
uint32_t res2; /* unknown/unused */
|
||||||
|
struct mylo_partition partitions[MYLO_MAX_PARTITIONS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mylo_partition_header {
|
||||||
|
uint32_t len; /* length of the partition data */
|
||||||
|
uint32_t crc; /* CRC value of the partition data */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mylo_system_params {
|
||||||
|
uint32_t magic; /* must be MYLO_MAGIC_SYS_PARAMS */
|
||||||
|
uint32_t res0;
|
||||||
|
uint32_t res1;
|
||||||
|
uint32_t mylo_ver;
|
||||||
|
uint16_t vid; /* Vendor ID */
|
||||||
|
uint16_t did; /* Device ID */
|
||||||
|
uint16_t svid; /* Sub Vendor ID */
|
||||||
|
uint16_t sdid; /* Sub Device ID */
|
||||||
|
uint32_t rev; /* device revision */
|
||||||
|
uint32_t fwhi;
|
||||||
|
uint32_t fwlo;
|
||||||
|
uint32_t tftp_addr;
|
||||||
|
uint32_t prog_start;
|
||||||
|
uint32_t flash_size; /* size of boot FLASH in bytes */
|
||||||
|
uint32_t dram_size; /* size of onboard RAM in bytes */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mylo_eth_addr {
|
||||||
|
uint8_t mac[6];
|
||||||
|
uint8_t csum[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MYLO_ETHADDR_COUNT 8 /* maximum number of ethernet address
|
||||||
|
in the board parameters */
|
||||||
|
|
||||||
|
struct mylo_board_params {
|
||||||
|
uint32_t magic; /* must be MYLO_MAGIC_BOARD_PARAMS */
|
||||||
|
uint32_t res0;
|
||||||
|
uint32_t res1;
|
||||||
|
uint32_t res2;
|
||||||
|
struct mylo_eth_addr addr[MYLO_ETHADDR_COUNT];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _MYLOADER_H_*/
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* ADM6996 GPIO platform data
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License v2 as published by the
|
||||||
|
* Free Software Foundation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __PLATFORM_ADM6996_GPIO_H
|
||||||
|
#define __PLATFORM_ADM6996_GPIO_H
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
|
||||||
|
enum adm6996_model {
|
||||||
|
ADM6996FC = 1,
|
||||||
|
ADM6996M = 2,
|
||||||
|
ADM6996L = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct adm6996_gpio_platform_data {
|
||||||
|
u8 eecs;
|
||||||
|
u8 eesk;
|
||||||
|
u8 eedi;
|
||||||
|
enum adm6996_model model;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
106
6.12/target/linux/generic/files/include/linux/routerboot.h
Normal file
106
6.12/target/linux/generic/files/include/linux/routerboot.h
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Mikrotik's RouterBOOT definitions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ROUTERBOOT_H
|
||||||
|
#define _ROUTERBOOT_H
|
||||||
|
|
||||||
|
#define RB_MAC_SIZE 6
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Magic numbers
|
||||||
|
*/
|
||||||
|
#define RB_MAGIC_HARD 0x64726148 /* "Hard" */
|
||||||
|
#define RB_MAGIC_SOFT 0x74666F53 /* "Soft" */
|
||||||
|
#define RB_MAGIC_DAWN 0x6E776144 /* "Dawn" */
|
||||||
|
|
||||||
|
#define RB_ID_TERMINATOR 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ID values for Hardware settings
|
||||||
|
*/
|
||||||
|
#define RB_ID_HARD_01 1
|
||||||
|
#define RB_ID_HARD_02 2
|
||||||
|
#define RB_ID_FLASH_INFO 3
|
||||||
|
#define RB_ID_MAC_ADDRESS_PACK 4
|
||||||
|
#define RB_ID_BOARD_NAME 5
|
||||||
|
#define RB_ID_BIOS_VERSION 6
|
||||||
|
#define RB_ID_HARD_07 7
|
||||||
|
#define RB_ID_SDRAM_TIMINGS 8
|
||||||
|
#define RB_ID_DEVICE_TIMINGS 9
|
||||||
|
#define RB_ID_SOFTWARE_ID 10
|
||||||
|
#define RB_ID_SERIAL_NUMBER 11
|
||||||
|
#define RB_ID_HARD_12 12
|
||||||
|
#define RB_ID_MEMORY_SIZE 13
|
||||||
|
#define RB_ID_MAC_ADDRESS_COUNT 14
|
||||||
|
#define RB_ID_HW_OPTIONS 21
|
||||||
|
#define RB_ID_WLAN_DATA 22
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ID values for Software settings
|
||||||
|
*/
|
||||||
|
#define RB_ID_UART_SPEED 1
|
||||||
|
#define RB_ID_BOOT_DELAY 2
|
||||||
|
#define RB_ID_BOOT_DEVICE 3
|
||||||
|
#define RB_ID_BOOT_KEY 4
|
||||||
|
#define RB_ID_CPU_MODE 5
|
||||||
|
#define RB_ID_FW_VERSION 6
|
||||||
|
#define RB_ID_SOFT_07 7
|
||||||
|
#define RB_ID_SOFT_08 8
|
||||||
|
#define RB_ID_BOOT_PROTOCOL 9
|
||||||
|
#define RB_ID_SOFT_10 10
|
||||||
|
#define RB_ID_SOFT_11 11
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UART_SPEED values
|
||||||
|
*/
|
||||||
|
#define RB_UART_SPEED_115200 0
|
||||||
|
#define RB_UART_SPEED_57600 1
|
||||||
|
#define RB_UART_SPEED_38400 2
|
||||||
|
#define RB_UART_SPEED_19200 3
|
||||||
|
#define RB_UART_SPEED_9600 4
|
||||||
|
#define RB_UART_SPEED_4800 5
|
||||||
|
#define RB_UART_SPEED_2400 6
|
||||||
|
#define RB_UART_SPEED_1200 7
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BOOT_DELAY values
|
||||||
|
*/
|
||||||
|
#define RB_BOOT_DELAY_0SEC 0
|
||||||
|
#define RB_BOOT_DELAY_1SEC 1
|
||||||
|
#define RB_BOOT_DELAY_2SEC 2
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BOOT_DEVICE values
|
||||||
|
*/
|
||||||
|
#define RB_BOOT_DEVICE_ETHER 0
|
||||||
|
#define RB_BOOT_DEVICE_NANDETH 1
|
||||||
|
#define RB_BOOT_DEVICE_ETHONCE 2
|
||||||
|
#define RB_BOOT_DEVICE_NANDONLY 3
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BOOT_KEY values
|
||||||
|
*/
|
||||||
|
#define RB_BOOT_KEY_ANY 0
|
||||||
|
#define RB_BOOT_KEY_DEL 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CPU_MODE values
|
||||||
|
*/
|
||||||
|
#define RB_CPU_MODE_POWERSAVE 0
|
||||||
|
#define RB_CPU_MODE_REGULAR 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BOOT_PROTOCOL values
|
||||||
|
*/
|
||||||
|
#define RB_BOOT_PROTOCOL_BOOTP 0
|
||||||
|
#define RB_BOOT_PROTOCOL_DHCP 1
|
||||||
|
|
||||||
|
#endif /* _ROUTERBOOT_H */
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Platform data definition for the rt2x00 driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _RT2X00_PLATFORM_H
|
||||||
|
#define _RT2X00_PLATFORM_H
|
||||||
|
|
||||||
|
struct rt2x00_platform_data {
|
||||||
|
char *eeprom_file_name;
|
||||||
|
const u8 *mac_address;
|
||||||
|
|
||||||
|
int disable_2ghz;
|
||||||
|
int disable_5ghz;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _RT2X00_PLATFORM_H */
|
42
6.12/target/linux/generic/files/include/linux/rtl8366.h
Normal file
42
6.12/target/linux/generic/files/include/linux/rtl8366.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Platform data definition for the Realtek RTL8366RB/S ethernet switch driver
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _RTL8366_H
|
||||||
|
#define _RTL8366_H
|
||||||
|
|
||||||
|
#define RTL8366_DRIVER_NAME "rtl8366"
|
||||||
|
#define RTL8366S_DRIVER_NAME "rtl8366s"
|
||||||
|
#define RTL8366RB_DRIVER_NAME "rtl8366rb"
|
||||||
|
|
||||||
|
struct rtl8366_smi;
|
||||||
|
|
||||||
|
enum rtl8366_type {
|
||||||
|
RTL8366_TYPE_UNKNOWN,
|
||||||
|
RTL8366_TYPE_S,
|
||||||
|
RTL8366_TYPE_RB,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rtl8366_initval {
|
||||||
|
unsigned reg;
|
||||||
|
u16 val;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rtl8366_platform_data {
|
||||||
|
unsigned gpio_sda;
|
||||||
|
unsigned gpio_sck;
|
||||||
|
void (*hw_reset)(struct rtl8366_smi *smi, bool active);
|
||||||
|
|
||||||
|
unsigned num_initvals;
|
||||||
|
struct rtl8366_initval *initvals;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata);
|
||||||
|
|
||||||
|
#endif /* _RTL8366_H */
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue