[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aP8xPOG68qL2IQ-A@smile.fi.intel.com>
Date: Mon, 27 Oct 2025 10:45:48 +0200
From: Andy Shevchenko <andriy.shevchenko@...ux.intel.com>
To: Christian Eggers <ceggers@...i.de>
Cc: Javier Carrasco <javier.carrasco.cruz@...il.com>,
	Mark Brown <broonie@...nel.org>,
	Krystian Garbaciak <krystian.garbaciak@...semi.com>,
	linux-kernel@...r.kernel.org
Subject: Re: regmap: Modeling a shared page selection register
On Sat, Oct 25, 2025 at 06:22:26PM +0200, Christian Eggers wrote:
> Hi,
> 
> I try to develop a driver for the JLSemi JL5104 Ethernet switch (no public
> documentation  available, sorry).  The device is attached via the MDIO bus 
> and offers different register accesses (it occupies 8 PHY addresses):
> 
> 1. Direct access (on each PHY address for the first 16 registers)
> 2. Paged access (only on PHY address 0, last 16 registers are paged, 
>    register 31 is used for page selection)
> 
> There are also 'indirectly' accessed registers, but that is not part of
> this question.
> 
> I would like to build regmap for this like the following:
> 
> ---------------------------------------8<---------------------------------------------------
> /* Regmap addresses are built from the phy address, the page number and the register address */
> #define JL51XX_PHY_PHY_MASK		GENMASK(25, 21)  /* PHY address, 5 bit */
> #define JL51XX_PHY_PAGE_MASK		GENMASK(20,  5)  /* Page address within PHY, 16 bit */
> #define JL51XX_PHY_REG_MASK		GENMASK( 4,  0)  /* Register within page, 5 bit */
> 
> 
> #define JL51XX_PHY0_BASE		0x00000000U
> #define JL51XX_PHY_BLOCK_SIZE		0x00200000U
> #define JL51XX_PHY_BASE(PHY)		(JL51XX_PHY0_BASE + (PHY) * JL51XX_PHY_BLOCK_SIZE)
> #define JL51XX_PHY_END(PHY)		(JL51XX_PHY_BASE(PHY) + JL51XX_PHY_BLOCK_SIZE - 1)
> 
> /* Page selection register, shared between all PHYs !!! */
> #define JL51XX_PHY_PAGE_SEL			31
Ranges are sets of virtual (subject to page select) registers. If you have a
common subset of the register with paging available, it should be a _single_
range. I.o.w. _a_ range per set of the registers with a unique selector.
See, for example, drivers/pinctrl/pinctrl-cy8c95x0.c how it does it.
The HW has more selectors (i.e. PWM settings) than what is currently
implemented, but you can get an idea.
> #define JL51XX_PAGED_REGMAP_RANGE(PHY) \
> {									\
> 	.name = "JL51xx PHY" __stringify(PHY) " paged registers", 	\
> 	.range_min	= JL51XX_PHY_BASE(PHY), 			\
> 	.range_max	= JL51XX_PHY_END(PHY), 				\
> 	/*.selector_reg	= JL51XX_PHY_BASE(PHY) + JL51XX_PHY_PAGE_SEL, */				\
> 	.selector_reg	= JL51XX_PHY_PAGE_SEL, 				\
> 	.selector_mask	= GENMASK(15, 0), 				\
> 	.selector_shift	= 0,						\
> 	.window_start	= JL51XX_PHY_BASE(PHY),				\
> 	.window_len	= 32,						\
> }
> static const struct regmap_range_cfg jl51xx_paged_regmap_ranges[] = {
> 	JL51XX_PAGED_REGMAP_RANGE(0),
> 	JL51XX_PAGED_REGMAP_RANGE(1),
> 	JL51XX_PAGED_REGMAP_RANGE(2),
> 	JL51XX_PAGED_REGMAP_RANGE(3),
> 	JL51XX_PAGED_REGMAP_RANGE(4),
> 	JL51XX_PAGED_REGMAP_RANGE(5),
> 	JL51XX_PAGED_REGMAP_RANGE(6),
> 	JL51XX_PAGED_REGMAP_RANGE(7),
> };
> 
> static const struct regmap_config jl51xx_paged_regmap_config = {
> 	.name = "JL51xx direct accessible (paged) registers",
> 
> 	.reg_bits	= 32,  // roundup(phy[5] + page[8] + reg[5], 8)
> 	.reg_stride	= 1,
> 	.val_bits	= 16,
> 	.readable_reg	= jl51xx_paged_regmap_readable_reg,
> 	.volatile_reg	= jl51xx_paged_regmap_volatile_reg,
> 	.lock 		= jl51xx_regmap_lock,
> 	.unlock		= jl51xx_regmap_unlock,
> 	.max_register	= JL51XX_PHY0_BASE
> 			  + JL51XX_NUM_PHYS * JL51XX_PHY_BLOCK_SIZE - 1,
> 	.cache_type	= REGCACHE_MAPLE,
> 	.ranges 	= jl51xx_paged_regmap_ranges,
> 	.num_ranges 	= ARRAY_SIZE(jl51xx_paged_regmap_ranges),
> };
> 
> ...
> 	jl51xx->regmap_paged = devm_regmap_init(dev,
> 						&jl51xx_regmap_mdio_c22_bus,
> 						jl51xx, &jl51xx_paged_regmap_config);
> 
> --------------------------------------->8---------------------------------------------------
> 
> The problem is, that devm_regmap_init() prints the following error:
> 
> > jl51xx 2188000.ethernet-1:00: Range 0: selector for 1 in window
> 
> So, regmap complains because the selector_reg (JL51XX_PHY_PAGE_SEL) of one
> range appears within the range_min..rage_max area of another/all ranges 
> (PHYs in my case). But this is how the hardware actually works.
Of course, the idea of paging is that you have a single address space which
maps on per-page base. Also consider leaving the default page address be
untouched (meaning that most likely you will have direct access to the
registers _and_ via virtual address space to the "default" page).
> Of course, I could access the JL51XX_PHY_PAGE_SEL also via unique aliases
> which lay inside the individual ranges (see the line which is commented out). 
> But this would break caching (as all aliases of the PAGE_SEL register share
> the same resource in hardware). Caching this particular register is the 
> most significant speedup I can imagine for this device, as it has to be 
> accessed for almost every other register access.
> 
> On the other hand, the PAGE_SEL register is not meaningful for the first
> 16 registers of each PHY.  So, coding individual cached page selection logic
> (within the driver, without using regmap ranges at all) could avoid
> writing the PAGE_SEL register when accessing registers which are
> not dependent on the page selection logic.
> 
> I have also tried to model this using only a single regmap_range (for
> all PHYs). But then, the 'PHY address' part of the regmap_adress gets
> lost within regmap's page selection logic (the PHY address becomes
> part of the page address, until it is by the masked out by the 
> 'selector_mask').
Perhaps it's due to the detail I just described in the previous paragraph
(of my answer).
> After commenting out the "selector / range" checking logic in __regmap_init(),
> everything seems to work fine:
> 
> #if 0
> 			if (range_cfg->range_min <= sel_reg &&
> 			    sel_reg <= range_cfg->range_max) {
> 				dev_err(map->dev,
> 					"Range %d: selector for %d in window\n",
> 					i, j);
> 				goto err_range;
> 			}
> #endif
> 
> This check dates back since 2012, so it feels wrong to me to simply remove it:
> 6863ca622759 ("regmap: Add support for register indirect addressing.")
Right, the logic behind paging implementation in regmap is a bit tricky
(and has some known non-critical bugs, FWIW). There is no documentation
and it appears that most of the drivers using it, do it wrongly.
I am a bit lazy to spend time for googling, but you may try to exercise the
lore.kernel.org search with my name and things related to the regmap paging /
selector and find some discussions in the past.
> So should I better open coding the page selection stuff into my driver?
-- 
With Best Regards,
Andy Shevchenko
Powered by blists - more mailing lists
 
