Contributors: 5
Author Tokens Token Proportion Commits Commit Proportion
Kurt Van Dijck 3991 99.63% 1 20.00%
Alexey Khoroshilov 9 0.22% 1 20.00%
Alexey Dobriyan 3 0.07% 1 20.00%
Thomas Gleixner 2 0.05% 1 20.00%
Ingo Molnar 1 0.02% 1 20.00%
Total 4006 5


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2008-2010
 *
 * - Kurt Van Dijck, EIA Electronics
 */

#include <linux/firmware.h>
#include <linux/sched/signal.h>
#include <asm/div64.h>
#include <asm/io.h>

#include "softing.h"

/*
 * low level DPRAM command.
 * Make sure that card->dpram[DPRAM_FCT_HOST] is preset
 */
static int _softing_fct_cmd(struct softing *card, int16_t cmd, uint16_t vector,
		const char *msg)
{
	int ret;
	unsigned long stamp;

	iowrite16(cmd, &card->dpram[DPRAM_FCT_PARAM]);
	iowrite8(vector >> 8, &card->dpram[DPRAM_FCT_HOST + 1]);
	iowrite8(vector, &card->dpram[DPRAM_FCT_HOST]);
	/* be sure to flush this to the card */
	wmb();
	stamp = jiffies + 1 * HZ;
	/* wait for card */
	do {
		/* DPRAM_FCT_HOST is _not_ aligned */
		ret = ioread8(&card->dpram[DPRAM_FCT_HOST]) +
			(ioread8(&card->dpram[DPRAM_FCT_HOST + 1]) << 8);
		/* don't have any cached variables */
		rmb();
		if (ret == RES_OK)
			/* read return-value now */
			return ioread16(&card->dpram[DPRAM_FCT_RESULT]);

		if ((ret != vector) || time_after(jiffies, stamp))
			break;
		/* process context => relax */
		usleep_range(500, 10000);
	} while (1);

	ret = (ret == RES_NONE) ? -ETIMEDOUT : -ECANCELED;
	dev_alert(&card->pdev->dev, "firmware %s failed (%i)\n", msg, ret);
	return ret;
}

static int softing_fct_cmd(struct softing *card, int16_t cmd, const char *msg)
{
	int ret;

	ret = _softing_fct_cmd(card, cmd, 0, msg);
	if (ret > 0) {
		dev_alert(&card->pdev->dev, "%s returned %u\n", msg, ret);
		ret = -EIO;
	}
	return ret;
}

int softing_bootloader_command(struct softing *card, int16_t cmd,
		const char *msg)
{
	int ret;
	unsigned long stamp;

	iowrite16(RES_NONE, &card->dpram[DPRAM_RECEIPT]);
	iowrite16(cmd, &card->dpram[DPRAM_COMMAND]);
	/* be sure to flush this to the card */
	wmb();
	stamp = jiffies + 3 * HZ;
	/* wait for card */
	do {
		ret = ioread16(&card->dpram[DPRAM_RECEIPT]);
		/* don't have any cached variables */
		rmb();
		if (ret == RES_OK)
			return 0;
		if (time_after(jiffies, stamp))
			break;
		/* process context => relax */
		usleep_range(500, 10000);
	} while (!signal_pending(current));

	ret = (ret == RES_NONE) ? -ETIMEDOUT : -ECANCELED;
	dev_alert(&card->pdev->dev, "bootloader %s failed (%i)\n", msg, ret);
	return ret;
}

