From: Linus Torvalds Date: Thu, 25 Jul 2024 17:42:22 +0000 (-0700) Subject: Merge tag 'driver-core-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git... X-Git-Tag: v6.11-rc1~49 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c2a96b7f187fb6a455836d4a6e113947ff11de97;p=thirdparty%2Flinux.git Merge tag 'driver-core-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core updates from Greg KH: "Here is the big set of driver core changes for 6.11-rc1. Lots of stuff in here, with not a huge diffstat, but apis are evolving which required lots of files to be touched. Highlights of the changes in here are: - platform remove callback api final fixups (Uwe took many releases to get here, finally!) - Rust bindings for basic firmware apis and initial driver-core interactions. It's not all that useful for a "write a whole driver in rust" type of thing, but the firmware bindings do help out the phy rust drivers, and the driver core bindings give a solid base on which others can start their work. There is still a long way to go here before we have a multitude of rust drivers being added, but it's a great first step. - driver core const api changes. This reached across all bus types, and there are some fix-ups for some not-common bus types that linux-next and 0-day testing shook out. This work is being done to help make the rust bindings more safe, as well as the C code, moving toward the end-goal of allowing us to put driver structures into read-only memory. We aren't there yet, but are getting closer. - minor devres cleanups and fixes found by code inspection - arch_topology minor changes - other minor driver core cleanups All of these have been in linux-next for a very long time with no reported problems" * tag 'driver-core-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (55 commits) ARM: sa1100: make match function take a const pointer sysfs/cpu: Make crash_hotplug attribute world-readable dio: Have dio_bus_match() callback take a const * zorro: make match function take a const pointer driver core: module: make module_[add|remove]_driver take a const * driver core: make driver_find_device() take a const * driver core: make driver_[create|remove]_file take a const * firmware_loader: fix soundness issue in `request_internal` firmware_loader: annotate doctests as `no_run` devres: Correct code style for functions that return a pointer type devres: Initialize an uninitialized struct member devres: Fix memory leakage caused by driver API devm_free_percpu() devres: Fix devm_krealloc() wasting memory driver core: platform: Switch to use kmemdup_array() driver core: have match() callback in struct bus_type take a const * MAINTAINERS: add Rust device abstractions to DRIVER CORE device: rust: improve safety comments MAINTAINERS: add Danilo as FIRMWARE LOADER maintainer MAINTAINERS: add Rust FW abstractions to FIRMWARE LOADER firmware: rust: improve safety comments ... --- c2a96b7f187fb6a455836d4a6e113947ff11de97 diff --cc drivers/fsi/fsi-occ.c index f7157c1d77d80,21d2666c41954..f58b158d097ce --- a/drivers/fsi/fsi-occ.c +++ b/drivers/fsi/fsi-occ.c @@@ -718,9 -719,7 +718,7 @@@ static void occ_remove(struct platform_ else device_for_each_child(&pdev->dev, NULL, occ_unregister_of_child); - ida_simple_remove(&occ_ida, occ->idx); + ida_free(&occ_ida, occ->idx); - - return 0; } static const struct of_device_id occ_match[] = { diff --cc drivers/gpu/drm/stm/lvds.c index bfc8cb13fbc5c,0000000000000..2fa2c81784e97 mode 100644,000000..100644 --- a/drivers/gpu/drm/stm/lvds.c +++ b/drivers/gpu/drm/stm/lvds.c @@@ -1,1226 -1,0 +1,1224 @@@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2023, STMicroelectronics - All Rights Reserved + * Author(s): Raphaël GALLAIS-POU for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LVDS Host registers */ +#define LVDS_CR 0x0000 /* configuration register */ +#define LVDS_DMLCR0 0x0004 /* data mapping lsb configuration register 0 */ +#define LVDS_DMMCR0 0x0008 /* data mapping msb configuration register 0 */ +#define LVDS_DMLCR1 0x000C /* data mapping lsb configuration register 1 */ +#define LVDS_DMMCR1 0x0010 /* data mapping msb configuration register 1 */ +#define LVDS_DMLCR2 0x0014 /* data mapping lsb configuration register 2 */ +#define LVDS_DMMCR2 0x0018 /* data mapping msb configuration register 2 */ +#define LVDS_DMLCR3 0x001C /* data mapping lsb configuration register 3 */ +#define LVDS_DMMCR3 0x0020 /* data mapping msb configuration register 3 */ +#define LVDS_DMLCR4 0x0024 /* data mapping lsb configuration register 4 */ +#define LVDS_DMMCR4 0x0028 /* data mapping msb configuration register 4 */ +#define LVDS_CDL1CR 0x002C /* channel distrib link 1 configuration register */ +#define LVDS_CDL2CR 0x0030 /* channel distrib link 2 configuration register */ + +#define CDL1CR_DEFAULT 0x04321 /* Default value for CDL1CR */ +#define CDL2CR_DEFAULT 0x59876 /* Default value for CDL2CR */ + +#define LVDS_DMLCR(bit) (LVDS_DMLCR0 + 0x8 * (bit)) +#define LVDS_DMMCR(bit) (LVDS_DMMCR0 + 0x8 * (bit)) + +/* LVDS Wrapper registers */ +#define LVDS_WCLKCR 0x11B0 /* Wrapper clock control register */ + +#define LVDS_HWCFGR 0x1FF0 /* HW configuration register */ +#define LVDS_VERR 0x1FF4 /* Version register */ +#define LVDS_IPIDR 0x1FF8 /* Identification register */ +#define LVDS_SIDR 0x1FFC /* Size Identification register */ + +/* Bitfield description */ +#define CR_LVDSEN BIT(0) /* LVDS PHY Enable */ +#define CR_HSPOL BIT(1) /* Horizontal Synchronization Polarity */ +#define CR_VSPOL BIT(2) /* Vertical Synchronization Polarity */ +#define CR_DEPOL BIT(3) /* Data Enable Polarity */ +#define CR_CI BIT(4) /* Control Internal (software controlled bit) */ +#define CR_LKMOD BIT(5) /* Link Mode, for both Links */ +#define CR_LKPHA BIT(6) /* Link Phase, for both Links */ +#define CR_LK1POL GENMASK(20, 16) /* Link-1 output Polarity */ +#define CR_LK2POL GENMASK(25, 21) /* Link-2 output Polarity */ + +#define DMMCR_MAP0 GENMASK(4, 0) /* Mapping for bit 0 of datalane x */ +#define DMMCR_MAP1 GENMASK(9, 5) /* Mapping for bit 1 of datalane x */ +#define DMMCR_MAP2 GENMASK(14, 10) /* Mapping for bit 2 of datalane x */ +#define DMMCR_MAP3 GENMASK(19, 15) /* Mapping for bit 3 of datalane x */ +#define DMLCR_MAP4 GENMASK(4, 0) /* Mapping for bit 4 of datalane x */ +#define DMLCR_MAP5 GENMASK(9, 5) /* Mapping for bit 5 of datalane x */ +#define DMLCR_MAP6 GENMASK(14, 10) /* Mapping for bit 6 of datalane x */ + +#define CDLCR_DISTR0 GENMASK(3, 0) /* Channel distribution for lane 0 */ +#define CDLCR_DISTR1 GENMASK(7, 4) /* Channel distribution for lane 1 */ +#define CDLCR_DISTR2 GENMASK(11, 8) /* Channel distribution for lane 2 */ +#define CDLCR_DISTR3 GENMASK(15, 12) /* Channel distribution for lane 3 */ +#define CDLCR_DISTR4 GENMASK(19, 16) /* Channel distribution for lane 4 */ + +#define PHY_GCR_BIT_CLK_OUT BIT(0) /* BIT clock enable */ +#define PHY_GCR_LS_CLK_OUT BIT(4) /* LS clock enable */ +#define PHY_GCR_DP_CLK_OUT BIT(8) /* DP clock enable */ +#define PHY_GCR_RSTZ BIT(24) /* LVDS PHY digital reset */ +#define PHY_GCR_DIV_RSTN BIT(25) /* Output divider reset */ +#define PHY_SCR_TX_EN BIT(16) /* Transmission mode enable */ +/* Current mode driver enable */ +#define PHY_CMCR_CM_EN_DL (BIT(28) | BIT(20) | BIT(12) | BIT(4)) +#define PHY_CMCR_CM_EN_DL4 BIT(4) +/* Bias enable */ +#define PHY_BCR1_EN_BIAS_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0)) +#define PHY_BCR2_BIAS_EN BIT(28) +/* Voltage mode driver enable */ +#define PHY_BCR3_VM_EN_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0)) +#define PHY_DCR_POWER_OK BIT(12) +#define PHY_CFGCR_EN_DIG_DL GENMASK(4, 0) /* LVDS PHY digital lane enable */ +#define PHY_PLLCR1_PLL_EN BIT(0) /* LVDS PHY PLL enable */ +#define PHY_PLLCR1_EN_SD BIT(1) /* LVDS PHY PLL sigma-delta signal enable */ +#define PHY_PLLCR1_EN_TWG BIT(2) /* LVDS PHY PLL triangular wave generator enable */ +#define PHY_PLLCR1_DIV_EN BIT(8) /* LVDS PHY PLL dividers enable */ +#define PHY_PLLCR2_NDIV GENMASK(25, 16) /* NDIV mask value */ +#define PHY_PLLCR2_BDIV GENMASK(9, 0) /* BDIV mask value */ +#define PHY_PLLSR_PLL_LOCK BIT(0) /* LVDS PHY PLL lock status */ +#define PHY_PLLSDCR1_MDIV GENMASK(9, 0) /* MDIV mask value */ +#define PHY_PLLTESTCR_TDIV GENMASK(25, 16) /* TDIV mask value */ +#define PHY_PLLTESTCR_CLK_EN BIT(0) /* Test clock enable */ +#define PHY_PLLTESTCR_EN BIT(8) /* Test divider output enable */ + +#define WCLKCR_SECND_CLKPIX_SEL BIT(0) /* Pixel clock selection */ +#define WCLKCR_SRCSEL BIT(8) /* Source selection for the pixel clock */ + +/* Sleep & timeout for pll lock/unlock */ +#define SLEEP_US 1000 +#define TIMEOUT_US 200000 + +/* + * The link phase defines whether an ODD pixel is carried over together with + * the next EVEN pixel or together with the previous EVEN pixel. + * + * LVDS_DUAL_LINK_EVEN_ODD_PIXELS (LKPHA = 0) + * + * ,--------. ,--------. ,--------. ,--------. ,---------. + * | ODD LK \/ PIXEL 3 \/ PIXEL 1 \/ PIXEL' 1 \/ PIXEL' 3 | + * | EVEN LK /\ PIXEL 2 /\ PIXEL' 0 /\ PIXEL' 2 /\ PIXEL' 4 | + * `--------' `--------' `--------' `--------' `---------' + * + * LVDS_DUAL_LINK_ODD_EVEN_PIXELS (LKPHA = 1) + * + * ,--------. ,--------. ,--------. ,--------. ,---------. + * | ODD LK \/ PIXEL 3 \/ PIXEL 1 \/ PIXEL' 1 \/ PIXEL' 3 | + * | EVEN LK /\ PIXEL 4 /\ PIXEL 2 /\ PIXEL' 0 /\ PIXEL' 2 | + * `--------' `--------' `--------' `--------' `---------' + * + */ +enum lvds_link_type { + LVDS_SINGLE_LINK_PRIMARY = 0, + LVDS_SINGLE_LINK_SECONDARY, + LVDS_DUAL_LINK_EVEN_ODD_PIXELS, + LVDS_DUAL_LINK_ODD_EVEN_PIXELS, +}; + +enum lvds_pixel { + PIX_R_0 = 0, + PIX_R_1, + PIX_R_2, + PIX_R_3, + PIX_R_4, + PIX_R_5, + PIX_R_6, + PIX_R_7, + PIX_G_0, + PIX_G_1, + PIX_G_2, + PIX_G_3, + PIX_G_4, + PIX_G_5, + PIX_G_6, + PIX_G_7, + PIX_B_0, + PIX_B_1, + PIX_B_2, + PIX_B_3, + PIX_B_4, + PIX_B_5, + PIX_B_6, + PIX_B_7, + PIX_H_S, + PIX_V_S, + PIX_D_E, + PIX_C_E, + PIX_C_I, + PIX_TOG, + PIX_ONE, + PIX_ZER, +}; + +struct phy_reg_offsets { + u32 GCR; /* Global Control Register */ + u32 CMCR1; /* Current Mode Control Register 1 */ + u32 CMCR2; /* Current Mode Control Register 2 */ + u32 SCR; /* Serial Control Register */ + u32 BCR1; /* Bias Control Register 1 */ + u32 BCR2; /* Bias Control Register 2 */ + u32 BCR3; /* Bias Control Register 3 */ + u32 MPLCR; /* Monitor PLL Lock Control Register */ + u32 DCR; /* Debug Control Register */ + u32 SSR1; /* Spare Status Register 1 */ + u32 CFGCR; /* Configuration Control Register */ + u32 PLLCR1; /* PLL_MODE 1 Control Register */ + u32 PLLCR2; /* PLL_MODE 2 Control Register */ + u32 PLLSR; /* PLL Status Register */ + u32 PLLSDCR1; /* PLL_SD_1 Control Register */ + u32 PLLSDCR2; /* PLL_SD_2 Control Register */ + u32 PLLTWGCR1;/* PLL_TWG_1 Control Register */ + u32 PLLTWGCR2;/* PLL_TWG_2 Control Register */ + u32 PLLCPCR; /* PLL_CP Control Register */ + u32 PLLTESTCR;/* PLL_TEST Control Register */ +}; + +struct lvds_phy_info { + u32 base; + struct phy_reg_offsets ofs; +}; + +static struct lvds_phy_info lvds_phy_16ff_primary = { + .base = 0x1000, + .ofs = { + .GCR = 0x0, + .CMCR1 = 0xC, + .CMCR2 = 0x10, + .SCR = 0x20, + .BCR1 = 0x2C, + .BCR2 = 0x30, + .BCR3 = 0x34, + .MPLCR = 0x64, + .DCR = 0x84, + .SSR1 = 0x88, + .CFGCR = 0xA0, + .PLLCR1 = 0xC0, + .PLLCR2 = 0xC4, + .PLLSR = 0xC8, + .PLLSDCR1 = 0xCC, + .PLLSDCR2 = 0xD0, + .PLLTWGCR1 = 0xD4, + .PLLTWGCR2 = 0xD8, + .PLLCPCR = 0xE0, + .PLLTESTCR = 0xE8, + } +}; + +static struct lvds_phy_info lvds_phy_16ff_secondary = { + .base = 0x1100, + .ofs = { + .GCR = 0x0, + .CMCR1 = 0xC, + .CMCR2 = 0x10, + .SCR = 0x20, + .BCR1 = 0x2C, + .BCR2 = 0x30, + .BCR3 = 0x34, + .MPLCR = 0x64, + .DCR = 0x84, + .SSR1 = 0x88, + .CFGCR = 0xA0, + .PLLCR1 = 0xC0, + .PLLCR2 = 0xC4, + .PLLSR = 0xC8, + .PLLSDCR1 = 0xCC, + .PLLSDCR2 = 0xD0, + .PLLTWGCR1 = 0xD4, + .PLLTWGCR2 = 0xD8, + .PLLCPCR = 0xE0, + .PLLTESTCR = 0xE8, + } +}; + +struct stm_lvds { + void __iomem *base; + struct device *dev; + struct clk *pclk; /* APB peripheral clock */ + struct clk *pllref_clk; /* Reference clock for the internal PLL */ + struct clk_hw lvds_ck_px; /* Pixel clock */ + u32 pixel_clock_rate; /* Pixel clock rate */ + + struct lvds_phy_info *primary; + struct lvds_phy_info *secondary; + + struct drm_bridge lvds_bridge; + struct drm_bridge *next_bridge; + struct drm_connector connector; + struct drm_encoder *encoder; + struct drm_panel *panel; + + u32 hw_version; + u32 link_type; +}; + +#define bridge_to_stm_lvds(b) \ + container_of(b, struct stm_lvds, lvds_bridge) + +#define connector_to_stm_lvds(c) \ + container_of(c, struct stm_lvds, connector) + +#define lvds_is_dual_link(lvds) \ + ({ \ + typeof(lvds) __lvds = (lvds); \ + __lvds == LVDS_DUAL_LINK_EVEN_ODD_PIXELS || \ + __lvds == LVDS_DUAL_LINK_ODD_EVEN_PIXELS; \ + }) + +static inline void lvds_write(struct stm_lvds *lvds, u32 reg, u32 val) +{ + writel(val, lvds->base + reg); +} + +static inline u32 lvds_read(struct stm_lvds *lvds, u32 reg) +{ + return readl(lvds->base + reg); +} + +static inline void lvds_set(struct stm_lvds *lvds, u32 reg, u32 mask) +{ + lvds_write(lvds, reg, lvds_read(lvds, reg) | mask); +} + +static inline void lvds_clear(struct stm_lvds *lvds, u32 reg, u32 mask) +{ + lvds_write(lvds, reg, lvds_read(lvds, reg) & ~mask); +} + +/* + * Expected JEIDA-RGB888 data to be sent in LSB format + * bit6 ............................bit0 + * CHAN0 {ONE, ONE, ZERO, ZERO, ZERO, ONE, ONE} + * CHAN1 {G2, R7, R6, R5, R4, R3, R2} + * CHAN2 {B3, B2, G7, G6, G5, G4, G3} + * CHAN3 {DE, VS, HS, B7, B6, B5, B4} + * CHAN4 {CE, B1, B0, G1, G0, R1, R0} + */ +static enum lvds_pixel lvds_bitmap_jeida_rgb888[5][7] = { + { PIX_ONE, PIX_ONE, PIX_ZER, PIX_ZER, PIX_ZER, PIX_ONE, PIX_ONE }, + { PIX_G_2, PIX_R_7, PIX_R_6, PIX_R_5, PIX_R_4, PIX_R_3, PIX_R_2 }, + { PIX_B_3, PIX_B_2, PIX_G_7, PIX_G_6, PIX_G_5, PIX_G_4, PIX_G_3 }, + { PIX_D_E, PIX_V_S, PIX_H_S, PIX_B_7, PIX_B_6, PIX_B_5, PIX_B_4 }, + { PIX_C_E, PIX_B_1, PIX_B_0, PIX_G_1, PIX_G_0, PIX_R_1, PIX_R_0 } +}; + +/* + * Expected VESA-RGB888 data to be sent in LSB format + * bit6 ............................bit0 + * CHAN0 {ONE, ONE, ZERO, ZERO, ZERO, ONE, ONE} + * CHAN1 {G0, R5, R4, R3, R2, R1, R0} + * CHAN2 {B1, B0, G5, G4, G3, G2, G1} + * CHAN3 {DE, VS, HS, B5, B4, B3, B2} + * CHAN4 {CE, B7, B6, G7, G6, R7, R6} + */ +static enum lvds_pixel lvds_bitmap_vesa_rgb888[5][7] = { + { PIX_ONE, PIX_ONE, PIX_ZER, PIX_ZER, PIX_ZER, PIX_ONE, PIX_ONE }, + { PIX_G_0, PIX_R_5, PIX_R_4, PIX_R_3, PIX_R_2, PIX_R_1, PIX_R_0 }, + { PIX_B_1, PIX_B_0, PIX_G_5, PIX_G_4, PIX_G_3, PIX_G_2, PIX_G_1 }, + { PIX_D_E, PIX_V_S, PIX_H_S, PIX_B_5, PIX_B_4, PIX_B_3, PIX_B_2 }, + { PIX_C_E, PIX_B_7, PIX_B_6, PIX_G_7, PIX_G_6, PIX_R_7, PIX_R_6 } +}; + +/* + * Clocks and PHY related functions + */ +static int lvds_pll_enable(struct stm_lvds *lvds, struct lvds_phy_info *phy) +{ + struct drm_device *drm = lvds->lvds_bridge.dev; + u32 lvds_gcr; + int val, ret; + + /* + * PLL lock timing control for the monitor unmask after startup (pll_en) + * Adjusted value so that the masking window is opened at start-up + */ + lvds_write(lvds, phy->base + phy->ofs.MPLCR, (0x200 - 0x160) << 16); + + /* Enable bias */ + lvds_write(lvds, phy->base + phy->ofs.BCR2, PHY_BCR2_BIAS_EN); + + /* Enable DP, LS, BIT clock output */ + lvds_gcr = PHY_GCR_DP_CLK_OUT | PHY_GCR_LS_CLK_OUT | PHY_GCR_BIT_CLK_OUT; + lvds_set(lvds, phy->base + phy->ofs.GCR, lvds_gcr); + + /* Power up all output dividers */ + lvds_set(lvds, phy->base + phy->ofs.PLLTESTCR, PHY_PLLTESTCR_EN); + lvds_set(lvds, phy->base + phy->ofs.PLLCR1, PHY_PLLCR1_DIV_EN); + + /* Set PHY in serial transmission mode */ + lvds_set(lvds, phy->base + phy->ofs.SCR, PHY_SCR_TX_EN); + + /* Enable the LVDS PLL & wait for its lock */ + lvds_set(lvds, phy->base + phy->ofs.PLLCR1, PHY_PLLCR1_PLL_EN); + ret = readl_poll_timeout_atomic(lvds->base + phy->base + phy->ofs.PLLSR, + val, val & PHY_PLLSR_PLL_LOCK, + SLEEP_US, TIMEOUT_US); + if (ret) + drm_err(drm, "!TIMEOUT! waiting PLL, let's continue\n"); + + /* WCLKCR_SECND_CLKPIX_SEL is for dual link */ + lvds_write(lvds, LVDS_WCLKCR, WCLKCR_SECND_CLKPIX_SEL); + + lvds_set(lvds, phy->ofs.PLLTESTCR, PHY_PLLTESTCR_CLK_EN); + + return ret; +} + +static int pll_get_clkout_khz(int clkin_khz, int bdiv, int mdiv, int ndiv) +{ + int divisor = ndiv * bdiv; + + /* Prevents from division by 0 */ + if (!divisor) + return 0; + + return clkin_khz * mdiv / divisor; +} + +#define TDIV 70 +#define NDIV_MIN 2 +#define NDIV_MAX 6 +#define BDIV_MIN 2 +#define BDIV_MAX 6 +#define MDIV_MIN 1 +#define MDIV_MAX 1023 + +static int lvds_pll_get_params(struct stm_lvds *lvds, + unsigned int clkin_khz, unsigned int clkout_khz, + unsigned int *bdiv, unsigned int *mdiv, unsigned int *ndiv) +{ + int delta, best_delta; /* all in khz */ + int i, o, n; + + /* Early checks preventing division by 0 & odd results */ + if (clkin_khz <= 0 || clkout_khz <= 0) + return -EINVAL; + + best_delta = 1000000; /* big started value (1000000khz) */ + + for (i = NDIV_MIN; i <= NDIV_MAX; i++) { + for (o = BDIV_MIN; o <= BDIV_MAX; o++) { + n = DIV_ROUND_CLOSEST(i * o * clkout_khz, clkin_khz); + /* Check ndiv according to vco range */ + if (n < MDIV_MIN || n > MDIV_MAX) + continue; + /* Check if new delta is better & saves parameters */ + delta = pll_get_clkout_khz(clkin_khz, i, n, o) - clkout_khz; + if (delta < 0) + delta = -delta; + if (delta < best_delta) { + *ndiv = i; + *mdiv = n; + *bdiv = o; + best_delta = delta; + } + /* fast return in case of "perfect result" */ + if (!delta) + return 0; + } + } + + return 0; +} + +static void lvds_pll_config(struct stm_lvds *lvds, struct lvds_phy_info *phy) +{ + unsigned int pll_in_khz, bdiv = 0, mdiv = 0, ndiv = 0; + struct clk_hw *hwclk; + int multiplier; + + /* + * The LVDS PHY includes a low power low jitter high performance and + * highly configuration Phase Locked Loop supporting integer and + * fractional multiplication ratios and Spread Spectrum Clocking. In + * integer mode, the only software supported feature for now, the PLL is + * made of a pre-divider NDIV, a feedback multiplier MDIV, followed by + * several post-dividers, each one with a specific application. + * + * ,------. ,-----. ,-----. + * Fref --> | NDIV | -Fpdf-> | PFD | --> | VCO | --------> Fvco + * `------' ,-> | | `-----' | + * | `-----' | + * | ,------. | + * `-------- | MDIV | <-----' + * `------' + * + * From the output of the VCO, the clock can be optionally extracted on + * the RCC clock observer, with a divider TDIV, for testing purpose, or + * is passed through a programmable post-divider BDIV. Finally, the + * frequency can be divided further with two fixed dividers. + * + * ,--------. + * ,-----> | DP div | ----------------> Fdp + * ,------. | `--------' + * Fvco --> | BDIV | ------------------------------------> Fbit + * | `------' ,------. | + * `-------------> | TDIV | --.---------------------> ClkObs + * '------' | ,--------. + * `--> | LS div | ------> Fls + * '--------' + * + * The LS and DP clock dividers operate at a fixed ratio of 7 and 3.5 + * respectively with regards to fbit. LS divider converts the bit clock + * to a pixel clock per lane per clock sample (Fls). This is useful + * when used to generate a dot clock for the display unit RGB output, + * and DP divider is. + */ + + hwclk = __clk_get_hw(lvds->pllref_clk); + if (!hwclk) + return; + + pll_in_khz = clk_hw_get_rate(hwclk) / 1000; + + if (lvds_is_dual_link(lvds->link_type)) + multiplier = 2; + else + multiplier = 1; + + lvds_pll_get_params(lvds, pll_in_khz, + lvds->pixel_clock_rate * 7 / 1000 / multiplier, + &bdiv, &mdiv, &ndiv); + + /* Set BDIV, MDIV and NDIV */ + lvds_write(lvds, phy->base + phy->ofs.PLLCR2, ndiv << 16); + lvds_set(lvds, phy->base + phy->ofs.PLLCR2, bdiv); + lvds_write(lvds, phy->base + phy->ofs.PLLSDCR1, mdiv); + + /* Hardcode TDIV as dynamic values are not yet implemented */ + lvds_write(lvds, phy->base + phy->ofs.PLLTESTCR, TDIV << 16); + + /* + * For now, PLL just needs to be in integer mode + * Fractional and spread spectrum clocking are not yet implemented + * + * PLL integer mode: + * - PMRY_PLL_TWG_STEP = PMRY_PLL_SD_INT_RATIO + * - EN_TWG = 0 + * - EN_SD = 0 + * - DOWN_SPREAD = 0 + * + * PLL fractional mode: + * - EN_TWG = 0 + * - EN_SD = 1 + * - DOWN_SPREAD = 0 + * + * Spread Spectrum Clocking + * - EN_TWG = 1 + * - EN_SD = 1 + */ + + /* Disable TWG and SD */ + lvds_clear(lvds, phy->base + phy->ofs.PLLCR1, PHY_PLLCR1_EN_TWG | PHY_PLLCR1_EN_SD); + + /* Power up bias and PLL dividers */ + lvds_set(lvds, phy->base + phy->ofs.DCR, PHY_DCR_POWER_OK); + lvds_set(lvds, phy->base + phy->ofs.CMCR1, PHY_CMCR_CM_EN_DL); + lvds_set(lvds, phy->base + phy->ofs.CMCR2, PHY_CMCR_CM_EN_DL4); + + /* Set up voltage mode */ + lvds_set(lvds, phy->base + phy->ofs.PLLCPCR, 0x1); + lvds_set(lvds, phy->base + phy->ofs.BCR3, PHY_BCR3_VM_EN_DL); + lvds_set(lvds, phy->base + phy->ofs.BCR1, PHY_BCR1_EN_BIAS_DL); + /* Enable digital datalanes */ + lvds_set(lvds, phy->base + phy->ofs.CFGCR, PHY_CFGCR_EN_DIG_DL); +} + +static int lvds_pixel_clk_enable(struct clk_hw *hw) +{ + struct stm_lvds *lvds = container_of(hw, struct stm_lvds, lvds_ck_px); + struct drm_device *drm = lvds->lvds_bridge.dev; + struct lvds_phy_info *phy; + int ret; + + ret = clk_prepare_enable(lvds->pclk); + if (ret) { + drm_err(drm, "Failed to enable lvds peripheral clk\n"); + return ret; + } + + ret = clk_prepare_enable(lvds->pllref_clk); + if (ret) { + drm_err(drm, "Failed to enable lvds reference clk\n"); + clk_disable_unprepare(lvds->pclk); + return ret; + } + + /* In case we are operating in dual link the second PHY is set before the primary PHY. */ + if (lvds->secondary) { + phy = lvds->secondary; + + /* Release LVDS PHY from reset mode */ + lvds_set(lvds, phy->base + phy->ofs.GCR, PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ); + lvds_pll_config(lvds, phy); + + ret = lvds_pll_enable(lvds, phy); + if (ret) { + drm_err(drm, "Failed to enable secondary PHY PLL: %d\n", ret); + return ret; + } + } + + if (lvds->primary) { + phy = lvds->primary; + + /* Release LVDS PHY from reset mode */ + lvds_set(lvds, phy->base + phy->ofs.GCR, PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ); + lvds_pll_config(lvds, phy); + + ret = lvds_pll_enable(lvds, phy); + if (ret) { + drm_err(drm, "Failed to enable primary PHY PLL: %d\n", ret); + return ret; + } + } + + return 0; +} + +static void lvds_pixel_clk_disable(struct clk_hw *hw) +{ + struct stm_lvds *lvds = container_of(hw, struct stm_lvds, lvds_ck_px); + + /* + * For each PHY: + * Disable DP, LS, BIT clock outputs + * Shutdown the PLL + * Assert LVDS PHY in reset mode + */ + + if (lvds->primary) { + lvds_clear(lvds, lvds->primary->base + lvds->primary->ofs.GCR, + (PHY_GCR_DP_CLK_OUT | PHY_GCR_LS_CLK_OUT | PHY_GCR_BIT_CLK_OUT)); + lvds_clear(lvds, lvds->primary->base + lvds->primary->ofs.PLLCR1, + PHY_PLLCR1_PLL_EN); + lvds_clear(lvds, lvds->primary->base + lvds->primary->ofs.GCR, + PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ); + } + + if (lvds->secondary) { + lvds_clear(lvds, lvds->secondary->base + lvds->secondary->ofs.GCR, + (PHY_GCR_DP_CLK_OUT | PHY_GCR_LS_CLK_OUT | PHY_GCR_BIT_CLK_OUT)); + lvds_clear(lvds, lvds->secondary->base + lvds->secondary->ofs.PLLCR1, + PHY_PLLCR1_PLL_EN); + lvds_clear(lvds, lvds->secondary->base + lvds->secondary->ofs.GCR, + PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ); + } + + clk_disable_unprepare(lvds->pllref_clk); + clk_disable_unprepare(lvds->pclk); +} + +static unsigned long lvds_pixel_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct stm_lvds *lvds = container_of(hw, struct stm_lvds, lvds_ck_px); + struct drm_device *drm = lvds->lvds_bridge.dev; + unsigned int pll_in_khz, bdiv, mdiv, ndiv; + int ret, multiplier, pll_out_khz; + u32 val; + + ret = clk_prepare_enable(lvds->pclk); + if (ret) { + drm_err(drm, "Failed to enable lvds peripheral clk\n"); + return 0; + } + + if (lvds_is_dual_link(lvds->link_type)) + multiplier = 2; + else + multiplier = 1; + + val = lvds_read(lvds, lvds->primary->base + lvds->primary->ofs.PLLCR2); + + ndiv = (val & PHY_PLLCR2_NDIV) >> 16; + bdiv = (val & PHY_PLLCR2_BDIV) >> 0; + + mdiv = (unsigned int)lvds_read(lvds, + lvds->primary->base + lvds->primary->ofs.PLLSDCR1); + + pll_in_khz = (unsigned int)(parent_rate / 1000); + + /* Compute values if not yet accessible */ + if (val == 0 || mdiv == 0) { + lvds_pll_get_params(lvds, pll_in_khz, + lvds->pixel_clock_rate * 7 / 1000 / multiplier, + &bdiv, &mdiv, &ndiv); + } + + pll_out_khz = pll_get_clkout_khz(pll_in_khz, bdiv, mdiv, ndiv); + drm_dbg(drm, "ndiv %d , bdiv %d, mdiv %d, pll_out_khz %d\n", + ndiv, bdiv, mdiv, pll_out_khz); + + /* + * 1/7 because for each pixel in 1 lane there is 7 bits + * We want pixclk, not bitclk + */ + lvds->pixel_clock_rate = pll_out_khz * 1000 * multiplier / 7; + + clk_disable_unprepare(lvds->pclk); + + return (unsigned long)lvds->pixel_clock_rate; +} + +static long lvds_pixel_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct stm_lvds *lvds = container_of(hw, struct stm_lvds, lvds_ck_px); + unsigned int pll_in_khz, bdiv = 0, mdiv = 0, ndiv = 0; + const struct drm_connector *connector; + const struct drm_display_mode *mode; + int multiplier; + + connector = &lvds->connector; + if (!connector) + return -EINVAL; + + if (list_empty(&connector->modes)) { + drm_dbg(connector->dev, "connector: empty modes list\n"); + return -EINVAL; + } + + mode = list_first_entry(&connector->modes, + struct drm_display_mode, head); + + pll_in_khz = (unsigned int)(*parent_rate / 1000); + + if (lvds_is_dual_link(lvds->link_type)) + multiplier = 2; + else + multiplier = 1; + + lvds_pll_get_params(lvds, pll_in_khz, mode->clock * 7 / multiplier, &bdiv, &mdiv, &ndiv); + + /* + * 1/7 because for each pixel in 1 lane there is 7 bits + * We want pixclk, not bitclk + */ + lvds->pixel_clock_rate = (unsigned long)pll_get_clkout_khz(pll_in_khz, bdiv, mdiv, ndiv) + * 1000 * multiplier / 7; + + return lvds->pixel_clock_rate; +} + +static const struct clk_ops lvds_pixel_clk_ops = { + .enable = lvds_pixel_clk_enable, + .disable = lvds_pixel_clk_disable, + .recalc_rate = lvds_pixel_clk_recalc_rate, + .round_rate = lvds_pixel_clk_round_rate, +}; + +static const struct clk_init_data clk_data = { + .name = "clk_pix_lvds", + .ops = &lvds_pixel_clk_ops, + .parent_names = (const char * []) {"ck_ker_lvdsphy"}, + .num_parents = 1, + .flags = CLK_IGNORE_UNUSED, +}; + +static void lvds_pixel_clk_unregister(void *data) +{ + struct stm_lvds *lvds = data; + + of_clk_del_provider(lvds->dev->of_node); + clk_hw_unregister(&lvds->lvds_ck_px); +} + +static int lvds_pixel_clk_register(struct stm_lvds *lvds) +{ + struct device_node *node = lvds->dev->of_node; + int ret; + + lvds->lvds_ck_px.init = &clk_data; + + /* set the rate by default at 148500000 */ + lvds->pixel_clock_rate = 148500000; + + ret = clk_hw_register(lvds->dev, &lvds->lvds_ck_px); + if (ret) + return ret; + + ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, + &lvds->lvds_ck_px); + if (ret) + clk_hw_unregister(&lvds->lvds_ck_px); + + return ret; +} + +/* + * Host configuration related + */ +static void lvds_config_data_mapping(struct stm_lvds *lvds) +{ + struct drm_device *drm = lvds->lvds_bridge.dev; + const struct drm_display_info *info; + enum lvds_pixel (*bitmap)[7]; + u32 lvds_dmlcr, lvds_dmmcr; + int i; + + info = &(&lvds->connector)->display_info; + if (!info->num_bus_formats || !info->bus_formats) { + drm_warn(drm, "No LVDS bus format reported\n"); + return; + } + + switch (info->bus_formats[0]) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: /* VESA-RGB666 */ + drm_warn(drm, "Pixel format with data mapping not yet supported.\n"); + return; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: /* VESA-RGB888 */ + bitmap = lvds_bitmap_vesa_rgb888; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: /* JEIDA-RGB888 */ + bitmap = lvds_bitmap_jeida_rgb888; + break; + default: + drm_warn(drm, "Unsupported LVDS bus format 0x%04x\n", info->bus_formats[0]); + return; + } + + /* Set bitmap for each lane */ + for (i = 0; i < 5; i++) { + lvds_dmlcr = ((bitmap[i][0]) + + (bitmap[i][1] << 5) + + (bitmap[i][2] << 10) + + (bitmap[i][3] << 15)); + lvds_dmmcr = ((bitmap[i][4]) + + (bitmap[i][5] << 5) + + (bitmap[i][6] << 10)); + + lvds_write(lvds, LVDS_DMLCR(i), lvds_dmlcr); + lvds_write(lvds, LVDS_DMMCR(i), lvds_dmmcr); + } +} + +static void lvds_config_mode(struct stm_lvds *lvds) +{ + u32 bus_flags, lvds_cr = 0, lvds_cdl1cr = 0, lvds_cdl2cr = 0; + const struct drm_display_mode *mode; + const struct drm_connector *connector; + + connector = &lvds->connector; + if (!connector) + return; + + if (list_empty(&connector->modes)) { + drm_dbg(connector->dev, "connector: empty modes list\n"); + return; + } + + bus_flags = connector->display_info.bus_flags; + mode = list_first_entry(&connector->modes, + struct drm_display_mode, head); + + lvds_clear(lvds, LVDS_CR, CR_LKMOD); + lvds_clear(lvds, LVDS_CDL1CR, CDLCR_DISTR0 | CDLCR_DISTR1 | CDLCR_DISTR2 | + CDLCR_DISTR3 | CDLCR_DISTR4); + lvds_clear(lvds, LVDS_CDL2CR, CDLCR_DISTR0 | CDLCR_DISTR1 | CDLCR_DISTR2 | + CDLCR_DISTR3 | CDLCR_DISTR4); + + /* Set channel distribution */ + if (lvds->primary) + lvds_cdl1cr = CDL1CR_DEFAULT; + + if (lvds->secondary) { + lvds_cr |= CR_LKMOD; + lvds_cdl2cr = CDL2CR_DEFAULT; + } + + /* Set signal polarity */ + if (bus_flags & DRM_BUS_FLAG_DE_LOW) + lvds_cr |= CR_DEPOL; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + lvds_cr |= CR_HSPOL; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + lvds_cr |= CR_VSPOL; + + switch (lvds->link_type) { + case LVDS_DUAL_LINK_EVEN_ODD_PIXELS: /* LKPHA = 0 */ + lvds_cr &= ~CR_LKPHA; + break; + case LVDS_DUAL_LINK_ODD_EVEN_PIXELS: /* LKPHA = 1 */ + lvds_cr |= CR_LKPHA; + break; + default: + drm_notice(lvds->lvds_bridge.dev, "No phase precised, setting default\n"); + lvds_cr &= ~CR_LKPHA; + break; + } + + /* Write config to registers */ + lvds_set(lvds, LVDS_CR, lvds_cr); + lvds_write(lvds, LVDS_CDL1CR, lvds_cdl1cr); + lvds_write(lvds, LVDS_CDL2CR, lvds_cdl2cr); +} + +static int lvds_connector_get_modes(struct drm_connector *connector) +{ + struct stm_lvds *lvds = connector_to_stm_lvds(connector); + + return drm_panel_get_modes(lvds->panel, connector); +} + +static int lvds_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + const struct drm_display_mode *panel_mode; + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return -EINVAL; + + if (list_empty(&connector->modes)) { + drm_dbg(connector->dev, "connector: empty modes list\n"); + return -EINVAL; + } + + if (!conn_state->crtc) + return -EINVAL; + + panel_mode = list_first_entry(&connector->modes, + struct drm_display_mode, head); + + /* We're not allowed to modify the resolution. */ + crtc_state = drm_atomic_get_crtc_state(state, conn_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (crtc_state->mode.hdisplay != panel_mode->hdisplay || + crtc_state->mode.vdisplay != panel_mode->vdisplay) + return -EINVAL; + + /* The flat panel mode is fixed, just copy it to the adjusted mode. */ + drm_mode_copy(&crtc_state->adjusted_mode, panel_mode); + + return 0; +} + +static const struct drm_connector_helper_funcs lvds_conn_helper_funcs = { + .get_modes = lvds_connector_get_modes, + .atomic_check = lvds_connector_atomic_check, +}; + +static const struct drm_connector_funcs lvds_conn_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int lvds_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct stm_lvds *lvds = bridge_to_stm_lvds(bridge); + struct drm_connector *connector = &lvds->connector; + struct drm_encoder *encoder = bridge->encoder; + int ret; + + if (!bridge->encoder) { + drm_err(bridge->dev, "Parent encoder object not found\n"); + return -ENODEV; + } + + /* Set the encoder type as caller does not know it */ + bridge->encoder->encoder_type = DRM_MODE_ENCODER_LVDS; + + /* No cloning support */ + bridge->encoder->possible_clones = 0; + + /* If we have a next bridge just attach it. */ + if (lvds->next_bridge) + return drm_bridge_attach(bridge->encoder, lvds->next_bridge, + bridge, flags); + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + drm_err(bridge->dev, "Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + /* Otherwise if we have a panel, create a connector. */ + if (!lvds->panel) + return 0; + + ret = drm_connector_init(bridge->dev, connector, + &lvds_conn_funcs, DRM_MODE_CONNECTOR_LVDS); + if (ret < 0) + return ret; + + drm_connector_helper_add(connector, &lvds_conn_helper_funcs); + + ret = drm_connector_attach_encoder(connector, encoder); + + return ret; +} + +static void lvds_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct drm_atomic_state *state = old_bridge_state->base.state; + struct stm_lvds *lvds = bridge_to_stm_lvds(bridge); + struct drm_connector_state *conn_state; + struct drm_connector *connector; + int ret; + + ret = clk_prepare_enable(lvds->pclk); + if (ret) { + drm_err(bridge->dev, "Failed to enable lvds peripheral clk\n"); + return; + } + + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + if (!connector) + return; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return; + + lvds_config_mode(lvds); + + /* Set Data Mapping */ + lvds_config_data_mapping(lvds); + + /* Turn the output on. */ + lvds_set(lvds, LVDS_CR, CR_LVDSEN); + + if (lvds->panel) { + drm_panel_prepare(lvds->panel); + drm_panel_enable(lvds->panel); + } +} + +static void lvds_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct stm_lvds *lvds = bridge_to_stm_lvds(bridge); + + if (lvds->panel) { + drm_panel_disable(lvds->panel); + drm_panel_unprepare(lvds->panel); + } + + /* Disable LVDS module */ + lvds_clear(lvds, LVDS_CR, CR_LVDSEN); + + clk_disable_unprepare(lvds->pclk); +} + +static const struct drm_bridge_funcs lvds_bridge_funcs = { + .attach = lvds_attach, + .atomic_enable = lvds_atomic_enable, + .atomic_disable = lvds_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, +}; + +static int lvds_probe(struct platform_device *pdev) +{ + struct device_node *port1, *port2, *remote; + struct device *dev = &pdev->dev; + struct reset_control *rstc; + struct stm_lvds *lvds; + int ret, dual_link; + + dev_dbg(dev, "Probing LVDS driver...\n"); + + lvds = devm_kzalloc(dev, sizeof(*lvds), GFP_KERNEL); + if (!lvds) + return -ENOMEM; + + lvds->dev = dev; + + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, + &lvds->panel, &lvds->next_bridge); + if (ret) { + dev_err_probe(dev, ret, "Panel not found\n"); + return ret; + } + + lvds->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(lvds->base)) { + ret = PTR_ERR(lvds->base); + dev_err(dev, "Unable to get regs %d\n", ret); + return ret; + } + + lvds->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(lvds->pclk)) { + ret = PTR_ERR(lvds->pclk); + dev_err(dev, "Unable to get peripheral clock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(lvds->pclk); + if (ret) { + dev_err(dev, "%s: Failed to enable peripheral clk\n", __func__); + return ret; + } + + rstc = devm_reset_control_get_exclusive(dev, NULL); + + if (IS_ERR(rstc)) { + ret = PTR_ERR(rstc); + goto err_lvds_probe; + } + + reset_control_assert(rstc); + usleep_range(10, 20); + reset_control_deassert(rstc); + + port1 = of_graph_get_port_by_id(dev->of_node, 1); + port2 = of_graph_get_port_by_id(dev->of_node, 2); + dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2); + + switch (dual_link) { + case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS: + lvds->link_type = LVDS_DUAL_LINK_ODD_EVEN_PIXELS; + lvds->primary = &lvds_phy_16ff_primary; + lvds->secondary = &lvds_phy_16ff_secondary; + break; + case DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS: + lvds->link_type = LVDS_DUAL_LINK_EVEN_ODD_PIXELS; + lvds->primary = &lvds_phy_16ff_primary; + lvds->secondary = &lvds_phy_16ff_secondary; + break; + case -EINVAL: + /* + * drm_of_lvds_get_dual_pixel_order returns 4 possible values. + * In the case where the returned value is an error, it can be + * either ENODEV or EINVAL. Seeing the structure of this + * function, EINVAL means that either port1 or port2 is not + * present in the device tree. + * In that case, the lvds panel can be a single link panel, or + * there is a semantical error in the device tree code. + */ + remote = of_get_next_available_child(port1, NULL); + if (remote) { + if (of_graph_get_remote_endpoint(remote)) { + lvds->link_type = LVDS_SINGLE_LINK_PRIMARY; + lvds->primary = &lvds_phy_16ff_primary; + lvds->secondary = NULL; + } else { + ret = -EINVAL; + } + + of_node_put(remote); + } + + remote = of_get_next_available_child(port2, NULL); + if (remote) { + if (of_graph_get_remote_endpoint(remote)) { + lvds->link_type = LVDS_SINGLE_LINK_SECONDARY; + lvds->primary = NULL; + lvds->secondary = &lvds_phy_16ff_secondary; + } else { + ret = (ret == -EINVAL) ? -EINVAL : 0; + } + + of_node_put(remote); + } + break; + default: + ret = -EINVAL; + goto err_lvds_probe; + } + of_node_put(port1); + of_node_put(port2); + + lvds->pllref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(lvds->pllref_clk)) { + ret = PTR_ERR(lvds->pllref_clk); + dev_err(dev, "Unable to get reference clock: %d\n", ret); + goto err_lvds_probe; + } + + ret = lvds_pixel_clk_register(lvds); + if (ret) { + dev_err(dev, "Failed to register LVDS pixel clock: %d\n", ret); + goto err_lvds_probe; + } + + lvds->lvds_bridge.funcs = &lvds_bridge_funcs; + lvds->lvds_bridge.of_node = dev->of_node; + lvds->hw_version = lvds_read(lvds, LVDS_VERR); + + dev_info(dev, "version 0x%02x initialized\n", lvds->hw_version); + + drm_bridge_add(&lvds->lvds_bridge); + + platform_set_drvdata(pdev, lvds); + + clk_disable_unprepare(lvds->pclk); + + return 0; + +err_lvds_probe: + clk_disable_unprepare(lvds->pclk); + + return ret; +} + - static int lvds_remove(struct platform_device *pdev) ++static void lvds_remove(struct platform_device *pdev) +{ + struct stm_lvds *lvds = platform_get_drvdata(pdev); + + lvds_pixel_clk_unregister(lvds); + + drm_bridge_remove(&lvds->lvds_bridge); - - return 0; +} + +static const struct of_device_id lvds_dt_ids[] = { + { + .compatible = "st,stm32mp25-lvds", + .data = NULL + }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, lvds_dt_ids); + +static struct platform_driver lvds_platform_driver = { + .probe = lvds_probe, + .remove = lvds_remove, + .driver = { + .name = "stm32-display-lvds", + .owner = THIS_MODULE, + .of_match_table = lvds_dt_ids, + }, +}; + +module_platform_driver(lvds_platform_driver); + +MODULE_AUTHOR("Raphaël Gallais-Pou "); +MODULE_AUTHOR("Philippe Cornu "); +MODULE_AUTHOR("Yannick Fertre "); +MODULE_DESCRIPTION("STMicroelectronics LVDS Display Interface Transmitter DRM driver"); +MODULE_LICENSE("GPL"); diff --cc drivers/net/ethernet/renesas/rtsn.c index 577227c007ab4,0000000000000..0e6cea42f0077 mode 100644,000000..100644 --- a/drivers/net/ethernet/renesas/rtsn.c +++ b/drivers/net/ethernet/renesas/rtsn.c @@@ -1,1391 -1,0 +1,1389 @@@ +// SPDX-License-Identifier: GPL-2.0 + +/* Renesas Ethernet-TSN device driver + * + * Copyright (C) 2022 Renesas Electronics Corporation + * Copyright (C) 2023 Niklas Söderlund + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtsn.h" +#include "rcar_gen4_ptp.h" + +struct rtsn_private { + struct net_device *ndev; + struct platform_device *pdev; + void __iomem *base; + struct rcar_gen4_ptp_private *ptp_priv; + struct clk *clk; + struct reset_control *reset; + + u32 num_tx_ring; + u32 num_rx_ring; + u32 tx_desc_bat_size; + dma_addr_t tx_desc_bat_dma; + struct rtsn_desc *tx_desc_bat; + u32 rx_desc_bat_size; + dma_addr_t rx_desc_bat_dma; + struct rtsn_desc *rx_desc_bat; + dma_addr_t tx_desc_dma; + dma_addr_t rx_desc_dma; + struct rtsn_ext_desc *tx_ring; + struct rtsn_ext_ts_desc *rx_ring; + struct sk_buff **tx_skb; + struct sk_buff **rx_skb; + spinlock_t lock; /* Register access lock */ + u32 cur_tx; + u32 dirty_tx; + u32 cur_rx; + u32 dirty_rx; + u8 ts_tag; + struct napi_struct napi; + struct rtnl_link_stats64 stats; + + struct mii_bus *mii; + phy_interface_t iface; + int link; + int speed; + + int tx_data_irq; + int rx_data_irq; +}; + +static u32 rtsn_read(struct rtsn_private *priv, enum rtsn_reg reg) +{ + return ioread32(priv->base + reg); +} + +static void rtsn_write(struct rtsn_private *priv, enum rtsn_reg reg, u32 data) +{ + iowrite32(data, priv->base + reg); +} + +static void rtsn_modify(struct rtsn_private *priv, enum rtsn_reg reg, + u32 clear, u32 set) +{ + rtsn_write(priv, reg, (rtsn_read(priv, reg) & ~clear) | set); +} + +static int rtsn_reg_wait(struct rtsn_private *priv, enum rtsn_reg reg, + u32 mask, u32 expected) +{ + u32 val; + + return readl_poll_timeout(priv->base + reg, val, + (val & mask) == expected, + RTSN_INTERVAL_US, RTSN_TIMEOUT_US); +} + +static void rtsn_ctrl_data_irq(struct rtsn_private *priv, bool enable) +{ + if (enable) { + rtsn_write(priv, TDIE0, TDIE_TDID_TDX(TX_CHAIN_IDX)); + rtsn_write(priv, RDIE0, RDIE_RDID_RDX(RX_CHAIN_IDX)); + } else { + rtsn_write(priv, TDID0, TDIE_TDID_TDX(TX_CHAIN_IDX)); + rtsn_write(priv, RDID0, RDIE_RDID_RDX(RX_CHAIN_IDX)); + } +} + +static void rtsn_get_timestamp(struct rtsn_private *priv, struct timespec64 *ts) +{ + struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv; + + ptp_priv->info.gettime64(&ptp_priv->info, ts); +} + +static int rtsn_tx_free(struct net_device *ndev, bool free_txed_only) +{ + struct rtsn_private *priv = netdev_priv(ndev); + struct rtsn_ext_desc *desc; + struct sk_buff *skb; + int free_num = 0; + int entry, size; + + for (; priv->cur_tx - priv->dirty_tx > 0; priv->dirty_tx++) { + entry = priv->dirty_tx % priv->num_tx_ring; + desc = &priv->tx_ring[entry]; + if (free_txed_only && (desc->die_dt & DT_MASK) != DT_FEMPTY) + break; + + dma_rmb(); + size = le16_to_cpu(desc->info_ds) & TX_DS; + skb = priv->tx_skb[entry]; + if (skb) { + if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { + struct skb_shared_hwtstamps shhwtstamps; + struct timespec64 ts; + + rtsn_get_timestamp(priv, &ts); + memset(&shhwtstamps, 0, sizeof(shhwtstamps)); + shhwtstamps.hwtstamp = timespec64_to_ktime(ts); + skb_tstamp_tx(skb, &shhwtstamps); + } + dma_unmap_single(ndev->dev.parent, + le32_to_cpu(desc->dptr), + size, DMA_TO_DEVICE); + dev_kfree_skb_any(priv->tx_skb[entry]); + free_num++; + + priv->stats.tx_packets++; + priv->stats.tx_bytes += size; + } + + desc->die_dt = DT_EEMPTY; + } + + desc = &priv->tx_ring[priv->num_tx_ring]; + desc->die_dt = DT_LINK; + + return free_num; +} + +static int rtsn_rx(struct net_device *ndev, int budget) +{ + struct rtsn_private *priv = netdev_priv(ndev); + unsigned int ndescriptors; + unsigned int rx_packets; + unsigned int i; + bool get_ts; + + get_ts = priv->ptp_priv->tstamp_rx_ctrl & + RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT; + + ndescriptors = priv->dirty_rx + priv->num_rx_ring - priv->cur_rx; + rx_packets = 0; + for (i = 0; i < ndescriptors; i++) { + const unsigned int entry = priv->cur_rx % priv->num_rx_ring; + struct rtsn_ext_ts_desc *desc = &priv->rx_ring[entry]; + struct sk_buff *skb; + dma_addr_t dma_addr; + u16 pkt_len; + + /* Stop processing descriptors if budget is consumed. */ + if (rx_packets >= budget) + break; + + /* Stop processing descriptors on first empty. */ + if ((desc->die_dt & DT_MASK) == DT_FEMPTY) + break; + + dma_rmb(); + pkt_len = le16_to_cpu(desc->info_ds) & RX_DS; + + skb = priv->rx_skb[entry]; + priv->rx_skb[entry] = NULL; + dma_addr = le32_to_cpu(desc->dptr); + dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ, + DMA_FROM_DEVICE); + + /* Get timestamp if enabled. */ + if (get_ts) { + struct skb_shared_hwtstamps *shhwtstamps; + struct timespec64 ts; + + shhwtstamps = skb_hwtstamps(skb); + memset(shhwtstamps, 0, sizeof(*shhwtstamps)); + + ts.tv_sec = (u64)le32_to_cpu(desc->ts_sec); + ts.tv_nsec = le32_to_cpu(desc->ts_nsec & cpu_to_le32(0x3fffffff)); + + shhwtstamps->hwtstamp = timespec64_to_ktime(ts); + } + + skb_put(skb, pkt_len); + skb->protocol = eth_type_trans(skb, ndev); + napi_gro_receive(&priv->napi, skb); + + /* Update statistics. */ + priv->stats.rx_packets++; + priv->stats.rx_bytes += pkt_len; + + /* Update counters. */ + priv->cur_rx++; + rx_packets++; + } + + /* Refill the RX ring buffers */ + for (; priv->cur_rx - priv->dirty_rx > 0; priv->dirty_rx++) { + const unsigned int entry = priv->dirty_rx % priv->num_rx_ring; + struct rtsn_ext_ts_desc *desc = &priv->rx_ring[entry]; + struct sk_buff *skb; + dma_addr_t dma_addr; + + desc->info_ds = cpu_to_le16(PKT_BUF_SZ); + + if (!priv->rx_skb[entry]) { + skb = napi_alloc_skb(&priv->napi, + PKT_BUF_SZ + RTSN_ALIGN - 1); + if (!skb) + break; + skb_reserve(skb, NET_IP_ALIGN); + dma_addr = dma_map_single(ndev->dev.parent, skb->data, + le16_to_cpu(desc->info_ds), + DMA_FROM_DEVICE); + if (dma_mapping_error(ndev->dev.parent, dma_addr)) + desc->info_ds = cpu_to_le16(0); + desc->dptr = cpu_to_le32(dma_addr); + skb_checksum_none_assert(skb); + priv->rx_skb[entry] = skb; + } + + dma_wmb(); + desc->die_dt = DT_FEMPTY | D_DIE; + } + + priv->rx_ring[priv->num_rx_ring].die_dt = DT_LINK; + + return rx_packets; +} + +static int rtsn_poll(struct napi_struct *napi, int budget) +{ + struct rtsn_private *priv; + struct net_device *ndev; + unsigned long flags; + int work_done; + + ndev = napi->dev; + priv = netdev_priv(ndev); + + /* Processing RX Descriptor Ring */ + work_done = rtsn_rx(ndev, budget); + + /* Processing TX Descriptor Ring */ + spin_lock_irqsave(&priv->lock, flags); + rtsn_tx_free(ndev, true); + netif_wake_subqueue(ndev, 0); + spin_unlock_irqrestore(&priv->lock, flags); + + /* Re-enable TX/RX interrupts */ + if (work_done < budget && napi_complete_done(napi, work_done)) { + spin_lock_irqsave(&priv->lock, flags); + rtsn_ctrl_data_irq(priv, true); + spin_unlock_irqrestore(&priv->lock, flags); + } + + return work_done; +} + +static int rtsn_desc_alloc(struct rtsn_private *priv) +{ + struct device *dev = &priv->pdev->dev; + unsigned int i; + + priv->tx_desc_bat_size = sizeof(struct rtsn_desc) * TX_NUM_CHAINS; + priv->tx_desc_bat = dma_alloc_coherent(dev, priv->tx_desc_bat_size, + &priv->tx_desc_bat_dma, + GFP_KERNEL); + + if (!priv->tx_desc_bat) + return -ENOMEM; + + for (i = 0; i < TX_NUM_CHAINS; i++) + priv->tx_desc_bat[i].die_dt = DT_EOS; + + priv->rx_desc_bat_size = sizeof(struct rtsn_desc) * RX_NUM_CHAINS; + priv->rx_desc_bat = dma_alloc_coherent(dev, priv->rx_desc_bat_size, + &priv->rx_desc_bat_dma, + GFP_KERNEL); + + if (!priv->rx_desc_bat) + return -ENOMEM; + + for (i = 0; i < RX_NUM_CHAINS; i++) + priv->rx_desc_bat[i].die_dt = DT_EOS; + + return 0; +} + +static void rtsn_desc_free(struct rtsn_private *priv) +{ + if (priv->tx_desc_bat) + dma_free_coherent(&priv->pdev->dev, priv->tx_desc_bat_size, + priv->tx_desc_bat, priv->tx_desc_bat_dma); + priv->tx_desc_bat = NULL; + + if (priv->rx_desc_bat) + dma_free_coherent(&priv->pdev->dev, priv->rx_desc_bat_size, + priv->rx_desc_bat, priv->rx_desc_bat_dma); + priv->rx_desc_bat = NULL; +} + +static void rtsn_chain_free(struct rtsn_private *priv) +{ + struct device *dev = &priv->pdev->dev; + + dma_free_coherent(dev, + sizeof(struct rtsn_ext_desc) * (priv->num_tx_ring + 1), + priv->tx_ring, priv->tx_desc_dma); + priv->tx_ring = NULL; + + dma_free_coherent(dev, + sizeof(struct rtsn_ext_ts_desc) * (priv->num_rx_ring + 1), + priv->rx_ring, priv->rx_desc_dma); + priv->rx_ring = NULL; + + kfree(priv->tx_skb); + priv->tx_skb = NULL; + + kfree(priv->rx_skb); + priv->rx_skb = NULL; +} + +static int rtsn_chain_init(struct rtsn_private *priv, int tx_size, int rx_size) +{ + struct net_device *ndev = priv->ndev; + struct sk_buff *skb; + int i; + + priv->num_tx_ring = tx_size; + priv->num_rx_ring = rx_size; + + priv->tx_skb = kcalloc(tx_size, sizeof(*priv->tx_skb), GFP_KERNEL); + priv->rx_skb = kcalloc(rx_size, sizeof(*priv->rx_skb), GFP_KERNEL); + + if (!priv->rx_skb || !priv->tx_skb) + goto error; + + for (i = 0; i < rx_size; i++) { + skb = netdev_alloc_skb(ndev, PKT_BUF_SZ + RTSN_ALIGN - 1); + if (!skb) + goto error; + skb_reserve(skb, NET_IP_ALIGN); + priv->rx_skb[i] = skb; + } + + /* Allocate TX, RX descriptors */ + priv->tx_ring = dma_alloc_coherent(ndev->dev.parent, + sizeof(struct rtsn_ext_desc) * (tx_size + 1), + &priv->tx_desc_dma, GFP_KERNEL); + priv->rx_ring = dma_alloc_coherent(ndev->dev.parent, + sizeof(struct rtsn_ext_ts_desc) * (rx_size + 1), + &priv->rx_desc_dma, GFP_KERNEL); + + if (!priv->tx_ring || !priv->rx_ring) + goto error; + + return 0; +error: + rtsn_chain_free(priv); + + return -ENOMEM; +} + +static void rtsn_chain_format(struct rtsn_private *priv) +{ + struct net_device *ndev = priv->ndev; + struct rtsn_ext_ts_desc *rx_desc; + struct rtsn_ext_desc *tx_desc; + struct rtsn_desc *bat_desc; + dma_addr_t dma_addr; + unsigned int i; + + priv->cur_tx = 0; + priv->cur_rx = 0; + priv->dirty_rx = 0; + priv->dirty_tx = 0; + + /* TX */ + memset(priv->tx_ring, 0, sizeof(*tx_desc) * priv->num_tx_ring); + for (i = 0, tx_desc = priv->tx_ring; i < priv->num_tx_ring; i++, tx_desc++) + tx_desc->die_dt = DT_EEMPTY | D_DIE; + + tx_desc->dptr = cpu_to_le32((u32)priv->tx_desc_dma); + tx_desc->die_dt = DT_LINK; + + bat_desc = &priv->tx_desc_bat[TX_CHAIN_IDX]; + bat_desc->die_dt = DT_LINK; + bat_desc->dptr = cpu_to_le32((u32)priv->tx_desc_dma); + + /* RX */ + memset(priv->rx_ring, 0, sizeof(*rx_desc) * priv->num_rx_ring); + for (i = 0, rx_desc = priv->rx_ring; i < priv->num_rx_ring; i++, rx_desc++) { + dma_addr = dma_map_single(ndev->dev.parent, + priv->rx_skb[i]->data, PKT_BUF_SZ, + DMA_FROM_DEVICE); + if (!dma_mapping_error(ndev->dev.parent, dma_addr)) + rx_desc->info_ds = cpu_to_le16(PKT_BUF_SZ); + rx_desc->dptr = cpu_to_le32((u32)dma_addr); + rx_desc->die_dt = DT_FEMPTY | D_DIE; + } + rx_desc->dptr = cpu_to_le32((u32)priv->rx_desc_dma); + rx_desc->die_dt = DT_LINK; + + bat_desc = &priv->rx_desc_bat[RX_CHAIN_IDX]; + bat_desc->die_dt = DT_LINK; + bat_desc->dptr = cpu_to_le32((u32)priv->rx_desc_dma); +} + +static int rtsn_dmac_init(struct rtsn_private *priv) +{ + int ret; + + ret = rtsn_chain_init(priv, TX_CHAIN_SIZE, RX_CHAIN_SIZE); + if (ret) + return ret; + + rtsn_chain_format(priv); + + return 0; +} + +static enum rtsn_mode rtsn_read_mode(struct rtsn_private *priv) +{ + return (rtsn_read(priv, OSR) & OSR_OPS) >> 1; +} + +static int rtsn_wait_mode(struct rtsn_private *priv, enum rtsn_mode mode) +{ + unsigned int i; + + /* Need to busy loop as mode changes can happen in atomic context. */ + for (i = 0; i < RTSN_TIMEOUT_US / RTSN_INTERVAL_US; i++) { + if (rtsn_read_mode(priv) == mode) + return 0; + + udelay(RTSN_INTERVAL_US); + } + + return -ETIMEDOUT; +} + +static int rtsn_change_mode(struct rtsn_private *priv, enum rtsn_mode mode) +{ + int ret; + + rtsn_write(priv, OCR, mode); + ret = rtsn_wait_mode(priv, mode); + if (ret) + netdev_err(priv->ndev, "Failed to switch operation mode\n"); + return ret; +} + +static int rtsn_get_data_irq_status(struct rtsn_private *priv) +{ + u32 val; + + val = rtsn_read(priv, TDIS0) | TDIS_TDS(TX_CHAIN_IDX); + val |= rtsn_read(priv, RDIS0) | RDIS_RDS(RX_CHAIN_IDX); + + return val; +} + +static irqreturn_t rtsn_irq(int irq, void *dev_id) +{ + struct rtsn_private *priv = dev_id; + int ret = IRQ_NONE; + + spin_lock(&priv->lock); + + if (rtsn_get_data_irq_status(priv)) { + /* Clear TX/RX irq status */ + rtsn_write(priv, TDIS0, TDIS_TDS(TX_CHAIN_IDX)); + rtsn_write(priv, RDIS0, RDIS_RDS(RX_CHAIN_IDX)); + + if (napi_schedule_prep(&priv->napi)) { + /* Disable TX/RX interrupts */ + rtsn_ctrl_data_irq(priv, false); + + __napi_schedule(&priv->napi); + } + + ret = IRQ_HANDLED; + } + + spin_unlock(&priv->lock); + + return ret; +} + +static int rtsn_request_irq(unsigned int irq, irq_handler_t handler, + unsigned long flags, struct rtsn_private *priv, + const char *ch) +{ + char *name; + int ret; + + name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, "%s:%s", + priv->ndev->name, ch); + if (!name) + return -ENOMEM; + + ret = request_irq(irq, handler, flags, name, priv); + if (ret) + netdev_err(priv->ndev, "Cannot request IRQ %s\n", name); + + return ret; +} + +static void rtsn_free_irqs(struct rtsn_private *priv) +{ + free_irq(priv->tx_data_irq, priv); + free_irq(priv->rx_data_irq, priv); +} + +static int rtsn_request_irqs(struct rtsn_private *priv) +{ + int ret; + + priv->rx_data_irq = platform_get_irq_byname(priv->pdev, "rx"); + if (priv->rx_data_irq < 0) + return priv->rx_data_irq; + + priv->tx_data_irq = platform_get_irq_byname(priv->pdev, "tx"); + if (priv->tx_data_irq < 0) + return priv->tx_data_irq; + + ret = rtsn_request_irq(priv->tx_data_irq, rtsn_irq, 0, priv, "tx"); + if (ret) + return ret; + + ret = rtsn_request_irq(priv->rx_data_irq, rtsn_irq, 0, priv, "rx"); + if (ret) { + free_irq(priv->tx_data_irq, priv); + return ret; + } + + return 0; +} + +static int rtsn_reset(struct rtsn_private *priv) +{ + reset_control_reset(priv->reset); + mdelay(1); + + return rtsn_wait_mode(priv, OCR_OPC_DISABLE); +} + +static int rtsn_axibmi_init(struct rtsn_private *priv) +{ + int ret; + + ret = rtsn_reg_wait(priv, RR, RR_RST, RR_RST_COMPLETE); + if (ret) + return ret; + + /* Set AXIWC */ + rtsn_write(priv, AXIWC, AXIWC_DEFAULT); + + /* Set AXIRC */ + rtsn_write(priv, AXIRC, AXIRC_DEFAULT); + + /* TX Descriptor chain setting */ + rtsn_write(priv, TATLS0, TATLS0_TEDE | TATLS0_TATEN(TX_CHAIN_IDX)); + rtsn_write(priv, TATLS1, priv->tx_desc_bat_dma + TX_CHAIN_ADDR_OFFSET); + rtsn_write(priv, TATLR, TATLR_TATL); + + ret = rtsn_reg_wait(priv, TATLR, TATLR_TATL, 0); + if (ret) + return ret; + + /* RX Descriptor chain setting */ + rtsn_write(priv, RATLS0, + RATLS0_RETS | RATLS0_REDE | RATLS0_RATEN(RX_CHAIN_IDX)); + rtsn_write(priv, RATLS1, priv->rx_desc_bat_dma + RX_CHAIN_ADDR_OFFSET); + rtsn_write(priv, RATLR, RATLR_RATL); + + ret = rtsn_reg_wait(priv, RATLR, RATLR_RATL, 0); + if (ret) + return ret; + + /* Enable TX/RX interrupts */ + rtsn_ctrl_data_irq(priv, true); + + return 0; +} + +static void rtsn_mhd_init(struct rtsn_private *priv) +{ + /* TX General setting */ + rtsn_write(priv, TGC1, TGC1_STTV_DEFAULT | TGC1_TQTM_SFM); + rtsn_write(priv, TMS0, TMS_MFS_MAX); + + /* RX Filter IP */ + rtsn_write(priv, CFCR0, CFCR_SDID(RX_CHAIN_IDX)); + rtsn_write(priv, FMSCR, FMSCR_FMSIE(RX_CHAIN_IDX)); +} + +static int rtsn_get_phy_params(struct rtsn_private *priv) +{ + int ret; + + ret = of_get_phy_mode(priv->pdev->dev.of_node, &priv->iface); + if (ret) + return ret; + + switch (priv->iface) { + case PHY_INTERFACE_MODE_MII: + priv->speed = 100; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + priv->speed = 1000; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static void rtsn_set_phy_interface(struct rtsn_private *priv) +{ + u32 val; + + switch (priv->iface) { + case PHY_INTERFACE_MODE_MII: + val = MPIC_PIS_MII; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + val = MPIC_PIS_GMII; + break; + default: + return; + } + + rtsn_modify(priv, MPIC, MPIC_PIS_MASK, val); +} + +static void rtsn_set_rate(struct rtsn_private *priv) +{ + u32 val; + + switch (priv->speed) { + case 10: + val = MPIC_LSC_10M; + break; + case 100: + val = MPIC_LSC_100M; + break; + case 1000: + val = MPIC_LSC_1G; + break; + default: + return; + } + + rtsn_modify(priv, MPIC, MPIC_LSC_MASK, val); +} + +static int rtsn_rmac_init(struct rtsn_private *priv) +{ + const u8 *mac_addr = priv->ndev->dev_addr; + int ret; + + /* Set MAC address */ + rtsn_write(priv, MRMAC0, (mac_addr[0] << 8) | mac_addr[1]); + rtsn_write(priv, MRMAC1, (mac_addr[2] << 24) | (mac_addr[3] << 16) | + (mac_addr[4] << 8) | mac_addr[5]); + + /* Set xMII type */ + rtsn_set_phy_interface(priv); + rtsn_set_rate(priv); + + /* Enable MII */ + rtsn_modify(priv, MPIC, MPIC_PSMCS_MASK | MPIC_PSMHT_MASK, + MPIC_PSMCS_DEFAULT | MPIC_PSMHT_DEFAULT); + + /* Link verification */ + rtsn_modify(priv, MLVC, MLVC_PLV, MLVC_PLV); + ret = rtsn_reg_wait(priv, MLVC, MLVC_PLV, 0); + if (ret) + return ret; + + return ret; +} + +static int rtsn_hw_init(struct rtsn_private *priv) +{ + int ret; + + ret = rtsn_reset(priv); + if (ret) + return ret; + + /* Change to CONFIG mode */ + ret = rtsn_change_mode(priv, OCR_OPC_CONFIG); + if (ret) + return ret; + + ret = rtsn_axibmi_init(priv); + if (ret) + return ret; + + rtsn_mhd_init(priv); + + ret = rtsn_rmac_init(priv); + if (ret) + return ret; + + ret = rtsn_change_mode(priv, OCR_OPC_DISABLE); + if (ret) + return ret; + + /* Change to OPERATION mode */ + ret = rtsn_change_mode(priv, OCR_OPC_OPERATION); + + return ret; +} + +static int rtsn_mii_access(struct mii_bus *bus, bool read, int phyad, + int regad, u16 data) +{ + struct rtsn_private *priv = bus->priv; + u32 val; + int ret; + + val = MPSM_PDA(phyad) | MPSM_PRA(regad) | MPSM_PSME; + + if (!read) + val |= MPSM_PSMAD | MPSM_PRD_SET(data); + + rtsn_write(priv, MPSM, val); + + ret = rtsn_reg_wait(priv, MPSM, MPSM_PSME, 0); + if (ret) + return ret; + + if (read) + ret = MPSM_PRD_GET(rtsn_read(priv, MPSM)); + + return ret; +} + +static int rtsn_mii_read(struct mii_bus *bus, int addr, int regnum) +{ + return rtsn_mii_access(bus, true, addr, regnum, 0); +} + +static int rtsn_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val) +{ + return rtsn_mii_access(bus, false, addr, regnum, val); +} + +static int rtsn_mdio_alloc(struct rtsn_private *priv) +{ + struct platform_device *pdev = priv->pdev; + struct device *dev = &pdev->dev; + struct device_node *mdio_node; + struct mii_bus *mii; + int ret; + + mii = mdiobus_alloc(); + if (!mii) + return -ENOMEM; + + mdio_node = of_get_child_by_name(dev->of_node, "mdio"); + if (!mdio_node) { + ret = -ENODEV; + goto out_free_bus; + } + + /* Enter config mode before registering the MDIO bus */ + ret = rtsn_reset(priv); + if (ret) + goto out_free_bus; + + ret = rtsn_change_mode(priv, OCR_OPC_CONFIG); + if (ret) + goto out_free_bus; + + rtsn_modify(priv, MPIC, MPIC_PSMCS_MASK | MPIC_PSMHT_MASK, + MPIC_PSMCS_DEFAULT | MPIC_PSMHT_DEFAULT); + + /* Register the MDIO bus */ + mii->name = "rtsn_mii"; + snprintf(mii->id, MII_BUS_ID_SIZE, "%s-%x", + pdev->name, pdev->id); + mii->priv = priv; + mii->read = rtsn_mii_read; + mii->write = rtsn_mii_write; + mii->parent = dev; + + ret = of_mdiobus_register(mii, mdio_node); + of_node_put(mdio_node); + if (ret) + goto out_free_bus; + + priv->mii = mii; + + return 0; + +out_free_bus: + mdiobus_free(mii); + return ret; +} + +static void rtsn_mdio_free(struct rtsn_private *priv) +{ + mdiobus_unregister(priv->mii); + mdiobus_free(priv->mii); + priv->mii = NULL; +} + +static void rtsn_adjust_link(struct net_device *ndev) +{ + struct rtsn_private *priv = netdev_priv(ndev); + struct phy_device *phydev = ndev->phydev; + bool new_state = false; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + if (phydev->link) { + if (phydev->speed != priv->speed) { + new_state = true; + priv->speed = phydev->speed; + } + + if (!priv->link) { + new_state = true; + priv->link = phydev->link; + } + } else if (priv->link) { + new_state = true; + priv->link = 0; + priv->speed = 0; + } + + if (new_state) { + /* Need to transition to CONFIG mode before reconfiguring and + * then back to the original mode. Any state change to/from + * CONFIG or OPERATION must go over DISABLED to stop Rx/Tx. + */ + enum rtsn_mode orgmode = rtsn_read_mode(priv); + + /* Transit to CONFIG */ + if (orgmode != OCR_OPC_CONFIG) { + if (orgmode != OCR_OPC_DISABLE && + rtsn_change_mode(priv, OCR_OPC_DISABLE)) + goto out; + if (rtsn_change_mode(priv, OCR_OPC_CONFIG)) + goto out; + } + + rtsn_set_rate(priv); + + /* Transition to original mode */ + if (orgmode != OCR_OPC_CONFIG) { + if (rtsn_change_mode(priv, OCR_OPC_DISABLE)) + goto out; + if (orgmode != OCR_OPC_DISABLE && + rtsn_change_mode(priv, orgmode)) + goto out; + } + } +out: + spin_unlock_irqrestore(&priv->lock, flags); + + if (new_state) + phy_print_status(phydev); +} + +static int rtsn_phy_init(struct rtsn_private *priv) +{ + struct device_node *np = priv->ndev->dev.parent->of_node; + struct phy_device *phydev; + struct device_node *phy; + + priv->link = 0; + + phy = of_parse_phandle(np, "phy-handle", 0); + if (!phy) + return -ENOENT; + + phydev = of_phy_connect(priv->ndev, phy, rtsn_adjust_link, 0, + priv->iface); + of_node_put(phy); + if (!phydev) + return -ENOENT; + + /* Only support full-duplex mode */ + phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT); + phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT); + phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT); + + phy_attached_info(phydev); + + return 0; +} + +static void rtsn_phy_deinit(struct rtsn_private *priv) +{ + phy_disconnect(priv->ndev->phydev); + priv->ndev->phydev = NULL; +} + +static int rtsn_init(struct rtsn_private *priv) +{ + int ret; + + ret = rtsn_desc_alloc(priv); + if (ret) + return ret; + + ret = rtsn_dmac_init(priv); + if (ret) + goto error_free_desc; + + ret = rtsn_hw_init(priv); + if (ret) + goto error_free_chain; + + ret = rtsn_phy_init(priv); + if (ret) + goto error_free_chain; + + ret = rtsn_request_irqs(priv); + if (ret) + goto error_free_phy; + + return 0; +error_free_phy: + rtsn_phy_deinit(priv); +error_free_chain: + rtsn_chain_free(priv); +error_free_desc: + rtsn_desc_free(priv); + return ret; +} + +static void rtsn_deinit(struct rtsn_private *priv) +{ + rtsn_free_irqs(priv); + rtsn_phy_deinit(priv); + rtsn_chain_free(priv); + rtsn_desc_free(priv); +} + +static void rtsn_parse_mac_address(struct device_node *np, + struct net_device *ndev) +{ + struct rtsn_private *priv = netdev_priv(ndev); + u8 addr[ETH_ALEN]; + u32 mrmac0; + u32 mrmac1; + + /* Try to read address from Device Tree. */ + if (!of_get_mac_address(np, addr)) { + eth_hw_addr_set(ndev, addr); + return; + } + + /* Try to read address from device. */ + mrmac0 = rtsn_read(priv, MRMAC0); + mrmac1 = rtsn_read(priv, MRMAC1); + + addr[0] = (mrmac0 >> 8) & 0xff; + addr[1] = (mrmac0 >> 0) & 0xff; + addr[2] = (mrmac1 >> 24) & 0xff; + addr[3] = (mrmac1 >> 16) & 0xff; + addr[4] = (mrmac1 >> 8) & 0xff; + addr[5] = (mrmac1 >> 0) & 0xff; + + if (is_valid_ether_addr(addr)) { + eth_hw_addr_set(ndev, addr); + return; + } + + /* Fallback to a random address */ + eth_hw_addr_random(ndev); +} + +static int rtsn_open(struct net_device *ndev) +{ + struct rtsn_private *priv = netdev_priv(ndev); + int ret; + + napi_enable(&priv->napi); + + ret = rtsn_init(priv); + if (ret) { + napi_disable(&priv->napi); + return ret; + } + + phy_start(ndev->phydev); + + netif_start_queue(ndev); + + return 0; +} + +static int rtsn_stop(struct net_device *ndev) +{ + struct rtsn_private *priv = netdev_priv(ndev); + + phy_stop(priv->ndev->phydev); + napi_disable(&priv->napi); + rtsn_change_mode(priv, OCR_OPC_DISABLE); + rtsn_deinit(priv); + + return 0; +} + +static netdev_tx_t rtsn_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct rtsn_private *priv = netdev_priv(ndev); + struct rtsn_ext_desc *desc; + int ret = NETDEV_TX_OK; + unsigned long flags; + dma_addr_t dma_addr; + int entry; + + spin_lock_irqsave(&priv->lock, flags); + + /* Drop packet if it won't fit in a single descriptor. */ + if (skb->len >= TX_DS) { + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + goto out; + } + + if (priv->cur_tx - priv->dirty_tx > priv->num_tx_ring) { + netif_stop_subqueue(ndev, 0); + ret = NETDEV_TX_BUSY; + goto out; + } + + if (skb_put_padto(skb, ETH_ZLEN)) + goto out; + + dma_addr = dma_map_single(ndev->dev.parent, skb->data, skb->len, + DMA_TO_DEVICE); + if (dma_mapping_error(ndev->dev.parent, dma_addr)) { + dev_kfree_skb_any(skb); + goto out; + } + + entry = priv->cur_tx % priv->num_tx_ring; + priv->tx_skb[entry] = skb; + desc = &priv->tx_ring[entry]; + desc->dptr = cpu_to_le32(dma_addr); + desc->info_ds = cpu_to_le16(skb->len); + desc->info1 = cpu_to_le64(skb->len); + + if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + priv->ts_tag++; + desc->info_ds |= cpu_to_le16(TXC); + desc->info = priv->ts_tag; + } + + skb_tx_timestamp(skb); + dma_wmb(); + + desc->die_dt = DT_FSINGLE | D_DIE; + priv->cur_tx++; + + /* Start xmit */ + rtsn_write(priv, TRCR0, BIT(TX_CHAIN_IDX)); +out: + spin_unlock_irqrestore(&priv->lock, flags); + return ret; +} + +static void rtsn_get_stats64(struct net_device *ndev, + struct rtnl_link_stats64 *storage) +{ + struct rtsn_private *priv = netdev_priv(ndev); + *storage = priv->stats; +} + +static int rtsn_do_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) +{ + if (!netif_running(ndev)) + return -ENODEV; + + return phy_do_ioctl_running(ndev, ifr, cmd); +} + +static int rtsn_hwtstamp_get(struct net_device *ndev, + struct kernel_hwtstamp_config *config) +{ + struct rcar_gen4_ptp_private *ptp_priv; + struct rtsn_private *priv; + + if (!netif_running(ndev)) + return -ENODEV; + + priv = netdev_priv(ndev); + ptp_priv = priv->ptp_priv; + + config->flags = 0; + + config->tx_type = + ptp_priv->tstamp_tx_ctrl ? HWTSTAMP_TX_ON : HWTSTAMP_TX_OFF; + + switch (ptp_priv->tstamp_rx_ctrl & RCAR_GEN4_RXTSTAMP_TYPE) { + case RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + break; + case RCAR_GEN4_RXTSTAMP_TYPE_ALL: + config->rx_filter = HWTSTAMP_FILTER_ALL; + break; + default: + config->rx_filter = HWTSTAMP_FILTER_NONE; + break; + } + + return 0; +} + +static int rtsn_hwtstamp_set(struct net_device *ndev, + struct kernel_hwtstamp_config *config, + struct netlink_ext_ack *extack) +{ + struct rcar_gen4_ptp_private *ptp_priv; + struct rtsn_private *priv; + u32 tstamp_rx_ctrl; + u32 tstamp_tx_ctrl; + + if (!netif_running(ndev)) + return -ENODEV; + + priv = netdev_priv(ndev); + ptp_priv = priv->ptp_priv; + + if (config->flags) + return -EINVAL; + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + tstamp_tx_ctrl = 0; + break; + case HWTSTAMP_TX_ON: + tstamp_tx_ctrl = RCAR_GEN4_TXTSTAMP_ENABLED; + break; + default: + return -ERANGE; + } + + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + tstamp_rx_ctrl = 0; + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED | + RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT; + break; + default: + config->rx_filter = HWTSTAMP_FILTER_ALL; + tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED | + RCAR_GEN4_RXTSTAMP_TYPE_ALL; + break; + } + + ptp_priv->tstamp_tx_ctrl = tstamp_tx_ctrl; + ptp_priv->tstamp_rx_ctrl = tstamp_rx_ctrl; + + return 0; +} + +static const struct net_device_ops rtsn_netdev_ops = { + .ndo_open = rtsn_open, + .ndo_stop = rtsn_stop, + .ndo_start_xmit = rtsn_start_xmit, + .ndo_get_stats64 = rtsn_get_stats64, + .ndo_eth_ioctl = rtsn_do_ioctl, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = eth_mac_addr, + .ndo_hwtstamp_set = rtsn_hwtstamp_set, + .ndo_hwtstamp_get = rtsn_hwtstamp_get, +}; + +static int rtsn_get_ts_info(struct net_device *ndev, + struct kernel_ethtool_ts_info *info) +{ + struct rtsn_private *priv = netdev_priv(ndev); + + info->phc_index = ptp_clock_index(priv->ptp_priv->clock); + info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE | + SOF_TIMESTAMPING_RX_SOFTWARE | + SOF_TIMESTAMPING_SOFTWARE | + SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON); + info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | BIT(HWTSTAMP_FILTER_ALL); + + return 0; +} + +static const struct ethtool_ops rtsn_ethtool_ops = { + .nway_reset = phy_ethtool_nway_reset, + .get_link = ethtool_op_get_link, + .get_ts_info = rtsn_get_ts_info, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, +}; + +static const struct of_device_id rtsn_match_table[] = { + { .compatible = "renesas,r8a779g0-ethertsn", }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, rtsn_match_table); + +static int rtsn_probe(struct platform_device *pdev) +{ + struct rtsn_private *priv; + struct net_device *ndev; + struct resource *res; + int ret; + + ndev = alloc_etherdev_mqs(sizeof(struct rtsn_private), TX_NUM_CHAINS, + RX_NUM_CHAINS); + if (!ndev) + return -ENOMEM; + + priv = netdev_priv(ndev); + priv->pdev = pdev; + priv->ndev = ndev; + priv->ptp_priv = rcar_gen4_ptp_alloc(pdev); + + spin_lock_init(&priv->lock); + platform_set_drvdata(pdev, priv); + + priv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + goto error_free; + } + + priv->reset = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(priv->reset)) { + ret = PTR_ERR(priv->reset); + goto error_free; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsnes"); + if (!res) { + dev_err(&pdev->dev, "Can't find tsnes resource\n"); + ret = -EINVAL; + goto error_free; + } + + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) { + ret = PTR_ERR(priv->base); + goto error_free; + } + + SET_NETDEV_DEV(ndev, &pdev->dev); + + ndev->features = NETIF_F_RXCSUM; + ndev->hw_features = NETIF_F_RXCSUM; + ndev->base_addr = res->start; + ndev->netdev_ops = &rtsn_netdev_ops; + ndev->ethtool_ops = &rtsn_ethtool_ops; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gptp"); + if (!res) { + dev_err(&pdev->dev, "Can't find gptp resource\n"); + ret = -EINVAL; + goto error_free; + } + + priv->ptp_priv->addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->ptp_priv->addr)) { + ret = PTR_ERR(priv->ptp_priv->addr); + goto error_free; + } + + ret = rtsn_get_phy_params(priv); + if (ret) + goto error_free; + + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + netif_napi_add(ndev, &priv->napi, rtsn_poll); + + rtsn_parse_mac_address(pdev->dev.of_node, ndev); + + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + + device_set_wakeup_capable(&pdev->dev, 1); + + ret = rcar_gen4_ptp_register(priv->ptp_priv, RCAR_GEN4_PTP_REG_LAYOUT, + clk_get_rate(priv->clk)); + if (ret) + goto error_pm; + + ret = rtsn_mdio_alloc(priv); + if (ret) + goto error_ptp; + + ret = register_netdev(ndev); + if (ret) + goto error_mdio; + + netdev_info(ndev, "MAC address %pM\n", ndev->dev_addr); + + return 0; + +error_mdio: + rtsn_mdio_free(priv); +error_ptp: + rcar_gen4_ptp_unregister(priv->ptp_priv); +error_pm: + netif_napi_del(&priv->napi); + rtsn_change_mode(priv, OCR_OPC_DISABLE); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); +error_free: + free_netdev(ndev); + + return ret; +} + - static int rtsn_remove(struct platform_device *pdev) ++static void rtsn_remove(struct platform_device *pdev) +{ + struct rtsn_private *priv = platform_get_drvdata(pdev); + + unregister_netdev(priv->ndev); + rtsn_mdio_free(priv); + rcar_gen4_ptp_unregister(priv->ptp_priv); + rtsn_change_mode(priv, OCR_OPC_DISABLE); + netif_napi_del(&priv->napi); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + free_netdev(priv->ndev); - - return 0; +} + +static struct platform_driver rtsn_driver = { + .probe = rtsn_probe, + .remove = rtsn_remove, + .driver = { + .name = "rtsn", + .of_match_table = rtsn_match_table, + } +}; +module_platform_driver(rtsn_driver); + +MODULE_AUTHOR("Phong Hoang, Niklas Söderlund"); +MODULE_DESCRIPTION("Renesas Ethernet-TSN device driver"); +MODULE_LICENSE("GPL"); diff --cc drivers/reset/reset-meson-audio-arb.c index 894ad9d37a665,8740f5f6abf80..421ccb40da8c7 --- a/drivers/reset/reset-meson-audio-arb.c +++ b/drivers/reset/reset-meson-audio-arb.c @@@ -128,8 -128,8 +128,6 @@@ static void meson_audio_arb_remove(stru spin_lock(&arb->lock); writel(0, arb->regs); spin_unlock(&arb->lock); -- - return 0; - clk_disable_unprepare(arb->clk); } static int meson_audio_arb_probe(struct platform_device *pdev) diff --cc drivers/reset/reset-rzg2l-usbphy-ctrl.c index 255c894a47820,8f6fbd9785912..1cd157f4f03b4 --- a/drivers/reset/reset-rzg2l-usbphy-ctrl.c +++ b/drivers/reset/reset-rzg2l-usbphy-ctrl.c @@@ -158,41 -153,10 +158,41 @@@ static int rzg2l_usbphy_ctrl_probe(stru writel(val, priv->base + RESET); spin_unlock_irqrestore(&priv->lock, flags); + priv->rcdev.ops = &rzg2l_usbphy_ctrl_reset_ops; + priv->rcdev.of_reset_n_cells = 1; + priv->rcdev.nr_resets = NUM_PORTS; + priv->rcdev.of_node = dev->of_node; + priv->rcdev.dev = dev; + + error = devm_reset_controller_register(dev, &priv->rcdev); + if (error) + goto err_pm_runtime_put; + + vdev = platform_device_alloc("rzg2l-usb-vbus-regulator", pdev->id); + if (!vdev) { + error = -ENOMEM; + goto err_pm_runtime_put; + } + vdev->dev.parent = dev; + priv->vdev = vdev; + + error = platform_device_add(vdev); + if (error) + goto err_device_put; + return 0; + +err_device_put: + platform_device_put(vdev); +err_pm_runtime_put: + pm_runtime_put(&pdev->dev); +err_pm_disable_reset_deassert: + pm_runtime_disable(&pdev->dev); + reset_control_assert(priv->rstc); + return error; } - static int rzg2l_usbphy_ctrl_remove(struct platform_device *pdev) + static void rzg2l_usbphy_ctrl_remove(struct platform_device *pdev) { struct rzg2l_usbphy_ctrl_priv *priv = dev_get_drvdata(&pdev->dev); diff --cc rust/bindings/bindings_helper.h index 6deee85a29c89,18a3f05115cb2..53c996e4bedfe --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@@ -7,11 -7,9 +7,12 @@@ */ #include +#include +#include +#include #include #include + #include #include #include #include diff --cc rust/kernel/lib.rs index 2cf7c6b6f66b9,7707cb013ce90..e6b7d3a80bbce --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@@ -27,10 -27,11 +27,13 @@@ compile_error!("Missing kernel configur extern crate self as kernel; pub mod alloc; +#[cfg(CONFIG_BLOCK)] +pub mod block; mod build_assert; + pub mod device; pub mod error; + #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)] + pub mod firmware; pub mod init; pub mod ioctl; #[cfg(CONFIG_KUNIT)]