root/drivers/soc/fsl/qe/tsa.c
// SPDX-License-Identifier: GPL-2.0
/*
 * TSA driver
 *
 * Copyright 2022 CS GROUP France
 *
 * Author: Herve Codina <herve.codina@bootlin.com>
 */

#include "tsa.h"
#include <dt-bindings/soc/cpm1-fsl,tsa.h>
#include <dt-bindings/soc/qe-fsl,tsa.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <soc/fsl/qe/ucc.h>

/* TSA SI RAM routing tables entry (CPM1) */
#define TSA_CPM1_SIRAM_ENTRY_LAST       BIT(16)
#define TSA_CPM1_SIRAM_ENTRY_BYTE       BIT(17)
#define TSA_CPM1_SIRAM_ENTRY_CNT_MASK   GENMASK(21, 18)
#define TSA_CPM1_SIRAM_ENTRY_CNT(x)     FIELD_PREP(TSA_CPM1_SIRAM_ENTRY_CNT_MASK, x)
#define TSA_CPM1_SIRAM_ENTRY_CSEL_MASK  GENMASK(24, 22)
#define TSA_CPM1_SIRAM_ENTRY_CSEL_NU    FIELD_PREP_CONST(TSA_CPM1_SIRAM_ENTRY_CSEL_MASK, 0x0)
#define TSA_CPM1_SIRAM_ENTRY_CSEL_SCC2  FIELD_PREP_CONST(TSA_CPM1_SIRAM_ENTRY_CSEL_MASK, 0x2)
#define TSA_CPM1_SIRAM_ENTRY_CSEL_SCC3  FIELD_PREP_CONST(TSA_CPM1_SIRAM_ENTRY_CSEL_MASK, 0x3)
#define TSA_CPM1_SIRAM_ENTRY_CSEL_SCC4  FIELD_PREP_CONST(TSA_CPM1_SIRAM_ENTRY_CSEL_MASK, 0x4)
#define TSA_CPM1_SIRAM_ENTRY_CSEL_SMC1  FIELD_PREP_CONST(TSA_CPM1_SIRAM_ENTRY_CSEL_MASK, 0x5)
#define TSA_CPM1_SIRAM_ENTRY_CSEL_SMC2  FIELD_PREP_CONST(TSA_CPM1_SIRAM_ENTRY_CSEL_MASK, 0x6)

/* TSA SI RAM routing tables entry (QE) */
#define TSA_QE_SIRAM_ENTRY_LAST         BIT(0)
#define TSA_QE_SIRAM_ENTRY_BYTE         BIT(1)
#define TSA_QE_SIRAM_ENTRY_CNT_MASK     GENMASK(4, 2)
#define TSA_QE_SIRAM_ENTRY_CNT(x)       FIELD_PREP(TSA_QE_SIRAM_ENTRY_CNT_MASK, x)
#define TSA_QE_SIRAM_ENTRY_CSEL_MASK    GENMASK(8, 5)
#define TSA_QE_SIRAM_ENTRY_CSEL_NU      FIELD_PREP_CONST(TSA_QE_SIRAM_ENTRY_CSEL_MASK, 0x0)
#define TSA_QE_SIRAM_ENTRY_CSEL_UCC5    FIELD_PREP_CONST(TSA_QE_SIRAM_ENTRY_CSEL_MASK, 0x1)
#define TSA_QE_SIRAM_ENTRY_CSEL_UCC1    FIELD_PREP_CONST(TSA_QE_SIRAM_ENTRY_CSEL_MASK, 0x9)
#define TSA_QE_SIRAM_ENTRY_CSEL_UCC2    FIELD_PREP_CONST(TSA_QE_SIRAM_ENTRY_CSEL_MASK, 0xa)
#define TSA_QE_SIRAM_ENTRY_CSEL_UCC3    FIELD_PREP_CONST(TSA_QE_SIRAM_ENTRY_CSEL_MASK, 0xb)
#define TSA_QE_SIRAM_ENTRY_CSEL_UCC4    FIELD_PREP_CONST(TSA_QE_SIRAM_ENTRY_CSEL_MASK, 0xc)

/*
 * SI mode register :
 * - CPM1: 32bit register split in 2*16bit (16bit TDM)
 * - QE: 4x16bit registers, one per TDM
 */
#define TSA_CPM1_SIMODE         0x00
#define TSA_QE_SIAMR            0x00
#define TSA_QE_SIBMR            0x02
#define TSA_QE_SICMR            0x04
#define TSA_QE_SIDMR            0x06
#define   TSA_CPM1_SIMODE_SMC2                  BIT(31)
#define   TSA_CPM1_SIMODE_SMC1                  BIT(15)
#define   TSA_CPM1_SIMODE_TDMA_MASK             GENMASK(11, 0)
#define   TSA_CPM1_SIMODE_TDMA(x)               FIELD_PREP(TSA_CPM1_SIMODE_TDMA_MASK, x)
#define   TSA_CPM1_SIMODE_TDMB_MASK             GENMASK(27, 16)
#define   TSA_CPM1_SIMODE_TDMB(x)               FIELD_PREP(TSA_CPM1_SIMODE_TDMB_MASK, x)
#define     TSA_QE_SIMODE_TDM_SAD_MASK          GENMASK(15, 12)
#define     TSA_QE_SIMODE_TDM_SAD(x)            FIELD_PREP(TSA_QE_SIMODE_TDM_SAD_MASK, x)
#define     TSA_CPM1_SIMODE_TDM_MASK            GENMASK(11, 0)
#define     TSA_SIMODE_TDM_SDM_MASK             GENMASK(11, 10)
#define       TSA_SIMODE_TDM_SDM_NORM           FIELD_PREP_CONST(TSA_SIMODE_TDM_SDM_MASK, 0x0)
#define       TSA_SIMODE_TDM_SDM_ECHO           FIELD_PREP_CONST(TSA_SIMODE_TDM_SDM_MASK, 0x1)
#define       TSA_SIMODE_TDM_SDM_INTL_LOOP      FIELD_PREP_CONST(TSA_SIMODE_TDM_SDM_MASK, 0x2)
#define       TSA_SIMODE_TDM_SDM_LOOP_CTRL      FIELD_PREP_CONST(TSA_SIMODE_TDM_SDM_MASK, 0x3)
#define     TSA_SIMODE_TDM_RFSD_MASK            GENMASK(9, 8)
#define     TSA_SIMODE_TDM_RFSD(x)              FIELD_PREP(TSA_SIMODE_TDM_RFSD_MASK, x)
#define     TSA_SIMODE_TDM_DSC                  BIT(7)
#define     TSA_SIMODE_TDM_CRT                  BIT(6)
#define     TSA_CPM1_SIMODE_TDM_STZ             BIT(5) /* bit 5: STZ in CPM1 */
#define     TSA_QE_SIMODE_TDM_SL                BIT(5) /* bit 5: SL in QE */
#define     TSA_SIMODE_TDM_CE                   BIT(4)
#define     TSA_SIMODE_TDM_FE                   BIT(3)
#define     TSA_SIMODE_TDM_GM                   BIT(2)
#define     TSA_SIMODE_TDM_TFSD_MASK            GENMASK(1, 0)
#define     TSA_SIMODE_TDM_TFSD(x)              FIELD_PREP(TSA_SIMODE_TDM_TFSD_MASK, x)