static int fw_parse(const uint8_t **pmem, uint16_t *ptype, uint32_t *paddr,
		uint16_t *plen, const uint8_t **pdat)
{
	uint16_t checksum[2];
	const uint8_t *mem;
	const uint8_t *end;

	/*
	 * firmware records are a binary, unaligned stream composed of:
	 * uint16_t type;
	 * uint32_t addr;
	 * uint16_t len;
	 * uint8_t dat[len];
	 * uint16_t checksum;
	 * all values in little endian.
	 * We could define a struct for this, with __attribute__((packed)),
	 * but would that solve the alignment in _all_ cases (cfr. the
	 * struct itself may be an odd address)?
	 *
	 * I chose to use leXX_to_cpup() since this solves both
	 * endianness & alignment.
	 */
	mem = *pmem;
	*ptype = le16_to_cpup((void *)&mem[0]);
	*paddr = le32_to_cpup((void *)&mem[2]);
	*plen = le16_to_cpup((void *)&mem[6]);
	*pdat = &mem[8];
	/* verify checksum */
	end = &mem[8 + *plen];
	checksum[0] = le16_to_cpup((void *)end);
	for (checksum[1] = 0; mem < end; ++mem)
		checksum[1] += *mem;
	if (checksum[0] != checksum[1])
		return -EINVAL;
	/* increment */
	*pmem += 10 + *plen;
	return 0;
}

int softing_load_fw(const char *file, struct softing *card,
		__iomem uint8_t *dpram, unsigned int size, int offset)
{
	const struct firmware *fw;
	int ret;
	const uint8_t *mem, *end, *dat;
	uint16_t type, len;
	uint32_t addr;
	uint8_t *buf = NULL, *new_buf;
	int buflen = 0;
	int8_t type_end = 0;

	ret = request_firmware(&fw, file, &card->pdev->dev);
	if (ret < 0)
		return ret;
	dev_dbg(&card->pdev->dev, "%s, firmware(%s) got %u bytes"
		", offset %c0x%04x\n",
		card->pdat->name, file, (unsigned int)fw->size,
		(offset >= 0) ? '+' : '-', (unsigned int)abs(offset));
	/* parse the firmware */
	mem = fw->data;
	end = &mem[fw->size];
	/* look for header record */
	ret = fw_parse(&mem, &type, &addr, &len, &dat);
	if (ret < 0)
		goto failed;
	if (type != 0xffff)
		goto failed;
	if (strncmp("Structured Binary Format, Softing GmbH" , dat, len)) {
		ret = -EINVAL;
		goto failed;
	}
	/* ok, we had a header */
	while (mem < end) {
		ret = fw_parse(&mem, &type, &addr, &len, &dat);
		if (ret < 0)
			goto failed;
		if (type == 3) {
			/* start address, not used here */
			continue;
		} else if (type == 1) {
			/* eof */
			type_end = 1;
			break;
		} else if (type != 0) {
			ret = -EINVAL;
			goto failed;
		}

		if ((addr + len + offset) > size)
			goto failed;
		memcpy_toio(&dpram[addr + offset], dat, len);
		/* be sure to flush caches from IO space */
		mb();
		if (len > buflen) {
			/* align buflen */
			buflen = (len + (1024-1)) & ~(1024-1);
			new_buf = krealloc(buf, buflen, GFP_KERNEL);
			if (!new_buf) {
				ret = -ENOMEM;
				goto failed;
			}
			buf = new_buf;
		}
		/* verify record data */
		memcpy_fromio(buf, &dpram[addr + offset], len);
		if (memcmp(buf, dat, len)) {
			/* is not ok */
			dev_alert(&card->pdev->dev, "DPRAM readback failed\n");
			ret = -EIO;
			goto failed;
		}
	}
	if (!type_end)
		/* no end record seen */
		goto failed;
	ret = 0;
failed:
	kfree(buf);
	release_firmware(fw);
	if (ret < 0)
		dev_info(&card->pdev->dev, "firmware %s failed\n", file);
	return ret;
}

