[PATCH v6 02/10] soc: fsl: cpm1: Add support for TSA

Christophe Leroy christophe.leroy at csgroup.eu
Fri Feb 17 08:26:53 PST 2023



Le 17/02/2023 à 15:56, Herve Codina a écrit :
> The TSA (Time Slot Assigner) purpose is to route some
> TDM time-slots to other internal serial controllers.
> 
> It is available in some PowerQUICC SoC such as the
> MPC885 or MPC866.
> 
> It is also available on some Quicc Engine SoCs.
> This current version support CPM1 SoCs only and some
> enhancement are needed to support Quicc Engine SoCs.
> 
> Signed-off-by: Herve Codina <herve.codina at bootlin.com>
> Acked-by: Li Yang <leoyang.li at nxp.com>

Reviewed-by: Christophe Leroy <christophe.leroy at csgroup.eu>

> ---
>   drivers/soc/fsl/qe/Kconfig  |  11 +
>   drivers/soc/fsl/qe/Makefile |   1 +
>   drivers/soc/fsl/qe/tsa.c    | 846 ++++++++++++++++++++++++++++++++++++
>   drivers/soc/fsl/qe/tsa.h    |  42 ++
>   4 files changed, 900 insertions(+)
>   create mode 100644 drivers/soc/fsl/qe/tsa.c
>   create mode 100644 drivers/soc/fsl/qe/tsa.h
> 
> diff --git a/drivers/soc/fsl/qe/Kconfig b/drivers/soc/fsl/qe/Kconfig
> index 357c5800b112..b0088495c323 100644
> --- a/drivers/soc/fsl/qe/Kconfig
> +++ b/drivers/soc/fsl/qe/Kconfig
> @@ -33,6 +33,17 @@ config UCC
>   	bool
>   	default y if UCC_FAST || UCC_SLOW
>   
> +config CPM_TSA
> +	tristate "CPM TSA support"
> +	depends on OF && HAS_IOMEM
> +	depends on CPM1 || COMPILE_TEST
> +	help
> +	  Freescale CPM Time Slot Assigner (TSA)
> +	  controller.
> +
> +	  This option enables support for this
> +	  controller
> +
>   config QE_TDM
>   	bool
>   	default y if FSL_UCC_HDLC
> diff --git a/drivers/soc/fsl/qe/Makefile b/drivers/soc/fsl/qe/Makefile
> index 55a555304f3a..45c961acc81b 100644
> --- a/drivers/soc/fsl/qe/Makefile
> +++ b/drivers/soc/fsl/qe/Makefile
> @@ -4,6 +4,7 @@
>   #
>   obj-$(CONFIG_QUICC_ENGINE)+= qe.o qe_common.o qe_ic.o qe_io.o
>   obj-$(CONFIG_CPM)	+= qe_common.o
> +obj-$(CONFIG_CPM_TSA)	+= tsa.o
>   obj-$(CONFIG_UCC)	+= ucc.o
>   obj-$(CONFIG_UCC_SLOW)	+= ucc_slow.o
>   obj-$(CONFIG_UCC_FAST)	+= ucc_fast.o
> diff --git a/drivers/soc/fsl/qe/tsa.c b/drivers/soc/fsl/qe/tsa.c
> new file mode 100644
> index 000000000000..3646153117b3
> --- /dev/null
> +++ b/drivers/soc/fsl/qe/tsa.c
> @@ -0,0 +1,846 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * TSA driver
> + *
> + * Copyright 2022 CS GROUP France
> + *
> + * Author: Herve Codina <herve.codina at bootlin.com>
> + */
> +
> +#include "tsa.h"
> +#include <dt-bindings/soc/cpm1-fsl,tsa.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>
> +
> +
> +/* TSA SI RAM routing tables entry */
> +#define TSA_SIRAM_ENTRY_LAST		(1 << 16)
> +#define TSA_SIRAM_ENTRY_BYTE		(1 << 17)
> +#define TSA_SIRAM_ENTRY_CNT(x)		(((x) & 0x0f) << 18)
> +#define TSA_SIRAM_ENTRY_CSEL_MASK	(0x7 << 22)
> +#define TSA_SIRAM_ENTRY_CSEL_NU		(0x0 << 22)
> +#define TSA_SIRAM_ENTRY_CSEL_SCC2	(0x2 << 22)
> +#define TSA_SIRAM_ENTRY_CSEL_SCC3	(0x3 << 22)
> +#define TSA_SIRAM_ENTRY_CSEL_SCC4	(0x4 << 22)
> +#define TSA_SIRAM_ENTRY_CSEL_SMC1	(0x5 << 22)
> +#define TSA_SIRAM_ENTRY_CSEL_SMC2	(0x6 << 22)
> +
> +/* SI mode register (32 bits) */
> +#define TSA_SIMODE	0x00
> +#define   TSA_SIMODE_SMC2			0x80000000
> +#define   TSA_SIMODE_SMC1			0x00008000
> +#define   TSA_SIMODE_TDMA(x)			((x) << 0)
> +#define   TSA_SIMODE_TDMB(x)			((x) << 16)
> +#define     TSA_SIMODE_TDM_MASK			0x0fff
> +#define     TSA_SIMODE_TDM_SDM_MASK		0x0c00
> +#define       TSA_SIMODE_TDM_SDM_NORM		0x0000
> +#define       TSA_SIMODE_TDM_SDM_ECHO		0x0400
> +#define       TSA_SIMODE_TDM_SDM_INTL_LOOP	0x0800
> +#define       TSA_SIMODE_TDM_SDM_LOOP_CTRL	0x0c00
> +#define     TSA_SIMODE_TDM_RFSD(x)		((x) << 8)
> +#define     TSA_SIMODE_TDM_DSC			0x0080
> +#define     TSA_SIMODE_TDM_CRT			0x0040
> +#define     TSA_SIMODE_TDM_STZ			0x0020
> +#define     TSA_SIMODE_TDM_CE			0x0010
> +#define     TSA_SIMODE_TDM_FE			0x0008
> +#define     TSA_SIMODE_TDM_GM			0x0004
> +#define     TSA_SIMODE_TDM_TFSD(x)		((x) << 0)
> +
> +/* SI global mode register (8 bits) */
> +#define TSA_SIGMR	0x04
> +#define TSA_SIGMR_ENB			(1<<3)
> +#define TSA_SIGMR_ENA			(1<<2)
> +#define TSA_SIGMR_RDM_MASK		0x03
> +#define   TSA_SIGMR_RDM_STATIC_TDMA	0x00
> +#define   TSA_SIGMR_RDM_DYN_TDMA	0x01
> +#define   TSA_SIGMR_RDM_STATIC_TDMAB	0x02
> +#define   TSA_SIGMR_RDM_DYN_TDMAB	0x03
> +
> +/* SI status register (8 bits) */
> +#define TSA_SISTR	0x06
> +
> +/* SI command register (8 bits) */
> +#define TSA_SICMR	0x07
> +
> +/* SI clock route register (32 bits) */
> +#define TSA_SICR	0x0C
> +#define   TSA_SICR_SCC2(x)		((x) << 8)
> +#define   TSA_SICR_SCC3(x)		((x) << 16)
> +#define   TSA_SICR_SCC4(x)		((x) << 24)
> +#define     TSA_SICR_SCC_MASK		0x0ff
> +#define     TSA_SICR_SCC_GRX		(1 << 7)
> +#define     TSA_SICR_SCC_SCX_TSA	(1 << 6)
> +#define     TSA_SICR_SCC_RXCS_MASK	(0x7 << 3)
> +#define       TSA_SICR_SCC_RXCS_BRG1	(0x0 << 3)
> +#define       TSA_SICR_SCC_RXCS_BRG2	(0x1 << 3)
> +#define       TSA_SICR_SCC_RXCS_BRG3	(0x2 << 3)
> +#define       TSA_SICR_SCC_RXCS_BRG4	(0x3 << 3)
> +#define       TSA_SICR_SCC_RXCS_CLK15	(0x4 << 3)
> +#define       TSA_SICR_SCC_RXCS_CLK26	(0x5 << 3)
> +#define       TSA_SICR_SCC_RXCS_CLK37	(0x6 << 3)
> +#define       TSA_SICR_SCC_RXCS_CLK48	(0x7 << 3)
> +#define     TSA_SICR_SCC_TXCS_MASK	(0x7 << 0)
> +#define       TSA_SICR_SCC_TXCS_BRG1	(0x0 << 0)
> +#define       TSA_SICR_SCC_TXCS_BRG2	(0x1 << 0)
> +#define       TSA_SICR_SCC_TXCS_BRG3	(0x2 << 0)
> +#define       TSA_SICR_SCC_TXCS_BRG4	(0x3 << 0)
> +#define       TSA_SICR_SCC_TXCS_CLK15	(0x4 << 0)
> +#define       TSA_SICR_SCC_TXCS_CLK26	(0x5 << 0)
> +#define       TSA_SICR_SCC_TXCS_CLK37	(0x6 << 0)
> +#define       TSA_SICR_SCC_TXCS_CLK48	(0x7 << 0)
> +
> +/* Serial interface RAM pointer register (32 bits) */
> +#define TSA_SIRP	0x10
> +
> +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
> +
> +struct tsa {
> +	struct device *dev;
> +	void *__iomem si_regs;
> +	void *__iomem si_ram;
> +	resource_size_t si_ram_sz;
> +	spinlock_t	lock;
> +	int tdms; /* TSA_TDMx ORed */
> +	struct tsa_tdm tdm[2]; /* TDMa and TDMb */
> +	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_write8(void *__iomem addr, u32 val)
> +{
> +	iowrite8(val, addr);
> +}
> +
> +static inline u32 tsa_read32(void *__iomem addr)
> +{
> +	return ioread32be(addr);
> +}
> +
> +static inline void tsa_clrbits32(void *__iomem addr, u32 clr)
> +{
> +	tsa_write32(addr, tsa_read32(addr) & ~clr);
> +}
> +
> +static inline void tsa_clrsetbits32(void *__iomem addr, u32 clr, u32 set)
> +{
> +	tsa_write32(addr, (tsa_read32(addr) & ~clr) | set);
> +}
> +
> +int tsa_serial_connect(struct tsa_serial *tsa_serial)
> +{
> +	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_SICR_SCC2(TSA_SICR_SCC_MASK);
> +		set = TSA_SICR_SCC2(TSA_SICR_SCC_SCX_TSA);
> +		break;
> +	case FSL_CPM_TSA_SCC3:
> +		clear = TSA_SICR_SCC3(TSA_SICR_SCC_MASK);
> +		set = TSA_SICR_SCC3(TSA_SICR_SCC_SCX_TSA);
> +		break;
> +	case FSL_CPM_TSA_SCC4:
> +		clear = TSA_SICR_SCC4(TSA_SICR_SCC_MASK);
> +		set = TSA_SICR_SCC4(TSA_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_SICR, clear, set);
> +	spin_unlock_irqrestore(&tsa->lock, flags);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(tsa_serial_connect);
> +
> +int tsa_serial_disconnect(struct tsa_serial *tsa_serial)
> +{
> +	struct tsa *tsa = tsa_serial_get_tsa(tsa_serial);
> +	unsigned long flags;
> +	u32 clear;
> +
> +	switch (tsa_serial->id) {
> +	case FSL_CPM_TSA_SCC2:
> +		clear = TSA_SICR_SCC2(TSA_SICR_SCC_MASK);
> +		break;
> +	case FSL_CPM_TSA_SCC3:
> +		clear = TSA_SICR_SCC3(TSA_SICR_SCC_MASK);
> +		break;
> +	case FSL_CPM_TSA_SCC4:
> +		clear = TSA_SICR_SCC4(TSA_SICR_SCC_MASK);
> +		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_SICR, clear, 0);
> +	spin_unlock_irqrestore(&tsa->lock, flags);
> +
> +	return 0;
> +}
> +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_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 const char *tsa_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 u32 tsa_serial_id2csel(struct tsa *tsa, u32 serial_id)
> +{
> +	switch (serial_id) {
> +	case FSL_CPM_TSA_SCC2:	return TSA_SIRAM_ENTRY_CSEL_SCC2;
> +	case FSL_CPM_TSA_SCC3:	return TSA_SIRAM_ENTRY_CSEL_SCC3;
> +	case FSL_CPM_TSA_SCC4:	return TSA_SIRAM_ENTRY_CSEL_SCC4;
> +	case FSL_CPM_TSA_SMC1:	return TSA_SIRAM_ENTRY_CSEL_SMC1;
> +	case FSL_CPM_TSA_SMC2:	return TSA_SIRAM_ENTRY_CSEL_SMC2;
> +	default:
> +		break;
> +	}
> +	return TSA_SIRAM_ENTRY_CSEL_NU;
> +}
> +
> +static int tsa_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_SIRAM_ENTRY_LAST);
> +	}
> +
> +	left = count;
> +	while (left) {
> +		val = TSA_SIRAM_ENTRY_BYTE | tsa_serial_id2csel(tsa, serial_id);
> +
> +		if (left > 16) {
> +			cnt = 16;
> +		} else {
> +			cnt = left;
> +			val |= TSA_SIRAM_ENTRY_LAST;
> +			area->last_entry = addr;
> +		}
> +		val |= TSA_SIRAM_ENTRY_CNT(cnt - 1);
> +
> +		tsa_write32(addr, val);
> +		addr += 4;
> +		left -= cnt;
> +	}
> +
> +	return 0;
> +}
> +
> +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 device_node *tdm_np;
> +	struct tsa_tdm *tdm;
> +	struct clk *clk;
> +	u32 tdm_id, val;
> +	int ret;
> +	int i;
> +
> +	tsa->tdms = 0;
> +	tsa->tdm[0].is_enable = false;
> +	tsa->tdm[1].is_enable = false;
> +
> +	for_each_available_child_of_node(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);
> +			of_node_put(tdm_np);
> +			return ret;
> +		}
> +		switch (tdm_id) {
> +		case 0:
> +			tsa->tdms |= BIT(TSA_TDMA);
> +			break;
> +		case 1:
> +			tsa->tdms |= BIT(TSA_TDMB);
> +			break;
> +		default:
> +			dev_err(tsa->dev, "%pOF: Invalid tdm_id (%u)\n", tdm_np,
> +				tdm_id);
> +			of_node_put(tdm_np);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	for_each_available_child_of_node(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);
> +			of_node_put(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);
> +			of_node_put(tdm_np);
> +			return ret;
> +		}
> +		if (val > 3) {
> +			dev_err(tsa->dev,
> +				"%pOF: Invalid fsl,rx-frame-sync-delay-bits (%u)\n",
> +				tdm_np, val);
> +			of_node_put(tdm_np);
> +			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);
> +			of_node_put(tdm_np);
> +			return ret;
> +		}
> +		if (val > 3) {
> +			dev_err(tsa->dev,
> +				"%pOF: Invalid fsl,tx-frame-sync-delay-bits (%u)\n",
> +				tdm_np, val);
> +			of_node_put(tdm_np);
> +			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 (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, "l1rsync");
> +		if (IS_ERR(clk)) {
> +			ret = PTR_ERR(clk);
> +			of_node_put(tdm_np);
> +			goto err;
> +		}
> +		ret = clk_prepare_enable(clk);
> +		if (ret) {
> +			clk_put(clk);
> +			of_node_put(tdm_np);
> +			goto err;
> +		}
> +		tdm->l1rsync_clk = clk;
> +
> +		clk = of_clk_get_by_name(tdm_np, "l1rclk");
> +		if (IS_ERR(clk)) {
> +			ret = PTR_ERR(clk);
> +			of_node_put(tdm_np);
> +			goto err;
> +		}
> +		ret = clk_prepare_enable(clk);
> +		if (ret) {
> +			clk_put(clk);
> +			of_node_put(tdm_np);
> +			goto err;
> +		}
> +		tdm->l1rclk_clk = clk;
> +
> +		if (!(tdm->simode_tdm & TSA_SIMODE_TDM_CRT)) {
> +			clk = of_clk_get_by_name(tdm_np, "l1tsync");
> +			if (IS_ERR(clk)) {
> +				ret = PTR_ERR(clk);
> +				of_node_put(tdm_np);
> +				goto err;
> +			}
> +			ret = clk_prepare_enable(clk);
> +			if (ret) {
> +				clk_put(clk);
> +				of_node_put(tdm_np);
> +				goto err;
> +			}
> +			tdm->l1tsync_clk = clk;
> +
> +			clk = of_clk_get_by_name(tdm_np, "l1tclk");
> +			if (IS_ERR(clk)) {
> +				ret = PTR_ERR(clk);
> +				of_node_put(tdm_np);
> +				goto err;
> +			}
> +			ret = clk_prepare_enable(clk);
> +			if (ret) {
> +				clk_put(clk);
> +				of_node_put(tdm_np);
> +				goto err;
> +			}
> +			tdm->l1tclk_clk = clk;
> +		}
> +
> +		ret = tsa_of_parse_tdm_rx_route(tsa, tdm_np, tsa->tdms, tdm_id);
> +		if (ret) {
> +			of_node_put(tdm_np);
> +			goto err;
> +		}
> +
> +		ret = tsa_of_parse_tdm_tx_route(tsa, tdm_np, tsa->tdms, tdm_id);
> +		if (ret) {
> +			of_node_put(tdm_np);
> +			goto err;
> +		}
> +
> +		tdm->is_enable = true;
> +	}
> +	return 0;
> +
> +err:
> +	for (i = 0; i < 2; 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 */
> +	for (i = 0; i < tsa->si_ram_sz; i += 4)
> +		tsa_write32(tsa->si_ram + i, TSA_SIRAM_ENTRY_LAST);
> +}
> +
> +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;
> +	u32 val;
> +	int ret;
> +
> +	tsa = devm_kzalloc(&pdev->dev, sizeof(*tsa), GFP_KERNEL);
> +	if (!tsa)
> +		return -ENOMEM;
> +
> +	tsa->dev = &pdev->dev;
> +
> +	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;
> +
> +	/* Set SIMODE */
> +	val = 0;
> +	if (tsa->tdm[0].is_enable)
> +		val |= TSA_SIMODE_TDMA(tsa->tdm[0].simode_tdm);
> +	if (tsa->tdm[1].is_enable)
> +		val |= TSA_SIMODE_TDMB(tsa->tdm[1].simode_tdm);
> +
> +	tsa_clrsetbits32(tsa->si_regs + TSA_SIMODE,
> +			 TSA_SIMODE_TDMA(TSA_SIMODE_TDM_MASK) |
> +			 TSA_SIMODE_TDMB(TSA_SIMODE_TDM_MASK),
> +			 val);
> +
> +	/* Set SIGMR */
> +	val = (tsa->tdms == BIT(TSA_TDMA)) ?
> +		TSA_SIGMR_RDM_STATIC_TDMA : TSA_SIGMR_RDM_STATIC_TDMAB;
> +	if (tsa->tdms & BIT(TSA_TDMA))
> +		val |= TSA_SIGMR_ENA;
> +	if (tsa->tdms & BIT(TSA_TDMB))
> +		val |= TSA_SIGMR_ENB;
> +	tsa_write8(tsa->si_regs + TSA_SIGMR, val);
> +
> +	platform_set_drvdata(pdev, tsa);
> +
> +	return 0;
> +}
> +
> +static int tsa_remove(struct platform_device *pdev)
> +{
> +	struct tsa *tsa = platform_get_drvdata(pdev);
> +	int i;
> +
> +	for (i = 0; i < 2; 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 0;
> +}
> +
> +static const struct of_device_id tsa_id_table[] = {
> +	{ .compatible = "fsl,cpm1-tsa" },
> +	{} /* 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 at bootlin.com>");
> +MODULE_DESCRIPTION("CPM TSA driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/soc/fsl/qe/tsa.h b/drivers/soc/fsl/qe/tsa.h
> new file mode 100644
> index 000000000000..d9df89b6da3e
> --- /dev/null
> +++ b/drivers/soc/fsl/qe/tsa.h
> @@ -0,0 +1,42 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * TSA management
> + *
> + * Copyright 2022 CS GROUP France
> + *
> + * Author: Herve Codina <herve.codina at bootlin.com>
> + */
> +#ifndef __SOC_FSL_TSA_H__
> +#define __SOC_FSL_TSA_H__
> +
> +#include <linux/types.h>
> +
> +struct device_node;
> +struct device;
> +struct tsa_serial;
> +
> +struct tsa_serial *tsa_serial_get_byphandle(struct device_node *np,
> +					    const char *phandle_name);
> +void tsa_serial_put(struct tsa_serial *tsa_serial);
> +struct tsa_serial *devm_tsa_serial_get_byphandle(struct device *dev,
> +						 struct device_node *np,
> +						 const char *phandle_name);
> +
> +/* Connect and disconnect the TSA serial */
> +int tsa_serial_connect(struct tsa_serial *tsa_serial);
> +int tsa_serial_disconnect(struct tsa_serial *tsa_serial);
> +
> +/* Cell information */
> +struct tsa_serial_info {
> +	unsigned long rx_fs_rate;
> +	unsigned long rx_bit_rate;
> +	u8 nb_rx_ts;
> +	unsigned long tx_fs_rate;
> +	unsigned long tx_bit_rate;
> +	u8 nb_tx_ts;
> +};
> +
> +/* Get information */
> +int tsa_serial_get_info(struct tsa_serial *tsa_serial, struct tsa_serial_info *info);
> +
> +#endif /* __SOC_FSL_TSA_H__ */


More information about the linux-arm-kernel mailing list