/* CPM SI global mode register (8 bits) */
#define TSA_CPM1_SIGMR  0x04
#define TSA_CPM1_SIGMR_ENB                      BIT(3)
#define TSA_CPM1_SIGMR_ENA                      BIT(2)
#define TSA_CPM1_SIGMR_RDM_MASK                 GENMASK(1, 0)
#define   TSA_CPM1_SIGMR_RDM_STATIC_TDMA        FIELD_PREP_CONST(TSA_CPM1_SIGMR_RDM_MASK, 0x0)
#define   TSA_CPM1_SIGMR_RDM_DYN_TDMA           FIELD_PREP_CONST(TSA_CPM1_SIGMR_RDM_MASK, 0x1)
#define   TSA_CPM1_SIGMR_RDM_STATIC_TDMAB       FIELD_PREP_CONST(TSA_CPM1_SIGMR_RDM_MASK, 0x2)
#define   TSA_CPM1_SIGMR_RDM_DYN_TDMAB          FIELD_PREP_CONST(TSA_CPM1_SIGMR_RDM_MASK, 0x3)

/* QE SI global mode register high (8 bits) */
#define TSA_QE_SIGLMRH  0x08
#define TSA_QE_SIGLMRH_END      BIT(3)
#define TSA_QE_SIGLMRH_ENC      BIT(2)
#define TSA_QE_SIGLMRH_ENB      BIT(1)
#define TSA_QE_SIGLMRH_ENA      BIT(0)

/* SI clock route register (32 bits) */
#define TSA_CPM1_SICR   0x0C
#define   TSA_CPM1_SICR_SCC2_MASK               GENMASK(15, 8)
#define   TSA_CPM1_SICR_SCC2(x)                 FIELD_PREP(TSA_CPM1_SICR_SCC2_MASK, x)
#define   TSA_CPM1_SICR_SCC3_MASK               GENMASK(23, 16)
#define   TSA_CPM1_SICR_SCC3(x)                 FIELD_PREP(TSA_CPM1_SICR_SCC3_MASK, x)
#define   TSA_CPM1_SICR_SCC4_MASK               GENMASK(31, 24)
#define   TSA_CPM1_SICR_SCC4(x)                 FIELD_PREP(TSA_CPM1_SICR_SCC4_MASK, x)
#define     TSA_CPM1_SICR_SCC_MASK              GENMASK(7, 0)
#define     TSA_CPM1_SICR_SCC_GRX               BIT(7)
#define     TSA_CPM1_SICR_SCC_SCX_TSA           BIT(6)
#define     TSA_CPM1_SICR_SCC_RXCS_MASK         GENMASK(5, 3)
#define       TSA_CPM1_SICR_SCC_RXCS_BRG1       FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_RXCS_MASK, 0x0)
#define       TSA_CPM1_SICR_SCC_RXCS_BRG2       FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_RXCS_MASK, 0x1)
#define       TSA_CPM1_SICR_SCC_RXCS_BRG3       FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_RXCS_MASK, 0x2)
#define       TSA_CPM1_SICR_SCC_RXCS_BRG4       FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_RXCS_MASK, 0x3)
#define       TSA_CPM1_SICR_SCC_RXCS_CLK15      FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_RXCS_MASK, 0x4)
#define       TSA_CPM1_SICR_SCC_RXCS_CLK26      FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_RXCS_MASK, 0x5)
#define       TSA_CPM1_SICR_SCC_RXCS_CLK37      FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_RXCS_MASK, 0x6)
#define       TSA_CPM1_SICR_SCC_RXCS_CLK48      FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_RXCS_MASK, 0x7)
#define     TSA_CPM1_SICR_SCC_TXCS_MASK         GENMASK(2, 0)
#define       TSA_CPM1_SICR_SCC_TXCS_BRG1       FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_TXCS_MASK, 0x0)
#define       TSA_CPM1_SICR_SCC_TXCS_BRG2       FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_TXCS_MASK, 0x1)
#define       TSA_CPM1_SICR_SCC_TXCS_BRG3       FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_TXCS_MASK, 0x2)
#define       TSA_CPM1_SICR_SCC_TXCS_BRG4       FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_TXCS_MASK, 0x3)
#define       TSA_CPM1_SICR_SCC_TXCS_CLK15      FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_TXCS_MASK, 0x4)
#define       TSA_CPM1_SICR_SCC_TXCS_CLK26      FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_TXCS_MASK, 0x5)
#define       TSA_CPM1_SICR_SCC_TXCS_CLK37      FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_TXCS_MASK, 0x6)
#define       TSA_CPM1_SICR_SCC_TXCS_CLK48      FIELD_PREP_CONST(TSA_CPM1_SICR_SCC_TXCS_MASK, 0x7)

struct tsa_entries_area {
        void __iomem *entries_start;
        void __iomem *entries_next;
        void __iomem *last_entry;
};

struct tsa_tdm {
        bool is_enable;
        struct clk *l1rclk_clk;
        struct clk *l1rsync_clk;
        struct clk *l1tclk_clk;
        struct clk *l1tsync_clk;
        u32 simode_tdm;
};

