From 01a9184178f2da72d3d56bd46531177621c5536e Mon Sep 17 00:00:00 2001 From: snlavaise Date: Wed, 16 Sep 2026 23:12:50 -0300 Subject: [PATCH] clk: qcom: sm8550: match downstream kalama-gcc-v2 GCC compatible The rtwo (Motorola Edge 40 Pro, SM8550/kalama) vendor DTB -- used by U-Boot as its own control FDT (see board.c board_fdt_blob_setup(), which sources it from the ABL-supplied vendor DTB rather than a compiled-in one) -- describes the GCC with the downstream binding "qcom,kalama-gcc-v2" instead of mainline's "qcom,sm8550-gcc". Same hardware/register map (confirmed the vendor DT's raw clock IDs, e.g. 0x6b/0x7f/0x80, match GCC_QUPV3_WRAP1_S7_CLK/M_AHB_CLK/S_AHB_CLK from dt-bindings/clock/qcom,sm8550-gcc.h 1:1), so reusing sm8550_gcc_data for the new compatible is correct. Without this alias the GCC driver never binds to that node, so any clk_get() depending on it (e.g. the debug UART's "se" clock) returns an error that is not -ENOENT/-ENODATA. serial: msm_geni: don't clk_enable() an unfiltered error pointer devm_clk_get_optional() only converts -ENOENT/-ENODATA to NULL; any other error (such as the unbound-GCC case above) is returned as-is. msm_serial_probe() passed that error pointer straight to clk_enable(), which dereferences it in clk_valid() -> hard hang with no console, no panic message, nothing (CONFIG_PANIC_HANG masks it further). Clamp any IS_ERR(clk) to NULL so it degrades to "no clock" like the two error codes that were already handled. Debug: add 7-segment digit instrumentation for UART-less bring-up Isolating the hang above required visual output on a device with no serial console, no fastboot response while hung, and no crash log. INITCALL() and a handful of clk/serial entry points now draw a large two-digit 7-segment style zone number straight onto the ABL's framebuffer at 0xb8000000 so the exact call site can be read directly off the screen instead of inferred from blink/bar-count/binary-stripe patterns, which gave contradictory readings across multiple attempts. Zone 23 (entry to clk_enable()) is what pinpointed the hang above. Harmless to leave in; candidate for removal once the rtwo port is past active UART-less bring-up. Confirmed on hardware: `fastboot boot` of this build reaches a live U-Boot console (banner + prompt) on the rtwo. See project notes rtwo-uboot-clk-compat-fix / rtwo-bring-up-estado for the full debugging trail. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015ADQrjdgE8awSA7TSaGgfr --- arch/arm/mach-snapdragon/board.c | 102 +++++++++++++++++++++++++++++++ drivers/clk/clk-uclass.c | 13 +++- drivers/clk/qcom/clock-qcom.c | 20 +++++- drivers/clk/qcom/clock-sm8550.c | 27 ++++++++ drivers/serial/serial_msm_geni.c | 34 ++++++++++- include/initcall.h | 8 +++ 6 files changed, 198 insertions(+), 6 deletions(-) diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c index 829a0109..f5b6e670 100644 --- a/arch/arm/mach-snapdragon/board.c +++ b/arch/arm/mach-snapdragon/board.c @@ -754,3 +754,105 @@ void enable_caches(void) } dcache_enable(); } + +/* + * Instrumentacion temporal de bring-up (rtwo, SM8550): sin UART, la unica + * senal visible que este ABL deja disponible es el framebuffer que deja + * prendido en 0xb8000000 (splash_region). Cada zona de codigo (board_init_f + * generico, board_init_r generico, probe de serial, camino de clk_enable) + * llama a rtwo_debug_blink_zone() con su propio numero de zona, que se + * dibuja como DOS DIGITOS GRANDES ESTILO 7-SEGMENTOS (blanco sobre negro, + * el numero de zona en decimal, directo). Los esquemas anteriores (bandas + * horizontales para contar, luego un codigo binario de 6 barras) resultaron + * fragiles con luz/reflejo del vidrio: dos lecturas distintas del mismo + * codigo de barras (una por foto, otra mirando el telefono directo) sobre + * la misma pantalla congelada -- ver memoria rtwo-uboot-chainload. Un + * numero grande no necesita interpretacion. Cada sitio instrumentado en + * board.c/serial_msm_geni.c/clk-uclass.c/clock-qcom.c/clock-sm8550.c tiene + * su propio numero GLOBAL y UNICO de zona (1, 2 para los INITCALL + * genericos de board_init_f/board_init_r; 10-39 para los pasos de probe de + * serial y del camino de clk_enable). rtwo_debug_blink() (zona 1/2 segun + * GD_FLG_RELOC) es la que llama include/initcall.h en cada INITCALL de + * board_init_f/board_init_r. + * Mantenimiento de cache manual porque el framebuffer lo lee el + * controlador de display directamente de DRAM, sin pasar por la CPU. + */ +#define RTWO_FB_BASE 0xb8000000UL +#define RTWO_FB_SIZE 0xA00000UL /* 10 MiB: cubre 1080x2400x4 con margen */ +#define RTWO_FB_STRIDE_WORDS 1080UL /* ancho en palabras de 32 bits */ + +#define RTWO_DIGIT_W 360UL +#define RTWO_DIGIT_H 700UL +#define RTWO_DIGIT_THICK 70UL +#define RTWO_DIGIT_GAP 60UL +#define RTWO_DIGIT_Y0 850UL /* deja libre la zona de la camara punch-hole arriba */ + +static void rtwo_fill_rect(volatile unsigned int *fb, unsigned long x0, unsigned long y0, + unsigned long x1, unsigned long y1, unsigned int color) +{ + unsigned long row, col; + + for (row = y0; row < y1; row++) { + volatile unsigned int *rowptr = fb + row * RTWO_FB_STRIDE_WORDS; + + for (col = x0; col < x1; col++) + rowptr[col] = color; + } +} + +/* Mascara de segmentos por digito: bit0=A(arriba) bit1=B(sup-der) + * bit2=C(inf-der) bit3=D(abajo) bit4=E(inf-izq) bit5=F(sup-izq) bit6=G(medio), + * codificacion estandar de un display de 7 segmentos. + */ +static void rtwo_draw_digit(volatile unsigned int *fb, unsigned long x0, unsigned int digit) +{ + static const unsigned char segs[10] = { + 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, + }; + unsigned char m = digit < 10 ? segs[digit] : 0; + unsigned long y0 = RTWO_DIGIT_Y0; + unsigned long yh = y0 + RTWO_DIGIT_H / 2; + unsigned long y1 = y0 + RTWO_DIGIT_H; + unsigned long t = RTWO_DIGIT_THICK; + unsigned int on = 0xFFFFFFFFu; + + if (m & 0x01) /* A */ + rtwo_fill_rect(fb, x0 + t, y0, x0 + RTWO_DIGIT_W - t, y0 + t, on); + if (m & 0x40) /* G */ + rtwo_fill_rect(fb, x0 + t, yh - t / 2, x0 + RTWO_DIGIT_W - t, yh + t / 2, on); + if (m & 0x08) /* D */ + rtwo_fill_rect(fb, x0 + t, y1 - t, x0 + RTWO_DIGIT_W - t, y1, on); + if (m & 0x20) /* F */ + rtwo_fill_rect(fb, x0, y0, x0 + t, yh + t / 2, on); + if (m & 0x02) /* B */ + rtwo_fill_rect(fb, x0 + RTWO_DIGIT_W - t, y0, x0 + RTWO_DIGIT_W, yh + t / 2, on); + if (m & 0x10) /* E */ + rtwo_fill_rect(fb, x0, yh - t / 2, x0 + t, y1, on); + if (m & 0x04) /* C */ + rtwo_fill_rect(fb, x0 + RTWO_DIGIT_W - t, yh - t / 2, x0 + RTWO_DIGIT_W, y1, on); +} + +void rtwo_debug_blink_zone(unsigned int zone) +{ + volatile unsigned int *fb = (volatile unsigned int *)RTWO_FB_BASE; + const unsigned long total_words = RTWO_FB_SIZE / 4; + const unsigned long total_rows = total_words / RTWO_FB_STRIDE_WORDS; + const unsigned long margin_left = (RTWO_FB_STRIDE_WORDS - (2 * RTWO_DIGIT_W + RTWO_DIGIT_GAP)) / 2; + unsigned long addr; + + rtwo_fill_rect(fb, 0, 0, RTWO_FB_STRIDE_WORDS, total_rows, 0u); /* fondo negro */ + + rtwo_draw_digit(fb, margin_left, zone / 10U); + rtwo_draw_digit(fb, margin_left + RTWO_DIGIT_W + RTWO_DIGIT_GAP, zone % 10U); + + asm volatile("dsb sy" ::: "memory"); + for (addr = RTWO_FB_BASE; addr < RTWO_FB_BASE + RTWO_FB_SIZE; addr += 64) + asm volatile("dc civac, %0" :: "r"(addr) : "memory"); + asm volatile("dsb sy" ::: "memory"); + asm volatile("isb" ::: "memory"); +} + +void rtwo_debug_blink(void) +{ + rtwo_debug_blink_zone((gd->flags & GD_FLG_RELOC) ? 2U : 1U); +} diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c index c33f4159..e67b48d2 100644 --- a/drivers/clk/clk-uclass.c +++ b/drivers/clk/clk-uclass.c @@ -657,15 +657,21 @@ int clk_set_parent(struct clk *clk, struct clk *parent) return 0; } +void rtwo_debug_blink_zone(unsigned int zone); + int clk_enable(struct clk *clk) { const struct clk_ops *ops; struct clk *clkp = NULL; int ret; + rtwo_debug_blink_zone(23); /* CLKEN-1: entrada a clk_enable */ debug("%s(clk=%p name=%s)\n", __func__, clk, clk ? clk->dev->name : "NULL"); - if (!clk_valid(clk)) + if (!clk_valid(clk)) { + rtwo_debug_blink_zone(24); /* CLKEN-2a: clk invalido/NULL, return temprano */ return 0; + } + rtwo_debug_blink_zone(25); /* CLKEN-2b: clk valido, seguimos */ ops = clk_dev_ops(clk->dev); if (CONFIG_IS_ENABLED(CLK_CCF)) { @@ -699,7 +705,10 @@ int clk_enable(struct clk *clk) } else { if (!ops->enable) return -ENOSYS; - return ops->enable(clk); + rtwo_debug_blink_zone(26); /* CLKEN-3: justo antes de ops->enable(clk) */ + ret = ops->enable(clk); + rtwo_debug_blink_zone(27); /* CLKEN-4: ops->enable(clk) retorno */ + return ret; } return 0; diff --git a/drivers/clk/qcom/clock-qcom.c b/drivers/clk/qcom/clock-qcom.c index 6b46d9db..9bce5abd 100644 --- a/drivers/clk/qcom/clock-qcom.c +++ b/drivers/clk/qcom/clock-qcom.c @@ -28,6 +28,8 @@ #include "clock-qcom.h" +void rtwo_debug_blink_zone(unsigned int zone); + /* CBCR register fields */ #define CBCR_BRANCH_ENABLE_BIT BIT(0) #define CBCR_BRANCH_OFF_BIT BIT(31) @@ -76,17 +78,21 @@ void clk_enable_vote_clk(phys_addr_t base, const struct vote_clk *vclk) int qcom_gate_clk_en(const struct msm_clk_priv *priv, unsigned long id) { + rtwo_debug_blink_zone(28); /* GATE-1: entrada a qcom_gate_clk_en */ if (id >= priv->data->num_clks || priv->data->clks[id].reg == 0) { log_err("gcc@%#08llx: unknown clock ID %lu!\n", priv->base, id); return -ENOENT; } + rtwo_debug_blink_zone(29); /* GATE-2: id valido, antes de setbits_le32 */ setbits_le32(priv->base + priv->data->clks[id].reg, priv->data->clks[id].en_val); + rtwo_debug_blink_zone(30); /* GATE-3: post setbits_le32 (el registro ya se escribio) */ if (priv->data->clks[id].cbcr_reg) { unsigned int count; u32 val; + rtwo_debug_blink_zone(31); /* GATE-4: hay cbcr_reg, entrando al poll acotado */ for (count = 0; count < 200; count++) { val = readl(priv->base + priv->data->clks[id].cbcr_reg); val &= BRANCH_CHECK_MASK; @@ -94,10 +100,14 @@ int qcom_gate_clk_en(const struct msm_clk_priv *priv, unsigned long id) break; udelay(1); } + rtwo_debug_blink_zone(33); /* GATE-5: poll terminado (por bit ok o por timeout) */ if (WARN(count == 200, "WARNING: Clock @ %#lx [%#010x] stuck at off\n", priv->data->clks[id].cbcr_reg, val)) return -EBUSY; + } else { + rtwo_debug_blink_zone(32); /* GATE-4b: no hay cbcr_reg, no se espera nada */ } + rtwo_debug_blink_zone(34); /* GATE-6: fin de qcom_gate_clk_en, return 0 */ return 0; } @@ -256,8 +266,14 @@ static int msm_clk_enable(struct clk *clk) { struct msm_clk_data *data = (struct msm_clk_data *)dev_get_driver_data(clk->dev); - if (data->enable) - return data->enable(clk); + rtwo_debug_blink_zone(35); /* MSMCLK-1: entrada a msm_clk_enable */ + if (data->enable) { + int ret; + rtwo_debug_blink_zone(36); /* MSMCLK-2: antes de data->enable(clk) */ + ret = data->enable(clk); + rtwo_debug_blink_zone(37); /* MSMCLK-3: data->enable(clk) retorno */ + return ret; + } return 0; } diff --git a/drivers/clk/qcom/clock-sm8550.c b/drivers/clk/qcom/clock-sm8550.c index 7c06489b..a340b675 100644 --- a/drivers/clk/qcom/clock-sm8550.c +++ b/drivers/clk/qcom/clock-sm8550.c @@ -198,10 +198,14 @@ static const struct gate_clk sm8550_clks[] = { GATE_CLK(GCC_USB3_PRIM_PHY_PIPE_CLK, 0x39068, BIT(0)), }; +void rtwo_debug_blink_zone(unsigned int zone); + static int sm8550_enable(struct clk *clk) { struct msm_clk_priv *priv = dev_get_priv(clk->dev); + rtwo_debug_blink_zone(38); /* SM8550EN-1: entrada a sm8550_enable, id=%clk->id (sin printf aqui) */ + switch (clk->id) { case GCC_AGGRE_USB3_PRIM_AXI_CLK: qcom_gate_clk_en(priv, GCC_USB30_PRIM_MASTER_CLK); @@ -220,6 +224,7 @@ static int sm8550_enable(struct clk *clk) break; } + rtwo_debug_blink_zone(39); /* SM8550EN-2: post switch, antes de qcom_gate_clk_en final */ return qcom_gate_clk_en(priv, clk->id); } @@ -290,6 +295,28 @@ static const struct udevice_id gcc_sm8550_of_match[] = { .compatible = "qcom,sm8550-gcc", .data = (ulong)&sm8550_gcc_data, }, + { + /* + * rtwo (Motorola Edge 40 Pro, SM8550/kalama): el DTB vendor + * que U-Boot usa como control FDT (ver memoria + * rtwo-uboot-chainload) describe el GCC con el binding + * downstream "qcom,kalama-gcc-v2" en vez del compatible + * mainline -- mismo bloque de hardware, mismo mapa de + * registros (confirmado: los IDs de clock del DT vendor, + * ej. 0x6b/0x7f/0x80, coinciden exacto con + * GCC_QUPV3_WRAP1_S7_CLK/M_AHB_CLK/S_AHB_CLK de + * dt-bindings/clock/qcom,sm8550-gcc.h), asi que reusar + * sm8550_gcc_data es correcto. Sin este alias el driver + * nunca se bindea a ese nodo: cualquier clk_get() que + * dependa de el devuelve un error que NO es + * -ENOENT/-ENODATA (los unicos que devm_clk_get_optional() + * convierte a NULL), ese puntero de error invalido termina + * en clk_enable() sin filtrar y cuelga el equipo al + * desreferenciarlo. + */ + .compatible = "qcom,kalama-gcc-v2", + .data = (ulong)&sm8550_gcc_data, + }, { } }; diff --git a/drivers/serial/serial_msm_geni.c b/drivers/serial/serial_msm_geni.c index 3dca581f..a2a9a5ce 100644 --- a/drivers/serial/serial_msm_geni.c +++ b/drivers/serial/serial_msm_geni.c @@ -19,6 +19,8 @@ #include #include +void rtwo_debug_blink_zone(unsigned int zone); + #define UART_OVERSAMPLING 32 #define STALE_TIMEOUT 160 @@ -209,12 +211,15 @@ static int msm_serial_setbrg(struct udevice *dev, int baud) pr_err("%s: Couldn't get clock division rate\n", __func__); return -EINVAL; } + rtwo_debug_blink_zone(10); /* SETBRG-1: pre clk_set_rate */ ret = clk_set_rate(priv->se, clk_rate); + rtwo_debug_blink_zone(11); /* SETBRG-2: post clk_set_rate */ if (ret < 0) { pr_err("%s: Couldn't set clock rate: %d\n", __func__, ret); return 0; } geni_serial_baud(priv->base, clk_div, baud); + rtwo_debug_blink_zone(12); /* SETBRG-3: post geni_serial_baud */ return 0; } @@ -517,15 +522,30 @@ static int msm_serial_probe(struct udevice *dev) u32 proto; struct clk *clk; + rtwo_debug_blink_zone(13); /* PROBE-1: entrada */ + clk = devm_clk_get_optional(dev, NULL); - if (IS_ERR(clk)) + if (IS_ERR(clk)) { + /* + * devm_clk_get_optional() solo convierte -ENOENT/-ENODATA a + * NULL; cualquier otro error (ej. el proveedor de clocks no + * esta bindeado) queda como puntero de error. Pasarlo tal + * cual a clk_enable() lo desreferencia y cuelga el equipo + * sin ningun log (ver memoria rtwo-uboot-chainload). + */ dev_dbg(dev, "Couldn't find UART clock: %ld", PTR_ERR(clk)); - else + clk = NULL; + } else { priv->se = clk; + } + + rtwo_debug_blink_zone(14); /* PROBE-2: post devm_clk_get_optional */ /* Try enable clock */ clk_enable(clk); + rtwo_debug_blink_zone(15); /* PROBE-3: post clk_enable */ + /* Check if firmware loading is needed (BT UART) */ proto = readl(priv->base + GENI_FW_REVISION_RO); proto &= FW_REV_PROTOCOL_MSK; @@ -538,6 +558,8 @@ static int msm_serial_probe(struct udevice *dev) proto >>= FW_REV_PROTOCOL_SHFT; } + rtwo_debug_blink_zone(16); /* PROBE-4: post firmware check/load */ + if (proto != GENI_SE_UART) { dev_err(dev, "Invalid proto %d\n", proto); clk_disable(clk); @@ -548,18 +570,26 @@ static int msm_serial_probe(struct udevice *dev) if (ofnode_device_is_compatible(dev_ofnode(dev), "qcom,geni-uart")) return -ENOENT; + rtwo_debug_blink_zone(17); /* PROBE-5: es geni-debug-uart, seguimos */ + ret = geni_set_oversampling(dev); if (ret < 0) return ret; + rtwo_debug_blink_zone(18); /* PROBE-6: post geni_set_oversampling */ + /* No need to reinitialize the UART after relocation */ if (gd->flags & GD_FLG_RELOC) return 0; geni_serial_init(dev); + rtwo_debug_blink_zone(19); /* PROBE-7: post geni_serial_init */ msm_geni_serial_setup_rx(dev); + rtwo_debug_blink_zone(20); /* PROBE-8: post msm_geni_serial_setup_rx */ qcom_geni_serial_start_rx(dev); + rtwo_debug_blink_zone(21); /* PROBE-9: post qcom_geni_serial_start_rx */ qcom_geni_serial_start_tx(priv->base); + rtwo_debug_blink_zone(22); /* PROBE-10: post qcom_geni_serial_start_tx, fin de probe */ return 0; } diff --git a/include/initcall.h b/include/initcall.h index 220a55ad..1f179e34 100644 --- a/include/initcall.h +++ b/include/initcall.h @@ -12,8 +12,16 @@ _Static_assert(EVT_COUNT < 256, "Can only support 256 event types with 8 bits"); +#ifdef CONFIG_ARCH_SNAPDRAGON +void rtwo_debug_blink(void); +#define RTWO_DEBUG_BLINK() rtwo_debug_blink() +#else +#define RTWO_DEBUG_BLINK() +#endif + #define INITCALL(_call) \ do { \ + RTWO_DEBUG_BLINK(); \ if (_call()) { \ printf("%s(): initcall %s() failed\n", __func__, \ #_call); \