Re: [PATCH 1/5] net: Add davicom wemac ethernet driver found on Allwinner A10 SoC's

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello Maxime, Stefan,

Please find below some comments regarding your PHY implementation in the driver 
as well as the transmit and transmit completion routines.

Le vendredi 15 mars 2013 21:50:00, Maxime Ripard a écrit :
> From: Stefan Roese <sr@xxxxxxx>
> 
> The Allwinner A10 has an ethernet controller that is advertised as
> coming from Davicom.
> 
> The exact feature set of this controller is unknown, since there is no
> public documentation for this IP, and this driver is mostly the one
> published by Allwinner that has been heavily cleaned up.
> 
> Signed-off-by: Stefan Roese <sr@xxxxxxx>
> Signed-off-by: Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>

[snip]

> +
> +wemac: ethernet@01c0b000 {
> +       compatible = "davicom,wemac";
> +       reg = <0x01c0b000 0x1000>;
> +       interrupts = <55>;
> +       allwinner,power-gpios = <&pio 7 19 0>;
> +};

You do not define any handle for the PHY chip, but I think you should make this 
configurable and use the standard Device Tree helpers for finding and connecting 
a PHY driver to your EMAC adapter driver.
[snip]

> +#define WEMAC_PHY		0x100	/* PHY address 0x01 */

This should be made configurable if possible.

[snip]

> + */
> +
> +struct wemac_board_info {
> +	struct clk		*clk;
> +	struct device		*dev;
> +	spinlock_t		lock;
> +	void __iomem		*membase;
> +	struct mii_if_info	mii;
> +	u32			msg_enable;
> +	struct net_device	*ndev;
> +	struct mutex		phy_lock;
> +	struct delayed_work	phy_poll;

Consider using phylib which should allow you to remove struct mii_if_info and 
the mutex and poll delayed workqueue.

> +	unsigned		power_gpio;

you could make this an int, so that you use gpio_is_valid() on this one.

> +	struct sk_buff		*skb_last;
> +	u16			tx_fifo_stat;
> +};
> +
> +static inline struct wemac_board_info *to_wemac_board(struct net_device
> *dev) +{
> +	return netdev_priv(dev);
> +}
> +
> +static int wemac_phy_read(struct net_device *dev, int phyaddr, int reg)
> +{
> +	struct wemac_board_info *db = netdev_priv(dev);
> +	unsigned long flags;
> +	int ret;
> +
> +	mutex_lock(&db->phy_lock);
> +
> +	spin_lock_irqsave(&db->lock, flags);
> +	/* issue the phy address and reg */
> +	writel(phyaddr | reg, db->membase + EMAC_MAC_MADR_REG);
> +	/* pull up the phy io line */
> +	writel(0x1, db->membase + EMAC_MAC_MCMD_REG);
> +	spin_unlock_irqrestore(&db->lock, flags);
> +
> +	/* Wait read complete */
> +	mdelay(1);
> +
> +	/* push down the phy io line and read data */
> +	spin_lock_irqsave(&db->lock, flags);
> +	/* push down the phy io line */
> +	writel(0x0, db->membase + EMAC_MAC_MCMD_REG);
> +	/* and read data */
> +	ret = readl(db->membase + EMAC_MAC_MRDD_REG);
> +	spin_unlock_irqrestore(&db->lock, flags);
> +
> +	mutex_unlock(&db->phy_lock);

This sounds pretty complicated as a locking scheme, using phylib should make 
this simpler anyway.

> +
> +	return ret;
> +}
> +
> +/* Write a word to phyxcer */
> +static void wemac_phy_write(struct net_device *dev,
> +			    int phyaddr, int reg, int value)
> +{
> +	struct wemac_board_info *db = netdev_priv(dev);
> +	unsigned long flags;
> +
> +	mutex_lock(&db->phy_lock);
> +
> +	spin_lock_irqsave(&db->lock, flags);
> +	/* issue the phy address and reg */
> +	writel(phyaddr | reg, db->membase + EMAC_MAC_MADR_REG);
> +	/* pull up the phy io line */
> +	writel(0x1, db->membase + EMAC_MAC_MCMD_REG);
> +	spin_unlock_irqrestore(&db->lock, flags);
> +
> +	/* Wait write complete */
> +	mdelay(1);
> +
> +	spin_lock_irqsave(&db->lock, flags);
> +	/* push down the phy io line */
> +	writel(0x0, db->membase + EMAC_MAC_MCMD_REG);
> +	/* and write data */
> +	writel(value, db->membase + EMAC_MAC_MWTD_REG);
> +	spin_unlock_irqrestore(&db->lock, flags);
> +
> +	mutex_unlock(&db->phy_lock);

Ditto

> +}
> +
> +static int emacrx_completed_flag = 1;