#define TSA_TDMA        0
#define TSA_TDMB        1
#define TSA_TDMC        2 /* QE implementation only */
#define TSA_TDMD        3 /* QE implementation only */

enum tsa_version {
        TSA_CPM1 = 1, /* Avoid 0 value */
        TSA_QE,
};

struct tsa {
        struct device *dev;
        void __iomem *si_regs;
        void __iomem *si_ram;
        resource_size_t si_ram_sz;
        spinlock_t      lock; /* Lock for read/modify/write sequence */
        enum tsa_version version;
        int tdms; /* TSA_TDMx ORed */
#if IS_ENABLED(CONFIG_QUICC_ENGINE)
        struct tsa_tdm tdm[4]; /* TDMa, TDMb, TDMc and TDMd */
#else
        struct tsa_tdm tdm[2]; /* TDMa and TDMb */
#endif
        /* Same number of serials for CPM1 and QE:
         * CPM1: NU, 3 SCCs and 2 SMCs
         * QE: NU and 5 UCCs
         */
        struct tsa_serial {
                unsigned int id;
                struct tsa_serial_info info;
        } serials[6];
};

static inline struct tsa *tsa_serial_get_tsa(struct tsa_serial *tsa_serial)
{
        /* The serials table is indexed by the serial id */
        return container_of(tsa_serial, struct tsa, serials[tsa_serial->id]);
}

static inline void tsa_write32(void __iomem *addr, u32 val)
{
        iowrite32be(val, addr);
}

static inline void tsa_write16(void __iomem *addr, u16 val)
{
        iowrite16be(val, addr);
}

static inline void tsa_write8(void __iomem *addr, u8 val)
{
        iowrite8(val, addr);
}

static inline u32 tsa_read32(void __iomem *addr)
{
        return ioread32be(addr);
}

static inline u16 tsa_read16(void __iomem *addr)
{
        return ioread16be(addr);
}

static inline void tsa_clrbits32(void __iomem *addr, u32 clr)
{
        tsa_write32(addr, tsa_read32(addr) & ~clr);
}

static inline void tsa_clrbits16(void __iomem *addr, u16 clr)
{
        tsa_write16(addr, tsa_read16(addr) & ~clr);
}

static inline void tsa_clrsetbits32(void __iomem *addr, u32 clr, u32 set)
{
        tsa_write32(addr, (tsa_read32(addr) & ~clr) | set);
}

static bool tsa_is_qe(const struct tsa *tsa)
{
        if (IS_ENABLED(CONFIG_QUICC_ENGINE) && IS_ENABLED(CONFIG_CPM))
                return tsa->version == TSA_QE;

        return IS_ENABLED(CONFIG_QUICC_ENGINE);
}

static int tsa_qe_serial_get_num(struct tsa_serial *tsa_serial)
{
        struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);

        switch (tsa_serial->id) {
        case FSL_QE_TSA_UCC1: return 0;
        case FSL_QE_TSA_UCC2: return 1;
        case FSL_QE_TSA_UCC3: return 2;
        case FSL_QE_TSA_UCC4: return 3;
        case FSL_QE_TSA_UCC5: return 4;
        default:
                break;
        }

        dev_err(tsa->dev, "Unsupported serial id %u\n", tsa_serial->id);
        return -EINVAL;
}

int tsa_serial_get_num(struct tsa_serial *tsa_serial)
{
        struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);

        /*
         * There is no need to get the serial num out of the TSA driver in the
         * CPM case.
         * Further more, in CPM, we can have 2 types of serial SCCs and FCCs.
         * What kind of numbering to use that can be global to both SCCs and
         * FCCs ?
         */
        return tsa_is_qe(tsa) ? tsa_qe_serial_get_num(tsa_serial) : -EOPNOTSUPP;
}
EXPORT_SYMBOL(tsa_serial_get_num);

static int tsa_cpm1_serial_connect(struct tsa_serial *tsa_serial, bool connect)
{
        struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);
        unsigned long flags;
        u32 clear;
        u32 set;

        switch (tsa_serial->id) {
        case FSL_CPM_TSA_SCC2:
                clear = TSA_CPM1_SICR_SCC2(TSA_CPM1_SICR_SCC_MASK);
                set = TSA_CPM1_SICR_SCC2(TSA_CPM1_SICR_SCC_SCX_TSA);
                break;
        case FSL_CPM_TSA_SCC3:
                clear = TSA_CPM1_SICR_SCC3(TSA_CPM1_SICR_SCC_MASK);
                set = TSA_CPM1_SICR_SCC3(TSA_CPM1_SICR_SCC_SCX_TSA);
                break;
        case FSL_CPM_TSA_SCC4:
                clear = TSA_CPM1_SICR_SCC4(TSA_CPM1_SICR_SCC_MASK);
                set = TSA_CPM1_SICR_SCC4(TSA_CPM1_SICR_SCC_SCX_TSA);
                break;
        default:
                dev_err(tsa->dev, "Unsupported serial id %u\n", tsa_serial->id);
                return -EINVAL;
        }

        spin_lock_irqsave(&tsa->lock, flags);
        tsa_clrsetbits32(tsa->si_regs + TSA_CPM1_SICR, clear,
                         connect ? set : 0);
        spin_unlock_irqrestore(&tsa->lock, flags);

        return 0;
}

static int tsa_qe_serial_connect(struct tsa_serial *tsa_serial, bool connect)
{
        struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);
        unsigned long flags;
        int ucc_num;
        int ret;

        ucc_num = tsa_qe_serial_get_num(tsa_serial);
        if (ucc_num < 0)
                return ucc_num;

        spin_lock_irqsave(&tsa->lock, flags);
        ret = ucc_set_qe_mux_tsa(ucc_num, connect);
        spin_unlock_irqrestore(&tsa->lock, flags);
        if (ret) {
                dev_err(tsa->dev, "Connect serial id %u to TSA failed (%d)\n",
                        tsa_serial->id, ret);
                return ret;
        }
        return 0;
}

int tsa_serial_connect(struct tsa_serial *tsa_serial)
{
        struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);

        return tsa_is_qe(tsa) ?
                tsa_qe_serial_connect(tsa_serial, true) :
                tsa_cpm1_serial_connect(tsa_serial, true);
}
EXPORT_SYMBOL(tsa_serial_connect);

