Commit 9970de8b authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'PTP_CLK-pin-configuration-for-SJA1105-DSA-driver'



Vladimir Oltean says:

====================
PTP_CLK pin configuration for SJA1105 DSA driver

This series adds support for the PTP_CLK pin on SJA1105 to be configured
via the PTP subsystem, in the "periodic output" and "external timestamp
input" modes.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents b0f83057 747e5eb3
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -38,10 +38,13 @@ struct sja1105_regs {
	u64 config;
	u64 sgmii;
	u64 rmii_pll1;
	u64 ptppinst;
	u64 ptppindur;
	u64 ptp_control;
	u64 ptpclkval;
	u64 ptpclkrate;
	u64 ptpclkcorp;
	u64 ptpsyncts;
	u64 ptpschtm;
	u64 ptpegr_ts[SJA1105_NUM_PORTS];
	u64 pad_mii_tx[SJA1105_NUM_PORTS];
@@ -214,5 +217,7 @@ size_t sja1105_vlan_lookup_entry_packing(void *buf, void *entry_ptr,
					 enum packing_op op);
size_t sja1105pqrs_mac_config_entry_packing(void *buf, void *entry_ptr,
					    enum packing_op op);
size_t sja1105pqrs_avb_params_entry_packing(void *buf, void *entry_ptr,
					    enum packing_op op);

#endif
+23 −1
Original line number Diff line number Diff line
@@ -124,6 +124,9 @@
#define SJA1105ET_SIZE_GENERAL_PARAMS_DYN_CMD			\
	SJA1105_SIZE_DYN_CMD

#define SJA1105PQRS_SIZE_AVB_PARAMS_DYN_CMD			\
	(SJA1105_SIZE_DYN_CMD + SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY)

#define SJA1105_MAX_DYN_CMD_SIZE				\
	SJA1105PQRS_SIZE_MAC_CONFIG_DYN_CMD

@@ -481,6 +484,18 @@ sja1105et_general_params_entry_packing(void *buf, void *entry_ptr,
	return 0;
}

static void
sja1105pqrs_avb_params_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd,
				   enum packing_op op)
{
	u8 *p = buf + SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY;
	const int size = SJA1105_SIZE_DYN_CMD;

	sja1105_packing(p, &cmd->valid,   31, 31, size, op);
	sja1105_packing(p, &cmd->errors,  30, 30, size, op);
	sja1105_packing(p, &cmd->rdwrset, 29, 29, size, op);
}

#define OP_READ		BIT(0)
#define OP_WRITE	BIT(1)
#define OP_DEL		BIT(2)
@@ -610,7 +625,14 @@ struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = {
		.addr = 0x38,
	},
	[BLK_IDX_L2_FORWARDING_PARAMS] = {0},
	[BLK_IDX_AVB_PARAMS] = {0},
	[BLK_IDX_AVB_PARAMS] = {
		.entry_packing = sja1105pqrs_avb_params_entry_packing,
		.cmd_packing = sja1105pqrs_avb_params_cmd_packing,
		.max_entry_count = SJA1105_MAX_AVB_PARAMS_COUNT,
		.access = (OP_READ | OP_WRITE),
		.packed_size = SJA1105PQRS_SIZE_AVB_PARAMS_DYN_CMD,
		.addr = 0x8003,
	},
	[BLK_IDX_GENERAL_PARAMS] = {
		.entry_packing = sja1105et_general_params_entry_packing,
		.cmd_packing = sja1105et_general_params_cmd_packing,
+40 −0
Original line number Diff line number Diff line
@@ -479,6 +479,43 @@ static int sja1105_init_general_params(struct sja1105_private *priv)
	return 0;
}

