brintos

brintos / linux-shallow public Read only

0
0
Text · 10.5 KiB · dd436bd Raw
430 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Microchip's LAN865x 10BASE-T1S MAC-PHY driver4 *5 * Author: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>6 */7 8#include <linux/module.h>9#include <linux/kernel.h>10#include <linux/phy.h>11#include <linux/oa_tc6.h>12 13#define DRV_NAME			"lan8650"14 15/* MAC Network Control Register */16#define LAN865X_REG_MAC_NET_CTL		0x0001000017#define MAC_NET_CTL_TXEN		BIT(3) /* Transmit Enable */18#define MAC_NET_CTL_RXEN		BIT(2) /* Receive Enable */19 20/* MAC Network Configuration Reg */21#define LAN865X_REG_MAC_NET_CFG		0x0001000122#define MAC_NET_CFG_PROMISCUOUS_MODE	BIT(4)23#define MAC_NET_CFG_MULTICAST_MODE	BIT(6)24#define MAC_NET_CFG_UNICAST_MODE	BIT(7)25 26/* MAC Hash Register Bottom */27#define LAN865X_REG_MAC_L_HASH		0x0001002028/* MAC Hash Register Top */29#define LAN865X_REG_MAC_H_HASH		0x0001002130/* MAC Specific Addr 1 Bottom Reg */31#define LAN865X_REG_MAC_L_SADDR1	0x0001002232/* MAC Specific Addr 1 Top Reg */33#define LAN865X_REG_MAC_H_SADDR1	0x0001002334 35struct lan865x_priv {36	struct work_struct multicast_work;37	struct net_device *netdev;38	struct spi_device *spi;39	struct oa_tc6 *tc6;40};41 42static int lan865x_set_hw_macaddr_low_bytes(struct oa_tc6 *tc6, const u8 *mac)43{44	u32 regval;45 46	regval = (mac[3] << 24) | (mac[2] << 16) | (mac[1] << 8) | mac[0];47 48	return oa_tc6_write_register(tc6, LAN865X_REG_MAC_L_SADDR1, regval);49}50 51static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)52{53	int restore_ret;54	u32 regval;55	int ret;56 57	/* Configure MAC address low bytes */58	ret = lan865x_set_hw_macaddr_low_bytes(priv->tc6, mac);59	if (ret)60		return ret;61 62	/* Prepare and configure MAC address high bytes */63	regval = (mac[5] << 8) | mac[4];64	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_SADDR1,65				    regval);66	if (!ret)67		return 0;68 69	/* Restore the old MAC address low bytes from netdev if the new MAC70	 * address high bytes setting failed.71	 */72	restore_ret = lan865x_set_hw_macaddr_low_bytes(priv->tc6,73						       priv->netdev->dev_addr);74	if (restore_ret)75		return restore_ret;76 77	return ret;78}79 80static const struct ethtool_ops lan865x_ethtool_ops = {81	.get_link_ksettings = phy_ethtool_get_link_ksettings,82	.set_link_ksettings = phy_ethtool_set_link_ksettings,83};84 85static int lan865x_set_mac_address(struct net_device *netdev, void *addr)86{87	struct lan865x_priv *priv = netdev_priv(netdev);88	struct sockaddr *address = addr;89	int ret;90 91	ret = eth_prepare_mac_addr_change(netdev, addr);92	if (ret < 0)93		return ret;94 95	if (ether_addr_equal(address->sa_data, netdev->dev_addr))96		return 0;97 98	ret = lan865x_set_hw_macaddr(priv, address->sa_data);99	if (ret)100		return ret;101 102	eth_commit_mac_addr_change(netdev, addr);103 104	return 0;105}106 107static u32 get_address_bit(u8 addr[ETH_ALEN], u32 bit)108{109	return ((addr[bit / 8]) >> (bit % 8)) & 1;110}111 112static u32 lan865x_hash(u8 addr[ETH_ALEN])113{114	u32 hash_index = 0;115 116	for (int i = 0; i < 6; i++) {117		u32 hash = 0;118 119		for (int j = 0; j < 8; j++)120			hash ^= get_address_bit(addr, (j * 6) + i);121 122		hash_index |= (hash << i);123	}124 125	return hash_index;126}127 128static int lan865x_set_specific_multicast_addr(struct lan865x_priv *priv)129{130	struct netdev_hw_addr *ha;131	u32 hash_lo = 0;132	u32 hash_hi = 0;133	int ret;134 135	netdev_for_each_mc_addr(ha, priv->netdev) {136		u32 bit_num = lan865x_hash(ha->addr);137 138		if (bit_num >= BIT(5))139			hash_hi |= (1 << (bit_num - BIT(5)));140		else141			hash_lo |= (1 << bit_num);142	}143 144	/* Enabling specific multicast addresses */145	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH, hash_hi);146	if (ret) {147		netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",148			   ret);149		return ret;150	}151 152	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH, hash_lo);153	if (ret)154		netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",155			   ret);156 157	return ret;158}159 160static int lan865x_set_all_multicast_addr(struct lan865x_priv *priv)161{162	int ret;163 164	/* Enabling all multicast addresses */165	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH,166				    0xffffffff);167	if (ret) {168		netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",169			   ret);170		return ret;171	}172 173	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH,174				    0xffffffff);175	if (ret)176		netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",177			   ret);178 179	return ret;180}181 182static int lan865x_clear_all_multicast_addr(struct lan865x_priv *priv)183{184	int ret;185 186	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH, 0);187	if (ret) {188		netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",189			   ret);190		return ret;191	}192 193	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH, 0);194	if (ret)195		netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",196			   ret);197 198	return ret;199}200 201static void lan865x_multicast_work_handler(struct work_struct *work)202{203	struct lan865x_priv *priv = container_of(work, struct lan865x_priv,204						 multicast_work);205	u32 regval = 0;206	int ret;207 208	if (priv->netdev->flags & IFF_PROMISC) {209		/* Enabling promiscuous mode */210		regval |= MAC_NET_CFG_PROMISCUOUS_MODE;211		regval &= (~MAC_NET_CFG_MULTICAST_MODE);212		regval &= (~MAC_NET_CFG_UNICAST_MODE);213	} else if (priv->netdev->flags & IFF_ALLMULTI) {214		/* Enabling all multicast mode */215		if (lan865x_set_all_multicast_addr(priv))216			return;217 218		regval &= (~MAC_NET_CFG_PROMISCUOUS_MODE);219		regval |= MAC_NET_CFG_MULTICAST_MODE;220		regval &= (~MAC_NET_CFG_UNICAST_MODE);221	} else if (!netdev_mc_empty(priv->netdev)) {222		/* Enabling specific multicast mode */223		if (lan865x_set_specific_multicast_addr(priv))224			return;225 226		regval &= (~MAC_NET_CFG_PROMISCUOUS_MODE);227		regval |= MAC_NET_CFG_MULTICAST_MODE;228		regval &= (~MAC_NET_CFG_UNICAST_MODE);229	} else {230		/* Enabling local mac address only */231		if (lan865x_clear_all_multicast_addr(priv))232			return;233	}234	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CFG, regval);235	if (ret)236		netdev_err(priv->netdev, "Failed to enable promiscuous/multicast/normal mode: %d\n",237			   ret);238}239 240static void lan865x_set_multicast_list(struct net_device *netdev)241{242	struct lan865x_priv *priv = netdev_priv(netdev);243 244	schedule_work(&priv->multicast_work);245}246 247static netdev_tx_t lan865x_send_packet(struct sk_buff *skb,248				       struct net_device *netdev)249{250	struct lan865x_priv *priv = netdev_priv(netdev);251 252	return oa_tc6_start_xmit(priv->tc6, skb);253}254 255static int lan865x_hw_disable(struct lan865x_priv *priv)256{257	u32 regval;258 259	if (oa_tc6_read_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, &regval))260		return -ENODEV;261 262	regval &= ~(MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN);263 264	if (oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, regval))265		return -ENODEV;266 267	return 0;268}269 270static int lan865x_net_close(struct net_device *netdev)271{272	struct lan865x_priv *priv = netdev_priv(netdev);273	int ret;274 275	netif_stop_queue(netdev);276	phy_stop(netdev->phydev);277	ret = lan865x_hw_disable(priv);278	if (ret) {279		netdev_err(netdev, "Failed to disable the hardware: %d\n", ret);280		return ret;281	}282 283	return 0;284}285 286static int lan865x_hw_enable(struct lan865x_priv *priv)287{288	u32 regval;289 290	if (oa_tc6_read_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, &regval))291		return -ENODEV;292 293	regval |= MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN;294 295	if (oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, regval))296		return -ENODEV;297 298	return 0;299}300 301static int lan865x_net_open(struct net_device *netdev)302{303	struct lan865x_priv *priv = netdev_priv(netdev);304	int ret;305 306	ret = lan865x_hw_enable(priv);307	if (ret) {308		netdev_err(netdev, "Failed to enable hardware: %d\n", ret);309		return ret;310	}311 312	phy_start(netdev->phydev);313 314	return 0;315}316 317static const struct net_device_ops lan865x_netdev_ops = {318	.ndo_open		= lan865x_net_open,319	.ndo_stop		= lan865x_net_close,320	.ndo_start_xmit		= lan865x_send_packet,321	.ndo_set_rx_mode	= lan865x_set_multicast_list,322	.ndo_set_mac_address	= lan865x_set_mac_address,323};324 325static int lan865x_probe(struct spi_device *spi)326{327	struct net_device *netdev;328	struct lan865x_priv *priv;329	int ret;330 331	netdev = alloc_etherdev(sizeof(struct lan865x_priv));332	if (!netdev)333		return -ENOMEM;334 335	priv = netdev_priv(netdev);336	priv->netdev = netdev;337	priv->spi = spi;338	spi_set_drvdata(spi, priv);339	INIT_WORK(&priv->multicast_work, lan865x_multicast_work_handler);340 341	priv->tc6 = oa_tc6_init(spi, netdev);342	if (!priv->tc6) {343		ret = -ENODEV;344		goto free_netdev;345	}346 347	/* As per the point s3 in the below errata, SPI receive Ethernet frame348	 * transfer may halt when starting the next frame in the same data block349	 * (chunk) as the end of a previous frame. The RFA field should be350	 * configured to 01b or 10b for proper operation. In these modes, only351	 * one receive Ethernet frame will be placed in a single data block.352	 * When the RFA field is written to 01b, received frames will be forced353	 * to only start in the first word of the data block payload (SWO=0). As354	 * recommended, enable zero align receive frame feature for proper355	 * operation.356	 *357	 * https://ww1.microchip.com/downloads/aemDocuments/documents/AIS/ProductDocuments/Errata/LAN8650-1-Errata-80001075.pdf358	 */359	ret = oa_tc6_zero_align_receive_frame_enable(priv->tc6);360	if (ret) {361		dev_err(&spi->dev, "Failed to set ZARFE: %d\n", ret);362		goto oa_tc6_exit;363	}364 365	/* Get the MAC address from the SPI device tree node */366	if (device_get_ethdev_address(&spi->dev, netdev))367		eth_hw_addr_random(netdev);368 369	ret = lan865x_set_hw_macaddr(priv, netdev->dev_addr);370	if (ret) {371		dev_err(&spi->dev, "Failed to configure MAC: %d\n", ret);372		goto oa_tc6_exit;373	}374 375	netdev->if_port = IF_PORT_10BASET;376	netdev->irq = spi->irq;377	netdev->netdev_ops = &lan865x_netdev_ops;378	netdev->ethtool_ops = &lan865x_ethtool_ops;379 380	ret = register_netdev(netdev);381	if (ret) {382		dev_err(&spi->dev, "Register netdev failed (ret = %d)", ret);383		goto oa_tc6_exit;384	}385 386	return 0;387 388oa_tc6_exit:389	oa_tc6_exit(priv->tc6);390free_netdev:391	free_netdev(priv->netdev);392	return ret;393}394 395static void lan865x_remove(struct spi_device *spi)396{397	struct lan865x_priv *priv = spi_get_drvdata(spi);398 399	cancel_work_sync(&priv->multicast_work);400	unregister_netdev(priv->netdev);401	oa_tc6_exit(priv->tc6);402	free_netdev(priv->netdev);403}404 405static const struct spi_device_id spidev_spi_ids[] = {406	{ .name = "lan8650" },407	{},408};409 410static const struct of_device_id lan865x_dt_ids[] = {411	{ .compatible = "microchip,lan8650" },412	{ /* Sentinel */ }413};414MODULE_DEVICE_TABLE(of, lan865x_dt_ids);415 416static struct spi_driver lan865x_driver = {417	.driver = {418		.name = DRV_NAME,419		.of_match_table = lan865x_dt_ids,420	 },421	.probe = lan865x_probe,422	.remove = lan865x_remove,423	.id_table = spidev_spi_ids,424};425module_spi_driver(lan865x_driver);426 427MODULE_DESCRIPTION(DRV_NAME " 10Base-T1S MACPHY Ethernet Driver");428MODULE_AUTHOR("Parthiban Veerasooran <parthiban.veerasooran@microchip.com>");429MODULE_LICENSE("GPL");430