int tsa_serial_disconnect(struct tsa_serial *tsa_serial)
{
        struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);

        return tsa_is_qe(tsa) ?
                tsa_qe_serial_connect(tsa_serial, false) :
                tsa_cpm1_serial_connect(tsa_serial, false);
}
EXPORT_SYMBOL(tsa_serial_disconnect);

int tsa_serial_get_info(struct tsa_serial *tsa_serial, struct tsa_serial_info *info)
{
        memcpy(info, &tsa_serial->info, sizeof(*info));
        return 0;
}
EXPORT_SYMBOL(tsa_serial_get_info);

static void tsa_cpm1_init_entries_area(struct tsa *tsa, struct tsa_entries_area *area,
                                       u32 tdms, u32 tdm_id, bool is_rx)
{
        resource_size_t quarter;
        resource_size_t half;

        quarter = tsa->si_ram_sz / 4;
        half = tsa->si_ram_sz / 2;

        if (tdms == BIT(TSA_TDMA)) {
                /* Only TDMA */
                if (is_rx) {
                        /* First half of si_ram */
                        area->entries_start = tsa->si_ram;
                        area->entries_next = area->entries_start + half;
                        area->last_entry = NULL;
                } else {
                        /* Second half of si_ram */
                        area->entries_start = tsa->si_ram + half;
                        area->entries_next = area->entries_start + half;
                        area->last_entry = NULL;
                }
        } else {
                /* Only TDMB or both TDMs */
                if (tdm_id == TSA_TDMA) {
                        if (is_rx) {
                                /* First half of first half of si_ram */
                                area->entries_start = tsa->si_ram;
                                area->entries_next = area->entries_start + quarter;
                                area->last_entry = NULL;
                        } else {
                                /* First half of second half of si_ram */
                                area->entries_start = tsa->si_ram + (2 * quarter);
                                area->entries_next = area->entries_start + quarter;
                                area->last_entry = NULL;
                        }
                } else {
                        if (is_rx) {
                                /* Second half of first half of si_ram */
                                area->entries_start = tsa->si_ram + quarter;
                                area->entries_next = area->entries_start + quarter;
                                area->last_entry = NULL;
                        } else {
                                /* Second half of second half of si_ram */
                                area->entries_start = tsa->si_ram + (3 * quarter);
                                area->entries_next = area->entries_start + quarter;
                                area->last_entry = NULL;
                        }
                }
        }
}

static void tsa_qe_init_entries_area(struct tsa *tsa, struct tsa_entries_area *area,
                                     u32 tdms, u32 tdm_id, bool is_rx)
{
        resource_size_t eighth;
        resource_size_t half;

        eighth = tsa->si_ram_sz / 8;
        half = tsa->si_ram_sz / 2;

        /*
         * One half of the SI RAM used for Tx, the other one for Rx.
         * In each half, 1/4 of the area is assigned to each TDM.
         */
        if (is_rx) {
                /* Rx: Second half of si_ram */
                area->entries_start = tsa->si_ram + half + (eighth * tdm_id);
                area->entries_next = area->entries_start + eighth;
                area->last_entry = NULL;
        } else {
                /* Tx: First half of si_ram */
                area->entries_start = tsa->si_ram + (eighth * tdm_id);
                area->entries_next = area->entries_start + eighth;
                area->last_entry = NULL;
        }
}

static void tsa_init_entries_area(struct tsa *tsa, struct tsa_entries_area *area,
                                  u32 tdms, u32 tdm_id, bool is_rx)
{
        if (tsa_is_qe(tsa))
                tsa_qe_init_entries_area(tsa, area, tdms, tdm_id, is_rx);
        else
                tsa_cpm1_init_entries_area(tsa, area, tdms, tdm_id, is_rx);
}

static const char *tsa_cpm1_serial_id2name(struct tsa *tsa, u32 serial_id)
{
        switch (serial_id) {
        case FSL_CPM_TSA_NU:    return "Not used";
        case FSL_CPM_TSA_SCC2:  return "SCC2";
        case FSL_CPM_TSA_SCC3:  return "SCC3";
        case FSL_CPM_TSA_SCC4:  return "SCC4";
        case FSL_CPM_TSA_SMC1:  return "SMC1";
        case FSL_CPM_TSA_SMC2:  return "SMC2";
        default:
                break;
        }
        return NULL;
}

static const char *tsa_qe_serial_id2name(struct tsa *tsa, u32 serial_id)
{
        switch (serial_id) {
        case FSL_QE_TSA_NU:     return "Not used";
        case FSL_QE_TSA_UCC1:   return "UCC1";
        case FSL_QE_TSA_UCC2:   return "UCC2";
        case FSL_QE_TSA_UCC3:   return "UCC3";
        case FSL_QE_TSA_UCC4:   return "UCC4";
        case FSL_QE_TSA_UCC5:   return "UCC5";
        default:
                break;
        }
        return NULL;
}

static const char *tsa_serial_id2name(struct tsa *tsa, u32 serial_id)
{
        return tsa_is_qe(tsa) ?
                tsa_qe_serial_id2name(tsa, serial_id) :
                tsa_cpm1_serial_id2name(tsa, serial_id);
}

static u32 tsa_cpm1_serial_id2csel(struct tsa *tsa, u32 serial_id)
{
        switch (serial_id) {
        case FSL_CPM_TSA_SCC2:  return TSA_CPM1_SIRAM_ENTRY_CSEL_SCC2;
        case FSL_CPM_TSA_SCC3:  return TSA_CPM1_SIRAM_ENTRY_CSEL_SCC3;
        case FSL_CPM_TSA_SCC4:  return TSA_CPM1_SIRAM_ENTRY_CSEL_SCC4;
        case FSL_CPM_TSA_SMC1:  return TSA_CPM1_SIRAM_ENTRY_CSEL_SMC1;
        case FSL_CPM_TSA_SMC2:  return TSA_CPM1_SIRAM_ENTRY_CSEL_SMC2;
        default:
                break;
        }
        return TSA_CPM1_SIRAM_ENTRY_CSEL_NU;
}