static int sja1105_init_avb_params(struct sja1105_private *priv)
{
	struct sja1105_avb_params_entry *avb;
	struct sja1105_table *table;

	table = &priv->static_config.tables[BLK_IDX_AVB_PARAMS];

	/* Discard previous AVB Parameters Table */
	if (table->entry_count) {
		kfree(table->entries);
		table->entry_count = 0;
	}

	table->entries = kcalloc(SJA1105_MAX_AVB_PARAMS_COUNT,
				 table->ops->unpacked_entry_size, GFP_KERNEL);
	if (!table->entries)
		return -ENOMEM;

	table->entry_count = SJA1105_MAX_AVB_PARAMS_COUNT;

	avb = table->entries;

	/* Configure the MAC addresses for meta frames */
	avb->destmeta = SJA1105_META_DMAC;
	avb->srcmeta  = SJA1105_META_SMAC;
	/* On P/Q/R/S, configure the direction of the PTP_CLK pin as input by
	 * default. This is because there might be boards with a hardware
	 * layout where enabling the pin as output might cause an electrical
	 * clash. On E/T the pin is always an output, which the board designers
	 * probably already knew, so even if there are going to be electrical
	 * issues, there's nothing we can do.
	 */
	avb->cas_master = false;

	return 0;
}

#define SJA1105_RATE_MBPS(speed) (((speed) * 64000) / 1000)

