Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,14 +272,15 @@ cJSON or prints diagnostics in those shared files must sit inside
`tools/gen_*_padmux.py` and are in `.clang-format-hook-exclude`. Novatek's
generator is the odd one: the NA51089 SDK has no pin table, only the
driver's `pinmux_config_*()` code, so it compiles that code on the host with
every register write logged and runs it -- it needs a C compiler and the
SDK, and its `--selftest` needs only the compiler.
every register write logged and runs it -- once per die, NA51089, NA51055
and NA51084 -- and it needs a C compiler and the SDK; its `--selftest`
needs only the compiler.
`ipchw_padmux_by_func/_by_prefix/_by_pad` are safe to call concurrently once the SoC has been detected;
detection itself is not, so call `getchipname()` once at startup.
`ipchw_padmux_get/_set` touch `/dev/mem` and must stay on one thread.
- `gpio get/set/scan` reach a GPIO controller per vendor: HiSilicon's bank
words in `src/reginfo.c` (`get_chip_gpio_adress()`), SigmaStar Infinity6C's
per-pad registers in `src/hal/sstar_gpio.c`, and the NA51089's four
per-pad registers in `src/hal/sstar_gpio.c`, and the Novatek dies' four
DATA/DIR/SET/CLR banks in `src/hal/novatek_gpio.c`. Ingenic has none, so
only `gpio mux` and `reginfo --pads` work there.
- `src/fake_symbols.c` holds empty definitions of HiSilicon SDK audio symbols,
Expand Down
3 changes: 2 additions & 1 deletion docs/gpio.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ GPIO pad, decoded from the pad's own one-byte register into what it reads
(`in`), what the SoC is driving (`out`) and whether it drives at all (`oe_n`,
1 meaning input/Hi-Z) — rather than the per-group data and direction words.

On Novatek (NT98562/NT98566) the pad is the kernel's GPIO number as well, and
On Novatek (NT98562/NT98566, NT9852x, NT98528/NT98529) the pad is the
kernel's GPIO number as well, and
its name is accepted too: `gpio get P_GPIO22`, `gpio get 54` and
`/sys/class/gpio/gpio54` are one wire (`P_GPIO(n)` is `0x20 + n`, and so on in
steps of `0x20` through C, P, S, L, D, H, A and DSI). The `5_6` form is refused
Expand Down
95 changes: 67 additions & 28 deletions docs/padmux.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,24 @@ peripheral becomes an input.

### Novatek -- a field per peripheral, and a gate per pad

NA51089 (NT98562, NT98566) is SigmaStar's arrangement with a second lock on
it. In the TOP block at `0xF0010000` each peripheral has a location field --
NA51089 (NT98562, NT98566), NA51055 (NT9852x) and NA51084 (NT98528,
NT98529) are SigmaStar's arrangement with a second lock on it. In the TOP block at `0xF0010000` each peripheral has a location field --
`REG5[7:6]` is I2C3, and 1, 2 and 3 put it on `P_GPIO21/22`, `C_GPIO11/12`
or `DSI_GPIO8/9` -- and each pad has a **gate bit** in one of eight bitmaps
at `TOP + 0xA0..0xE8`, where 1 keeps the pad a GPIO whatever any field says.
The vendor's `pinmux_config_*()` sets both, every time: the field, then
`GPIO_ID_EMUM_FUNC` into the gate of each pad it uses.
`GPIO_ID_EMUM_FUNC` into the gate of each pad it uses. NA51055 and NA51084
have the same TOP layout without the DSI group, and more S, L and D pads.

Which die it is comes from the TOP chip-ID word (`/proc/nvt_info/nvt_pinmux/
chip_id`), and it names the die rather than the part. NT98562 and NT98566 are
both `0x7021` and differ in an eFuse package word whose decoding the SDK
ships as a binary, so both report as NT98566; `0x5021` is NA51084, which
u-boot's clock code shows to be the NT98528/NT98529 die (the 529 told apart
by OTP), and reports as NT98528. `0x4821` (NA51055, under which the SDK
mentions both NT98520 and NT98525) and `0xBC21` (NA51090, NT98636) take their
name from the device tree, as before; NA51103 (`0x8B20`) is NT98332G. The
last two have no table: see below.