static int tsa_cpm1_add_entry(struct tsa *tsa, struct tsa_entries_area *area,
                              u32 count, u32 serial_id)
{
        void __iomem *addr;
        u32 left;
        u32 val;
        u32 cnt;
        u32 nb;

        addr = area->last_entry ? area->last_entry + 4 : area->entries_start;

        nb = DIV_ROUND_UP(count, 8);
        if ((addr + (nb * 4)) > area->entries_next) {
                dev_err(tsa->dev, "si ram area full\n");
                return -ENOSPC;
        }

        if (area->last_entry) {
                /* Clear last flag */
                tsa_clrbits32(area->last_entry, TSA_CPM1_SIRAM_ENTRY_LAST);
        }

        left = count;
        while (left) {
                val = TSA_CPM1_SIRAM_ENTRY_BYTE | tsa_cpm1_serial_id2csel(tsa, serial_id);

                if (left > 16) {
                        cnt = 16;
                } else {
                        cnt = left;
                        val |= TSA_CPM1_SIRAM_ENTRY_LAST;
                        area->last_entry = addr;
                }
                val |= TSA_CPM1_SIRAM_ENTRY_CNT(cnt - 1);

                tsa_write32(addr, val);
                addr += 4;
                left -= cnt;
        }

        return 0;
}

static u32 tsa_qe_serial_id2csel(struct tsa *tsa, u32 serial_id)
{
        switch (serial_id) {
        case FSL_QE_TSA_UCC1:   return TSA_QE_SIRAM_ENTRY_CSEL_UCC1;
        case FSL_QE_TSA_UCC2:   return TSA_QE_SIRAM_ENTRY_CSEL_UCC2;
        case FSL_QE_TSA_UCC3:   return TSA_QE_SIRAM_ENTRY_CSEL_UCC3;
        case FSL_QE_TSA_UCC4:   return TSA_QE_SIRAM_ENTRY_CSEL_UCC4;
        case FSL_QE_TSA_UCC5:   return TSA_QE_SIRAM_ENTRY_CSEL_UCC5;
        default:
                break;
        }
        return TSA_QE_SIRAM_ENTRY_CSEL_NU;
}

static int tsa_qe_add_entry(struct tsa *tsa, struct tsa_entries_area *area,
                            u32 count, u32 serial_id)
{
        void __iomem *addr;
        u32 left;
        u32 val;
        u32 cnt;
        u32 nb;

        addr = area->last_entry ? area->last_entry + 2 : area->entries_start;

        nb = DIV_ROUND_UP(count, 8);
        if ((addr + (nb * 2)) > area->entries_next) {
                dev_err(tsa->dev, "si ram area full\n");
                return -ENOSPC;
        }

        if (area->last_entry) {
                /* Clear last flag */
                tsa_clrbits16(area->last_entry, TSA_QE_SIRAM_ENTRY_LAST);
        }

        left = count;
        while (left) {
                val = TSA_QE_SIRAM_ENTRY_BYTE | tsa_qe_serial_id2csel(tsa, serial_id);

                if (left > 8) {
                        cnt = 8;
                } else {
                        cnt = left;
                        val |= TSA_QE_SIRAM_ENTRY_LAST;
                        area->last_entry = addr;
                }
                val |= TSA_QE_SIRAM_ENTRY_CNT(cnt - 1);

                tsa_write16(addr, val);
                addr += 2;
                left -= cnt;
        }

        return 0;
}

static int tsa_add_entry(struct tsa *tsa, struct tsa_entries_area *area,
                         u32 count, u32 serial_id)
{
        return tsa_is_qe(tsa) ?
                tsa_qe_add_entry(tsa, area, count, serial_id) :
                tsa_cpm1_add_entry(tsa, area, count, serial_id);
}

static int tsa_of_parse_tdm_route(struct tsa *tsa, struct device_node *tdm_np,
                                  u32 tdms, u32 tdm_id, bool is_rx)
{
        struct tsa_entries_area area;
        const char *route_name;
        u32 serial_id;
        int len, i;
        u32 count;
        const char *serial_name;
        struct tsa_serial_info *serial_info;
        struct tsa_tdm *tdm;
        int ret;
        u32 ts;

        route_name = is_rx ? "fsl,rx-ts-routes" : "fsl,tx-ts-routes";

        len = of_property_count_u32_elems(tdm_np,  route_name);
        if (len < 0) {
                dev_err(tsa->dev, "%pOF: failed to read %s\n", tdm_np, route_name);
                return len;
        }
        if (len % 2 != 0) {
                dev_err(tsa->dev, "%pOF: wrong %s format\n", tdm_np, route_name);
                return -EINVAL;
        }

        tsa_init_entries_area(tsa, &area, tdms, tdm_id, is_rx);
        ts = 0;
        for (i = 0; i < len; i += 2) {
                of_property_read_u32_index(tdm_np, route_name, i, &count);
                of_property_read_u32_index(tdm_np, route_name, i + 1, &serial_id);

                if (serial_id >= ARRAY_SIZE(tsa->serials)) {
                        dev_err(tsa->dev, "%pOF: invalid serial id (%u)\n",
                                tdm_np, serial_id);
                        return -EINVAL;
                }

                serial_name = tsa_serial_id2name(tsa, serial_id);
                if (!serial_name) {
                        dev_err(tsa->dev, "%pOF: unsupported serial id (%u)\n",
                                tdm_np, serial_id);
                        return -EINVAL;
                }

                dev_dbg(tsa->dev, "tdm_id=%u, %s ts %u..%u -> %s\n",
                        tdm_id, route_name, ts, ts + count - 1, serial_name);
                ts += count;

                ret = tsa_add_entry(tsa, &area, count, serial_id);
                if (ret)
                        return ret;

                serial_info = &tsa->serials[serial_id].info;
                tdm = &tsa->tdm[tdm_id];
                if (is_rx) {
                        serial_info->rx_fs_rate = clk_get_rate(tdm->l1rsync_clk);
                        serial_info->rx_bit_rate = clk_get_rate(tdm->l1rclk_clk);
                        serial_info->nb_rx_ts += count;
                } else {
                        serial_info->tx_fs_rate = tdm->l1tsync_clk ?
                                clk_get_rate(tdm->l1tsync_clk) :
                                clk_get_rate(tdm->l1rsync_clk);
                        serial_info->tx_bit_rate = tdm->l1tclk_clk ?
                                clk_get_rate(tdm->l1tclk_clk) :
                                clk_get_rate(tdm->l1rclk_clk);
                        serial_info->nb_tx_ts += count;
                }
        }
        return 0;
}

