]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
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
index f7157c1d77d807c2b1a8277298d18037b4c3dfe5,21d2666c41954f8edc421a56002c1b876cb4ff2e..f58b158d097ce524bb5fa2a5fa0eb2966e407b01
@@@ -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 bfc8cb13fbc5c03d30edb5cfd64b7012e3718201,0000000000000000000000000000000000000000..2fa2c81784e971a295ab4c3a221dd0f3dcdb837b
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 577227c007ab428645a63447371805c63f8c6705,0000000000000000000000000000000000000000..0e6cea42f007710992ce7cd126e58af1078c1c45
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
index 894ad9d37a6659e32e1827f471e286c2f6ceacf8,8740f5f6abf801f099c340667699633351a5a4c1..421ccb40da8c7392ed292badf6286f40293551dc
@@@ -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)
index 255c894a47820f16b304560645e76f1396cee4f3,8f6fbd978591282ab9ae50a3732fdc296cb525d7..1cd157f4f03b4713ebee95cabe0f21d7c7f13933
@@@ -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
index 6deee85a29c89665b5dc21f164288eac4d143a3d,18a3f05115cb269494edd46e5db1f1119ae97758..53c996e4bedfe11af0ae0a09b19bbaa6d9bc5a8c
@@@ -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
index 2cf7c6b6f66b9de99c7f62e6fd3f8c4082fa3a17,7707cb013ce90122cd5fb90ab7648caf090e1dd0..e6b7d3a80bbce6f16192022b77bc45f863a0d7fa
@@@ -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)]