diff options
Diffstat (limited to 'sound/firewire/cmp.c')
| -rw-r--r-- | sound/firewire/cmp.c | 308 | 
1 files changed, 308 insertions, 0 deletions
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c new file mode 100644 index 00000000000..4a37f3a6fab --- /dev/null +++ b/sound/firewire/cmp.c @@ -0,0 +1,308 @@ +/* + * Connection Management Procedures (IEC 61883-1) helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/sched.h> +#include "lib.h" +#include "iso-resources.h" +#include "cmp.h" + +#define IMPR_SPEED_MASK		0xc0000000 +#define IMPR_SPEED_SHIFT	30 +#define IMPR_XSPEED_MASK	0x00000060 +#define IMPR_XSPEED_SHIFT	5 +#define IMPR_PLUGS_MASK		0x0000001f + +#define IPCR_ONLINE		0x80000000 +#define IPCR_BCAST_CONN		0x40000000 +#define IPCR_P2P_CONN_MASK	0x3f000000 +#define IPCR_P2P_CONN_SHIFT	24 +#define IPCR_CHANNEL_MASK	0x003f0000 +#define IPCR_CHANNEL_SHIFT	16 + +enum bus_reset_handling { +	ABORT_ON_BUS_RESET, +	SUCCEED_ON_BUS_RESET, +}; + +static __attribute__((format(printf, 2, 3))) +void cmp_error(struct cmp_connection *c, const char *fmt, ...) +{ +	va_list va; + +	va_start(va, fmt); +	dev_err(&c->resources.unit->device, "%cPCR%u: %pV", +		'i', c->pcr_index, &(struct va_format){ fmt, &va }); +	va_end(va); +} + +static int pcr_modify(struct cmp_connection *c, +		      __be32 (*modify)(struct cmp_connection *c, __be32 old), +		      int (*check)(struct cmp_connection *c, __be32 pcr), +		      enum bus_reset_handling bus_reset_handling) +{ +	struct fw_device *device = fw_parent_device(c->resources.unit); +	__be32 *buffer = c->resources.buffer; +	int generation = c->resources.generation; +	int rcode, errors = 0; +	__be32 old_arg; +	int err; + +	buffer[0] = c->last_pcr_value; +	for (;;) { +		old_arg = buffer[0]; +		buffer[1] = modify(c, buffer[0]); + +		rcode = fw_run_transaction( +				device->card, TCODE_LOCK_COMPARE_SWAP, +				device->node_id, generation, device->max_speed, +				CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index), +				buffer, 8); + +		if (rcode == RCODE_COMPLETE) { +			if (buffer[0] == old_arg) /* success? */ +				break; + +			if (check) { +				err = check(c, buffer[0]); +				if (err < 0) +					return err; +			} +		} else if (rcode == RCODE_GENERATION) +			goto bus_reset; +		else if (rcode_is_permanent_error(rcode) || ++errors >= 3) +			goto io_error; +	} +	c->last_pcr_value = buffer[1]; + +	return 0; + +io_error: +	cmp_error(c, "transaction failed: %s\n", rcode_string(rcode)); +	return -EIO; + +bus_reset: +	return bus_reset_handling == ABORT_ON_BUS_RESET ? -EAGAIN : 0; +} + + +/** + * cmp_connection_init - initializes a connection manager + * @c: the connection manager to initialize + * @unit: a unit of the target device + * @ipcr_index: the index of the iPCR on the target device + */ +int cmp_connection_init(struct cmp_connection *c, +			struct fw_unit *unit, +			unsigned int ipcr_index) +{ +	__be32 impr_be; +	u32 impr; +	int err; + +	err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, +				 CSR_REGISTER_BASE + CSR_IMPR, +				 &impr_be, 4); +	if (err < 0) +		return err; +	impr = be32_to_cpu(impr_be); + +	if (ipcr_index >= (impr & IMPR_PLUGS_MASK)) +		return -EINVAL; + +	err = fw_iso_resources_init(&c->resources, unit); +	if (err < 0) +		return err; + +	c->connected = false; +	mutex_init(&c->mutex); +	c->last_pcr_value = cpu_to_be32(0x80000000); +	c->pcr_index = ipcr_index; +	c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT; +	if (c->max_speed == SCODE_BETA) +		c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT; + +	return 0; +} +EXPORT_SYMBOL(cmp_connection_init); + +/** + * cmp_connection_destroy - free connection manager resources + * @c: the connection manager + */ +void cmp_connection_destroy(struct cmp_connection *c) +{ +	WARN_ON(c->connected); +	mutex_destroy(&c->mutex); +	fw_iso_resources_destroy(&c->resources); +} +EXPORT_SYMBOL(cmp_connection_destroy); + + +static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) +{ +	ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN | +			     IPCR_P2P_CONN_MASK | +			     IPCR_CHANNEL_MASK); +	ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT); +	ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT); + +	return ipcr; +} + +static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) +{ +	if (ipcr & cpu_to_be32(IPCR_BCAST_CONN | +			       IPCR_P2P_CONN_MASK)) { +		cmp_error(c, "plug is already in use\n"); +		return -EBUSY; +	} +	if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) { +		cmp_error(c, "plug is not on-line\n"); +		return -ECONNREFUSED; +	} + +	return 0; +} + +/** + * cmp_connection_establish - establish a connection to the target + * @c: the connection manager + * @max_payload_bytes: the amount of data (including CIP headers) per packet + * + * This function establishes a point-to-point connection from the local + * computer to the target by allocating isochronous resources (channel and + * bandwidth) and setting the target's input plug control register.  When this + * function succeeds, the caller is responsible for starting transmitting + * packets. + */ +int cmp_connection_establish(struct cmp_connection *c, +			     unsigned int max_payload_bytes) +{ +	int err; + +	if (WARN_ON(c->connected)) +		return -EISCONN; + +	c->speed = min(c->max_speed, +		       fw_parent_device(c->resources.unit)->max_speed); + +	mutex_lock(&c->mutex); + +retry_after_bus_reset: +	err = fw_iso_resources_allocate(&c->resources, +					max_payload_bytes, c->speed); +	if (err < 0) +		goto err_mutex; + +	err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, +			 ABORT_ON_BUS_RESET); +	if (err == -EAGAIN) { +		fw_iso_resources_free(&c->resources); +		goto retry_after_bus_reset; +	} +	if (err < 0) +		goto err_resources; + +	c->connected = true; + +	mutex_unlock(&c->mutex); + +	return 0; + +err_resources: +	fw_iso_resources_free(&c->resources); +err_mutex: +	mutex_unlock(&c->mutex); + +	return err; +} +EXPORT_SYMBOL(cmp_connection_establish); + +/** + * cmp_connection_update - update the connection after a bus reset + * @c: the connection manager + * + * This function must be called from the driver's .update handler to reestablish + * any connection that might have been active. + * + * Returns zero on success, or a negative error code.  On an error, the + * connection is broken and the caller must stop transmitting iso packets. + */ +int cmp_connection_update(struct cmp_connection *c) +{ +	int err; + +	mutex_lock(&c->mutex); + +	if (!c->connected) { +		mutex_unlock(&c->mutex); +		return 0; +	} + +	err = fw_iso_resources_update(&c->resources); +	if (err < 0) +		goto err_unconnect; + +	err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, +			 SUCCEED_ON_BUS_RESET); +	if (err < 0) +		goto err_resources; + +	mutex_unlock(&c->mutex); + +	return 0; + +err_resources: +	fw_iso_resources_free(&c->resources); +err_unconnect: +	c->connected = false; +	mutex_unlock(&c->mutex); + +	return err; +} +EXPORT_SYMBOL(cmp_connection_update); + + +static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr) +{ +	return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK); +} + +/** + * cmp_connection_break - break the connection to the target + * @c: the connection manager + * + * This function deactives the connection in the target's input plug control + * register, and frees the isochronous resources of the connection.  Before + * calling this function, the caller should cease transmitting packets. + */ +void cmp_connection_break(struct cmp_connection *c) +{ +	int err; + +	mutex_lock(&c->mutex); + +	if (!c->connected) { +		mutex_unlock(&c->mutex); +		return; +	} + +	err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); +	if (err < 0) +		cmp_error(c, "plug is still connected\n"); + +	fw_iso_resources_free(&c->resources); + +	c->connected = false; + +	mutex_unlock(&c->mutex); +} +EXPORT_SYMBOL(cmp_connection_break);  |