static inline int tsa_of_parse_tdm_rx_route(struct tsa *tsa,
                                            struct device_node *tdm_np,
                                            u32 tdms, u32 tdm_id)
{
        return tsa_of_parse_tdm_route(tsa, tdm_np, tdms, tdm_id, true);
}

static inline int tsa_of_parse_tdm_tx_route(struct tsa *tsa,
                                            struct device_node *tdm_np,
                                            u32 tdms, u32 tdm_id)
{
        return tsa_of_parse_tdm_route(tsa, tdm_np, tdms, tdm_id, false);
}

static int tsa_of_parse_tdms(struct tsa *tsa, struct device_node *np)
{
        struct tsa_tdm *tdm;
        struct clk *clk;
        u32 tdm_id, val;
        int ret;
        int i;

        tsa->tdms = 0;
        for (i = 0; i < ARRAY_SIZE(tsa->tdm); i++)
                tsa->tdm[i].is_enable = false;

        for_each_available_child_of_node_scoped(np, tdm_np) {
                ret = of_property_read_u32(tdm_np, "reg", &tdm_id);
                if (ret) {
                        dev_err(tsa->dev, "%pOF: failed to read reg\n", tdm_np);
                        return ret;
                }
                switch (tdm_id) {
                case 0:
                        tsa->tdms |= BIT(TSA_TDMA);
                        break;
                case 1:
                        tsa->tdms |= BIT(TSA_TDMB);
                        break;
                case 2:
                        if (!tsa_is_qe(tsa))
                                goto invalid_tdm; /* Not available on CPM1 */
                        tsa->tdms |= BIT(TSA_TDMC);
                        break;
                case 3:
                        if (!tsa_is_qe(tsa))
                                goto invalid_tdm;  /* Not available on CPM1 */
                        tsa->tdms |= BIT(TSA_TDMD);
                        break;
                default:
invalid_tdm:
                        dev_err(tsa->dev, "%pOF: Invalid tdm_id (%u)\n", tdm_np,
                                tdm_id);
                        return -EINVAL;
                }
        }

        for_each_available_child_of_node_scoped(np, tdm_np) {
                ret = of_property_read_u32(tdm_np, "reg", &tdm_id);
                if (ret) {
                        dev_err(tsa->dev, "%pOF: failed to read reg\n", tdm_np);
                        return ret;
                }

                tdm = &tsa->tdm[tdm_id];
                tdm->simode_tdm = TSA_SIMODE_TDM_SDM_NORM;

                val = 0;
                ret = of_property_read_u32(tdm_np, "fsl,rx-frame-sync-delay-bits",
                                           &val);
                if (ret && ret != -EINVAL) {
                        dev_err(tsa->dev,
                                "%pOF: failed to read fsl,rx-frame-sync-delay-bits\n",
                                tdm_np);
                        return ret;
                }
                if (val > 3) {
                        dev_err(tsa->dev,
                                "%pOF: Invalid fsl,rx-frame-sync-delay-bits (%u)\n",
                                tdm_np, val);
                        return -EINVAL;
                }
                tdm->simode_tdm |= TSA_SIMODE_TDM_RFSD(val);

                val = 0;
                ret = of_property_read_u32(tdm_np, "fsl,tx-frame-sync-delay-bits",
                                           &val);
                if (ret && ret != -EINVAL) {
                        dev_err(tsa->dev,
                                "%pOF: failed to read fsl,tx-frame-sync-delay-bits\n",
                                tdm_np);
                        return ret;
                }
                if (val > 3) {
                        dev_err(tsa->dev,
                                "%pOF: Invalid fsl,tx-frame-sync-delay-bits (%u)\n",
                                tdm_np, val);
                        return -EINVAL;
                }
                tdm->simode_tdm |= TSA_SIMODE_TDM_TFSD(val);

                if (of_property_read_bool(tdm_np, "fsl,common-rxtx-pins"))
                        tdm->simode_tdm |= TSA_SIMODE_TDM_CRT;

                if (of_property_read_bool(tdm_np, "fsl,clock-falling-edge"))
                        tdm->simode_tdm |= TSA_SIMODE_TDM_CE;

                if (of_property_read_bool(tdm_np, "fsl,fsync-rising-edge"))
                        tdm->simode_tdm |= TSA_SIMODE_TDM_FE;

                if (tsa_is_qe(tsa) &&
                    of_property_read_bool(tdm_np, "fsl,fsync-active-low"))
                        tdm->simode_tdm |= TSA_QE_SIMODE_TDM_SL;

                if (of_property_read_bool(tdm_np, "fsl,double-speed-clock"))
                        tdm->simode_tdm |= TSA_SIMODE_TDM_DSC;

                clk = of_clk_get_by_name(tdm_np, tsa_is_qe(tsa) ? "rsync" : "l1rsync");
                if (IS_ERR(clk)) {
                        ret = PTR_ERR(clk);
                        goto err;
                }
                ret = clk_prepare_enable(clk);
                if (ret) {
                        clk_put(clk);
                        goto err;
                }
                tdm->l1rsync_clk = clk;

                clk = of_clk_get_by_name(tdm_np, tsa_is_qe(tsa) ? "rclk" : "l1rclk");
                if (IS_ERR(clk)) {
                        ret = PTR_ERR(clk);
                        goto err;
                }
                ret = clk_prepare_enable(clk);
                if (ret) {
                        clk_put(clk);
                        goto err;
                }
                tdm->l1rclk_clk = clk;

                if (!(tdm->simode_tdm & TSA_SIMODE_TDM_CRT)) {
                        clk = of_clk_get_by_name(tdm_np, tsa_is_qe(tsa) ? "tsync" : "l1tsync");
                        if (IS_ERR(clk)) {
                                ret = PTR_ERR(clk);
                                goto err;
                        }
                        ret = clk_prepare_enable(clk);
                        if (ret) {
                                clk_put(clk);
                                goto err;
                        }
                        tdm->l1tsync_clk = clk;

                        clk = of_clk_get_by_name(tdm_np, tsa_is_qe(tsa) ? "tclk" : "l1tclk");
                        if (IS_ERR(clk)) {
                                ret = PTR_ERR(clk);
                                goto err;
                        }
                        ret = clk_prepare_enable(clk);
                        if (ret) {
                                clk_put(clk);
                                goto err;
                        }
                        tdm->l1tclk_clk = clk;
                }

                if (tsa_is_qe(tsa)) {
                        /*
                         * The starting address for TSA table must be set.
                         * 512 entries for Tx and 512 entries for Rx are
                         * available for 4 TDMs.
                         * We assign entries equally -> 128 Rx/Tx entries per
                         * TDM. In other words, 4 blocks of 32 entries per TDM.
                         */
                        tdm->simode_tdm |= TSA_QE_SIMODE_TDM_SAD(4 * tdm_id);
                }

                ret = tsa_of_parse_tdm_rx_route(tsa, tdm_np, tsa->tdms, tdm_id);
                if (ret)
                        goto err;

                ret = tsa_of_parse_tdm_tx_route(tsa, tdm_np, tsa->tdms, tdm_id);
                if (ret)
                        goto err;

                tdm->is_enable = true;
        }
        return 0;

err:
        for (i = 0; i < ARRAY_SIZE(tsa->tdm); i++) {
                if (tsa->tdm[i].l1rsync_clk) {
                        clk_disable_unprepare(tsa->tdm[i].l1rsync_clk);
                        clk_put(tsa->tdm[i].l1rsync_clk);
                }
                if (tsa->tdm[i].l1rclk_clk) {
                        clk_disable_unprepare(tsa->tdm[i].l1rclk_clk);
                        clk_put(tsa->tdm[i].l1rclk_clk);
                }
                if (tsa->tdm[i].l1tsync_clk) {
                        clk_disable_unprepare(tsa->tdm[i].l1rsync_clk);
                        clk_put(tsa->tdm[i].l1rsync_clk);
                }
                if (tsa->tdm[i].l1tclk_clk) {
                        clk_disable_unprepare(tsa->tdm[i].l1rclk_clk);
                        clk_put(tsa->tdm[i].l1rclk_clk);
                }
        }
        return ret;
}