So a pad's alternatives are **claims**: a name and the field values under
which the pad carries it. `get()` takes the first claim that holds, with the
Expand All @@ -316,10 +327,17 @@ the gate. Consequences:
PHY's management pin, clears `ETH` and so ends RMII.
- **The parallel and CCIR sensor modes ignore the gate.** They put pixel data
on the HSI pads while the vendor writes those gates to GPIO, so those claims
hold through it (`NVT_UNGATED` in the table). `gpio mux H_GPIO4 GPIO` on such
hold through it (`NVT_UNGATED` in the table), and `set()` writes the gate to
GPIO for them as the vendor does -- otherwise a gated claim under the same
fields would win, CCIR8 sync over CCIR8 data on NA51055's `P_GPIO9`. `gpio mux H_GPIO4 GPIO` on such
a board has to zero `SENSOR` to mean anything, and does -- the whole parallel
bus goes with it. The MIPI lanes on the same pads are the opposite: the gate
cleared and no field at all.
- **The primary LCD** is muxed by `pinmux_select_primary_lcd()`, which the
display driver reaches through `pinmux_set_host()`: `REG2` `LCD_TYPE` and
the L and DSI gates. Its claims are named by type -- `LCD_PARALLE_RGB565`
on the colour pins, `LCD_DE_ENABLE` on the DE pin. TV and HDMI have no pads
to mux on these parts; `pinmux_set_host()` answers `E_ID` for both.
- Pads are the kernel's GPIO numbers and names: `C_GPIO(n)` is `n`,
`P_GPIO(n)` is `0x20 + n`, and so on through S, L, D, H, A and DSI. `gpio`
takes the number or the name (`P_GPIO22`, and the vendor's `MC17` and
Expand Down Expand Up @@ -391,29 +409,44 @@ tools/gen_novatek_padmux.py --sdk /path/to/na51089_linux_sdk \
There is no datasheet and no pin list in the NA51089 SDK, and
`na51089_pinmux_host.c` is 2200 lines of nested `if`/`else if`/`switch` with
the conflict checks interleaved. So the generator **runs** it: it cuts the
`pinmux_config_*()` functions out, wraps every `top_regN.bit.FIELD = value`
in a macro that logs the write, compiles that on the host against the SDK's
own `top_reg.h`/`top.h`, and calls every function with every combination of
up to four of its options (96,550 calls, about half a minute). A combination the
vendor refuses is retried after one option of another group, which is the
only way MIPI data lanes 1-3 appear at all: `pinmux_config_mipi_lvds()`
refuses them until the sensor group is in CSI mode.

The source is read for one thing: which braces enclose each write. A gate
`pinmux_config_*()` functions and `pinmux_select_primary_lcd()` out, wraps
every `top_regN.bit.FIELD = value` in a macro that logs the write, compiles
that on the host against the SDK's own `top_reg.h`/`top.h`, and calls every
function with every combination of up to four of its options -- the LCD as
each type with every set of feature flags. A combination the vendor refuses
is retried after one option of another group, which is the only way MIPI data
lanes 1-3 appear at all: `pinmux_config_mipi_lvds()` refuses them until the
sensor group is in CSI mode.

The same SDK carries the NA51055 driver, which serves NA51055 and NA51084 and
branches on `nvt_get_chip_id()` -- NA51084 alone has I2C4/5, SPI4/5 and an
RGMII. The harness answers that call with a constant, so the driver is run
once as each die and each gets its own table: about 260,000 calls for the
three, a minute and a half.

The source is read for one thing: which braces enclose each write, and which
options the `& PIN_...` test or `case` opening each brace names. A gate
write's conditions are the field writes the same call made in the blocks
around it -- `SEN_MCLK=1` for `S_GPIO0`, not the sensor mode a sibling block
of the same call selected. Every state the vendor code produced is then
of the same call selected. A field an enclosing block wrote and a nested
block overwrote before the gate write takes the nested value:
`pinmux_select_primary_lcd()` resets `LCD_TYPE` at its top and sets it per
type in a `switch` before handing the pads over. Every state the vendor code produced is then
replayed against the table, and three answers are acceptable: the pad reads
as what the code meant; it reads as nothing because a later write of the same
call moved its field away; or two claims hold, and the table carries a more
specific claim for the one the code meant. Anything else is fatal.

`top.h` and `top.csv` disagree with the code in three places, and
`ANNOTATION_ERRATA` in the generator says which side wins and why: the csv's
`top.h` and `top.csv` disagree with the code, and `ANNOTATION_ERRATA` in the
generator says, per driver, which side wins and why. On NA51089: the csv's
`P_GPIO29` for SIF CH2_2 (no such pad, the code uses `P_GPIO19`), `top.h`'s
`P_GPIO14` for PICNT2_1 (the code and the csv use `L_GPIO1`), and
`PIN_MISC_CFG_SP2CLK_3RD`, which does nothing at all, because
`pinmux_config_misc()` tests `PIN_SENSOR_CFG_SP2CLK_3RD` in its place.
`pinmux_config_misc()` tests `PIN_SENSOR_CFG_SP2CLK_3RD` in its place. On
NA51055 the MISC annotations are NA51089's, copied, and name pads its code
does not use; PWM8..11's HSI location is commented out of the driver ("52x
compatible - donot support this function"); and `P_GPIO14` for PICNT2_1 is
right there, which is why the errata are per driver.

Both generators take `--verify <header>`, which re-derives and diffs instead
of writing. That needs the SDK, so CI cannot run it;
Expand Down Expand Up @@ -472,21 +505,27 @@ in.
- **Other Ingenic parts.** T21, T23, T31 and T40 have tables. T10, T20, T30 and
T41 do not, and would each need a source of one of the three kinds above --
which, for the parts checked so far, does not exist in the vendor releases.
- **Other Novatek parts, and NA51089's display.** NA51055, NA51084, NA51090
and NA51103 have no table: their chip IDs are recognised and get none.
LCD, TV and HDMI on NA51089 are muxed by `pinmux_select_primary_lcd()`
from state the config functions only stash, and are not modelled; a pad
given to the display reads as cleared with nothing claiming it.
- **Novatek states two options create at once.** In 19,185 of the replayed
calls some pad is claimed twice -- a 12-bit sensor's pixel clock and MCLK2
both on `S_GPIO1`, say. The vendor code checks for neither and no board
does it. The table answers by the vendor's last write, which is the best
evidence there is and not a measurement.
- **NA51090 (NT98636) and NA51103 (NT98332G).** Recognised, no table. Neither
driver is in the NA51089 SDK nor anywhere public that was found; u-boot
carries NA51090's sensor pinmux and nothing else, and its registers sit at
`0x2F0000000`, past what a 32-bit `mem_reg()` reaches. NA51068 (NT98321,
an NVR part) is in the SDK and is a per-pad selector like HiSilicon's,
which this backend is not; it has no chip ID in this tree and gets no
table.
- **NT98562 against NT98566, NT98528 against NT98529.** One die each, told
apart by eFuse or OTP words the SDK decodes only in binaries. The pad
tables are per die, so they are not affected; only the reported name is.
- **Novatek states two options create at once.** In about a fifth of the
replayed calls some pad is claimed twice -- a 12-bit sensor's pixel clock
and MCLK2 both on `S_GPIO1`, say. The vendor code checks for neither and no
board does it. The table answers by the vendor's last write, which is the
best evidence there is and not a measurement; each table's banner in the
header gives the counts.
- **Hardware.** Every claim here is verified against vendor source and on a
host. On top of that, HiSilicon, SigmaStar infinity6/6b0/6c, and Ingenic
T21, T23, T31 and SigmaStar infinity6e have been run on real cameras and
checked against an independent decode of their live registers. Ingenic T40
and Novatek NA51089 have not: nobody here has had one. On a NA51089,
and the Novatek dies have not: nobody here has had one. On a Novatek part,
`/proc/nvt_info/nvt_pinmux/pinmux_summary` is the kernel's own decode of the
same registers and is the thing to compare `reginfo --pads` against.

Expand Down
74 changes: 41 additions & 33 deletions src/hal/novatek.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,40 @@ static sensor_addr_t novatek_possible_i2c_addrs[] = {
{0, NULL}};

static bool nvt_get_chip_id() {
uint16_t reg;
unsigned int chip_id = 0;
char buf[8];
char *endptr;

if (line_from_file("/proc/nvt_info/nvt_pinmux/chip_id", "(.+)", buf,
sizeof(buf))) {
if (!line_from_file("/proc/nvt_info/nvt_pinmux/chip_id", "(.+)", buf,
sizeof(buf)))
return false;

reg = (uint16_t)strtol(buf, &endptr, 16);
/* The TOP block's chip-ID word, which is what tells the pad-mux
* table and the GPIO controller which SoC this is. Set for every ID
* the kernel hands over, named or not: a part with no table gets no
* table, which padmux_ops() says rather than guessing one. */
chip_generation = reg;
switch (reg) {
case 0x5021:
strcpy(chip_name, "NT98562");
return true;
case 0x7021:
strcpy(chip_name, "NT98566");
return true;
case 0x8B20:
strcpy(chip_name, "NT98332G");
return true;
}
/* The TOP block's chip-ID word, which is what tells the pad-mux table and
* the GPIO controller which SoC this is. Set for every ID the kernel hands
* over, named or not: a part with no table gets no table, which
* padmux_ops() says rather than guessing one. */
chip_generation = (uint16_t)strtol(buf, NULL, 16);

/* The ID names the die, not the part: NT98562 and NT98566 are both
* NA51089 and differ only in an eFuse package word whose decoding is not
* in the SDK, so the two read alike here. The dies with no name below
* (NA51055, NA51090) are named by the device tree. */
const char *name;
switch (chip_generation) {
case CHIP_NA51084:
/* u-boot's NA51084 code drives the "_528_" clock registers and allows
* 1200 MHz only on an NT98529, told apart by OTP. */
name = "NT98528";
break;
case CHIP_NA51089:
name = "NT98566";
break;
case CHIP_NA51103:
name = "NT98332G";
break;
default:
return false;
}

// if (mem_reg(IOADDR_TOP_REG_BASE + TOP_VERSION_REG_OFS, (uint32_t *)&reg,
// OP_READ)) {
// chip_id = (reg >> 16) & 0xFFFF;
// }
// printf("reg %x\n", chip_id);
return false;
strcpy(chip_name, name);
return true;
}

bool novatek_detect_cpu(char *chip_name) {
Expand All @@ -65,8 +66,13 @@ bool novatek_detect_cpu(char *chip_name) {
return true;

if (!line_from_file("/proc/device-tree/model", "Novatek ([A-Z]+[0-9]+)",
buf, sizeof(buf)))
return false;
buf, sizeof(buf))) {
/* NA51055 has a pad table and no part name the ID can give; with
* no device-tree model either, the die is the name. */
if (chip_generation != CHIP_NA51055)
return false;
strcpy(buf, "NA51055");
}

strcpy(chip_name, buf);

Expand Down Expand Up @@ -100,8 +106,10 @@ float novatek_get_temp() {
static int novatek_open_i2c_fd(int i2c_adapter_nr) {
if (i2c_adapter_nr<0 || i2c_adapter_nr>4)
i2c_adapter_nr=0;
if (!strncmp(chip_name, "NA51068", 7) ||
!strncmp(chip_name, "NA51103", 7) || !strncmp(chip_name, "NA51090", 7))
/* NA51103 by its ID: the ID path names it NT98332G, which a name test
* for "NA51103" never matched. */
if (chip_generation == CHIP_NA51103 || !strncmp(chip_name, "NA51068", 7) ||
!strncmp(chip_name, "NA51090", 7))
i2c_adapter_nr = 1;
char adapter_name[FILENAME_MAX];

Expand Down
15 changes: 8 additions & 7 deletions src/hal/novatek.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ void novatek_setup_hal();
/* na51068 */
//#define IOADDR_TOP_REG_BASE (IOADDR_GLOBAL_BASE + 0x0E030000)

/* TOP + 0xF0 bits [31:16], also /proc/nvt_info/nvt_pinmux/chip_id. One ID
* per die; the parts cut from a die are told apart by eFuse/OTP words. */
enum CHIP_ID {
CHIP_NA51055 = 0x4821, // NT98525, 128Kb L2, 5M@30
// NT98528, 256Kb L2, 4K@30
CHIP_NA51084 = 0x5021,
CHIP_NA51089 = 0x7021, // NT98562, 64Mb internal RAM
// NT98566, 128Mb internal RAM
CHIP_NA51090 = 0xBC21,
CHIP_NA51103 = 0x8B20 // NVT98332G
CHIP_NA51055 = 0x4821, // NT9852x: NT98520, NT98525
CHIP_NA51084 = 0x5021, // NT98528, NT98529 (the 1.2 GHz part, by OTP)
CHIP_NA51089 = 0x7021, // NT98562 (64 MB internal RAM),
// NT98566 (128 MB), by eFuse package word
CHIP_NA51090 = 0xBC21, // NT98636
CHIP_NA51103 = 0x8B20 // NT98332G
};

#endif /* HAL_NOVATEK_H */
37 changes: 23 additions & 14 deletions src/hal/novatek_gpio.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* The NA51089 GPIO controller, as the vendor's gpio-nvt-na51089.c drives it.
/* The NA51089, NA51055 and NA51084 GPIO controller, as the vendor's
* gpio-nvt-na51089.c and gpio-nvt-na51055.c drive it: the same block at the
* same address on all three.
*
* Four banks of eight words at 0xF0070000: DATA, DIR, SET and CLR, one word
* per pad group, so a pad's bit is its Linux GPIO number modulo 32 in word
Expand All @@ -16,23 +18,30 @@
#include "hal/novatek.h"
#include "tools.h"

static const uint8_t group_pads[8] = {
23, /* C_GPIO, the "MC" pads */
26, /* P_GPIO */
9, /* S_GPIO */
10, /* L_GPIO */
8, /* D_GPIO */
12, /* H_GPIO, the HSI pads */
3, /* A_GPIO */
11, /* DSI_GPIO */
};
/* C ("MC"), P, S, L, D, H (HSI), A, DSI. */
static const uint8_t na51089_pads[8] = {23, 26, 9, 10, 8, 12, 3, 11};
/* NA51055 and NA51084: more S, L and D pads, and no DSI group. */
static const uint8_t na51055_pads[8] = {23, 26, 13, 25, 11, 12, 3, 0};

bool novatek_gpio_supported(void) { return chip_generation == CHIP_NA51089; }
static const uint8_t *group_pads(void) {
switch (chip_generation) {
case CHIP_NA51089:
return na51089_pads;
case CHIP_NA51055:
case CHIP_NA51084:
return na51055_pads;
default:
return NULL;
}
}

bool novatek_gpio_supported(void) { return group_pads() != NULL; }

bool novatek_gpio_valid(int pad) {
if (!novatek_gpio_supported() || pad < 0 || pad >= NVT_GPIO_NPADS)
const uint8_t *pads = group_pads();
if (pads == NULL || pad < 0 || pad >= NVT_GPIO_NPADS)
return false;
return (pad & 31) < group_pads[pad >> 5];
return (pad & 31) < pads[pad >> 5];
}

uint32_t novatek_gpio_reg(int pad, unsigned bank) {
Expand Down
17 changes: 14 additions & 3 deletions src/hal/novatek_padmux.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* Novatek NA51089 (NT98562/NT98566): the pad-mux backend.
/* Novatek NA51089 (NT98562/NT98566), NA51055 (NT9852x) and NA51084
* (NT98528/NT98529): the pad-mux backend. The three share the TOP block
* layout below; NA51055 and NA51084 have no DSI pad group.
*
* Two things decide what a pad carries, and they are in different places:
*
Expand Down Expand Up @@ -63,6 +65,10 @@ typedef struct {

static const novatek_soc_t *nvt_soc(void) {
switch (chip_generation) {
case CHIP_NA51055:
return &NA51055_padmux;
case CHIP_NA51084:
return &NA51084_padmux;
case CHIP_NA51089:
return &NA51089_padmux;
default:
Expand Down Expand Up @@ -420,8 +426,13 @@ static int nvt_set(int pad, const char *func_name, const padmux_io_t *io) {
(uint32_t)k->value << k->shift))
return top_undo(&t, IPCHW_PADMUX_IO);
}
if (!(best->flags & NVT_UNGATED) &&
!top_write_field(&t, gr, gate_bit(pad), 0))
/* A gated claim needs the gate handed over. An ungated one is what the
* vendor sets the gate to GPIO for -- the parallel sensor modes write
* GPIO_ID_EMUM_GPIO to their data pads -- and leaving it clear would let
* a gated claim under the same fields win: CCIR8 data and CCIR8 sync
* both hold under SENSOR2=2. */
uint32_t gate = (best->flags & NVT_UNGATED) ? gate_bit(pad) : 0;
if (!top_write_field(&t, gr, gate_bit(pad), gate))
return top_undo(&t, IPCHW_PADMUX_IO);

int res = drop_competitors(soc, pad, &t, best);
Expand Down
Loading
Loading