int softing_load_app_fw(const char *file, struct softing *card)
{
	const struct firmware *fw;
	const uint8_t *mem, *end, *dat;
	int ret, j;
	uint16_t type, len;
	uint32_t addr, start_addr = 0;
	unsigned int sum, rx_sum;
	int8_t type_end = 0, type_entrypoint = 0;

	ret = request_firmware(&fw, file, &card->pdev->dev);
	if (ret) {
		dev_alert(&card->pdev->dev, "request_firmware(%s) got %i\n",
			file, ret);
		return ret;
	}
	dev_dbg(&card->pdev->dev, "firmware(%s) got %lu bytes\n",
		file, (unsigned long)fw->size);
	/* parse the firmware */
	mem = fw->data;
	end = &mem[fw->size];
	/* look for header record */
	ret = fw_parse(&mem, &type, &addr, &len, &dat);
	if (ret)
		goto failed;
	ret = -EINVAL;
	if (type != 0xffff) {
		dev_alert(&card->pdev->dev, "firmware starts with type 0x%x\n",
			type);
		goto failed;
	}
	if (strncmp("Structured Binary Format, Softing GmbH", dat, len)) {
		dev_alert(&card->pdev->dev, "firmware string '%.*s' fault\n",
				len, dat);
		goto failed;
	}
	/* ok, we had a header */
	while (mem < end) {
		ret = fw_parse(&mem, &type, &addr, &len, &dat);
		if (ret)
			goto failed;

		if (type == 3) {
			/* start address */
			start_addr = addr;
			type_entrypoint = 1;
			continue;
		} else if (type == 1) {
			/* eof */
			type_end = 1;
			break;
		} else if (type != 0) {
			dev_alert(&card->pdev->dev,
					"unknown record type 0x%04x\n", type);
			ret = -EINVAL;
			goto failed;
		}

		/* regualar data */
		for (sum = 0, j = 0; j < len; ++j)
			sum += dat[j];
		/* work in 16bit (target) */
		sum &= 0xffff;

		memcpy_toio(&card->dpram[card->pdat->app.offs], dat, len);
		iowrite32(card->pdat->app.offs + card->pdat->app.addr,
				&card->dpram[DPRAM_COMMAND + 2]);
		iowrite32(addr, &card->dpram[DPRAM_COMMAND + 6]);
		iowrite16(len, &card->dpram[DPRAM_COMMAND + 10]);
		iowrite8(1, &card->dpram[DPRAM_COMMAND + 12]);
		ret = softing_bootloader_command(card, 1, "loading app.");
		if (ret < 0)
			goto failed;
		/* verify checksum */
		rx_sum = ioread16(&card->dpram[DPRAM_RECEIPT + 2]);
		if (rx_sum != sum) {
			dev_alert(&card->pdev->dev, "SRAM seems to be damaged"
				", wanted 0x%04x, got 0x%04x\n", sum, rx_sum);
			ret = -EIO;
			goto failed;
		}
	}
	if (!type_end || !type_entrypoint)
		goto failed;
	/* start application in card */
	iowrite32(start_addr, &card->dpram[DPRAM_COMMAND + 2]);
	iowrite8(1, &card->dpram[DPRAM_COMMAND + 6]);
	ret = softing_bootloader_command(card, 3, "start app.");
	if (ret < 0)
		goto failed;
	ret = 0;
failed:
	release_firmware(fw);
	if (ret < 0)
		dev_info(&card->pdev->dev, "firmware %s failed\n", file);
	return ret;
}

static int softing_reset_chip(struct softing *card)
{
	int ret;

	do {
		/* reset chip */
		iowrite8(0, &card->dpram[DPRAM_RESET_RX_FIFO]);
		iowrite8(0, &card->dpram[DPRAM_RESET_RX_FIFO+1]);
		iowrite8(1, &card->dpram[DPRAM_RESET]);
		iowrite8(0, &card->dpram[DPRAM_RESET+1]);

		ret = softing_fct_cmd(card, 0, "reset_can");
		if (!ret)
			break;
		if (signal_pending(current))
			/* don't wait any longer */
			break;
	} while (1);
	card->tx.pending = 0;
	return ret;
}

int softing_chip_poweron(struct softing *card)
{
	int ret;
	/* sync */
	ret = _softing_fct_cmd(card, 99, 0x55, "sync-a");
	if (ret < 0)
		goto failed;

	ret = _softing_fct_cmd(card, 99, 0xaa, "sync-b");
	if (ret < 0)
		goto failed;

	ret = softing_reset_chip(card);
	if (ret < 0)
		goto failed;
	/* get_serial */
	ret = softing_fct_cmd(card, 43, "get_serial_number");
	if (ret < 0)
		goto failed;
	card->id.serial = ioread32(&card->dpram[DPRAM_FCT_PARAM]);
	/* get_version */
	ret = softing_fct_cmd(card, 12, "get_version");
	if (ret < 0)
		goto failed;
	card->id.fw_version = ioread16(&card->dpram[DPRAM_FCT_PARAM + 2]);
	card->id.hw_version = ioread16(&card->dpram[DPRAM_FCT_PARAM + 4]);
	card->id.license = ioread16(&card->dpram[DPRAM_FCT_PARAM + 6]);
	card->id.chip[0] = ioread16(&card->dpram[DPRAM_FCT_PARAM + 8]);
	card->id.chip[1] = ioread16(&card->dpram[DPRAM_FCT_PARAM + 10]);
	return 0;
failed:
	return ret;
}

