summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/microchip/fdma/fdma_api.c
blob: f7a42348e932cd28fb880638153a384f9145c084 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// SPDX-License-Identifier: GPL-2.0+

#include "fdma_api.h"

#include <linux/bits.h>
#include <linux/etherdevice.h>
#include <linux/types.h>

/* Add a DB to a DCB, providing a callback for getting the DB dataptr. */
static int __fdma_db_add(struct fdma *fdma, int dcb_idx, int db_idx, u64 status,
			 int (*cb)(struct fdma *fdma, int dcb_idx,
				   int db_idx, u64 *dataptr))
{
	struct fdma_db *db = fdma_db_get(fdma, dcb_idx, db_idx);
	u64 dataptr;
	int err;

	db->status = cpu_to_le64(status);

	err = cb(fdma, dcb_idx, db_idx, &dataptr);
	if (unlikely(err))
		return err;

	db->dataptr = cpu_to_le64(dataptr);

	return 0;
}

/* Add a DB to a DCB, using the callback set in the fdma_ops struct. */
int fdma_db_add(struct fdma *fdma, int dcb_idx, int db_idx, u64 status)
{
	return __fdma_db_add(fdma,
			     dcb_idx,
			     db_idx,
			     status,
			     fdma->ops.dataptr_cb);
}

/* Add a DCB with callbacks for getting the DB dataptr and the DCB nextptr. */
int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status,
		   int (*dcb_cb)(struct fdma *fdma, int dcb_idx, u64 *nextptr),
		   int (*db_cb)(struct fdma *fdma, int dcb_idx, int db_idx,
				u64 *dataptr))
{
	struct fdma_dcb *dcb = fdma_dcb_get(fdma, dcb_idx);
	u64 nextptr;
	int i, err;

	for (i = 0; i < fdma->n_dbs; i++) {
		err = __fdma_db_add(fdma, dcb_idx, i, status, db_cb);
		if (unlikely(err))
			return err;
	}

	err = dcb_cb(fdma, dcb_idx, &nextptr);
	if (unlikely(err))
		return err;

	fdma->last_dcb->nextptr = cpu_to_le64(nextptr);

	fdma->last_dcb = dcb;

	dcb->nextptr = cpu_to_le64(FDMA_DCB_INVALID_DATA);
	dcb->info = cpu_to_le64(info);

	return 0;
}
EXPORT_SYMBOL_GPL(__fdma_dcb_add);

/* Add a DCB, using the preset callbacks in the fdma_ops struct. */
int fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status)
{
	return __fdma_dcb_add(fdma,
			      dcb_idx,
			      info, status,
			      fdma->ops.nextptr_cb,
			      fdma->ops.dataptr_cb);
}
EXPORT_SYMBOL_GPL(fdma_dcb_add);

/* Initialize the DCB's and DB's. */
int fdma_dcbs_init(struct fdma *fdma, u64 info, u64 status)
{
	int i, err;

	fdma->last_dcb = fdma->dcbs;
	fdma->db_index = 0;
	fdma->dcb_index = 0;

	for (i = 0; i < fdma->n_dcbs; i++) {
		err = fdma_dcb_add(fdma, i, info, status);
		if (err)
			return err;
	}

	return 0;
}
EXPORT_SYMBOL_GPL(fdma_dcbs_init);

/* Allocate coherent DMA memory for FDMA. */
int fdma_alloc_coherent(struct device *dev, struct fdma *fdma)
{
	fdma->dcbs = dma_alloc_coherent(dev,
					fdma->size,
					&fdma->dma,
					GFP_KERNEL);
	if (!fdma->dcbs)
		return -ENOMEM;

	return 0;
}
EXPORT_SYMBOL_GPL(fdma_alloc_coherent);

/* Allocate physical memory for FDMA. */
int fdma_alloc_phys(struct fdma *fdma)
{
	fdma->dcbs = kzalloc(fdma->size, GFP_KERNEL);
	if (!fdma->dcbs)
		return -ENOMEM;

	fdma->dma = virt_to_phys(fdma->dcbs);

	return 0;
}
EXPORT_SYMBOL_GPL(fdma_alloc_phys);

/* Free coherent DMA memory. */
void fdma_free_coherent(struct device *dev, struct fdma *fdma)
{
	dma_free_coherent(dev, fdma->size, fdma->dcbs, fdma->dma);
}
EXPORT_SYMBOL_GPL(fdma_free_coherent);

/* Free virtual memory. */
void fdma_free_phys(struct fdma *fdma)
{
	kfree(fdma->dcbs);
}
EXPORT_SYMBOL_GPL(fdma_free_phys);

#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI)
/* Allocate coherent DMA memory and map it in the ATU. */
int fdma_alloc_coherent_and_map(struct device *dev, struct fdma *fdma,
				struct fdma_pci_atu *atu)
{
	struct fdma_pci_atu_region *region;
	int err;

	if (WARN_ON(fdma->atu_region))
		return -EBUSY;

	/* The ATU cannot express a limit finer than the region granularity, so
	 * the hardware widens the programmed limit to that boundary. Pad the
	 * allocation to match, or the outbound window would extend past the
	 * memory we own.
	 */
	fdma->size = ALIGN(fdma->size, FDMA_PCI_ATU_REGION_ALIGN);

	err = fdma_alloc_coherent(dev, fdma);
	if (err)
		return err;

	region = fdma_pci_atu_region_map(atu, fdma->dma, fdma->size);
	if (IS_ERR(region)) {
		fdma_free_coherent(dev, fdma);
		return PTR_ERR(region);
	}

	fdma->atu_region = region;

	return 0;
}
EXPORT_SYMBOL_GPL(fdma_alloc_coherent_and_map);

/* Free coherent DMA memory and unmap the memory in the ATU. */
void fdma_free_coherent_and_unmap(struct device *dev, struct fdma *fdma)
{
	fdma_pci_atu_region_unmap(fdma->atu_region);
	fdma->atu_region = NULL;
	fdma_free_coherent(dev, fdma);
}
EXPORT_SYMBOL_GPL(fdma_free_coherent_and_unmap);
#endif

/* Get the size of the FDMA memory */
u32 fdma_get_size(struct fdma *fdma)
{
	return ALIGN(sizeof(struct fdma_dcb) * fdma->n_dcbs, PAGE_SIZE);
}
EXPORT_SYMBOL_GPL(fdma_get_size);

/* Get the size of the FDMA memory. This function is only applicable if the
 * dataptr addresses and DCB's are in contiguous memory.
 */
u32 fdma_get_size_contiguous(struct fdma *fdma)
{
	return ALIGN(fdma->n_dcbs * sizeof(struct fdma_dcb) +
		     fdma->n_dcbs * fdma->n_dbs * fdma->db_size,
		     PAGE_SIZE);
}
EXPORT_SYMBOL_GPL(fdma_get_size_contiguous);