|
| 1 | +// SPDX-License-Identifier: GPL-2.0+ |
| 2 | +/* |
| 3 | + * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework |
| 4 | + * |
| 5 | + * Author: Parthiban Veerasooran <[email protected]> |
| 6 | + */ |
| 7 | + |
| 8 | +#include <linux/bitfield.h> |
| 9 | +#include <linux/oa_tc6.h> |
| 10 | + |
| 11 | +/* Control command header */ |
| 12 | +#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31) |
| 13 | +#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29) |
| 14 | +#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR GENMASK(27, 24) |
| 15 | +#define OA_TC6_CTRL_HEADER_ADDR GENMASK(23, 8) |
| 16 | +#define OA_TC6_CTRL_HEADER_LENGTH GENMASK(7, 1) |
| 17 | +#define OA_TC6_CTRL_HEADER_PARITY BIT(0) |
| 18 | + |
| 19 | +#define OA_TC6_CTRL_HEADER_SIZE 4 |
| 20 | +#define OA_TC6_CTRL_REG_VALUE_SIZE 4 |
| 21 | +#define OA_TC6_CTRL_IGNORED_SIZE 4 |
| 22 | +#define OA_TC6_CTRL_MAX_REGISTERS 128 |
| 23 | +#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\ |
| 24 | + (OA_TC6_CTRL_MAX_REGISTERS *\ |
| 25 | + OA_TC6_CTRL_REG_VALUE_SIZE) +\ |
| 26 | + OA_TC6_CTRL_IGNORED_SIZE) |
| 27 | + |
| 28 | +/* Internal structure for MAC-PHY drivers */ |
| 29 | +struct oa_tc6 { |
| 30 | + struct spi_device *spi; |
| 31 | + struct mutex spi_ctrl_lock; /* Protects spi control transfer */ |
| 32 | + void *spi_ctrl_tx_buf; |
| 33 | + void *spi_ctrl_rx_buf; |
| 34 | +}; |
| 35 | + |
| 36 | +enum oa_tc6_header_type { |
| 37 | + OA_TC6_CTRL_HEADER, |
| 38 | +}; |
| 39 | + |
| 40 | +enum oa_tc6_register_op { |
| 41 | + OA_TC6_CTRL_REG_WRITE = 1, |
| 42 | +}; |
| 43 | + |
| 44 | +static int oa_tc6_spi_transfer(struct oa_tc6 *tc6, |
| 45 | + enum oa_tc6_header_type header_type, u16 length) |
| 46 | +{ |
| 47 | + struct spi_transfer xfer = { 0 }; |
| 48 | + struct spi_message msg; |
| 49 | + |
| 50 | + xfer.tx_buf = tc6->spi_ctrl_tx_buf; |
| 51 | + xfer.rx_buf = tc6->spi_ctrl_rx_buf; |
| 52 | + xfer.len = length; |
| 53 | + |
| 54 | + spi_message_init(&msg); |
| 55 | + spi_message_add_tail(&xfer, &msg); |
| 56 | + |
| 57 | + return spi_sync(tc6->spi, &msg); |
| 58 | +} |
| 59 | + |
| 60 | +static int oa_tc6_get_parity(u32 p) |
| 61 | +{ |
| 62 | + /* Public domain code snippet, lifted from |
| 63 | + * http://www-graphics.stanford.edu/~seander/bithacks.html |
| 64 | + */ |
| 65 | + p ^= p >> 1; |
| 66 | + p ^= p >> 2; |
| 67 | + p = (p & 0x11111111U) * 0x11111111U; |
| 68 | + |
| 69 | + /* Odd parity is used here */ |
| 70 | + return !((p >> 28) & 1); |
| 71 | +} |
| 72 | + |
| 73 | +static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length, |
| 74 | + enum oa_tc6_register_op reg_op) |
| 75 | +{ |
| 76 | + u32 header; |
| 77 | + |
| 78 | + header = FIELD_PREP(OA_TC6_CTRL_HEADER_DATA_NOT_CTRL, |
| 79 | + OA_TC6_CTRL_HEADER) | |
| 80 | + FIELD_PREP(OA_TC6_CTRL_HEADER_WRITE_NOT_READ, reg_op) | |
| 81 | + FIELD_PREP(OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR, addr >> 16) | |
| 82 | + FIELD_PREP(OA_TC6_CTRL_HEADER_ADDR, addr) | |
| 83 | + FIELD_PREP(OA_TC6_CTRL_HEADER_LENGTH, length - 1); |
| 84 | + header |= FIELD_PREP(OA_TC6_CTRL_HEADER_PARITY, |
| 85 | + oa_tc6_get_parity(header)); |
| 86 | + |
| 87 | + return cpu_to_be32(header); |
| 88 | +} |
| 89 | + |
| 90 | +static void oa_tc6_update_ctrl_write_data(struct oa_tc6 *tc6, u32 value[], |
| 91 | + u8 length) |
| 92 | +{ |
| 93 | + __be32 *tx_buf = tc6->spi_ctrl_tx_buf + OA_TC6_CTRL_HEADER_SIZE; |
| 94 | + |
| 95 | + for (int i = 0; i < length; i++) |
| 96 | + *tx_buf++ = cpu_to_be32(value[i]); |
| 97 | +} |
| 98 | + |
| 99 | +static u16 oa_tc6_calculate_ctrl_buf_size(u8 length) |
| 100 | +{ |
| 101 | + /* Control command consists 4 bytes header + 4 bytes register value for |
| 102 | + * each register + 4 bytes ignored value. |
| 103 | + */ |
| 104 | + return OA_TC6_CTRL_HEADER_SIZE + OA_TC6_CTRL_REG_VALUE_SIZE * length + |
| 105 | + OA_TC6_CTRL_IGNORED_SIZE; |
| 106 | +} |
| 107 | + |
| 108 | +static void oa_tc6_prepare_ctrl_spi_buf(struct oa_tc6 *tc6, u32 address, |
| 109 | + u32 value[], u8 length, |
| 110 | + enum oa_tc6_register_op reg_op) |
| 111 | +{ |
| 112 | + __be32 *tx_buf = tc6->spi_ctrl_tx_buf; |
| 113 | + |
| 114 | + *tx_buf = oa_tc6_prepare_ctrl_header(address, length, reg_op); |
| 115 | + |
| 116 | + oa_tc6_update_ctrl_write_data(tc6, value, length); |
| 117 | +} |
| 118 | + |
| 119 | +static int oa_tc6_check_ctrl_write_reply(struct oa_tc6 *tc6, u8 size) |
| 120 | +{ |
| 121 | + u8 *tx_buf = tc6->spi_ctrl_tx_buf; |
| 122 | + u8 *rx_buf = tc6->spi_ctrl_rx_buf; |
| 123 | + |
| 124 | + rx_buf += OA_TC6_CTRL_IGNORED_SIZE; |
| 125 | + |
| 126 | + /* The echoed control write must match with the one that was |
| 127 | + * transmitted. |
| 128 | + */ |
| 129 | + if (memcmp(tx_buf, rx_buf, size - OA_TC6_CTRL_IGNORED_SIZE)) |
| 130 | + return -EPROTO; |
| 131 | + |
| 132 | + return 0; |
| 133 | +} |
| 134 | + |
| 135 | +static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], |
| 136 | + u8 length, enum oa_tc6_register_op reg_op) |
| 137 | +{ |
| 138 | + u16 size; |
| 139 | + int ret; |
| 140 | + |
| 141 | + /* Prepare control command and copy to SPI control buffer */ |
| 142 | + oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op); |
| 143 | + |
| 144 | + size = oa_tc6_calculate_ctrl_buf_size(length); |
| 145 | + |
| 146 | + /* Perform SPI transfer */ |
| 147 | + ret = oa_tc6_spi_transfer(tc6, OA_TC6_CTRL_HEADER, size); |
| 148 | + if (ret) { |
| 149 | + dev_err(&tc6->spi->dev, "SPI transfer failed for control: %d\n", |
| 150 | + ret); |
| 151 | + return ret; |
| 152 | + } |
| 153 | + |
| 154 | + /* Check echoed/received control write command reply for errors */ |
| 155 | + return oa_tc6_check_ctrl_write_reply(tc6, size); |
| 156 | +} |
| 157 | + |
| 158 | +/** |
| 159 | + * oa_tc6_write_registers - function for writing multiple consecutive registers. |
| 160 | + * @tc6: oa_tc6 struct. |
| 161 | + * @address: address of the first register to be written in the MAC-PHY. |
| 162 | + * @value: values to be written from the starting register address @address. |
| 163 | + * @length: number of consecutive registers to be written from @address. |
| 164 | + * |
| 165 | + * Maximum of 128 consecutive registers can be written starting at @address. |
| 166 | + * |
| 167 | + * Return: 0 on success otherwise failed. |
| 168 | + */ |
| 169 | +int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[], |
| 170 | + u8 length) |
| 171 | +{ |
| 172 | + int ret; |
| 173 | + |
| 174 | + if (!length || length > OA_TC6_CTRL_MAX_REGISTERS) { |
| 175 | + dev_err(&tc6->spi->dev, "Invalid register length parameter\n"); |
| 176 | + return -EINVAL; |
| 177 | + } |
| 178 | + |
| 179 | + mutex_lock(&tc6->spi_ctrl_lock); |
| 180 | + ret = oa_tc6_perform_ctrl(tc6, address, value, length, |
| 181 | + OA_TC6_CTRL_REG_WRITE); |
| 182 | + mutex_unlock(&tc6->spi_ctrl_lock); |
| 183 | + |
| 184 | + return ret; |
| 185 | +} |
| 186 | +EXPORT_SYMBOL_GPL(oa_tc6_write_registers); |
| 187 | + |
| 188 | +/** |
| 189 | + * oa_tc6_write_register - function for writing a MAC-PHY register. |
| 190 | + * @tc6: oa_tc6 struct. |
| 191 | + * @address: register address of the MAC-PHY to be written. |
| 192 | + * @value: value to be written in the @address register address of the MAC-PHY. |
| 193 | + * |
| 194 | + * Return: 0 on success otherwise failed. |
| 195 | + */ |
| 196 | +int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value) |
| 197 | +{ |
| 198 | + return oa_tc6_write_registers(tc6, address, &value, 1); |
| 199 | +} |
| 200 | +EXPORT_SYMBOL_GPL(oa_tc6_write_register); |
| 201 | + |
| 202 | +/** |
| 203 | + * oa_tc6_init - allocates and initializes oa_tc6 structure. |
| 204 | + * @spi: device with which data will be exchanged. |
| 205 | + * |
| 206 | + * Return: pointer reference to the oa_tc6 structure if the MAC-PHY |
| 207 | + * initialization is successful otherwise NULL. |
| 208 | + */ |
| 209 | +struct oa_tc6 *oa_tc6_init(struct spi_device *spi) |
| 210 | +{ |
| 211 | + struct oa_tc6 *tc6; |
| 212 | + |
| 213 | + tc6 = devm_kzalloc(&spi->dev, sizeof(*tc6), GFP_KERNEL); |
| 214 | + if (!tc6) |
| 215 | + return NULL; |
| 216 | + |
| 217 | + tc6->spi = spi; |
| 218 | + mutex_init(&tc6->spi_ctrl_lock); |
| 219 | + |
| 220 | + /* Set the SPI controller to pump at realtime priority */ |
| 221 | + tc6->spi->rt = true; |
| 222 | + spi_setup(tc6->spi); |
| 223 | + |
| 224 | + tc6->spi_ctrl_tx_buf = devm_kzalloc(&tc6->spi->dev, |
| 225 | + OA_TC6_CTRL_SPI_BUF_SIZE, |
| 226 | + GFP_KERNEL); |
| 227 | + if (!tc6->spi_ctrl_tx_buf) |
| 228 | + return NULL; |
| 229 | + |
| 230 | + tc6->spi_ctrl_rx_buf = devm_kzalloc(&tc6->spi->dev, |
| 231 | + OA_TC6_CTRL_SPI_BUF_SIZE, |
| 232 | + GFP_KERNEL); |
| 233 | + if (!tc6->spi_ctrl_rx_buf) |
| 234 | + return NULL; |
| 235 | + |
| 236 | + return tc6; |
| 237 | +} |
| 238 | +EXPORT_SYMBOL_GPL(oa_tc6_init); |
| 239 | + |
| 240 | +MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib"); |
| 241 | +MODULE_AUTHOR( "Parthiban Veerasooran <[email protected]>"); |
| 242 | +MODULE_LICENSE("GPL"); |
0 commit comments