This should be moved to your interface specific private structure, as it won't 
work when there are two instances of the same driver.

[snip]

> +
> +static void wemac_dumpblk_32bit(void __iomem *reg, int count)
> +{
> +	int i;
> +	int tmp;
> +
> +	for (i = 0; i < (round_up(count, 4) / 4); i++)
> +		tmp = readl(reg);
> +}

This could probably be removed, tmp is a write only location, did you remove 
the variable printing that came along this?

> +
> +static void wemac_schedule_poll(struct wemac_board_info *db)
> +{
> +	schedule_delayed_work(&db->phy_poll, HZ * 2);
> +}

phylib takes care of the polling for you.

> +
> +static int wemac_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
> +{
> +	struct wemac_board_info *dm = to_wemac_board(dev);
> +
> +	if (!netif_running(dev))
> +		return -EINVAL;
> +
> +	return generic_mii_ioctl(&dm->mii, if_mii(req), cmd, NULL);
> +}
> +
> +/* ethtool ops */
> +static void wemac_get_drvinfo(struct net_device *dev,
> +			      struct ethtool_drvinfo *info)
> +{
> +	strcpy(info->driver, DRV_NAME);
> +	strcpy(info->version, DRV_VERSION);

You should use strlcpy() here to ensure null-termination, and you should also 
fill in the bus_info member of struct ethtool_drvinfo.

> +}
> +

[snip]

> +unsigned int phy_link_check(struct net_device *dev)
> +{
> +	unsigned int reg_val;
> +
> +	reg_val = wemac_phy_read(dev, WEMAC_PHY, 1);
> +
> +	if (reg_val & 0x4) {
> +		netdev_info(dev, "EMAC PHY Linked...\n");
> +		return 1;
> +	} else {
> +		netdev_info(dev, "EMAC PHY Link waiting......\n");
> +		return 0;
> +	}
> +}

Please remove this and migrate to phylib.