static void tsa_init_si_ram(struct tsa *tsa)
{
        resource_size_t i;

        /* Fill all entries as the last one */
        if (tsa_is_qe(tsa)) {
                for (i = 0; i < tsa->si_ram_sz; i += 2)
                        tsa_write16(tsa->si_ram + i, TSA_QE_SIRAM_ENTRY_LAST);
        } else {
                for (i = 0; i < tsa->si_ram_sz; i += 4)
                        tsa_write32(tsa->si_ram + i, TSA_CPM1_SIRAM_ENTRY_LAST);
        }
}

static int tsa_cpm1_setup(struct tsa *tsa)
{
        u32 val;

        /* Set SIMODE */
        val = 0;
        if (tsa->tdm[0].is_enable)
                val |= TSA_CPM1_SIMODE_TDMA(tsa->tdm[0].simode_tdm);
        if (tsa->tdm[1].is_enable)
                val |= TSA_CPM1_SIMODE_TDMB(tsa->tdm[1].simode_tdm);

        tsa_clrsetbits32(tsa->si_regs + TSA_CPM1_SIMODE,
                         TSA_CPM1_SIMODE_TDMA(TSA_CPM1_SIMODE_TDM_MASK) |
                         TSA_CPM1_SIMODE_TDMB(TSA_CPM1_SIMODE_TDM_MASK),
                         val);

        /* Set SIGMR */
        val = (tsa->tdms == BIT(TSA_TDMA)) ?
                TSA_CPM1_SIGMR_RDM_STATIC_TDMA : TSA_CPM1_SIGMR_RDM_STATIC_TDMAB;
        if (tsa->tdms & BIT(TSA_TDMA))
                val |= TSA_CPM1_SIGMR_ENA;
        if (tsa->tdms & BIT(TSA_TDMB))
                val |= TSA_CPM1_SIGMR_ENB;
        tsa_write8(tsa->si_regs + TSA_CPM1_SIGMR, val);

        return 0;
}

static int tsa_qe_setup(struct tsa *tsa)
{
        unsigned int sixmr;
        u8 siglmrh = 0;
        unsigned int i;

        for (i = 0; i < ARRAY_SIZE(tsa->tdm); i++) {
                if (!tsa->tdm[i].is_enable)
                        continue;

                switch (i) {
                case 0:
                        sixmr = TSA_QE_SIAMR;
                        siglmrh |= TSA_QE_SIGLMRH_ENA;
                        break;
                case 1:
                        sixmr = TSA_QE_SIBMR;
                        siglmrh |= TSA_QE_SIGLMRH_ENB;
                        break;
                case 2:
                        sixmr = TSA_QE_SICMR;
                        siglmrh |= TSA_QE_SIGLMRH_ENC;
                        break;
                case 3:
                        sixmr = TSA_QE_SIDMR;
                        siglmrh |= TSA_QE_SIGLMRH_END;
                        break;
                default:
                        return -EINVAL;
                }

                /* Set SI mode register */
                tsa_write16(tsa->si_regs + sixmr, tsa->tdm[i].simode_tdm);
        }

        /* Enable TDMs */
        tsa_write8(tsa->si_regs + TSA_QE_SIGLMRH, siglmrh);

        return 0;
}

static int tsa_setup(struct tsa *tsa)
{
        return tsa_is_qe(tsa) ? tsa_qe_setup(tsa) : tsa_cpm1_setup(tsa);
}