static void sja1105_setup_policer(struct sja1105_l2_policing_entry *policing,
@@ -567,6 +604,9 @@ static int sja1105_static_config_load(struct sja1105_private *priv,
	if (rc < 0)
		return rc;
	rc = sja1105_init_general_params(priv);
	if (rc < 0)
		return rc;
	rc = sja1105_init_avb_params(priv);
	if (rc < 0)
		return rc;

+247 −38
Original line number Diff line number Diff line
@@ -14,6 +14,17 @@
#define SJA1105_MAX_ADJ_PPB		32000000
#define SJA1105_SIZE_PTP_CMD		4

/* PTPSYNCTS has no interrupt or update mechanism, because the intended
 * hardware use case is for the timestamp to be collected synchronously,
 * immediately after the CAS_MASTER SJA1105 switch has triggered a CASSYNC
 * pulse on the PTP_CLK pin. When used as a generic extts source, it needs
 * polling and a comparison with the old value. The polling interval is just
 * the Nyquist rate of a canonical PPS input (e.g. from a GPS module).
 * Anything of higher frequency than 1 Hz will be lost, since there is no
 * timestamp FIFO.
 */
#define SJA1105_EXTTS_INTERVAL		(HZ / 2)

/*            This range is actually +/- SJA1105_MAX_ADJ_PPB
 *            divided by 1000 (ppb -> ppm) and with a 16-bit
 *            "fractional" part (actually fixed point).
@@ -39,44 +50,13 @@ enum sja1105_ptp_clk_mode {
	PTP_SET_MODE = 0,
};

#define extts_to_data(d) \
		container_of((d), struct sja1105_ptp_data, extts_work)
#define ptp_caps_to_data(d) \
		container_of((d), struct sja1105_ptp_data, caps)
#define ptp_data_to_sja1105(d) \
		container_of((d), struct sja1105_private, ptp_data)

static int sja1105_init_avb_params(struct sja1105_private *priv,
				   bool on)
{
	struct sja1105_avb_params_entry *avb;
	struct sja1105_table *table;

	table = &priv->static_config.tables[BLK_IDX_AVB_PARAMS];

	/* Discard previous AVB Parameters Table */
	if (table->entry_count) {
		kfree(table->entries);
		table->entry_count = 0;
	}

	/* Configure the reception of meta frames only if requested */
	if (!on)
		return 0;

	table->entries = kcalloc(SJA1105_MAX_AVB_PARAMS_COUNT,
				 table->ops->unpacked_entry_size, GFP_KERNEL);
	if (!table->entries)
		return -ENOMEM;

	table->entry_count = SJA1105_MAX_AVB_PARAMS_COUNT;

	avb = table->entries;

	avb->destmeta = SJA1105_META_DMAC;
	avb->srcmeta  = SJA1105_META_SMAC;

	return 0;
}

/* Must be called only with priv->tagger_data.state bit
 * SJA1105_HWTS_RX_EN cleared
 */
@@ -86,17 +66,12 @@ static int sja1105_change_rxtstamping(struct sja1105_private *priv,
	struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
	struct sja1105_general_params_entry *general_params;
	struct sja1105_table *table;
	int rc;

	table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS];
	general_params = table->entries;
	general_params->send_meta1 = on;
	general_params->send_meta0 = on;

	rc = sja1105_init_avb_params(priv, on);
	if (rc < 0)
		return rc;

	/* Initialize the meta state machine to a known state */
	if (priv->tagger_data.stampable_skb) {
		kfree_skb(priv->tagger_data.stampable_skb);
@@ -206,6 +181,8 @@ void sja1105et_ptp_cmd_packing(u8 *buf, struct sja1105_ptp_cmd *cmd,
	sja1105_packing(buf, &valid,           31, 31, size, op);
	sja1105_packing(buf, &cmd->ptpstrtsch, 30, 30, size, op);
	sja1105_packing(buf, &cmd->ptpstopsch, 29, 29, size, op);
	sja1105_packing(buf, &cmd->startptpcp, 28, 28, size, op);
	sja1105_packing(buf, &cmd->stopptpcp,  27, 27, size, op);
	sja1105_packing(buf, &cmd->resptp,      2,  2, size, op);
	sja1105_packing(buf, &cmd->corrclk4ts,  1,  1, size, op);
	sja1105_packing(buf, &cmd->ptpclkadd,   0,  0, size, op);
@@ -221,6 +198,8 @@ void sja1105pqrs_ptp_cmd_packing(u8 *buf, struct sja1105_ptp_cmd *cmd,
	sja1105_packing(buf, &valid,           31, 31, size, op);
	sja1105_packing(buf, &cmd->ptpstrtsch, 30, 30, size, op);
	sja1105_packing(buf, &cmd->ptpstopsch, 29, 29, size, op);
	sja1105_packing(buf, &cmd->startptpcp, 28, 28, size, op);
	sja1105_packing(buf, &cmd->stopptpcp,  27, 27, size, op);
	sja1105_packing(buf, &cmd->resptp,      3,  3, size, op);
	sja1105_packing(buf, &cmd->corrclk4ts,  2,  2, size, op);
	sja1105_packing(buf, &cmd->ptpclkadd,   0,  0, size, op);
@@ -615,6 +594,227 @@ static int sja1105_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
	return rc;
}

static void sja1105_ptp_extts_work(struct work_struct *work)
{
	struct delayed_work *dw = to_delayed_work(work);
	struct sja1105_ptp_data *ptp_data = extts_to_data(dw);
	struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
	const struct sja1105_regs *regs = priv->info->regs;
	struct ptp_clock_event event;
	u64 ptpsyncts = 0;
	int rc;

	mutex_lock(&ptp_data->lock);

	rc = sja1105_xfer_u64(priv, SPI_READ, regs->ptpsyncts, &ptpsyncts,
			      NULL);
	if (rc < 0)
		dev_err_ratelimited(priv->ds->dev,
				    "Failed to read PTPSYNCTS: %d\n", rc);

	if (ptpsyncts && ptp_data->ptpsyncts != ptpsyncts) {
		event.index = 0;
		event.type = PTP_CLOCK_EXTTS;
		event.timestamp = ns_to_ktime(sja1105_ticks_to_ns(ptpsyncts));
		ptp_clock_event(ptp_data->clock, &event);

		ptp_data->ptpsyncts = ptpsyncts;
	}

	mutex_unlock(&ptp_data->lock);

	schedule_delayed_work(&ptp_data->extts_work, SJA1105_EXTTS_INTERVAL);
}

static int sja1105_change_ptp_clk_pin_func(struct sja1105_private *priv,
					   enum ptp_pin_function func)
{
	struct sja1105_avb_params_entry *avb;
	enum ptp_pin_function old_func;

	avb = priv->static_config.tables[BLK_IDX_AVB_PARAMS].entries;

	if (priv->info->device_id == SJA1105E_DEVICE_ID ||
	    priv->info->device_id == SJA1105T_DEVICE_ID ||
	    avb->cas_master)
		old_func = PTP_PF_PEROUT;
	else
		old_func = PTP_PF_EXTTS;

	if (func == old_func)
		return 0;

	avb->cas_master = (func == PTP_PF_PEROUT);

	return sja1105_dynamic_config_write(priv, BLK_IDX_AVB_PARAMS, 0, avb,
					    true);
}

/* The PTP_CLK pin may be configured to toggle with a 50% duty cycle and a
 * frequency f:
 *
 *           NSEC_PER_SEC
 * f = ----------------------
 *     (PTPPINDUR * 8 ns) * 2
 */
static int sja1105_per_out_enable(struct sja1105_private *priv,
				  struct ptp_perout_request *perout,
				  bool on)
{
	struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
	const struct sja1105_regs *regs = priv->info->regs;
	struct sja1105_ptp_cmd cmd = ptp_data->cmd;
	int rc;

	/* We only support one channel */
	if (perout->index != 0)
		return -EOPNOTSUPP;

	/* Reject requests with unsupported flags */
	if (perout->flags)
		return -EOPNOTSUPP;

	mutex_lock(&ptp_data->lock);

	rc = sja1105_change_ptp_clk_pin_func(priv, PTP_PF_PEROUT);
	if (rc)
		goto out;

	if (on) {
		struct timespec64 pin_duration_ts = {
			.tv_sec = perout->period.sec,
			.tv_nsec = perout->period.nsec,
		};
		struct timespec64 pin_start_ts = {
			.tv_sec = perout->start.sec,
			.tv_nsec = perout->start.nsec,
		};
		u64 pin_duration = timespec64_to_ns(&pin_duration_ts);
		u64 pin_start = timespec64_to_ns(&pin_start_ts);
		u32 pin_duration32;
		u64 now;

		/* ptppindur: 32 bit register which holds the interval between
		 * 2 edges on PTP_CLK. So check for truncation which happens
		 * at periods larger than around 68.7 seconds.
		 */
		pin_duration = ns_to_sja1105_ticks(pin_duration / 2);
		if (pin_duration > U32_MAX) {
			rc = -ERANGE;
			goto out;
		}
		pin_duration32 = pin_duration;

		/* ptppins: 64 bit register which needs to hold a PTP time
		 * larger than the current time, otherwise the startptpcp
		 * command won't do anything. So advance the current time
		 * by a number of periods in a way that won't alter the
		 * phase offset.
		 */
		rc = __sja1105_ptp_gettimex(priv->ds, &now, NULL);
		if (rc < 0)
			goto out;

		pin_start = future_base_time(pin_start, pin_duration,
					     now + 1ull * NSEC_PER_SEC);
		pin_start = ns_to_sja1105_ticks(pin_start);

		rc = sja1105_xfer_u64(priv, SPI_WRITE, regs->ptppinst,
				      &pin_start, NULL);
		if (rc < 0)
			goto out;

		rc = sja1105_xfer_u32(priv, SPI_WRITE, regs->ptppindur,
				      &pin_duration32, NULL);
		if (rc < 0)
			goto out;
	}

	if (on)
		cmd.startptpcp = true;
	else
		cmd.stopptpcp = true;

	rc = sja1105_ptp_commit(priv->ds, &cmd, SPI_WRITE);

out:
	mutex_unlock(&ptp_data->lock);

	return rc;
}

static int sja1105_extts_enable(struct sja1105_private *priv,
				struct ptp_extts_request *extts,
				bool on)
{
	int rc;

	/* We only support one channel */
	if (extts->index != 0)
		return -EOPNOTSUPP;

	/* Reject requests with unsupported flags */
	if (extts->flags)
		return -EOPNOTSUPP;

	rc = sja1105_change_ptp_clk_pin_func(priv, PTP_PF_EXTTS);
	if (rc)
		return rc;

	if (on)
		schedule_delayed_work(&priv->ptp_data.extts_work,
				      SJA1105_EXTTS_INTERVAL);
	else
		cancel_delayed_work_sync(&priv->ptp_data.extts_work);

	return 0;
}

static int sja1105_ptp_enable(struct ptp_clock_info *ptp,
			      struct ptp_clock_request *req, int on)
{
	struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
	struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
	int rc = -EOPNOTSUPP;

	if (req->type == PTP_CLK_REQ_PEROUT)
		rc = sja1105_per_out_enable(priv, &req->perout, on);
	else if (req->type == PTP_CLK_REQ_EXTTS)
		rc = sja1105_extts_enable(priv, &req->extts, on);

	return rc;
}

static int sja1105_ptp_verify_pin(struct ptp_clock_info *ptp, unsigned int pin,
				  enum ptp_pin_function func, unsigned int chan)
{
	struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
	struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);

	if (chan != 0 || pin != 0)
		return -1;

	switch (func) {
	case PTP_PF_NONE:
	case PTP_PF_PEROUT:
		break;
	case PTP_PF_EXTTS:
		if (priv->info->device_id == SJA1105E_DEVICE_ID ||
		    priv->info->device_id == SJA1105T_DEVICE_ID)
			return -1;
		break;
	default:
		return -1;
	}
	return 0;
}

static struct ptp_pin_desc sja1105_ptp_pin = {
	.name = "ptp_clk",
	.index = 0,
	.func = PTP_PF_NONE,
};

int sja1105_ptp_clock_register(struct dsa_switch *ds)
{
	struct sja1105_private *priv = ds->priv;
@@ -628,8 +828,14 @@ int sja1105_ptp_clock_register(struct dsa_switch *ds)
		.adjtime	= sja1105_ptp_adjtime,
		.gettimex64	= sja1105_ptp_gettimex,
		.settime64	= sja1105_ptp_settime,
		.enable		= sja1105_ptp_enable,
		.verify		= sja1105_ptp_verify_pin,
		.do_aux_work	= sja1105_rxtstamp_work,
		.max_adj	= SJA1105_MAX_ADJ_PPB,
		.pin_config	= &sja1105_ptp_pin,
		.n_pins		= 1,
		.n_ext_ts	= 1,
		.n_per_out	= 1,
	};

	skb_queue_head_init(&ptp_data->skb_rxtstamp_queue);
@@ -642,6 +848,8 @@ int sja1105_ptp_clock_register(struct dsa_switch *ds)
	ptp_data->cmd.corrclk4ts = true;
	ptp_data->cmd.ptpclkadd = PTP_SET_MODE;

	INIT_DELAYED_WORK(&ptp_data->extts_work, sja1105_ptp_extts_work);

	return sja1105_ptp_reset(ds);
}

@@ -653,6 +861,7 @@ void sja1105_ptp_clock_unregister(struct dsa_switch *ds)
	if (IS_ERR_OR_NULL(ptp_data->clock))
		return;

	cancel_delayed_work_sync(&ptp_data->extts_work);
	ptp_cancel_worker_sync(ptp_data->clock);
	skb_queue_purge(&ptp_data->skb_rxtstamp_queue);
	ptp_clock_unregister(ptp_data->clock);
+31 −0
Original line number Diff line number Diff line
@@ -21,7 +21,36 @@ static inline s64 sja1105_ticks_to_ns(s64 ticks)
	return ticks * SJA1105_TICK_NS;
}

