From 266141c2ba061fb44ad5e419dfcb277fde32d6c0 Mon Sep 17 00:00:00 2001 From: Yegor Yefremov Date: Thu, 14 Sep 2017 10:25:00 +0200 Subject: net: ath79: add ag71xx Ethernet driver Signed-off-by: Yegor Yefremov Signed-off-by: Antony Pavlov Signed-off-by: Oleksij Rempel Signed-off-by: Sascha Hauer --- drivers/net/Kconfig | 7 + drivers/net/Makefile | 1 + drivers/net/ag71xx.c | 673 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 681 insertions(+) create mode 100644 drivers/net/ag71xx.c (limited to 'drivers') diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index c3980e78f5..9d69b6aeb0 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -204,6 +204,13 @@ config DRIVER_NET_EFI_SNP bool "EFI SNP ethernet driver" depends on EFI_BOOTUP +config DRIVER_NET_AG71XX + bool "Atheros AG71xx ethernet driver" + depends on MACH_MIPS_ATH79 + select PHYLIB + help + This option enables support for Atheros AG71XX ethernet chip. + config DRIVER_NET_TSE depends on NIOS2 bool "Altera TSE ethernet driver" diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 42ea208e3b..86c8ac32f9 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -30,3 +30,4 @@ obj-$(CONFIG_DRIVER_NET_SMC91111) += smc91111.o obj-$(CONFIG_DRIVER_NET_TAP) += tap.o obj-$(CONFIG_DRIVER_NET_TSE) += altera_tse.o obj-$(CONFIG_DRIVER_NET_EFI_SNP) += efi-snp.o +obj-$(CONFIG_DRIVER_NET_AG71XX) += ag71xx.o diff --git a/drivers/net/ag71xx.c b/drivers/net/ag71xx.c new file mode 100644 index 0000000000..54f0bd9aa5 --- /dev/null +++ b/drivers/net/ag71xx.c @@ -0,0 +1,673 @@ +/* + * Atheros AR71xx built-in ethernet mac driver + * + * Copyright (C) 2008-2010 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * Based on Atheros' AG7100 driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Register offsets */ +#define AG71XX_REG_MAC_CFG1 0x0000 +#define AG71XX_REG_MAC_CFG2 0x0004 +#define AG71XX_REG_MAC_IPG 0x0008 +#define AG71XX_REG_MAC_HDX 0x000c +#define AG71XX_REG_MAC_MFL 0x0010 +#define AG71XX_REG_MII_CFG 0x0020 +#define AG71XX_REG_MII_CMD 0x0024 +#define AG71XX_REG_MII_ADDR 0x0028 +#define AG71XX_REG_MII_CTRL 0x002c +#define AG71XX_REG_MII_STATUS 0x0030 +#define AG71XX_REG_MII_IND 0x0034 +#define AG71XX_REG_MAC_IFCTL 0x0038 +#define AG71XX_REG_MAC_ADDR1 0x0040 +#define AG71XX_REG_MAC_ADDR2 0x0044 +#define AG71XX_REG_FIFO_CFG0 0x0048 +#define AG71XX_REG_FIFO_CFG1 0x004c +#define AG71XX_REG_FIFO_CFG2 0x0050 +#define AG71XX_REG_FIFO_CFG3 0x0054 +#define AG71XX_REG_FIFO_CFG4 0x0058 +#define AG71XX_REG_FIFO_CFG5 0x005c +#define AG71XX_REG_FIFO_RAM0 0x0060 +#define AG71XX_REG_FIFO_RAM1 0x0064 +#define AG71XX_REG_FIFO_RAM2 0x0068 +#define AG71XX_REG_FIFO_RAM3 0x006c +#define AG71XX_REG_FIFO_RAM4 0x0070 +#define AG71XX_REG_FIFO_RAM5 0x0074 +#define AG71XX_REG_FIFO_RAM6 0x0078 +#define AG71XX_REG_FIFO_RAM7 0x007c + +#define AG71XX_REG_TX_CTRL 0x0180 +#define AG71XX_REG_TX_DESC 0x0184 +#define AG71XX_REG_TX_STATUS 0x0188 +#define AG71XX_REG_RX_CTRL 0x018c +#define AG71XX_REG_RX_DESC 0x0190 +#define AG71XX_REG_RX_STATUS 0x0194 +#define AG71XX_REG_INT_ENABLE 0x0198 +#define AG71XX_REG_INT_STATUS 0x019c + +#define AG71XX_REG_FIFO_DEPTH 0x01a8 +#define AG71XX_REG_RX_SM 0x01b0 +#define AG71XX_REG_TX_SM 0x01b4 + +#define MAC_CFG1_TXE BIT(0) /* Tx Enable */ +#define MAC_CFG1_STX BIT(1) /* Synchronize Tx Enable */ +#define MAC_CFG1_RXE BIT(2) /* Rx Enable */ +#define MAC_CFG1_SRX BIT(3) /* Synchronize Rx Enable */ +#define MAC_CFG1_TFC BIT(4) /* Tx Flow Control Enable */ +#define MAC_CFG1_RFC BIT(5) /* Rx Flow Control Enable */ +#define MAC_CFG1_LB BIT(8) /* Loopback mode */ +#define MAC_CFG1_TX_RST BIT(18) /* Tx Reset */ +#define MAC_CFG1_RX_RST BIT(19) /* Rx Reset */ +#define MAC_CFG1_SR BIT(31) /* Soft Reset */ + +#define MAC_CFG2_FDX BIT(0) +#define MAC_CFG2_CRC_EN BIT(1) +#define MAC_CFG2_PAD_CRC_EN BIT(2) +#define MAC_CFG2_LEN_CHECK BIT(4) +#define MAC_CFG2_HUGE_FRAME_EN BIT(5) +#define MAC_CFG2_IF_1000 BIT(9) +#define MAC_CFG2_IF_10_100 BIT(8) + +#define FIFO_CFG0_WTM BIT(0) /* Watermark Module */ +#define FIFO_CFG0_RXS BIT(1) /* Rx System Module */ +#define FIFO_CFG0_RXF BIT(2) /* Rx Fabric Module */ +#define FIFO_CFG0_TXS BIT(3) /* Tx System Module */ +#define FIFO_CFG0_TXF BIT(4) /* Tx Fabric Module */ +#define FIFO_CFG0_ALL (FIFO_CFG0_WTM | FIFO_CFG0_RXS | FIFO_CFG0_RXF \ + | FIFO_CFG0_TXS | FIFO_CFG0_TXF) + +#define FIFO_CFG0_ENABLE_SHIFT 8 + +#define FIFO_CFG4_DE BIT(0) /* Drop Event */ +#define FIFO_CFG4_DV BIT(1) /* RX_DV Event */ +#define FIFO_CFG4_FC BIT(2) /* False Carrier */ +#define FIFO_CFG4_CE BIT(3) /* Code Error */ +#define FIFO_CFG4_CR BIT(4) /* CRC error */ +#define FIFO_CFG4_LM BIT(5) /* Length Mismatch */ +#define FIFO_CFG4_LO BIT(6) /* Length out of range */ +#define FIFO_CFG4_OK BIT(7) /* Packet is OK */ +#define FIFO_CFG4_MC BIT(8) /* Multicast Packet */ +#define FIFO_CFG4_BC BIT(9) /* Broadcast Packet */ +#define FIFO_CFG4_DR BIT(10) /* Dribble */ +#define FIFO_CFG4_LE BIT(11) /* Long Event */ +#define FIFO_CFG4_CF BIT(12) /* Control Frame */ +#define FIFO_CFG4_PF BIT(13) /* Pause Frame */ +#define FIFO_CFG4_UO BIT(14) /* Unsupported Opcode */ +#define FIFO_CFG4_VT BIT(15) /* VLAN tag detected */ +#define FIFO_CFG4_FT BIT(16) /* Frame Truncated */ +#define FIFO_CFG4_UC BIT(17) /* Unicast Packet */ + +#define FIFO_CFG5_DE BIT(0) /* Drop Event */ +#define FIFO_CFG5_DV BIT(1) /* RX_DV Event */ +#define FIFO_CFG5_FC BIT(2) /* False Carrier */ +#define FIFO_CFG5_CE BIT(3) /* Code Error */ +#define FIFO_CFG5_LM BIT(4) /* Length Mismatch */ +#define FIFO_CFG5_LO BIT(5) /* Length Out of Range */ +#define FIFO_CFG5_OK BIT(6) /* Packet is OK */ +#define FIFO_CFG5_MC BIT(7) /* Multicast Packet */ +#define FIFO_CFG5_BC BIT(8) /* Broadcast Packet */ +#define FIFO_CFG5_DR BIT(9) /* Dribble */ +#define FIFO_CFG5_CF BIT(10) /* Control Frame */ +#define FIFO_CFG5_PF BIT(11) /* Pause Frame */ +#define FIFO_CFG5_UO BIT(12) /* Unsupported Opcode */ +#define FIFO_CFG5_VT BIT(13) /* VLAN tag detected */ +#define FIFO_CFG5_LE BIT(14) /* Long Event */ +#define FIFO_CFG5_FT BIT(15) /* Frame Truncated */ +#define FIFO_CFG5_16 BIT(16) /* unknown */ +#define FIFO_CFG5_17 BIT(17) /* unknown */ +#define FIFO_CFG5_SF BIT(18) /* Short Frame */ +#define FIFO_CFG5_BM BIT(19) /* Byte Mode */ + +#define AG71XX_INT_TX_PS BIT(0) +#define AG71XX_INT_TX_UR BIT(1) +#define AG71XX_INT_TX_BE BIT(3) +#define AG71XX_INT_RX_PR BIT(4) +#define AG71XX_INT_RX_OF BIT(6) +#define AG71XX_INT_RX_BE BIT(7) + +#define MAC_IFCTL_SPEED BIT(16) + +#define MII_CFG_CLK_DIV_4 0 +#define MII_CFG_CLK_DIV_6 2 +#define MII_CFG_CLK_DIV_8 3 +#define MII_CFG_CLK_DIV_10 4 +#define MII_CFG_CLK_DIV_14 5 +#define MII_CFG_CLK_DIV_20 6 +#define MII_CFG_CLK_DIV_28 7 +#define MII_CFG_CLK_DIV_34 8 +#define MII_CFG_CLK_DIV_42 9 +#define MII_CFG_CLK_DIV_50 10 +#define MII_CFG_CLK_DIV_58 11 +#define MII_CFG_CLK_DIV_66 12 +#define MII_CFG_CLK_DIV_74 13 +#define MII_CFG_CLK_DIV_82 14 +#define MII_CFG_CLK_DIV_98 15 +#define MII_CFG_RESET BIT(31) + +#define MII_CMD_WRITE 0x0 +#define MII_CMD_READ 0x1 +#define MII_ADDR_SHIFT 8 +#define MII_IND_BUSY BIT(0) +#define MII_IND_INVALID BIT(2) + +#define TX_CTRL_TXE BIT(0) /* Tx Enable */ + +#define TX_STATUS_PS BIT(0) /* Packet Sent */ +#define TX_STATUS_UR BIT(1) /* Tx Underrun */ +#define TX_STATUS_BE BIT(3) /* Bus Error */ + +#define RX_CTRL_RXE BIT(0) /* Rx Enable */ + +#define RX_STATUS_PR BIT(0) /* Packet Received */ +#define RX_STATUS_OF BIT(2) /* Rx Overflow */ +#define RX_STATUS_BE BIT(3) /* Bus Error */ + +/* + * GMAC register macros + */ +#define AG71XX_ETH_CFG_RGMII_GE0 (1<<0) +#define AG71XX_ETH_CFG_MII_GE0_SLAVE (1<<4) + +enum ag71xx_type { + AG71XX_TYPE_AR9331_GE0, + AG71XX_TYPE_AR9344_GMAC0, +}; + +/* + * h/w descriptor + */ +typedef struct { + uint32_t pkt_start_addr; + + uint32_t is_empty : 1; + uint32_t res1 : 10; + uint32_t ftpp_override : 5; + uint32_t res2 : 4; + uint32_t pkt_size : 12; + + uint32_t next_desc ; +} ag7240_desc_t; + +#define NO_OF_TX_FIFOS 8 +#define NO_OF_RX_FIFOS 8 +#define TX_RING_SZ (NO_OF_TX_FIFOS * sizeof(ag7240_desc_t)) +#define MAX_RBUFF_SZ 0x600 /* 1518 rounded up */ + +#define MAX_WAIT 1000 + +struct ag71xx { + struct device_d *dev; + struct eth_device netdev; + void __iomem *regs; + void __iomem *regs_gmac; + struct mii_bus miibus; + const struct ag71xx_cfg *cfg; + + void *rx_buffer; + + unsigned char *rx_pkt[NO_OF_RX_FIFOS]; + ag7240_desc_t *fifo_tx; + ag7240_desc_t *fifo_rx; + dma_addr_t addr_tx; + dma_addr_t addr_rx; + + int next_tx; + int next_rx; +}; + +struct ag71xx_cfg { + enum ag71xx_type type; + void (*init_mii)(struct ag71xx *priv); +}; + +static inline void ag71xx_check_reg_offset(struct ag71xx *priv, int reg) +{ + switch (reg) { + case AG71XX_REG_MAC_CFG1 ... AG71XX_REG_MAC_MFL: + case AG71XX_REG_MAC_IFCTL ... AG71XX_REG_TX_SM: + case AG71XX_REG_MII_CFG ... AG71XX_REG_MII_IND: + break; + + default: + BUG(); + } +} + +static inline u32 ar7240_reg_rd(u32 reg) +{ + return __raw_readl(KSEG1ADDR(reg)); +} + +static inline void ar7240_reg_wr(u32 reg, u32 val) +{ + __raw_writel(val, KSEG1ADDR(reg)); +} + +static inline u32 ag71xx_gmac_rr(struct ag71xx *dev, int reg) +{ + return __raw_readl(dev->regs_gmac + reg); +} + +static inline void ag71xx_gmac_wr(struct ag71xx *dev, int reg, u32 val) +{ + __raw_writel(val, dev->regs_gmac + reg); +} + +static inline u32 ag71xx_rr(struct ag71xx *priv, int reg) +{ + ag71xx_check_reg_offset(priv, reg); + + return __raw_readl(priv->regs + reg); +} + +static inline void ag71xx_wr(struct ag71xx *priv, int reg, u32 val) +{ + ag71xx_check_reg_offset(priv, reg); + + __raw_writel(val, priv->regs + reg); + /* flush write */ + (void)__raw_readl(priv->regs + reg); +} + + +static int ag71xx_mii_wait(struct ag71xx *priv, int write) +{ + struct device_d *dev = priv->dev; + uint64_t start; + + start = get_time_ns(); + while (ag71xx_rr(priv, AG71XX_REG_MII_IND) & MII_IND_BUSY) { + if (!is_timeout_non_interruptible(start, 100 * USECOND)) + continue; + + dev_err(dev, "mii %s error: bus is still busy!\n", + write ? "write" : "read"); + return -ETIMEDOUT; + } + + return 0; +} + +static int ag71xx_ether_mii_read(struct mii_bus *miidev, int phy_addr, int reg) +{ + struct ag71xx *priv = miidev->priv; + const struct ag71xx_cfg *cfg = priv->cfg; + u16 addr = (phy_addr << MII_ADDR_SHIFT) | reg, val; + int ret; + + if (AG71XX_TYPE_AR9331_GE0 == cfg->type) + return 0xffff; + + ret = ag71xx_mii_wait(priv, 0); + if (ret) + return ret; + + ag71xx_wr(priv, AG71XX_REG_MII_CMD, MII_CMD_WRITE); + ag71xx_wr(priv, AG71XX_REG_MII_ADDR, addr); + ag71xx_wr(priv, AG71XX_REG_MII_CMD, MII_CMD_READ); + + ret = ag71xx_mii_wait(priv, 0); + if (ret) + return ret; + + val = ag71xx_rr(priv, AG71XX_REG_MII_STATUS); + ag71xx_wr(priv, AG71XX_REG_MII_CMD, MII_CMD_WRITE); + + return val; +} + +static int ag71xx_ether_mii_write(struct mii_bus *miidev, int phy_addr, + int reg, u16 val) +{ + struct ag71xx *priv = miidev->priv; + const struct ag71xx_cfg *cfg = priv->cfg; + u16 addr = (phy_addr << MII_ADDR_SHIFT) | reg; + int ret; + + if (AG71XX_TYPE_AR9331_GE0 == cfg->type) + return 0; + + ret = ag71xx_mii_wait(priv, 1); + if (ret) + return ret; + + ag71xx_wr(priv, AG71XX_REG_MII_ADDR, addr); + ag71xx_wr(priv, AG71XX_REG_MII_CTRL, val); + + ret = ag71xx_mii_wait(priv, 1); + if (ret) + return ret; + + return 0; +} + +static int ag71xx_ether_set_ethaddr(struct eth_device *edev, + const unsigned char *adr) +{ + return 0; +} + +static int ag71xx_ether_get_ethaddr(struct eth_device *edev, unsigned char *adr) +{ + /* We have no eeprom */ + return -ENODEV; +} + +static void ag71xx_ether_halt(struct eth_device *edev) +{ + struct ag71xx *priv = edev->priv; + struct device_d *dev = priv->dev; + uint64_t start; + + ag71xx_wr(priv, AG71XX_REG_RX_CTRL, 0); + start = get_time_ns(); + while (ag71xx_rr(priv, AG71XX_REG_RX_CTRL)) { + if (is_timeout_non_interruptible(start, 100 * USECOND)) { + dev_err(dev, "error: failed to stop device!\n"); + break; + } + } +} + +static int ag71xx_ether_rx(struct eth_device *edev) +{ + struct ag71xx *priv = edev->priv; + ag7240_desc_t *f; + unsigned int work_done; + + for (work_done = 0; work_done < NO_OF_RX_FIFOS; work_done++) { + unsigned int pktlen; + unsigned char *rx_pkt; + + f = &priv->fifo_rx[priv->next_rx]; + + if (f->is_empty) + break; + + pktlen = f->pkt_size; + rx_pkt = priv->rx_pkt[priv->next_rx]; + + /* invalidate */ + dma_sync_single_for_cpu((unsigned long)rx_pkt, pktlen, + DMA_FROM_DEVICE); + + net_receive(edev, rx_pkt, pktlen - 4); + + f->is_empty = 1; + + priv->next_rx = (priv->next_rx + 1) % NO_OF_RX_FIFOS; + } + + if (!(ag71xx_rr(priv, AG71XX_REG_RX_CTRL) & RX_CTRL_RXE)) { + f = &priv->fifo_rx[priv->next_rx]; + ag71xx_wr(priv, AG71XX_REG_RX_DESC, virt_to_phys(f)); + ag71xx_wr(priv, AG71XX_REG_RX_CTRL, RX_CTRL_RXE); + } + + return work_done; +} + +static int ag71xx_ether_send(struct eth_device *edev, void *packet, int length) +{ + struct ag71xx *priv = edev->priv; + struct device_d *dev = priv->dev; + ag7240_desc_t *f = &priv->fifo_tx[priv->next_tx]; + uint64_t start; + int ret = 0; + + /* flush */ + dma_sync_single_for_device((unsigned long)packet, length, DMA_TO_DEVICE); + + f->pkt_start_addr = virt_to_phys(packet); + f->res1 = 0; + f->pkt_size = length; + f->is_empty = 0; + ag71xx_wr(priv, AG71XX_REG_TX_DESC, virt_to_phys(f)); + ag71xx_wr(priv, AG71XX_REG_TX_CTRL, TX_CTRL_TXE); + + /* flush again?! */ + dma_sync_single_for_cpu((unsigned long)packet, length, DMA_TO_DEVICE); + + start = get_time_ns(); + while (!f->is_empty) { + if (!is_timeout_non_interruptible(start, 100 * USECOND)) + continue; + + dev_err(dev, "error: tx timed out\n"); + ret = -ETIMEDOUT; + break; + } + + f->pkt_start_addr = 0; + f->pkt_size = 0; + + priv->next_tx = (priv->next_tx + 1) % NO_OF_TX_FIFOS; + + return ret; +} + +static int ag71xx_ether_open(struct eth_device *edev) +{ + struct ag71xx *priv = edev->priv; + const struct ag71xx_cfg *cfg = priv->cfg; + + if (AG71XX_TYPE_AR9344_GMAC0 == cfg->type) + return phy_device_connect(edev, &priv->miibus, 0, + NULL, 0, PHY_INTERFACE_MODE_RGMII_TXID); + + return 0; +} + +static int ag71xx_ether_init(struct eth_device *edev) +{ + struct ag71xx *priv = edev->priv; + int i; + void *rxbuf = priv->rx_buffer; + + priv->next_rx = 0; + + for (i = 0; i < NO_OF_RX_FIFOS; i++) { + ag7240_desc_t *fr = &priv->fifo_rx[i]; + + priv->rx_pkt[i] = rxbuf; + fr->pkt_start_addr = virt_to_phys(rxbuf); + fr->pkt_size = MAX_RBUFF_SZ; + fr->is_empty = 1; + fr->next_desc = virt_to_phys(&priv->fifo_rx[(i + 1) % NO_OF_RX_FIFOS]); + + /* invalidate */ + dma_sync_single_for_device((unsigned long)rxbuf, MAX_RBUFF_SZ, + DMA_FROM_DEVICE); + + rxbuf += MAX_RBUFF_SZ; + } + + /* Clean Tx BD's */ + memset(priv->fifo_tx, 0, TX_RING_SZ); + + ag71xx_wr(priv, AG71XX_REG_RX_DESC, virt_to_phys(priv->fifo_rx)); + ag71xx_wr(priv, AG71XX_REG_RX_CTRL, RX_CTRL_RXE); + + return 0; +} + +static void ag71xx_ar9331_ge0_mii_init(struct ag71xx *priv) +{ + u32 rd; + + rd = ag71xx_rr(priv, AG71XX_REG_MAC_CFG2); + rd |= (MAC_CFG2_PAD_CRC_EN | MAC_CFG2_LEN_CHECK | MAC_CFG2_IF_10_100); + ag71xx_wr(priv, AG71XX_REG_MAC_CFG2, rd); + + /* config FIFOs */ + ag71xx_wr(priv, AG71XX_REG_FIFO_CFG0, 0x1f00); + + rd = ag71xx_gmac_rr(priv, AG71XX_REG_MAC_CFG1); + rd |= AG71XX_ETH_CFG_MII_GE0_SLAVE; + ag71xx_gmac_wr(priv, 0, rd); +} + +static void ag71xx_ar9344_gmac0_mii_init(struct ag71xx *priv) +{ + u32 rd; + + rd = ag71xx_rr(priv, AG71XX_REG_MAC_CFG2); + rd |= (MAC_CFG2_PAD_CRC_EN | MAC_CFG2_LEN_CHECK | MAC_CFG2_IF_1000); + ag71xx_wr(priv, AG71XX_REG_MAC_CFG2, rd); + + /* config FIFOs */ + ag71xx_wr(priv, AG71XX_REG_FIFO_CFG0, 0x1f00); + + ag71xx_gmac_wr(priv, AG71XX_REG_MAC_CFG1, 1); + udelay(1000); + ag71xx_wr(priv, AG71XX_REG_MII_CFG, 4 | (1 << 31)); + ag71xx_wr(priv, AG71XX_REG_MII_CFG, 4); +} + +static struct ag71xx_cfg ag71xx_cfg_ar9331_ge0 = { + .type = AG71XX_TYPE_AR9331_GE0, + .init_mii = ag71xx_ar9331_ge0_mii_init, +}; + +static struct ag71xx_cfg ag71xx_cfg_ar9344_gmac0 = { + .type = AG71XX_TYPE_AR9344_GMAC0, + .init_mii = ag71xx_ar9344_gmac0_mii_init, +}; + +static int ag71xx_probe(struct device_d *dev) +{ + void __iomem *regs, *regs_gmac; + struct mii_bus *miibus; + struct eth_device *edev; + struct ag71xx_cfg *cfg; + struct ag71xx *priv; + u32 mac_h, mac_l; + u32 rd, mask; + int ret; + + ret = dev_get_drvdata(dev, (const void **)&cfg); + if (ret) + return ret; + + regs_gmac = dev_request_mem_region_by_name(dev, "gmac"); + if (IS_ERR(regs_gmac)) + return PTR_ERR(regs_gmac); + + regs = dev_request_mem_region_by_name(dev, "ge0"); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv = xzalloc(sizeof(struct ag71xx)); + edev = &priv->netdev; + miibus = &priv->miibus; + edev->priv = priv; + + edev->init = ag71xx_ether_init; + edev->open = ag71xx_ether_open; + edev->send = ag71xx_ether_send; + edev->recv = ag71xx_ether_rx; + edev->halt = ag71xx_ether_halt; + edev->get_ethaddr = ag71xx_ether_get_ethaddr; + edev->set_ethaddr = ag71xx_ether_set_ethaddr; + + priv->dev = dev; + priv->regs = regs; + priv->regs_gmac = regs_gmac; + priv->cfg = cfg; + + miibus->read = ag71xx_ether_mii_read; + miibus->write = ag71xx_ether_mii_write; + miibus->priv = priv; + + /* enable switch core */ + rd = ar7240_reg_rd(AR71XX_PLL_BASE + AR933X_ETHSW_CLOCK_CONTROL_REG); + rd &= ~(0x1f); + rd |= 0x10; + if ((ar7240_reg_rd(WASP_BOOTSTRAP_REG) & WASP_REF_CLK_25) == 0) + rd |= 0x1; + ar7240_reg_wr((AR71XX_PLL_BASE + AR933X_ETHSW_CLOCK_CONTROL_REG), rd); + + if (ath79_reset_rr(AR933X_RESET_REG_RESET_MODULE) != 0) + ath79_reset_wr(AR933X_RESET_REG_RESET_MODULE, 0); + + /* reset GE0 MAC and MDIO */ + mask = AR933X_RESET_GE0_MAC | AR933X_RESET_GE0_MDIO + | AR933X_RESET_SWITCH; + + rd = ath79_reset_rr(AR933X_RESET_REG_RESET_MODULE); + rd |= mask; + ath79_reset_wr(AR933X_RESET_REG_RESET_MODULE, rd); + mdelay(100); + + rd = ath79_reset_rr(AR933X_RESET_REG_RESET_MODULE); + rd &= ~(mask); + ath79_reset_wr(AR933X_RESET_REG_RESET_MODULE, rd); + mdelay(100); + + ag71xx_wr(priv, AG71XX_REG_MAC_CFG1, + (MAC_CFG1_SR | MAC_CFG1_TX_RST | MAC_CFG1_RX_RST)); + ag71xx_wr(priv, AG71XX_REG_MAC_CFG1, + (MAC_CFG1_RXE | MAC_CFG1_TXE)); + + if (cfg->init_mii) + cfg->init_mii(priv); + + ag71xx_wr(priv, AG71XX_REG_FIFO_CFG1, 0x10ffff); + ag71xx_wr(priv, AG71XX_REG_FIFO_CFG2, 0xaaa0555); + + ag71xx_wr(priv, AG71XX_REG_FIFO_CFG4, 0x3ffff); + /* bit 19 should be set to 1 for GE0 */ + ag71xx_wr(priv, AG71XX_REG_FIFO_CFG5, (0x66b82) | (1 << 19)); + ag71xx_wr(priv, AG71XX_REG_FIFO_CFG3, 0x1f00140); + + priv->rx_buffer = xmemalign(PAGE_SIZE, NO_OF_RX_FIFOS * MAX_RBUFF_SZ); + priv->fifo_tx = dma_alloc_coherent(NO_OF_TX_FIFOS * sizeof(ag7240_desc_t), + &priv->addr_tx); + priv->fifo_rx = dma_alloc_coherent(NO_OF_RX_FIFOS * sizeof(ag7240_desc_t), + &priv->addr_rx); + priv->next_tx = 0; + + mac_l = 0x3344; + mac_h = 0x0004d980; + + ag71xx_wr(priv, AG71XX_REG_MAC_ADDR1, mac_l); + ag71xx_wr(priv, AG71XX_REG_MAC_ADDR2, mac_h); + + mdiobus_register(miibus); + eth_register(edev); + + dev_info(dev, "network device registered\n"); + + return 0; +} + +static __maybe_unused struct of_device_id ag71xx_dt_ids[] = { + { .compatible = "qca,ar9331-ge0", .data = &ag71xx_cfg_ar9331_ge0, }, + { .compatible = "qca,ar9344-gmac0", .data = &ag71xx_cfg_ar9344_gmac0, }, + { /* sentinel */ } +}; + +static struct driver_d ag71xx_driver = { + .name = "ag71xx-gmac", + .probe = ag71xx_probe, + .of_compatible = DRV_OF_COMPAT(ag71xx_dt_ids), +}; +device_platform_driver(ag71xx_driver); -- cgit v1.2.3 From e9edd1ad49eda3bf1c7f973480593baf9c739ecb Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Thu, 14 Sep 2017 10:25:06 +0200 Subject: net: phy: add ar8327 driver The Atheros ar8327 is a seven port gigabit ethernet switch. This patch adds a driver for the phys in it. Signed-off-by: Oleksij Rempel Signed-off-by: Sascha Hauer --- drivers/net/phy/Kconfig | 5 + drivers/net/phy/Makefile | 1 + drivers/net/phy/ar8327.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 286 insertions(+) create mode 100644 drivers/net/phy/ar8327.c (limited to 'drivers') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index d30f65b8e6..ea2e062656 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -8,6 +8,11 @@ if PHYLIB comment "MII PHY device drivers" +config AR8327N_PHY + bool "Driver for QCA AR8327N PHYs" + ---help--- + Currently supports the AR8327N PHY. + config AT803X_PHY bool "Driver for Atheros AT803X PHYs" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 10732f8070..13b8f6545d 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -1,4 +1,5 @@ obj-y += phy.o mdio_bus.o +obj-$(CONFIG_AR8327N_PHY) += ar8327.o obj-$(CONFIG_AT803X_PHY) += at803x.o obj-$(CONFIG_LXT_PHY) += lxt.o obj-$(CONFIG_MARVELL_PHY) += marvell.o diff --git a/drivers/net/phy/ar8327.c b/drivers/net/phy/ar8327.c new file mode 100644 index 0000000000..a0b10021dd --- /dev/null +++ b/drivers/net/phy/ar8327.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2017 Oleksij Rempel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include + +#define ATHR_PHY_MAX 5 + +/*****************/ +/* PHY Registers */ +/*****************/ +#define ATHR_PHY_CONTROL 0x00 +#define ATHR_PHY_STATUS 0x01 +#define ATHR_PHY_ID1 0x02 +#define ATHR_PHY_ID2 0x03 +#define ATHR_AUTONEG_ADVERT 0x04 +#define ATHR_LINK_PARTNER_ABILITY 0x05 +#define ATHR_AUTONEG_EXPANSION 0x06 +#define ATHR_NEXT_PAGE_TRANSMIT 0x07 +#define ATHR_LINK_PARTNER_NEXT_PAGE 0x08 +#define ATHR_1000BASET_CONTROL 0x09 +#define ATHR_1000BASET_STATUS 0x0a +#define ATHR_PHY_SPEC_CONTROL 0x10 +#define ATHR_PHY_SPEC_STATUS 0x11 +#define ATHR_DEBUG_PORT_ADDRESS 0x1d +#define ATHR_DEBUG_PORT_DATA 0x1e + +/* Advertisement register. */ +#define ATHR_ADVERTISE_ASYM_PAUSE 0x0800 +#define ATHR_ADVERTISE_PAUSE 0x0400 +#define ATHR_ADVERTISE_100FULL 0x0100 +#define ATHR_ADVERTISE_100HALF 0x0080 +#define ATHR_ADVERTISE_10FULL 0x0040 +#define ATHR_ADVERTISE_10HALF 0x0020 + +#define ATHR_ADVERTISE_ALL (ATHR_ADVERTISE_ASYM_PAUSE | ATHR_ADVERTISE_PAUSE | \ + ATHR_ADVERTISE_10HALF | ATHR_ADVERTISE_10FULL | \ + ATHR_ADVERTISE_100HALF | ATHR_ADVERTISE_100FULL) + +/* ATHR_PHY_CONTROL fields */ +#define ATHR_CTRL_SOFTWARE_RESET 0x8000 +#define ATHR_CTRL_AUTONEGOTIATION_ENABLE 0x1000 + +/* 1000BASET_CONTROL */ +#define ATHR_ADVERTISE_1000FULL 0x0200 + +/* Phy Specific status fields */ +#define ATHR_STATUS_LINK_PASS 0x0400 + +static u32 ar8327n_reg_read(struct phy_device *phydev, u32 reg_addr) +{ + u32 reg_word_addr; + u32 phy_addr, tmp_val, reg_val; + u16 phy_val; + u8 phy_reg; + + /* change reg_addr to 16-bit word address, 32-bit aligned */ + reg_word_addr = (reg_addr & 0xfffffffc) >> 1; + + /* configure register high address */ + phy_addr = 0x18; + phy_reg = 0x0; + phy_val = (u16) ((reg_word_addr >> 8) & 0x1ff); /* bit16-8 of reg address */ + mdiobus_write(phydev->bus, phy_addr, phy_reg, phy_val); + + /* For some registers such as MIBs, since it is read/clear, we should */ + /* read the lower 16-bit register then the higher one */ + + /* read register in lower address */ + phy_addr = 0x10 | ((reg_word_addr >> 5) & 0x7); /* bit7-5 of reg address */ + phy_reg = (u8) (reg_word_addr & 0x1f); /* bit4-0 of reg address */ + reg_val = (u32) mdiobus_read(phydev->bus, phy_addr, phy_reg); + + /* read register in higher address */ + reg_word_addr++; + phy_addr = 0x10 | ((reg_word_addr >> 5) & 0x7); /* bit7-5 of reg address */ + phy_reg = (u8) (reg_word_addr & 0x1f); /* bit4-0 of reg address */ + reg_val = (u32) mdiobus_read(phydev->bus, phy_addr, phy_reg); + reg_val |= (tmp_val << 16); + + return reg_val; +} + +static void ar8327n_reg_write(struct phy_device *phydev, u32 reg_addr, + u32 reg_val) +{ + u32 reg_word_addr; + u32 phy_addr; + u16 phy_val; + u8 phy_reg; + + /* change reg_addr to 16-bit word address, 32-bit aligned */ + reg_word_addr = (reg_addr & 0xfffffffc) >> 1; + + /* configure register high address */ + phy_addr = 0x18; + phy_reg = 0x0; + phy_val = (u16) ((reg_word_addr >> 8) & 0x1ff); /* bit16-8 of reg address */ + mdiobus_write(phydev->bus, phy_addr, phy_reg, phy_val); + + /* For some registers such as ARL and VLAN, since they include BUSY bit */ + /* in lower address, we should write the higher 16-bit register then the */ + /* lower one */ + + /* read register in higher address */ + reg_word_addr++; + phy_addr = 0x10 | ((reg_word_addr >> 5) & 0x7); /* bit7-5 of reg address */ + phy_reg = (u8) (reg_word_addr & 0x1f); /* bit4-0 of reg address */ + phy_val = (u16) ((reg_val >> 16) & 0xffff); + mdiobus_write(phydev->bus, phy_addr, phy_reg, phy_val); + + /* write register in lower address */ + reg_word_addr--; + phy_addr = 0x10 | ((reg_word_addr >> 5) & 0x7); /* bit7-5 of reg address */ + phy_reg = (u8) (reg_word_addr & 0x1f); /* bit4-0 of reg address */ + phy_val = (u16) (reg_val & 0xffff); + mdiobus_write(phydev->bus, phy_addr, phy_reg, phy_val); +} + +static int ar8327n_phy_is_link_alive(struct phy_device *phydev, int phy_addr) +{ + u16 val; + + val = mdiobus_read(phydev->bus, phy_addr, ATHR_PHY_SPEC_STATUS); + + return !!(val & ATHR_STATUS_LINK_PASS); +} + +static int ar8327n_phy_setup(struct phy_device *phydev) +{ + struct device_d *dev = &phydev->dev; + int phy_addr; + + /* start auto negotiation on each phy */ + for (phy_addr = 0; phy_addr < ATHR_PHY_MAX; phy_addr++) { + mdiobus_write(phydev->bus, phy_addr, ATHR_AUTONEG_ADVERT, + ATHR_ADVERTISE_ALL); + + mdiobus_write(phydev->bus, phy_addr, ATHR_1000BASET_CONTROL, + ATHR_ADVERTISE_1000FULL); + + /* Reset PHYs*/ + mdiobus_write(phydev->bus, phy_addr, ATHR_PHY_CONTROL, + ATHR_CTRL_AUTONEGOTIATION_ENABLE + | ATHR_CTRL_SOFTWARE_RESET); + } + + /* + * After the phy is reset, it takes a little while before + * it can respond properly. + */ + mdelay(1000); + + for (phy_addr = 0; phy_addr < ATHR_PHY_MAX; phy_addr++) { + int count; + + for (count = 20; count > 0; count--) { + u16 val; + val = mdiobus_read(phydev->bus, phy_addr, + ATHR_PHY_CONTROL); + + if (!(val & ATHR_CTRL_SOFTWARE_RESET)) + break; + + mdelay(150); + } + + if (!count) { + dev_err(dev, "error: port %d, negotiation timeout.\n", + phy_addr); + return -ETIMEDOUT; + } + } + + return 0; +} + +static int ar8327n_get_link(struct phy_device *phydev) +{ + int phy_addr; + int live_links = 0; + + for (phy_addr = 0; phy_addr < ATHR_PHY_MAX; phy_addr++) { + if (ar8327n_phy_is_link_alive(phydev, phy_addr)) + live_links++; + } + + return (live_links > 0); +} + +static int ar8327n_config_init(struct phy_device *phydev) +{ + struct device_d *dev = &phydev->dev; + int phy_addr = 0; + + if (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID) + return 0; + + /* if using header for register configuration, we have to */ + /* configure s17 register after frame transmission is enabled */ + + /* configure the RGMII */ + ar8327n_reg_write(phydev, 0x624, 0x7f7f7f7f); + ar8327n_reg_write(phydev, 0x10, 0x40000000); + ar8327n_reg_write(phydev, 0x4, 0x07600000); + ar8327n_reg_write(phydev, 0xc, 0x01000000); + ar8327n_reg_write(phydev, 0x7c, 0x0000007e); + + /* AR8327/AR8328 v1.0 fixup */ + if ((ar8327n_reg_read(phydev, 0x0) & 0xffff) == 0x1201) { + dev_warn(dev, "warning: untested device. PHY v1.0\n"); + for (phy_addr = 0x0; phy_addr <= ATHR_PHY_MAX; phy_addr++) { + /* For 100M waveform */ + mdiobus_write(phydev->bus, phy_addr, 0x1d, 0x0); + mdiobus_write(phydev->bus, phy_addr, 0x1e, 0x02ea); + /* Turn On Gigabit Clock */ + mdiobus_write(phydev->bus, phy_addr, 0x1d, 0x3d); + mdiobus_write(phydev->bus, phy_addr, 0x1e, 0x68a0); + } + } + + /* + * set the WAN Port(Port1) Disable Mode so + * it can not receive or transmit any frames. + */ + ar8327n_reg_write(phydev, 0x066c, + ar8327n_reg_read(phydev, 0x066c) & 0xfff8ffff); + + ar8327n_phy_setup(phydev); + + return 0; +} + +static int ar8327n_read_status(struct phy_device *phydev) +{ + /* for GMAC0 we have only one static mode */ + phydev->speed = SPEED_1000; + phydev->duplex = DUPLEX_FULL; + phydev->pause = phydev->asym_pause = 0; + phydev->link = ar8327n_get_link(phydev); + return 0; +} + +static int ar8327n_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int ar8327n_aneg_done(struct phy_device *phydev) +{ + return BMSR_ANEGCOMPLETE; +} + +static struct phy_driver ar8327n_driver[] = { +{ + /* QCA AR8327N */ + .phy_id = 0x004dd034, + .phy_id_mask = 0xffffffef, + .drv.name = "QCA AR8327N switch", + .config_init = ar8327n_config_init, + .features = PHY_GBIT_FEATURES, + .config_aneg = &ar8327n_config_aneg, + .read_status = &ar8327n_read_status, + .aneg_done = &ar8327n_aneg_done, +}}; + +static int atheros_phy_init(void) +{ + return phy_drivers_register(ar8327n_driver, + ARRAY_SIZE(ar8327n_driver)); +} +fs_initcall(atheros_phy_init); -- cgit v1.2.3 From 22e1ba9f3645057586abc866ed0925bd28d5b8d2 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Mon, 18 Sep 2017 18:41:45 +0200 Subject: net: ag71xx: disable eth interface on barebox shutdown If not disable, DMA RX engine will keep to write in to allocated memory area. As soon as some code or data of target OS will be placed to this area it will be overwritten by incoming network package. Signed-off-by: Oleksij Rempel Signed-off-by: Sascha Hauer --- drivers/net/ag71xx.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ag71xx.c b/drivers/net/ag71xx.c index 54f0bd9aa5..1cec263600 100644 --- a/drivers/net/ag71xx.c +++ b/drivers/net/ag71xx.c @@ -578,6 +578,7 @@ static int ag71xx_probe(struct device_d *dev) priv = xzalloc(sizeof(struct ag71xx)); edev = &priv->netdev; miibus = &priv->miibus; + dev->priv = edev; edev->priv = priv; edev->init = ag71xx_ether_init; @@ -659,6 +660,13 @@ static int ag71xx_probe(struct device_d *dev) return 0; } +static void ag71xx_remove(struct device_d *dev) +{ + struct eth_device *edev = dev->priv; + + ag71xx_ether_halt(edev); +} + static __maybe_unused struct of_device_id ag71xx_dt_ids[] = { { .compatible = "qca,ar9331-ge0", .data = &ag71xx_cfg_ar9331_ge0, }, { .compatible = "qca,ar9344-gmac0", .data = &ag71xx_cfg_ar9344_gmac0, }, @@ -668,6 +676,7 @@ static __maybe_unused struct of_device_id ag71xx_dt_ids[] = { static struct driver_d ag71xx_driver = { .name = "ag71xx-gmac", .probe = ag71xx_probe, + .remove = ag71xx_remove, .of_compatible = DRV_OF_COMPAT(ag71xx_dt_ids), }; device_platform_driver(ag71xx_driver); -- cgit v1.2.3 From 22b7ab1b4e9afc15d98783f13b613c328b07080b Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Sat, 30 Sep 2017 00:10:57 +0200 Subject: net: ag71xx: define parent devices without it we are not able to set mac address from other driver. Signed-off-by: Oleksij Rempel Signed-off-by: Sascha Hauer --- drivers/net/ag71xx.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ag71xx.c b/drivers/net/ag71xx.c index 1cec263600..a422aacdd8 100644 --- a/drivers/net/ag71xx.c +++ b/drivers/net/ag71xx.c @@ -580,6 +580,7 @@ static int ag71xx_probe(struct device_d *dev) miibus = &priv->miibus; dev->priv = edev; edev->priv = priv; + edev->parent = dev; edev->init = ag71xx_ether_init; edev->open = ag71xx_ether_open; @@ -597,6 +598,7 @@ static int ag71xx_probe(struct device_d *dev) miibus->read = ag71xx_ether_mii_read; miibus->write = ag71xx_ether_mii_write; miibus->priv = priv; + miibus->parent = dev; /* enable switch core */ rd = ar7240_reg_rd(AR71XX_PLL_BASE + AR933X_ETHSW_CLOCK_CONTROL_REG); -- cgit v1.2.3 From 4dbdf29897ab649497e5cee6ad3875ebaaa3ebee Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Sat, 30 Sep 2017 00:12:44 +0200 Subject: clk: add ar9344 clock driver Signed-off-by: Oleksij Rempel Signed-off-by: Sascha Hauer --- drivers/clk/Makefile | 3 +- drivers/clk/clk-ar9344.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/clk-ar9344.c (limited to 'drivers') diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index b5abe1cdf5..ddd971c607 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_ARCH_MXS) += mxs/ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_CLK_SOCFPGA) += socfpga/ -obj-$(CONFIG_MACH_MIPS_ATH79) += clk-ar933x.o +obj-$(CONFIG_SOC_QCA_AR9331) += clk-ar933x.o +obj-$(CONFIG_SOC_QCA_AR9344) += clk-ar9344.o obj-$(CONFIG_ARCH_IMX) += imx/ obj-$(CONFIG_COMMON_CLK_AT91) += at91/ diff --git a/drivers/clk/clk-ar9344.c b/drivers/clk/clk-ar9344.c new file mode 100644 index 0000000000..c3c49fb109 --- /dev/null +++ b/drivers/clk/clk-ar9344.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2017 Oleksij Rempel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AR9344_CPU_PLL_CONFIG 0x00 +#define AR9344_DDR_PLL_CONFIG 0x04 +#define AR9344_OUTDIV_M 0x3 +#define AR9344_OUTDIV_S 19 +#define AR9344_REFDIV_M 0x1f +#define AR9344_REFDIV_S 12 +#define AR9344_NINT_M 0x3f +#define AR9344_NINT_S 6 +#define AR9344_NFRAC_M 0x3f +#define AR9344_NFRAC_S 0 + + +#define AR9344_CPU_DDR_CLOCK_CONTROL 0x08 +#define AR9344_CPU_FROM_CPUPLL BIT(20) +#define AR9344_CPU_PLL_BYPASS BIT(2) +#define AR9344_CPU_POST_DIV_M 0x1f +#define AR9344_CPU_POST_DIV_S 5 + +static struct clk *clks[ATH79_CLK_END]; +static struct clk_onecell_data clk_data; + +struct clk_ar9344 { + struct clk clk; + void __iomem *base; + u32 div_shift; + u32 div_mask; + const char *parent; +}; + +static unsigned long clk_ar9344_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_ar9344 *f = container_of(clk, struct clk_ar9344, clk); + int outdiv, refdiv, nint, nfrac; + int cpu_post_div; + u32 clock_ctrl; + u32 val; + + clock_ctrl = __raw_readl(f->base + AR9344_CPU_DDR_CLOCK_CONTROL); + cpu_post_div = ((clock_ctrl >> AR9344_CPU_POST_DIV_S) + & AR9344_CPU_POST_DIV_M) + 1; + if (clock_ctrl & AR9344_CPU_PLL_BYPASS) { + return parent_rate; + } else if (clock_ctrl & AR9344_CPU_FROM_CPUPLL) { + val = __raw_readl(f->base + AR9344_CPU_PLL_CONFIG); + } else { + val = __raw_readl(f->base + AR9344_DDR_PLL_CONFIG); + } + + outdiv = (val >> AR9344_OUTDIV_S) & AR9344_OUTDIV_M; + refdiv = (val >> AR9344_REFDIV_S) & AR9344_REFDIV_M; + nint = (val >> AR9344_NINT_S) & AR9344_NINT_M; + nfrac = (val >> AR9344_NFRAC_S) & AR9344_NFRAC_M; + + return (parent_rate * (nint + (nfrac >> 9))) / (refdiv * (1 << outdiv)); +} + +struct clk_ops clk_ar9344_ops = { + .recalc_rate = clk_ar9344_recalc_rate, +}; + +static struct clk *clk_ar9344(const char *name, const char *parent, + void __iomem *base) +{ + struct clk_ar9344 *f = xzalloc(sizeof(*f)); + + f->parent = parent; + f->base = base; + f->div_shift = 0; + f->div_mask = 0; + + f->clk.ops = &clk_ar9344_ops; + f->clk.name = name; + f->clk.parent_names = &f->parent; + f->clk.num_parents = 1; + + clk_register(&f->clk); + + return &f->clk; +} + +static void ar9344_pll_init(void __iomem *base) +{ + clks[ATH79_CLK_CPU] = clk_ar9344("cpu", "ref", base); +} + +static int ar9344_clk_probe(struct device_d *dev) +{ + struct resource *iores; + void __iomem *base; + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + base = IOMEM(iores->start); + + ar9344_pll_init(base); + + clk_data.clks = clks; + clk_data.clk_num = ARRAY_SIZE(clks); + of_clk_add_provider(dev->device_node, of_clk_src_onecell_get, + &clk_data); + + return 0; +} + +static __maybe_unused struct of_device_id ar9344_clk_dt_ids[] = { + { + .compatible = "qca,ar9344-pll", + }, { + /* sentinel */ + } +}; + +static struct driver_d ar9344_clk_driver = { + .probe = ar9344_clk_probe, + .name = "ar9344_clk", + .of_compatible = DRV_OF_COMPAT(ar9344_clk_dt_ids), +}; + +static int ar9344_clk_init(void) +{ + return platform_driver_register(&ar9344_clk_driver); +} +postcore_initcall(ar9344_clk_init); -- cgit v1.2.3