static void softing_initialize_timestamp(struct softing *card)
{
	uint64_t ovf;

	card->ts_ref = ktime_get();

	/* 16MHz is the reference */
	ovf = 0x100000000ULL * 16;
	do_div(ovf, card->pdat->freq ?: 16);

	card->ts_overflow = ktime_add_us(0, ovf);
}

ktime_t softing_raw2ktime(struct softing *card, u32 raw)
{
	uint64_t rawl;
	ktime_t now, real_offset;
	ktime_t target;
	ktime_t tmp;

	now = ktime_get();
	real_offset = ktime_sub(ktime_get_real(), now);

	/* find nsec from card */
	rawl = raw * 16;
	do_div(rawl, card->pdat->freq ?: 16);
	target = ktime_add_us(card->ts_ref, rawl);
	/* test for overflows */
	tmp = ktime_add(target, card->ts_overflow);
	while (unlikely(ktime_to_ns(tmp) > ktime_to_ns(now))) {
		card->ts_ref = ktime_add(card->ts_ref, card->ts_overflow);
		target = tmp;
		tmp = ktime_add(target, card->ts_overflow);
	}
	return ktime_add(target, real_offset);
}

static inline int softing_error_reporting(struct net_device *netdev)
{
	struct softing_priv *priv = netdev_priv(netdev);

	return (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
		? 1 : 0;
}

int softing_startstop(struct net_device *dev, int up)
{
	int ret;
	struct softing *card;
	struct softing_priv *priv;
	struct net_device *netdev;
	int bus_bitmask_start;
	int j, error_reporting;
	struct can_frame msg;
	const struct can_bittiming *bt;

	priv = netdev_priv(dev);
	card = priv->card;

	if (!card->fw.up)
		return -EIO;

	ret = mutex_lock_interruptible(&card->fw.lock);
	if (ret)
		return ret;

	bus_bitmask_start = 0;
	if (dev && up)
		/* prepare to start this bus as well */
		bus_bitmask_start |= (1 << priv->index);
	/* bring netdevs down */
	for (j = 0; j < ARRAY_SIZE(card->net); ++j) {
		netdev = card->net[j];
		if (!netdev)
			continue;
		priv = netdev_priv(netdev);

		if (dev != netdev)
			netif_stop_queue(netdev);

		if (netif_running(netdev)) {
			if (dev != netdev)
				bus_bitmask_start |= (1 << j);
			priv->tx.pending = 0;
			priv->tx.echo_put = 0;
			priv->tx.echo_get = 0;
			/*
			 * this bus' may just have called open_candev()
			 * which is rather stupid to call close_candev()
			 * already
			 * but we may come here from busoff recovery too
			 * in which case the echo_skb _needs_ flushing too.
			 * just be sure to call open_candev() again
			 */
			close_candev(netdev);
		}
		priv->can.state = CAN_STATE_STOPPED;
	}
	card->tx.pending = 0;

	softing_enable_irq(card, 0);
	ret = softing_reset_chip(card);
	if (ret)
		goto failed;
	if (!bus_bitmask_start)
		/* no busses to be brought up */
		goto card_done;

	if ((bus_bitmask_start & 1) && (bus_bitmask_start & 2)
			&& (softing_error_reporting(card->net[0])
				!= softing_error_reporting(card->net[1]))) {
		dev_alert(&card->pdev->dev,
				"err_reporting flag differs for busses\n");
		goto invalid;
	}
	error_reporting = 0;
	if (bus_bitmask_start & 1) {
		netdev = card->net[0];
		priv = netdev_priv(netdev);
		error_reporting += softing_error_reporting(netdev);
		/* init chip 1 */
		bt = &priv->can.bittiming;
		iowrite16(bt->brp, &card->dpram[DPRAM_FCT_PARAM + 2]);
		iowrite16(bt->sjw, &card->dpram[DPRAM_FCT_PARAM + 4]);
		iowrite16(bt->phase_seg1 + bt->prop_seg,
				&card->dpram[DPRAM_FCT_PARAM + 6]);
		iowrite16(bt->phase_seg2, &card->dpram[DPRAM_FCT_PARAM + 8]);
		iowrite16((priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) ? 1 : 0,
				&card->dpram[DPRAM_FCT_PARAM + 10]);
		ret = softing_fct_cmd(card, 1, "initialize_chip[0]");
		if (ret < 0)
			goto failed;
		/* set mode */
		iowrite16(0, &card->dpram[DPRAM_FCT_PARAM + 2]);
		iowrite16(0, &card->dpram[DPRAM_FCT_PARAM + 4]);
		ret = softing_fct_cmd(card, 3, "set_mode[0]");
		if (ret < 0)
			goto failed;
		/* set filter */
		/* 11bit id & mask */
		iowrite16(0x0000, &card->dpram[DPRAM_FCT_PARAM + 2]);
		iowrite16(0x07ff, &card->dpram[DPRAM_FCT_PARAM + 4]);
		/* 29bit id.lo & mask.lo & id.hi & mask.hi */
		iowrite16(0x0000, &card->dpram[DPRAM_FCT_PARAM + 6]);
		iowrite16(0xffff, &card->dpram[DPRAM_FCT_PARAM + 8]);
		iowrite16(0x0000, &card->dpram[DPRAM_FCT_PARAM + 10]);
		iowrite16(0x1fff, &card->dpram[DPRAM_FCT_PARAM + 12]);
		ret = softing_fct_cmd(card, 7, "set_filter[0]");
		if (ret < 0)
			goto failed;
		/* set output control */
		iowrite16(priv->output, &card->dpram[DPRAM_FCT_PARAM + 2]);
		ret = softing_fct_cmd(card, 5, "set_output[0]");
		if (ret < 0)
			goto failed;
	}
	if (bus_bitmask_start & 2) {
		netdev = card->net[1];
		priv = netdev_priv(netdev);
		error_reporting += softing_error_reporting(netdev);
		/* init chip2 */
		bt = &priv->can.bittiming;
		iowrite16(bt->brp, &card->dpram[DPRAM_FCT_PARAM + 2]);
		iowrite16(bt->sjw, &card->dpram[DPRAM_FCT_PARAM + 4]);
		iowrite16(bt->phase_seg1 + bt->prop_seg,
				&card->dpram[DPRAM_FCT_PARAM + 6]);
		iowrite16(bt->phase_seg2, &card->dpram[DPRAM_FCT_PARAM + 8]);
		iowrite16((priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) ? 1 : 0,
				&card->dpram[DPRAM_FCT_PARAM + 10]);
		ret = softing_fct_cmd(card, 2, "initialize_chip[1]");
		if (ret < 0)
			goto failed;
		/* set mode2 */
		iowrite16(0, &card->dpram[DPRAM_FCT_PARAM + 2]);
		iowrite16(0, &card->dpram[DPRAM_FCT_PARAM + 4]);
		ret = softing_fct_cmd(card, 4, "set_mode[1]");
		if (ret < 0)
			goto failed;
		/* set filter2 */
		/* 11bit id & mask */
		iowrite16(0x0000, &card->dpram[DPRAM_FCT_PARAM + 2]);
		iowrite16(0x07ff, &card->dpram[DPRAM_FCT_PARAM + 4]);
		/* 29bit id.lo & mask.lo & id.hi & mask.hi */
		iowrite16(0x0000, &card->dpram[DPRAM_FCT_PARAM + 6]);
		iowrite16(0xffff, &card->dpram[DPRAM_FCT_PARAM + 8]);
		iowrite16(0x0000, &card->dpram[DPRAM_FCT_PARAM + 10]);
		iowrite16(0x1fff, &card->dpram[DPRAM_FCT_PARAM + 12]);
		ret = softing_fct_cmd(card, 8, "set_filter[1]");
		if (ret < 0)
			goto failed;
		/* set output control2 */
		iowrite16(priv->output, &card->dpram[DPRAM_FCT_PARAM + 2]);
		ret = softing_fct_cmd(card, 6, "set_output[1]");
		if (ret < 0)
			goto failed;
	}
	/* enable_error_frame */
	/*
	 * Error reporting is switched off at the moment since
	 * the receiving of them is not yet 100% verified
	 * This should be enabled sooner or later
	 *
	if (error_reporting) {
		ret = softing_fct_cmd(card, 51, "enable_error_frame");
		if (ret < 0)
			goto failed;
	}
	*/
	/* initialize interface */
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 2]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 4]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 6]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 8]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 10]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 12]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 14]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 16]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 18]);
	iowrite16(1, &card->dpram[DPRAM_FCT_PARAM + 20]);
	ret = softing_fct_cmd(card, 17, "initialize_interface");
	if (ret < 0)
		goto failed;
	/* enable_fifo */
	ret = softing_fct_cmd(card, 36, "enable_fifo");
	if (ret < 0)
		goto failed;
	/* enable fifo tx ack */
	ret = softing_fct_cmd(card, 13, "fifo_tx_ack[0]");
	if (ret < 0)
		goto failed;
	/* enable fifo tx ack2 */
	ret = softing_fct_cmd(card, 14, "fifo_tx_ack[1]");
	if (ret < 0)
		goto failed;
	/* start_chip */
	ret = softing_fct_cmd(card, 11, "start_chip");
	if (ret < 0)
		goto failed;
	iowrite8(0, &card->dpram[DPRAM_INFO_BUSSTATE]);
	iowrite8(0, &card->dpram[DPRAM_INFO_BUSSTATE2]);
	if (card->pdat->generation < 2) {
		iowrite8(0, &card->dpram[DPRAM_V2_IRQ_TOHOST]);
		/* flush the DPRAM caches */
		wmb();
	}

	softing_initialize_timestamp(card);

	/*
	 * do socketcan notifications/status changes
	 * from here, no errors should occur, or the failed: part
	 * must be reviewed
	 */
	memset(&msg, 0, sizeof(msg));
	msg.can_id = CAN_ERR_FLAG | CAN_ERR_RESTARTED;
	msg.can_dlc = CAN_ERR_DLC;
	for (j = 0; j < ARRAY_SIZE(card->net); ++j) {
		if (!(bus_bitmask_start & (1 << j)))
			continue;
		netdev = card->net[j];
		if (!netdev)
			continue;
		priv = netdev_priv(netdev);
		priv->can.state = CAN_STATE_ERROR_ACTIVE;
		open_candev(netdev);
		if (dev != netdev) {
			/* notify other busses on the restart */
			softing_netdev_rx(netdev, &msg, 0);
			++priv->can.can_stats.restarts;
		}
		netif_wake_queue(netdev);
	}

	/* enable interrupts */
	ret = softing_enable_irq(card, 1);
	if (ret)
		goto failed;
card_done:
	mutex_unlock(&card->fw.lock);
	return 0;
invalid:
	ret = -EINVAL;
failed:
	softing_enable_irq(card, 0);
	softing_reset_chip(card);
	mutex_unlock(&card->fw.lock);
	/* bring all other interfaces down */
	for (j = 0; j < ARRAY_SIZE(card->net); ++j) {
		netdev = card->net[j];
		if (!netdev)
			continue;
		dev_close(netdev);
	}
	return ret;
}

int softing_default_output(struct net_device *netdev)
{
	struct softing_priv *priv = netdev_priv(netdev);
	struct softing *card = priv->card;

	switch (priv->chip) {
	case 1000:
		return (card->pdat->generation < 2) ? 0xfb : 0xfa;
	case 5:
		return 0x60;
	default:
		return 0x40;
	}
}