/* Calculate the first base_time in the future that satisfies this
 * relationship:
 *
 * future_base_time = base_time + N x cycle_time >= now, or
 *
 *      now - base_time
 * N >= ---------------
 *         cycle_time
 *
 * Because N is an integer, the ceiling value of the above "a / b" ratio
 * is in fact precisely the floor value of "(a + b - 1) / b", which is
 * easier to calculate only having integer division tools.
 */
static inline s64 future_base_time(s64 base_time, s64 cycle_time, s64 now)
{
	s64 a, b, n;

	if (base_time >= now)
		return base_time;

	a = now - base_time;
	b = cycle_time;
	n = div_s64(a + b - 1, b);

	return base_time + n * cycle_time;
}

struct sja1105_ptp_cmd {
	u64 startptpcp;		/* start toggling PTP_CLK pin */
	u64 stopptpcp;		/* stop toggling PTP_CLK pin */
	u64 ptpstrtsch;		/* start schedule */
	u64 ptpstopsch;		/* stop schedule */
	u64 resptp;		/* reset */
@@ -30,12 +59,14 @@ struct sja1105_ptp_cmd {
};

struct sja1105_ptp_data {
	struct delayed_work extts_work;
	struct sk_buff_head skb_rxtstamp_queue;
	struct ptp_clock_info caps;
	struct ptp_clock *clock;
	struct sja1105_ptp_cmd cmd;
	/* Serializes all operations on the PTP hardware clock */
	struct mutex lock;
	u64 ptpsyncts;
};

int sja1105_ptp_clock_register(struct dsa_switch *ds);
Loading