162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci  Broadcom B43legacy wireless driver
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci  PIO Transmission
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci  Copyright (c) 2005 Michael Buesch <m@bues.ch>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci*/
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "b43legacy.h"
1462306a36Sopenharmony_ci#include "pio.h"
1562306a36Sopenharmony_ci#include "main.h"
1662306a36Sopenharmony_ci#include "xmit.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <linux/delay.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic void tx_start(struct b43legacy_pioqueue *queue)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	b43legacy_pio_write(queue, B43legacy_PIO_TXCTL,
2562306a36Sopenharmony_ci			    B43legacy_PIO_TXCTL_INIT);
2662306a36Sopenharmony_ci}
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic void tx_octet(struct b43legacy_pioqueue *queue,
2962306a36Sopenharmony_ci		     u8 octet)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	if (queue->need_workarounds) {
3262306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, octet);
3362306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXCTL,
3462306a36Sopenharmony_ci				    B43legacy_PIO_TXCTL_WRITELO);
3562306a36Sopenharmony_ci	} else {
3662306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXCTL,
3762306a36Sopenharmony_ci				    B43legacy_PIO_TXCTL_WRITELO);
3862306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, octet);
3962306a36Sopenharmony_ci	}
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic u16 tx_get_next_word(const u8 *txhdr,
4362306a36Sopenharmony_ci			    const u8 *packet,
4462306a36Sopenharmony_ci			    size_t txhdr_size,
4562306a36Sopenharmony_ci			    unsigned int *pos)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	const u8 *source;
4862306a36Sopenharmony_ci	unsigned int i = *pos;
4962306a36Sopenharmony_ci	u16 ret;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	if (i < txhdr_size)
5262306a36Sopenharmony_ci		source = txhdr;
5362306a36Sopenharmony_ci	else {
5462306a36Sopenharmony_ci		source = packet;
5562306a36Sopenharmony_ci		i -= txhdr_size;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci	ret = le16_to_cpu(*((__le16 *)(source + i)));
5862306a36Sopenharmony_ci	*pos += 2;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	return ret;
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic void tx_data(struct b43legacy_pioqueue *queue,
6462306a36Sopenharmony_ci		    u8 *txhdr,
6562306a36Sopenharmony_ci		    const u8 *packet,
6662306a36Sopenharmony_ci		    unsigned int octets)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	u16 data;
6962306a36Sopenharmony_ci	unsigned int i = 0;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (queue->need_workarounds) {
7262306a36Sopenharmony_ci		data = tx_get_next_word(txhdr, packet,
7362306a36Sopenharmony_ci					sizeof(struct b43legacy_txhdr_fw3), &i);
7462306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, data);
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci	b43legacy_pio_write(queue, B43legacy_PIO_TXCTL,
7762306a36Sopenharmony_ci			    B43legacy_PIO_TXCTL_WRITELO |
7862306a36Sopenharmony_ci			    B43legacy_PIO_TXCTL_WRITEHI);
7962306a36Sopenharmony_ci	while (i < octets - 1) {
8062306a36Sopenharmony_ci		data = tx_get_next_word(txhdr, packet,
8162306a36Sopenharmony_ci					sizeof(struct b43legacy_txhdr_fw3), &i);
8262306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, data);
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci	if (octets % 2)
8562306a36Sopenharmony_ci		tx_octet(queue, packet[octets -
8662306a36Sopenharmony_ci			 sizeof(struct b43legacy_txhdr_fw3) - 1]);
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic void tx_complete(struct b43legacy_pioqueue *queue,
9062306a36Sopenharmony_ci			struct sk_buff *skb)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	if (queue->need_workarounds) {
9362306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXDATA,
9462306a36Sopenharmony_ci				    skb->data[skb->len - 1]);
9562306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXCTL,
9662306a36Sopenharmony_ci				    B43legacy_PIO_TXCTL_WRITELO |
9762306a36Sopenharmony_ci				    B43legacy_PIO_TXCTL_COMPLETE);
9862306a36Sopenharmony_ci	} else
9962306a36Sopenharmony_ci		b43legacy_pio_write(queue, B43legacy_PIO_TXCTL,
10062306a36Sopenharmony_ci				    B43legacy_PIO_TXCTL_COMPLETE);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic u16 generate_cookie(struct b43legacy_pioqueue *queue,
10462306a36Sopenharmony_ci			   struct b43legacy_pio_txpacket *packet)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	u16 cookie = 0x0000;
10762306a36Sopenharmony_ci	int packetindex;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/* We use the upper 4 bits for the PIO
11062306a36Sopenharmony_ci	 * controller ID and the lower 12 bits
11162306a36Sopenharmony_ci	 * for the packet index (in the cache).
11262306a36Sopenharmony_ci	 */
11362306a36Sopenharmony_ci	switch (queue->mmio_base) {
11462306a36Sopenharmony_ci	case B43legacy_MMIO_PIO1_BASE:
11562306a36Sopenharmony_ci		break;
11662306a36Sopenharmony_ci	case B43legacy_MMIO_PIO2_BASE:
11762306a36Sopenharmony_ci		cookie = 0x1000;
11862306a36Sopenharmony_ci		break;
11962306a36Sopenharmony_ci	case B43legacy_MMIO_PIO3_BASE:
12062306a36Sopenharmony_ci		cookie = 0x2000;
12162306a36Sopenharmony_ci		break;
12262306a36Sopenharmony_ci	case B43legacy_MMIO_PIO4_BASE:
12362306a36Sopenharmony_ci		cookie = 0x3000;
12462306a36Sopenharmony_ci		break;
12562306a36Sopenharmony_ci	default:
12662306a36Sopenharmony_ci		B43legacy_WARN_ON(1);
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci	packetindex = pio_txpacket_getindex(packet);
12962306a36Sopenharmony_ci	B43legacy_WARN_ON(!(((u16)packetindex & 0xF000) == 0x0000));
13062306a36Sopenharmony_ci	cookie |= (u16)packetindex;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	return cookie;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic
13662306a36Sopenharmony_cistruct b43legacy_pioqueue *parse_cookie(struct b43legacy_wldev *dev,
13762306a36Sopenharmony_ci					u16 cookie,
13862306a36Sopenharmony_ci					struct b43legacy_pio_txpacket **packet)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct b43legacy_pio *pio = &dev->pio;
14162306a36Sopenharmony_ci	struct b43legacy_pioqueue *queue = NULL;
14262306a36Sopenharmony_ci	int packetindex;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	switch (cookie & 0xF000) {
14562306a36Sopenharmony_ci	case 0x0000:
14662306a36Sopenharmony_ci		queue = pio->queue0;
14762306a36Sopenharmony_ci		break;
14862306a36Sopenharmony_ci	case 0x1000:
14962306a36Sopenharmony_ci		queue = pio->queue1;
15062306a36Sopenharmony_ci		break;
15162306a36Sopenharmony_ci	case 0x2000:
15262306a36Sopenharmony_ci		queue = pio->queue2;
15362306a36Sopenharmony_ci		break;
15462306a36Sopenharmony_ci	case 0x3000:
15562306a36Sopenharmony_ci		queue = pio->queue3;
15662306a36Sopenharmony_ci		break;
15762306a36Sopenharmony_ci	default:
15862306a36Sopenharmony_ci		B43legacy_WARN_ON(1);
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci	packetindex = (cookie & 0x0FFF);
16162306a36Sopenharmony_ci	B43legacy_WARN_ON(!(packetindex >= 0 && packetindex
16262306a36Sopenharmony_ci			  < B43legacy_PIO_MAXTXPACKETS));
16362306a36Sopenharmony_ci	*packet = &(queue->tx_packets_cache[packetindex]);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	return queue;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ciunion txhdr_union {
16962306a36Sopenharmony_ci	struct b43legacy_txhdr_fw3 txhdr_fw3;
17062306a36Sopenharmony_ci};
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic int pio_tx_write_fragment(struct b43legacy_pioqueue *queue,
17362306a36Sopenharmony_ci				  struct sk_buff *skb,
17462306a36Sopenharmony_ci				  struct b43legacy_pio_txpacket *packet,
17562306a36Sopenharmony_ci				  size_t txhdr_size)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	union txhdr_union txhdr_data;
17862306a36Sopenharmony_ci	u8 *txhdr = NULL;
17962306a36Sopenharmony_ci	unsigned int octets;
18062306a36Sopenharmony_ci	int err;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	txhdr = (u8 *)(&txhdr_data.txhdr_fw3);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	B43legacy_WARN_ON(skb_shinfo(skb)->nr_frags != 0);
18562306a36Sopenharmony_ci	err = b43legacy_generate_txhdr(queue->dev,
18662306a36Sopenharmony_ci				 txhdr, skb->data, skb->len,
18762306a36Sopenharmony_ci				 IEEE80211_SKB_CB(skb),
18862306a36Sopenharmony_ci				 generate_cookie(queue, packet));
18962306a36Sopenharmony_ci	if (err)
19062306a36Sopenharmony_ci		return err;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	tx_start(queue);
19362306a36Sopenharmony_ci	octets = skb->len + txhdr_size;
19462306a36Sopenharmony_ci	if (queue->need_workarounds)
19562306a36Sopenharmony_ci		octets--;
19662306a36Sopenharmony_ci	tx_data(queue, txhdr, (u8 *)skb->data, octets);
19762306a36Sopenharmony_ci	tx_complete(queue, skb);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	return 0;
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic void free_txpacket(struct b43legacy_pio_txpacket *packet,
20362306a36Sopenharmony_ci			  int irq_context)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	struct b43legacy_pioqueue *queue = packet->queue;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if (packet->skb) {
20862306a36Sopenharmony_ci		if (irq_context)
20962306a36Sopenharmony_ci			dev_kfree_skb_irq(packet->skb);
21062306a36Sopenharmony_ci		else
21162306a36Sopenharmony_ci			dev_kfree_skb(packet->skb);
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci	list_move(&packet->list, &queue->txfree);
21462306a36Sopenharmony_ci	queue->nr_txfree++;
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic int pio_tx_packet(struct b43legacy_pio_txpacket *packet)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct b43legacy_pioqueue *queue = packet->queue;
22062306a36Sopenharmony_ci	struct sk_buff *skb = packet->skb;
22162306a36Sopenharmony_ci	u16 octets;
22262306a36Sopenharmony_ci	int err;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	octets = (u16)skb->len + sizeof(struct b43legacy_txhdr_fw3);
22562306a36Sopenharmony_ci	if (queue->tx_devq_size < octets) {
22662306a36Sopenharmony_ci		b43legacywarn(queue->dev->wl, "PIO queue too small. "
22762306a36Sopenharmony_ci			"Dropping packet.\n");
22862306a36Sopenharmony_ci		/* Drop it silently (return success) */
22962306a36Sopenharmony_ci		free_txpacket(packet, 1);
23062306a36Sopenharmony_ci		return 0;
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci	B43legacy_WARN_ON(queue->tx_devq_packets >
23362306a36Sopenharmony_ci			  B43legacy_PIO_MAXTXDEVQPACKETS);
23462306a36Sopenharmony_ci	B43legacy_WARN_ON(queue->tx_devq_used > queue->tx_devq_size);
23562306a36Sopenharmony_ci	/* Check if there is sufficient free space on the device
23662306a36Sopenharmony_ci	 * TX queue. If not, return and let the TX tasklet
23762306a36Sopenharmony_ci	 * retry later.
23862306a36Sopenharmony_ci	 */
23962306a36Sopenharmony_ci	if (queue->tx_devq_packets == B43legacy_PIO_MAXTXDEVQPACKETS)
24062306a36Sopenharmony_ci		return -EBUSY;
24162306a36Sopenharmony_ci	if (queue->tx_devq_used + octets > queue->tx_devq_size)
24262306a36Sopenharmony_ci		return -EBUSY;
24362306a36Sopenharmony_ci	/* Now poke the device. */
24462306a36Sopenharmony_ci	err = pio_tx_write_fragment(queue, skb, packet,
24562306a36Sopenharmony_ci			      sizeof(struct b43legacy_txhdr_fw3));
24662306a36Sopenharmony_ci	if (unlikely(err == -ENOKEY)) {
24762306a36Sopenharmony_ci		/* Drop this packet, as we don't have the encryption key
24862306a36Sopenharmony_ci		 * anymore and must not transmit it unencrypted. */
24962306a36Sopenharmony_ci		free_txpacket(packet, 1);
25062306a36Sopenharmony_ci		return 0;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	/* Account for the packet size.
25462306a36Sopenharmony_ci	 * (We must not overflow the device TX queue)
25562306a36Sopenharmony_ci	 */
25662306a36Sopenharmony_ci	queue->tx_devq_packets++;
25762306a36Sopenharmony_ci	queue->tx_devq_used += octets;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	/* Transmission started, everything ok, move the
26062306a36Sopenharmony_ci	 * packet to the txrunning list.
26162306a36Sopenharmony_ci	 */
26262306a36Sopenharmony_ci	list_move_tail(&packet->list, &queue->txrunning);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	return 0;
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic void tx_tasklet(struct tasklet_struct *t)
26862306a36Sopenharmony_ci{
26962306a36Sopenharmony_ci	struct b43legacy_pioqueue *queue = from_tasklet(queue, t, txtask);
27062306a36Sopenharmony_ci	struct b43legacy_wldev *dev = queue->dev;
27162306a36Sopenharmony_ci	unsigned long flags;
27262306a36Sopenharmony_ci	struct b43legacy_pio_txpacket *packet, *tmp_packet;
27362306a36Sopenharmony_ci	int err;
27462306a36Sopenharmony_ci	u16 txctl;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	spin_lock_irqsave(&dev->wl->irq_lock, flags);
27762306a36Sopenharmony_ci	if (queue->tx_frozen)
27862306a36Sopenharmony_ci		goto out_unlock;
27962306a36Sopenharmony_ci	txctl = b43legacy_pio_read(queue, B43legacy_PIO_TXCTL);
28062306a36Sopenharmony_ci	if (txctl & B43legacy_PIO_TXCTL_SUSPEND)
28162306a36Sopenharmony_ci		goto out_unlock;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	list_for_each_entry_safe(packet, tmp_packet, &queue->txqueue, list) {
28462306a36Sopenharmony_ci		/* Try to transmit the packet. This can fail, if
28562306a36Sopenharmony_ci		 * the device queue is full. In case of failure, the
28662306a36Sopenharmony_ci		 * packet is left in the txqueue.
28762306a36Sopenharmony_ci		 * If transmission succeed, the packet is moved to txrunning.
28862306a36Sopenharmony_ci		 * If it is impossible to transmit the packet, it
28962306a36Sopenharmony_ci		 * is dropped.
29062306a36Sopenharmony_ci		 */
29162306a36Sopenharmony_ci		err = pio_tx_packet(packet);
29262306a36Sopenharmony_ci		if (err)
29362306a36Sopenharmony_ci			break;
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ciout_unlock:
29662306a36Sopenharmony_ci	spin_unlock_irqrestore(&dev->wl->irq_lock, flags);
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_cistatic void setup_txqueues(struct b43legacy_pioqueue *queue)
30062306a36Sopenharmony_ci{
30162306a36Sopenharmony_ci	struct b43legacy_pio_txpacket *packet;
30262306a36Sopenharmony_ci	int i;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	queue->nr_txfree = B43legacy_PIO_MAXTXPACKETS;
30562306a36Sopenharmony_ci	for (i = 0; i < B43legacy_PIO_MAXTXPACKETS; i++) {
30662306a36Sopenharmony_ci		packet = &(queue->tx_packets_cache[i]);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci		packet->queue = queue;
30962306a36Sopenharmony_ci		INIT_LIST_HEAD(&packet->list);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci		list_add(&packet->list, &queue->txfree);
31262306a36Sopenharmony_ci	}
31362306a36Sopenharmony_ci}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_cistatic
31662306a36Sopenharmony_cistruct b43legacy_pioqueue *b43legacy_setup_pioqueue(struct b43legacy_wldev *dev,
31762306a36Sopenharmony_ci						    u16 pio_mmio_base)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	struct b43legacy_pioqueue *queue;
32062306a36Sopenharmony_ci	u32 value;
32162306a36Sopenharmony_ci	u16 qsize;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	queue = kzalloc(sizeof(*queue), GFP_KERNEL);
32462306a36Sopenharmony_ci	if (!queue)
32562306a36Sopenharmony_ci		goto out;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	queue->dev = dev;
32862306a36Sopenharmony_ci	queue->mmio_base = pio_mmio_base;
32962306a36Sopenharmony_ci	queue->need_workarounds = (dev->dev->id.revision < 3);
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	INIT_LIST_HEAD(&queue->txfree);
33262306a36Sopenharmony_ci	INIT_LIST_HEAD(&queue->txqueue);
33362306a36Sopenharmony_ci	INIT_LIST_HEAD(&queue->txrunning);
33462306a36Sopenharmony_ci	tasklet_setup(&queue->txtask, tx_tasklet);
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	value = b43legacy_read32(dev, B43legacy_MMIO_MACCTL);
33762306a36Sopenharmony_ci	value &= ~B43legacy_MACCTL_BE;
33862306a36Sopenharmony_ci	b43legacy_write32(dev, B43legacy_MMIO_MACCTL, value);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	qsize = b43legacy_read16(dev, queue->mmio_base
34162306a36Sopenharmony_ci				 + B43legacy_PIO_TXQBUFSIZE);
34262306a36Sopenharmony_ci	if (qsize == 0) {
34362306a36Sopenharmony_ci		b43legacyerr(dev->wl, "This card does not support PIO "
34462306a36Sopenharmony_ci		       "operation mode. Please use DMA mode "
34562306a36Sopenharmony_ci		       "(module parameter pio=0).\n");
34662306a36Sopenharmony_ci		goto err_freequeue;
34762306a36Sopenharmony_ci	}
34862306a36Sopenharmony_ci	if (qsize <= B43legacy_PIO_TXQADJUST) {
34962306a36Sopenharmony_ci		b43legacyerr(dev->wl, "PIO tx device-queue too small (%u)\n",
35062306a36Sopenharmony_ci		       qsize);
35162306a36Sopenharmony_ci		goto err_freequeue;
35262306a36Sopenharmony_ci	}
35362306a36Sopenharmony_ci	qsize -= B43legacy_PIO_TXQADJUST;
35462306a36Sopenharmony_ci	queue->tx_devq_size = qsize;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	setup_txqueues(queue);
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ciout:
35962306a36Sopenharmony_ci	return queue;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cierr_freequeue:
36262306a36Sopenharmony_ci	kfree(queue);
36362306a36Sopenharmony_ci	queue = NULL;
36462306a36Sopenharmony_ci	goto out;
36562306a36Sopenharmony_ci}
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_cistatic void cancel_transfers(struct b43legacy_pioqueue *queue)
36862306a36Sopenharmony_ci{
36962306a36Sopenharmony_ci	struct b43legacy_pio_txpacket *packet, *tmp_packet;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	tasklet_kill(&queue->txtask);
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	list_for_each_entry_safe(packet, tmp_packet, &queue->txrunning, list)
37462306a36Sopenharmony_ci		free_txpacket(packet, 0);
37562306a36Sopenharmony_ci	list_for_each_entry_safe(packet, tmp_packet, &queue->txqueue, list)
37662306a36Sopenharmony_ci		free_txpacket(packet, 0);
37762306a36Sopenharmony_ci}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_cistatic void b43legacy_destroy_pioqueue(struct b43legacy_pioqueue *queue)
38062306a36Sopenharmony_ci{
38162306a36Sopenharmony_ci	if (!queue)
38262306a36Sopenharmony_ci		return;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	cancel_transfers(queue);
38562306a36Sopenharmony_ci	kfree(queue);
38662306a36Sopenharmony_ci}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_civoid b43legacy_pio_free(struct b43legacy_wldev *dev)
38962306a36Sopenharmony_ci{
39062306a36Sopenharmony_ci	struct b43legacy_pio *pio;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	if (!b43legacy_using_pio(dev))
39362306a36Sopenharmony_ci		return;
39462306a36Sopenharmony_ci	pio = &dev->pio;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	b43legacy_destroy_pioqueue(pio->queue3);
39762306a36Sopenharmony_ci	pio->queue3 = NULL;
39862306a36Sopenharmony_ci	b43legacy_destroy_pioqueue(pio->queue2);
39962306a36Sopenharmony_ci	pio->queue2 = NULL;
40062306a36Sopenharmony_ci	b43legacy_destroy_pioqueue(pio->queue1);
40162306a36Sopenharmony_ci	pio->queue1 = NULL;
40262306a36Sopenharmony_ci	b43legacy_destroy_pioqueue(pio->queue0);
40362306a36Sopenharmony_ci	pio->queue0 = NULL;
40462306a36Sopenharmony_ci}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ciint b43legacy_pio_init(struct b43legacy_wldev *dev)
40762306a36Sopenharmony_ci{
40862306a36Sopenharmony_ci	struct b43legacy_pio *pio = &dev->pio;
40962306a36Sopenharmony_ci	struct b43legacy_pioqueue *queue;
41062306a36Sopenharmony_ci	int err = -ENOMEM;
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	queue = b43legacy_setup_pioqueue(dev, B43legacy_MMIO_PIO1_BASE);
41362306a36Sopenharmony_ci	if (!queue)
41462306a36Sopenharmony_ci		goto out;
41562306a36Sopenharmony_ci	pio->queue0 = queue;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	queue = b43legacy_setup_pioqueue(dev, B43legacy_MMIO_PIO2_BASE);
41862306a36Sopenharmony_ci	if (!queue)
41962306a36Sopenharmony_ci		goto err_destroy0;
42062306a36Sopenharmony_ci	pio->queue1 = queue;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	queue = b43legacy_setup_pioqueue(dev, B43legacy_MMIO_PIO3_BASE);
42362306a36Sopenharmony_ci	if (!queue)
42462306a36Sopenharmony_ci		goto err_destroy1;
42562306a36Sopenharmony_ci	pio->queue2 = queue;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	queue = b43legacy_setup_pioqueue(dev, B43legacy_MMIO_PIO4_BASE);
42862306a36Sopenharmony_ci	if (!queue)
42962306a36Sopenharmony_ci		goto err_destroy2;
43062306a36Sopenharmony_ci	pio->queue3 = queue;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	if (dev->dev->id.revision < 3)
43362306a36Sopenharmony_ci		dev->irq_mask |= B43legacy_IRQ_PIO_WORKAROUND;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	b43legacydbg(dev->wl, "PIO initialized\n");
43662306a36Sopenharmony_ci	err = 0;
43762306a36Sopenharmony_ciout:
43862306a36Sopenharmony_ci	return err;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_cierr_destroy2:
44162306a36Sopenharmony_ci	b43legacy_destroy_pioqueue(pio->queue2);
44262306a36Sopenharmony_ci	pio->queue2 = NULL;
44362306a36Sopenharmony_cierr_destroy1:
44462306a36Sopenharmony_ci	b43legacy_destroy_pioqueue(pio->queue1);
44562306a36Sopenharmony_ci	pio->queue1 = NULL;
44662306a36Sopenharmony_cierr_destroy0:
44762306a36Sopenharmony_ci	b43legacy_destroy_pioqueue(pio->queue0);
44862306a36Sopenharmony_ci	pio->queue0 = NULL;
44962306a36Sopenharmony_ci	goto out;
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ciint b43legacy_pio_tx(struct b43legacy_wldev *dev,
45362306a36Sopenharmony_ci		     struct sk_buff *skb)
45462306a36Sopenharmony_ci{
45562306a36Sopenharmony_ci	struct b43legacy_pioqueue *queue = dev->pio.queue1;
45662306a36Sopenharmony_ci	struct b43legacy_pio_txpacket *packet;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	B43legacy_WARN_ON(queue->tx_suspended);
45962306a36Sopenharmony_ci	B43legacy_WARN_ON(list_empty(&queue->txfree));
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	packet = list_entry(queue->txfree.next, struct b43legacy_pio_txpacket,
46262306a36Sopenharmony_ci			    list);
46362306a36Sopenharmony_ci	packet->skb = skb;
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci	list_move_tail(&packet->list, &queue->txqueue);
46662306a36Sopenharmony_ci	queue->nr_txfree--;
46762306a36Sopenharmony_ci	B43legacy_WARN_ON(queue->nr_txfree >= B43legacy_PIO_MAXTXPACKETS);
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	tasklet_schedule(&queue->txtask);
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	return 0;
47262306a36Sopenharmony_ci}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_civoid b43legacy_pio_handle_txstatus(struct b43legacy_wldev *dev,
47562306a36Sopenharmony_ci				   const struct b43legacy_txstatus *status)
47662306a36Sopenharmony_ci{
47762306a36Sopenharmony_ci	struct b43legacy_pioqueue *queue;
47862306a36Sopenharmony_ci	struct b43legacy_pio_txpacket *packet;
47962306a36Sopenharmony_ci	struct ieee80211_tx_info *info;
48062306a36Sopenharmony_ci	int retry_limit;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	queue = parse_cookie(dev, status->cookie, &packet);
48362306a36Sopenharmony_ci	B43legacy_WARN_ON(!queue);
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	if (!packet->skb)
48662306a36Sopenharmony_ci		return;
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	queue->tx_devq_packets--;
48962306a36Sopenharmony_ci	queue->tx_devq_used -= (packet->skb->len +
49062306a36Sopenharmony_ci				sizeof(struct b43legacy_txhdr_fw3));
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	info = IEEE80211_SKB_CB(packet->skb);
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	/* preserve the confiured retry limit before clearing the status
49562306a36Sopenharmony_ci	 * The xmit function has overwritten the rc's value with the actual
49662306a36Sopenharmony_ci	 * retry limit done by the hardware */
49762306a36Sopenharmony_ci	retry_limit = info->status.rates[0].count;
49862306a36Sopenharmony_ci	ieee80211_tx_info_clear_status(info);
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	if (status->acked)
50162306a36Sopenharmony_ci		info->flags |= IEEE80211_TX_STAT_ACK;
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	if (status->rts_count > dev->wl->hw->conf.short_frame_max_tx_count) {
50462306a36Sopenharmony_ci		/*
50562306a36Sopenharmony_ci		 * If the short retries (RTS, not data frame) have exceeded
50662306a36Sopenharmony_ci		 * the limit, the hw will not have tried the selected rate,
50762306a36Sopenharmony_ci		 * but will have used the fallback rate instead.
50862306a36Sopenharmony_ci		 * Don't let the rate control count attempts for the selected
50962306a36Sopenharmony_ci		 * rate in this case, otherwise the statistics will be off.
51062306a36Sopenharmony_ci		 */
51162306a36Sopenharmony_ci		info->status.rates[0].count = 0;
51262306a36Sopenharmony_ci		info->status.rates[1].count = status->frame_count;
51362306a36Sopenharmony_ci	} else {
51462306a36Sopenharmony_ci		if (status->frame_count > retry_limit) {
51562306a36Sopenharmony_ci			info->status.rates[0].count = retry_limit;
51662306a36Sopenharmony_ci			info->status.rates[1].count = status->frame_count -
51762306a36Sopenharmony_ci					retry_limit;
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci		} else {
52062306a36Sopenharmony_ci			info->status.rates[0].count = status->frame_count;
52162306a36Sopenharmony_ci			info->status.rates[1].idx = -1;
52262306a36Sopenharmony_ci		}
52362306a36Sopenharmony_ci	}
52462306a36Sopenharmony_ci	ieee80211_tx_status_irqsafe(dev->wl->hw, packet->skb);
52562306a36Sopenharmony_ci	packet->skb = NULL;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	free_txpacket(packet, 1);
52862306a36Sopenharmony_ci	/* If there are packets on the txqueue, poke the tasklet
52962306a36Sopenharmony_ci	 * to transmit them.
53062306a36Sopenharmony_ci	 */
53162306a36Sopenharmony_ci	if (!list_empty(&queue->txqueue))
53262306a36Sopenharmony_ci		tasklet_schedule(&queue->txtask);
53362306a36Sopenharmony_ci}
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_cistatic void pio_rx_error(struct b43legacy_pioqueue *queue,
53662306a36Sopenharmony_ci			 int clear_buffers,
53762306a36Sopenharmony_ci			 const char *error)
53862306a36Sopenharmony_ci{
53962306a36Sopenharmony_ci	int i;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	b43legacyerr(queue->dev->wl, "PIO RX error: %s\n", error);
54262306a36Sopenharmony_ci	b43legacy_pio_write(queue, B43legacy_PIO_RXCTL,
54362306a36Sopenharmony_ci			    B43legacy_PIO_RXCTL_READY);
54462306a36Sopenharmony_ci	if (clear_buffers) {
54562306a36Sopenharmony_ci		B43legacy_WARN_ON(queue->mmio_base != B43legacy_MMIO_PIO1_BASE);
54662306a36Sopenharmony_ci		for (i = 0; i < 15; i++) {
54762306a36Sopenharmony_ci			/* Dummy read. */
54862306a36Sopenharmony_ci			b43legacy_pio_read(queue, B43legacy_PIO_RXDATA);
54962306a36Sopenharmony_ci		}
55062306a36Sopenharmony_ci	}
55162306a36Sopenharmony_ci}
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_civoid b43legacy_pio_rx(struct b43legacy_pioqueue *queue)
55462306a36Sopenharmony_ci{
55562306a36Sopenharmony_ci	__le16 preamble[21] = { 0 };
55662306a36Sopenharmony_ci	struct b43legacy_rxhdr_fw3 *rxhdr;
55762306a36Sopenharmony_ci	u16 tmp;
55862306a36Sopenharmony_ci	u16 len;
55962306a36Sopenharmony_ci	u16 macstat;
56062306a36Sopenharmony_ci	int i;
56162306a36Sopenharmony_ci	int preamble_readwords;
56262306a36Sopenharmony_ci	struct sk_buff *skb;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXCTL);
56562306a36Sopenharmony_ci	if (!(tmp & B43legacy_PIO_RXCTL_DATAAVAILABLE))
56662306a36Sopenharmony_ci		return;
56762306a36Sopenharmony_ci	b43legacy_pio_write(queue, B43legacy_PIO_RXCTL,
56862306a36Sopenharmony_ci			    B43legacy_PIO_RXCTL_DATAAVAILABLE);
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	for (i = 0; i < 10; i++) {
57162306a36Sopenharmony_ci		tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXCTL);
57262306a36Sopenharmony_ci		if (tmp & B43legacy_PIO_RXCTL_READY)
57362306a36Sopenharmony_ci			goto data_ready;
57462306a36Sopenharmony_ci		udelay(10);
57562306a36Sopenharmony_ci	}
57662306a36Sopenharmony_ci	b43legacydbg(queue->dev->wl, "PIO RX timed out\n");
57762306a36Sopenharmony_ci	return;
57862306a36Sopenharmony_cidata_ready:
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	len = b43legacy_pio_read(queue, B43legacy_PIO_RXDATA);
58162306a36Sopenharmony_ci	if (unlikely(len > 0x700)) {
58262306a36Sopenharmony_ci		pio_rx_error(queue, 0, "len > 0x700");
58362306a36Sopenharmony_ci		return;
58462306a36Sopenharmony_ci	}
58562306a36Sopenharmony_ci	if (unlikely(len == 0 && queue->mmio_base !=
58662306a36Sopenharmony_ci		     B43legacy_MMIO_PIO4_BASE)) {
58762306a36Sopenharmony_ci		pio_rx_error(queue, 0, "len == 0");
58862306a36Sopenharmony_ci		return;
58962306a36Sopenharmony_ci	}
59062306a36Sopenharmony_ci	preamble[0] = cpu_to_le16(len);
59162306a36Sopenharmony_ci	if (queue->mmio_base == B43legacy_MMIO_PIO4_BASE)
59262306a36Sopenharmony_ci		preamble_readwords = 14 / sizeof(u16);
59362306a36Sopenharmony_ci	else
59462306a36Sopenharmony_ci		preamble_readwords = 18 / sizeof(u16);
59562306a36Sopenharmony_ci	for (i = 0; i < preamble_readwords; i++) {
59662306a36Sopenharmony_ci		tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXDATA);
59762306a36Sopenharmony_ci		preamble[i + 1] = cpu_to_le16(tmp);
59862306a36Sopenharmony_ci	}
59962306a36Sopenharmony_ci	rxhdr = (struct b43legacy_rxhdr_fw3 *)preamble;
60062306a36Sopenharmony_ci	macstat = le16_to_cpu(rxhdr->mac_status);
60162306a36Sopenharmony_ci	if (macstat & B43legacy_RX_MAC_FCSERR) {
60262306a36Sopenharmony_ci		pio_rx_error(queue,
60362306a36Sopenharmony_ci			     (queue->mmio_base == B43legacy_MMIO_PIO1_BASE),
60462306a36Sopenharmony_ci			     "Frame FCS error");
60562306a36Sopenharmony_ci		return;
60662306a36Sopenharmony_ci	}
60762306a36Sopenharmony_ci	if (queue->mmio_base == B43legacy_MMIO_PIO4_BASE) {
60862306a36Sopenharmony_ci		/* We received an xmit status. */
60962306a36Sopenharmony_ci		struct b43legacy_hwtxstatus *hw;
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci		hw = (struct b43legacy_hwtxstatus *)(preamble + 1);
61262306a36Sopenharmony_ci		b43legacy_handle_hwtxstatus(queue->dev, hw);
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci		return;
61562306a36Sopenharmony_ci	}
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci	skb = dev_alloc_skb(len);
61862306a36Sopenharmony_ci	if (unlikely(!skb)) {
61962306a36Sopenharmony_ci		pio_rx_error(queue, 1, "OOM");
62062306a36Sopenharmony_ci		return;
62162306a36Sopenharmony_ci	}
62262306a36Sopenharmony_ci	skb_put(skb, len);
62362306a36Sopenharmony_ci	for (i = 0; i < len - 1; i += 2) {
62462306a36Sopenharmony_ci		tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXDATA);
62562306a36Sopenharmony_ci		*((__le16 *)(skb->data + i)) = cpu_to_le16(tmp);
62662306a36Sopenharmony_ci	}
62762306a36Sopenharmony_ci	if (len % 2) {
62862306a36Sopenharmony_ci		tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXDATA);
62962306a36Sopenharmony_ci		skb->data[len - 1] = (tmp & 0x00FF);
63062306a36Sopenharmony_ci	}
63162306a36Sopenharmony_ci	b43legacy_rx(queue->dev, skb, rxhdr);
63262306a36Sopenharmony_ci}
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_civoid b43legacy_pio_tx_suspend(struct b43legacy_pioqueue *queue)
63562306a36Sopenharmony_ci{
63662306a36Sopenharmony_ci	b43legacy_power_saving_ctl_bits(queue->dev, -1, 1);
63762306a36Sopenharmony_ci	b43legacy_pio_write(queue, B43legacy_PIO_TXCTL,
63862306a36Sopenharmony_ci			    b43legacy_pio_read(queue, B43legacy_PIO_TXCTL)
63962306a36Sopenharmony_ci			    | B43legacy_PIO_TXCTL_SUSPEND);
64062306a36Sopenharmony_ci}
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_civoid b43legacy_pio_tx_resume(struct b43legacy_pioqueue *queue)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	b43legacy_pio_write(queue, B43legacy_PIO_TXCTL,
64562306a36Sopenharmony_ci			    b43legacy_pio_read(queue, B43legacy_PIO_TXCTL)
64662306a36Sopenharmony_ci			    & ~B43legacy_PIO_TXCTL_SUSPEND);
64762306a36Sopenharmony_ci	b43legacy_power_saving_ctl_bits(queue->dev, -1, -1);
64862306a36Sopenharmony_ci	tasklet_schedule(&queue->txtask);
64962306a36Sopenharmony_ci}
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_civoid b43legacy_pio_freeze_txqueues(struct b43legacy_wldev *dev)
65262306a36Sopenharmony_ci{
65362306a36Sopenharmony_ci	struct b43legacy_pio *pio;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	B43legacy_WARN_ON(!b43legacy_using_pio(dev));
65662306a36Sopenharmony_ci	pio = &dev->pio;
65762306a36Sopenharmony_ci	pio->queue0->tx_frozen = 1;
65862306a36Sopenharmony_ci	pio->queue1->tx_frozen = 1;
65962306a36Sopenharmony_ci	pio->queue2->tx_frozen = 1;
66062306a36Sopenharmony_ci	pio->queue3->tx_frozen = 1;
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_civoid b43legacy_pio_thaw_txqueues(struct b43legacy_wldev *dev)
66462306a36Sopenharmony_ci{
66562306a36Sopenharmony_ci	struct b43legacy_pio *pio;
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci	B43legacy_WARN_ON(!b43legacy_using_pio(dev));
66862306a36Sopenharmony_ci	pio = &dev->pio;
66962306a36Sopenharmony_ci	pio->queue0->tx_frozen = 0;
67062306a36Sopenharmony_ci	pio->queue1->tx_frozen = 0;
67162306a36Sopenharmony_ci	pio->queue2->tx_frozen = 0;
67262306a36Sopenharmony_ci	pio->queue3->tx_frozen = 0;
67362306a36Sopenharmony_ci	if (!list_empty(&pio->queue0->txqueue))
67462306a36Sopenharmony_ci		tasklet_schedule(&pio->queue0->txtask);
67562306a36Sopenharmony_ci	if (!list_empty(&pio->queue1->txqueue))
67662306a36Sopenharmony_ci		tasklet_schedule(&pio->queue1->txtask);
67762306a36Sopenharmony_ci	if (!list_empty(&pio->queue2->txqueue))
67862306a36Sopenharmony_ci		tasklet_schedule(&pio->queue2->txtask);
67962306a36Sopenharmony_ci	if (!list_empty(&pio->queue3->txqueue))
68062306a36Sopenharmony_ci		tasklet_schedule(&pio->queue3->txtask);
68162306a36Sopenharmony_ci}
682