Merge tag 'driver-core-6.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 25 Jul 2024 17:42:22 +0000 (10:42 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 25 Jul 2024 17:42:22 +0000 (10:42 -0700)
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
  ...

51 files changed:
1  2 
MAINTAINERS
drivers/acpi/bus.c
drivers/base/auxiliary.c
drivers/base/cpu.c
drivers/bus/mhi/ep/main.c
drivers/bus/sunxi-rsb.c
drivers/cxl/cxl.h
drivers/firmware/arm_ffa/bus.c
drivers/firmware/google/coreboot_table.c
drivers/fsi/fsi-occ.c
drivers/gpio/gpiolib.c
drivers/gpu/drm/drm_mipi_dsi.c
drivers/gpu/drm/stm/lvds.c
drivers/gpu/ipu-v3/ipu-pre.c
drivers/gpu/ipu-v3/ipu-prg.c
drivers/greybus/core.c
drivers/hid/hid-core.c
drivers/hid/intel-ish-hid/ishtp/bus.c
drivers/i2c/i2c-core-base.c
drivers/input/gameport/gameport.c
drivers/input/serio/serio.c
drivers/most/core.c
drivers/net/ethernet/intel/ice/ice_ptp.c
drivers/net/ethernet/renesas/rtsn.c
drivers/net/phy/phy_device.c
drivers/nvdimm/e820.c
drivers/nvdimm/of_pmem.c
drivers/parport/share.c
drivers/peci/core.c
drivers/peci/internal.h
drivers/platform/x86/wmi.c
drivers/reset/reset-meson-audio-arb.c
drivers/reset/reset-rzg2l-usbphy-ctrl.c
drivers/spi/spi.c
drivers/staging/greybus/gbphy.c
drivers/tty/serial/serial_base_bus.c
drivers/usb/core/driver.c
drivers/vdpa/vdpa.c
drivers/virtio/virtio.c
include/acpi/acpi_bus.h
include/linux/arm_ffa.h
include/linux/auxiliary_bus.h
include/linux/i2c.h
include/linux/mhi.h
include/linux/pci-epf.h
include/linux/pci.h
include/linux/phy.h
include/linux/spi/spi.h
rust/bindings/bindings_helper.h
rust/helpers.c
rust/kernel/lib.rs

diff --cc MAINTAINERS
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -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[] = {
Simple merge
Simple merge
index bfc8cb1,0000000..2fa2c81
mode 100644,000000..100644
--- /dev/null
@@@ -1,1226 -1,0 +1,1224 @@@
- static int lvds_remove(struct platform_device *pdev)
 +// SPDX-License-Identifier: GPL-2.0-only
 +/*
 + * Copyright (C) 2023, STMicroelectronics - All Rights Reserved
 + * Author(s): Raphaël GALLAIS-POU <raphael.gallais-pou@foss.st.com> for STMicroelectronics.
 + */
 +
 +#include <drm/drm_atomic_helper.h>
 +#include <drm/drm_bridge.h>
 +#include <drm/drm_device.h>
 +#include <drm/drm_of.h>
 +#include <drm/drm_panel.h>
 +#include <drm/drm_print.h>
 +#include <drm/drm_probe_helper.h>
 +
 +#include <linux/clk.h>
 +#include <linux/clk-provider.h>
 +#include <linux/io.h>
 +#include <linux/iopoll.h>
 +#include <linux/media-bus-format.h>
 +#include <linux/module.h>
 +#include <linux/of_device.h>
 +#include <linux/platform_device.h>
 +#include <linux/reset.h>
 +
 +/* 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;
 +}
 +
-       return 0;
++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);
 +}
 +
 +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 <raphael.gallais-pou@foss.st.com>");
 +MODULE_AUTHOR("Philippe Cornu <philippe.cornu@foss.st.com>");
 +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@foss.st.com>");
 +MODULE_DESCRIPTION("STMicroelectronics LVDS Display Interface Transmitter DRM driver");
 +MODULE_LICENSE("GPL");
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 577227c,0000000..0e6cea4
mode 100644,000000..100644
--- /dev/null
@@@ -1,1391 -1,0 +1,1389 @@@
- static int rtsn_remove(struct platform_device *pdev)
 +// SPDX-License-Identifier: GPL-2.0
 +
 +/* Renesas Ethernet-TSN device driver
 + *
 + * Copyright (C) 2022 Renesas Electronics Corporation
 + * Copyright (C) 2023 Niklas Söderlund <niklas.soderlund@ragnatech.se>
 + */
 +
 +#include <linux/clk.h>
 +#include <linux/dma-mapping.h>
 +#include <linux/etherdevice.h>
 +#include <linux/ethtool.h>
 +#include <linux/module.h>
 +#include <linux/net_tstamp.h>
 +#include <linux/of.h>
 +#include <linux/of_mdio.h>
 +#include <linux/of_net.h>
 +#include <linux/phy.h>
 +#include <linux/platform_device.h>
 +#include <linux/pm_runtime.h>
 +#include <linux/reset.h>
 +#include <linux/spinlock.h>
 +
 +#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;
 +}
 +
-       return 0;
++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);
 +}
 +
 +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");
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -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)
@@@ -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);
  
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -7,11 -7,9 +7,12 @@@
   */
  
  #include <kunit/test.h>
 +#include <linux/blk_types.h>
 +#include <linux/blk-mq.h>
 +#include <linux/blkdev.h>
  #include <linux/errname.h>
  #include <linux/ethtool.h>
+ #include <linux/firmware.h>
  #include <linux/jiffies.h>
  #include <linux/mdio.h>
  #include <linux/phy.h>
diff --cc rust/helpers.c
Simple merge
@@@ -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)]