static int tsa_probe(struct platform_device *pdev)
{
        struct device_node *np = pdev->dev.of_node;
        struct resource *res;
        struct tsa *tsa;
        unsigned int i;
        int ret;

        tsa = devm_kzalloc(&pdev->dev, sizeof(*tsa), GFP_KERNEL);
        if (!tsa)
                return -ENOMEM;

        tsa->dev = &pdev->dev;
        tsa->version = (enum tsa_version)(uintptr_t)of_device_get_match_data(&pdev->dev);
        switch (tsa->version) {
        case TSA_CPM1:
                dev_info(tsa->dev, "CPM1 version\n");
                break;
        case TSA_QE:
                dev_info(tsa->dev, "QE version\n");
                break;
        default:
                dev_err(tsa->dev, "Unknown version (%d)\n", tsa->version);
                return -EINVAL;
        }

        for (i = 0; i < ARRAY_SIZE(tsa->serials); i++)
                tsa->serials[i].id = i;

        spin_lock_init(&tsa->lock);

        tsa->si_regs = devm_platform_ioremap_resource_byname(pdev, "si_regs");
        if (IS_ERR(tsa->si_regs))
                return PTR_ERR(tsa->si_regs);

        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "si_ram");
        if (!res) {
                dev_err(tsa->dev, "si_ram resource missing\n");
                return -EINVAL;
        }
        tsa->si_ram_sz = resource_size(res);
        tsa->si_ram = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(tsa->si_ram))
                return PTR_ERR(tsa->si_ram);

        tsa_init_si_ram(tsa);

        ret = tsa_of_parse_tdms(tsa, np);
        if (ret)
                return ret;

        ret = tsa_setup(tsa);
        if (ret)
                return ret;

        platform_set_drvdata(pdev, tsa);

        return 0;
}

static void tsa_remove(struct platform_device *pdev)
{
        struct tsa *tsa = platform_get_drvdata(pdev);
        int i;

        for (i = 0; i < ARRAY_SIZE(tsa->tdm); i++) {
                if (tsa->tdm[i].l1rsync_clk) {
                        clk_disable_unprepare(tsa->tdm[i].l1rsync_clk);
                        clk_put(tsa->tdm[i].l1rsync_clk);
                }
                if (tsa->tdm[i].l1rclk_clk) {
                        clk_disable_unprepare(tsa->tdm[i].l1rclk_clk);
                        clk_put(tsa->tdm[i].l1rclk_clk);
                }
                if (tsa->tdm[i].l1tsync_clk) {
                        clk_disable_unprepare(tsa->tdm[i].l1rsync_clk);
                        clk_put(tsa->tdm[i].l1rsync_clk);
                }
                if (tsa->tdm[i].l1tclk_clk) {
                        clk_disable_unprepare(tsa->tdm[i].l1rclk_clk);
                        clk_put(tsa->tdm[i].l1rclk_clk);
                }
        }
}

static const struct of_device_id tsa_id_table[] = {
#if IS_ENABLED(CONFIG_CPM1)
        { .compatible = "fsl,cpm1-tsa", .data = (void *)TSA_CPM1 },
#endif
#if IS_ENABLED(CONFIG_QUICC_ENGINE)
        { .compatible = "fsl,qe-tsa", .data = (void *)TSA_QE },
#endif
        {} /* sentinel */
};
MODULE_DEVICE_TABLE(of, tsa_id_table);

static struct platform_driver tsa_driver = {
        .driver = {
                .name = "fsl-tsa",
                .of_match_table = of_match_ptr(tsa_id_table),
        },
        .probe = tsa_probe,
        .remove = tsa_remove,
};
module_platform_driver(tsa_driver);

struct tsa_serial *tsa_serial_get_byphandle(struct device_node *np,
                                            const char *phandle_name)
{
        struct of_phandle_args out_args;
        struct platform_device *pdev;
        struct tsa_serial *tsa_serial;
        struct tsa *tsa;
        int ret;

        ret = of_parse_phandle_with_fixed_args(np, phandle_name, 1, 0, &out_args);
        if (ret < 0)
                return ERR_PTR(ret);

        if (!of_match_node(tsa_driver.driver.of_match_table, out_args.np)) {
                of_node_put(out_args.np);
                return ERR_PTR(-EINVAL);
        }

        pdev = of_find_device_by_node(out_args.np);
        of_node_put(out_args.np);
        if (!pdev)
                return ERR_PTR(-ENODEV);

        tsa = platform_get_drvdata(pdev);
        if (!tsa) {
                platform_device_put(pdev);
                return ERR_PTR(-EPROBE_DEFER);
        }

        if (out_args.args_count != 1) {
                platform_device_put(pdev);
                return ERR_PTR(-EINVAL);
        }

        if (out_args.args[0] >= ARRAY_SIZE(tsa->serials)) {
                platform_device_put(pdev);
                return ERR_PTR(-EINVAL);
        }

        tsa_serial = &tsa->serials[out_args.args[0]];

        /*
         * Be sure that the serial id matches the phandle arg.
         * The tsa_serials table is indexed by serial ids. The serial id is set
         * during the probe() call and needs to be coherent.
         */
        if (WARN_ON(tsa_serial->id != out_args.args[0])) {
                platform_device_put(pdev);
                return ERR_PTR(-EINVAL);
        }

        return tsa_serial;
}
EXPORT_SYMBOL(tsa_serial_get_byphandle);

void tsa_serial_put(struct tsa_serial *tsa_serial)
{
        struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);

        put_device(tsa->dev);
}
EXPORT_SYMBOL(tsa_serial_put);

static void devm_tsa_serial_release(struct device *dev, void *res)
{
        struct tsa_serial **tsa_serial = res;

        tsa_serial_put(*tsa_serial);
}

struct tsa_serial *devm_tsa_serial_get_byphandle(struct device *dev,
                                                 struct device_node *np,
                                                 const char *phandle_name)
{
        struct tsa_serial *tsa_serial;
        struct tsa_serial **dr;

        dr = devres_alloc(devm_tsa_serial_release, sizeof(*dr), GFP_KERNEL);
        if (!dr)
                return ERR_PTR(-ENOMEM);

        tsa_serial = tsa_serial_get_byphandle(np, phandle_name);
        if (!IS_ERR(tsa_serial)) {
                *dr = tsa_serial;
                devres_add(dev, dr);
        } else {
                devres_free(dr);
        }

        return tsa_serial;
}
EXPORT_SYMBOL(devm_tsa_serial_get_byphandle);

MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
MODULE_DESCRIPTION("CPM/QE TSA driver");
MODULE_LICENSE("GPL");