> +
> +unsigned int emac_setup(struct net_device *ndev)
> +{
> +	unsigned int reg_val;
> +	unsigned int phy_val;
> +	unsigned int duplex_flag;
> +	struct wemac_board_info *db = netdev_priv(ndev);
> +
> +	/* set up TX */
> +	reg_val = readl(db->membase + EMAC_TX_MODE_REG);
> +
> +	writel(reg_val | EMAC_TX_MODE_ABORTED_FRAME_EN,
> +		db->membase + EMAC_TX_MODE_REG);
> +
> +	/* set up RX */
> +	reg_val = readl(db->membase + EMAC_RX_CTL_REG);
> +
> +	writel(reg_val | EMAC_RX_CTL_PASS_LEN_OOR_EN |
> +		EMAC_RX_CTL_ACCEPT_UNICAST_EN | EMAC_RX_CTL_DA_FILTER_EN |
> +		EMAC_RX_CTL_ACCEPT_MULTICAST_EN |
> +		EMAC_RX_CTL_ACCEPT_BROADCAST_EN,
> +		db->membase + EMAC_RX_CTL_REG);
> +
> +	/* set MAC */
> +	/* set MAC CTL0 */
> +	reg_val = readl(db->membase + EMAC_MAC_CTL0_REG);
> +	writel(reg_val | EMAC_MAC_CTL0_RX_FLOW_CTL_EN |
> +		EMAC_MAC_CTL0_TX_FLOW_CTL_EN,
> +		db->membase + EMAC_MAC_CTL0_REG);
> +
> +	/* set MAC CTL1 */
> +	reg_val = readl(db->membase + EMAC_MAC_CTL1_REG);
> +	phy_val = wemac_phy_read(ndev, WEMAC_PHY, 0);
> +	dev_dbg(db->dev, "PHY SETUP, reg 0 value: %x\n", phy_val);
> +	duplex_flag = !!(phy_val & EMAC_PHY_DUPLEX);
> +
> +	if (duplex_flag)
> +		reg_val |= EMAC_MAC_CTL1_DUPLEX_EN;
> +
> +	reg_val |= EMAC_MAC_CTL1_LEN_CHECK_EN;
> +	reg_val |= EMAC_MAC_CTL1_CRC_EN;
> +	reg_val |= EMAC_MAC_CTL1_PAD_EN;
> +	writel(reg_val, db->membase + EMAC_MAC_CTL1_REG);
> +
> +	/* set up IPGT */
> +	writel(EMAC_MAC_IPGT_FULL_DUPLEX, db->membase + EMAC_MAC_IPGT_REG);
> +
> +	/* set up IPGR */
> +	writel((EMAC_MAC_IPGR_IPG1 << 8) | EMAC_MAC_IPGR_IPG2,
> +		db->membase + EMAC_MAC_IPGR_REG);
> +
> +	/* set up Collison window */
> +	writel((EMAC_MAC_CLRT_COLLISION_WINDOW << 8) | EMAC_MAC_CLRT_RM,
> +		db->membase + EMAC_MAC_CLRT_REG);
> +
> +	/* set up Max Frame Length */
> +	writel(WEMAC_MAX_FRAME_LEN,
> +		db->membase + EMAC_MAC_MAXF_REG);
> +
> +	return 0;
> +}
> +
> +unsigned int wemac_powerup(struct net_device *ndev)
> +{
> +	struct wemac_board_info *db = netdev_priv(ndev);
> +	unsigned int reg_val;
> +
> +	/* initial EMAC */
> +	/* flush RX FIFO */
> +	reg_val = readl(db->membase + EMAC_RX_CTL_REG);
> +	reg_val |= 0x8;
> +	writel(reg_val, db->membase + EMAC_RX_CTL_REG);
> +	udelay(1);
> +
> +	/* initial MAC */
> +	/* soft reset MAC */
> +	reg_val = readl(db->membase + EMAC_MAC_CTL0_REG);
> +	reg_val &= ~EMAC_MAC_CTL0_SOFT_RESET;
> +	writel(reg_val, db->membase + EMAC_MAC_CTL0_REG);
> +
> +	/* set MII clock */
> +	reg_val = readl(db->membase + EMAC_MAC_MCFG_REG);
> +	reg_val &= (~(0xf << 2));
> +	reg_val |= (0xD << 2);
> +	writel(reg_val, db->membase + EMAC_MAC_MCFG_REG);
> +
> +	/* clear RX counter */
> +	writel(0x0, db->membase + EMAC_RX_FBC_REG);
> +
> +	/* disable all interrupt and clear interrupt status */
> +	writel(0, db->membase + EMAC_INT_CTL_REG);
> +	reg_val = readl(db->membase + EMAC_INT_STA_REG);
> +	writel(reg_val, db->membase + EMAC_INT_STA_REG);
> +
> +	udelay(1);
> +
> +	/* set up EMAC */
> +	emac_setup(ndev);
> +
> +	/* set mac_address to chip */
> +	writel(ndev->dev_addr[0] << 16 | ndev->dev_addr[1] << 8 | ndev->
> +	       dev_addr[2], db->membase + EMAC_MAC_A1_REG);
> +	writel(ndev->dev_addr[3] << 16 | ndev->dev_addr[4] << 8 | ndev->
> +	       dev_addr[5], db->membase + EMAC_MAC_A0_REG);
> +
> +	mdelay(1);
> +
> +	return 1;
> +}
> +
> +static void wemac_poll_work(struct work_struct *w)
> +{
> +	struct delayed_work *dw = container_of(w, struct delayed_work, work);
> +	struct wemac_board_info *db = container_of(dw,
> +						struct wemac_board_info,
> +						phy_poll);
> +	struct net_device *ndev = db->ndev;
> +
> +	mii_check_media(&db->mii, netif_msg_link(db), 0);
> +
> +	if (netif_running(ndev))
> +		wemac_schedule_poll(db);
> +}
> +
> +static int wemac_set_mac_address(struct net_device *dev, void *p)
> +{
> +	struct sockaddr *addr = p;
> +	struct wemac_board_info *db = netdev_priv(dev);
> +
> +	if (netif_running(dev))
> +		return -EBUSY;
> +
> +	memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN);
> +
> +	writel(dev->dev_addr[0] << 16 | dev->dev_addr[1] << 8 | dev->
> +	       dev_addr[2], db->membase + EMAC_MAC_A1_REG);
> +	writel(dev->dev_addr[3] << 16 | dev->dev_addr[4] << 8 | dev->
> +	       dev_addr[5], db->membase + EMAC_MAC_A0_REG);
> +
> +	return 0;
> +}
> +
> +/* Initialize wemac board */
> +static void wemac_init_wemac(struct net_device *dev)
> +{
> +	struct wemac_board_info *db = netdev_priv(dev);
> +	unsigned int phy_reg;
> +	unsigned int reg_val;
> +
> +	if (gpio_is_valid(db->power_gpio))
> +		gpio_set_value(db->power_gpio, 1);
> +
> +	/* PHY POWER UP */
> +	phy_reg = wemac_phy_read(dev, WEMAC_PHY, 0);
> +	wemac_phy_write(dev, WEMAC_PHY, 0, phy_reg & (~(1 << 11)));
> +	mdelay(1);
> +
> +	phy_reg = wemac_phy_read(dev, WEMAC_PHY, 0);
> +
> +	/* set EMAC SPEED, depend on PHY  */
> +	reg_val = readl(db->membase + EMAC_MAC_SUPP_REG);
> +	reg_val &= (~(0x1 << 8));
> +	reg_val |= (((phy_reg & (1 << 13)) >> 13) << 8);
> +	writel(reg_val, db->membase + EMAC_MAC_SUPP_REG);
> +
> +	/* set duplex depend on phy */
> +	reg_val = readl(db->membase + EMAC_MAC_CTL1_REG);
> +	reg_val &= (~(0x1 << 0));
> +	reg_val |= (((phy_reg & (1 << 8)) >> 8) << 0);
> +	writel(reg_val, db->membase + EMAC_MAC_CTL1_REG);
> +
> +	/* enable RX/TX */
> +	reg_val = readl(db->membase + EMAC_CTL_REG);
> +	writel(reg_val | EMAC_CTL_RESET | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN,
> +		db->membase + EMAC_CTL_REG);
> +
> +	/* enable RX/TX0/RX Hlevel interrup */
> +	reg_val = readl(db->membase + EMAC_INT_CTL_REG);
> +	reg_val |= (0xf << 0) | (0x01 << 8);
> +	writel(reg_val, db->membase + EMAC_INT_CTL_REG);
> +
> +	/* Init Driver variable */
> +	db->tx_fifo_stat = 0;
> +	dev->trans_start = 0;

I do not think this is required you are supposed to update this field only in 
the receive path.

> +}
> +
> +/* Our watchdog timed out. Called by the networking layer */
> +static void wemac_timeout(struct net_device *dev)
> +{
> +	struct wemac_board_info *db = netdev_priv(dev);
> +	unsigned long flags;
> +
> +	if (netif_msg_timer(db))
> +		dev_err(db->dev, "tx time out.\n");
> +
> +	/* Save previous register address */
> +	spin_lock_irqsave(&db->lock, flags);
> +
> +	netif_stop_queue(dev);
> +	wemac_reset(db);
> +	wemac_init_wemac(dev);
> +	/* We can accept TX packets again */
> +	dev->trans_start = jiffies;
> +	netif_wake_queue(dev);
> +
> +	/* Restore previous register address */
> +	spin_unlock_irqrestore(&db->lock, flags);
> +}
> +
> +/* Hardware start transmission.
> + * Send a packet to media from the upper layer.
> + */
> +static int wemac_start_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> +	struct wemac_board_info *db = netdev_priv(dev);
> +	unsigned long channel;
> +	unsigned long flags;
> +
> +	channel = db->tx_fifo_stat & 3;
> +	if (channel == 3)
> +		return 1;

start_xmit expects standardized return values such as NETDEV_TX_BUSY and 
NETDEV_TX_COMPLETE, please use them.

> +
> +	channel = (channel == 1 ? 1 : 0);
> +
> +	spin_lock_irqsave(&db->lock, flags);
> +
> +	writel(channel, db->membase + EMAC_TX_INS_REG);
> +
> +	wemac_outblk_32bit(db->membase + EMAC_TX_IO_DATA_REG,
> +			skb->data, skb->len);
> +	dev->stats.tx_bytes += skb->len;
> +
> +	db->tx_fifo_stat |= 1 << channel;
> +	/* TX control: First packet immediately send, second packet queue */
> +	if (channel == 0) {
> +		/* set TX len */
> +		writel(skb->len, db->membase + EMAC_TX_PL0_REG);
> +		/* start translate from fifo to phy */
> +		writel(readl(db->membase + EMAC_TX_CTL0_REG) | 1,
> +		       db->membase + EMAC_TX_CTL0_REG);

Do not you need some write barrier here to ensure your descriptor address and 
control are properly written before the EMAC will see these?

> +
> +		/* save the time stamp */
> +		dev->trans_start = jiffies;
> +	} else if (channel == 1) {
> +		/* set TX len */
> +		writel(skb->len, db->membase + EMAC_TX_PL1_REG);
> +		/* start translate from fifo to phy */
> +		writel(readl(db->membase + EMAC_TX_CTL1_REG) | 1,
> +		       db->membase + EMAC_TX_CTL1_REG);
> +
> +		/* save the time stamp */
> +		dev->trans_start = jiffies;
> +	}
> +
> +	if ((db->tx_fifo_stat & 3) == 3) {
> +		/* Second packet */
> +		netif_stop_queue(dev);

Why is that required? Does that mean that your EMAC can only transmit one 
packet at a time?

> +	}
> +
> +	spin_unlock_irqrestore(&db->lock, flags);
> +
> +	/* free this SKB */
> +	dev_kfree_skb(skb);
> +
> +	return 0;
> +}
> +
> +/* WEMAC interrupt handler
> + * receive the packet to upper layer, free the transmitted packet
> + */
> +static void wemac_tx_done(struct net_device *dev, struct wemac_board_info
> *db, +			  unsigned int tx_status)
> +{
> +	/* One packet sent complete */
> +	db->tx_fifo_stat &= ~(tx_status & 3);
> +	if (3 == (tx_status & 3))
> +		dev->stats.tx_packets += 2;
> +	else
> +		dev->stats.tx_packets++;
> +
> +	if (netif_msg_tx_done(db))
> +		dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);
> +
> +	netif_wake_queue(dev);

Why is that also required? According to your start_xmit function you should do 
this only when you have transmitted 2 packets no?

[snip]

> +
> +		reg_val = readl(db->membase + EMAC_RX_IO_DATA_REG);
> +		if (netif_msg_rx_status(db))
> +			dev_dbg(db->dev, "receive header: %x\n", reg_val);
> +		if (reg_val != 0x0143414d) {

Where is that magic value coming from?
-- 
Florian
--
To unsubscribe from this list: send the line "unsubscribe linux-doc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux FS]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux