From 63c108bf61936da12ef53ea782a584f6b33658b6 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 17 Mar 2011 15:46:38 +0100 Subject: usb net: Add SMSC95xx support Signed-off-by: Sascha Hauer --- drivers/net/usb/Kconfig | 4 + drivers/net/usb/Makefile | 1 + drivers/net/usb/smsc95xx.c | 938 +++++++++++++++++++++++++++++++++++++++++++++ drivers/net/usb/smsc95xx.h | 256 +++++++++++++ 4 files changed, 1199 insertions(+) create mode 100644 drivers/net/usb/smsc95xx.c create mode 100644 drivers/net/usb/smsc95xx.h diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index 6482626454..b53dcc7c46 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -8,4 +8,8 @@ config NET_USB_ASIX select MIIDEV bool "Asix compatible" +config NET_USB_SMSC95XX + select MIIDEV + bool "SMSC95xx" + endif diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile index 555f8c2f1f..564e44de4e 100644 --- a/drivers/net/usb/Makefile +++ b/drivers/net/usb/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_NET_USB) += usbnet.o obj-$(CONFIG_NET_USB_ASIX) += asix.o +obj-$(CONFIG_NET_USB_SMSC95XX) += smsc95xx.o diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c new file mode 100644 index 0000000000..ae137fb978 --- /dev/null +++ b/drivers/net/usb/smsc95xx.c @@ -0,0 +1,938 @@ +/*************************************************************************** + * + * Copyright (C) 2007-2008 SMSC + * + * 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. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smsc95xx.h" + +#define SMSC_CHIPNAME "smsc95xx" +#define SMSC_DRIVER_VERSION "1.0.4" +#define HS_USB_PKT_SIZE (512) +#define FS_USB_PKT_SIZE (64) +#define DEFAULT_HS_BURST_CAP_SIZE (16 * 1024 + 5 * HS_USB_PKT_SIZE) +#define DEFAULT_FS_BURST_CAP_SIZE (6 * 1024 + 33 * FS_USB_PKT_SIZE) +#define DEFAULT_BULK_IN_DELAY (0x00002000) +#define MAX_SINGLE_PACKET_SIZE (2048) +#define LAN95XX_EEPROM_MAGIC (0x9500) +#define EEPROM_MAC_OFFSET (0x01) +#define DEFAULT_TX_CSUM_ENABLE (1) +#define DEFAULT_RX_CSUM_ENABLE (1) +#define SMSC95XX_INTERNAL_PHY_ID (1) +#define SMSC95XX_TX_OVERHEAD (8) +#define SMSC95XX_TX_OVERHEAD_CSUM (12) + +#define ETH_ALEN 6 +#define NET_IP_ALIGN 2 +#define ETH_FRAME_LEN 1514 /* Max. octets in frame sans FCS */ +#define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */ + +#define netdev_warn(x, fmt, arg...) printf(fmt, ##arg) +#ifdef DEBUG +#define netif_dbg(x, y, z, fmt, arg...) printf(fmt, ##arg) +#else +#define netif_dbg(x, y, z, fmt, arg...) do {} while(0) +#endif + +#define FLOW_CTRL_RX 0x02 + +struct smsc95xx_priv { + u32 mac_cr; + int use_tx_csum; + int use_rx_csum; +}; + +static int turbo_mode = 0; + +static int smsc95xx_read_reg(struct usbnet *dev, u32 index, u32 *data) +{ + int ret; + + ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + USB_VENDOR_REQUEST_READ_REGISTER, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, index, data, 4, USB_CTRL_GET_TIMEOUT); + + if (ret < 0) + netdev_warn(dev->net, "Failed to read register index 0x%08x\n", index); + + le32_to_cpus(data); + + debug("%s: 0x%08x 0x%08x\n", __func__, index, *data); + + return ret; +} + +static int smsc95xx_write_reg(struct usbnet *dev, u32 index, u32 data) +{ + int ret; + + cpu_to_le32s(&data); + + ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + USB_VENDOR_REQUEST_WRITE_REGISTER, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 00, index, &data, 4, USB_CTRL_SET_TIMEOUT); + + if (ret < 0) + netdev_warn(dev->net, "Failed to write register index 0x%08x\n", index); + + debug("%s: 0x%08x 0x%08x\n", __func__, index, data); + + return ret; +} + +/* Loop until the read is completed with timeout + * called with phy_mutex held */ +static int smsc95xx_phy_wait_not_busy(struct usbnet *dev) +{ + u32 val; + int timeout = 1000; + + do { + smsc95xx_read_reg(dev, MII_ADDR, &val); + if (!(val & MII_BUSY_)) + return 0; + udelay(100); + } while (--timeout); + + return -EIO; +} + +static int smsc95xx_mdio_read(struct mii_device *mdev, int phy_id, int idx) +{ + struct eth_device *eth = mdev->edev; + struct usbnet *dev = eth->priv; + u32 val, addr; + + /* confirm MII not busy */ + if (smsc95xx_phy_wait_not_busy(dev)) { + netdev_warn(dev->net, "MII is busy in smsc95xx_mdio_read\n"); + return -EIO; + } + + /* set the address, index & direction (read from PHY) */ + addr = (phy_id << 11) | (idx << 6) | MII_READ_; + smsc95xx_write_reg(dev, MII_ADDR, addr); + + if (smsc95xx_phy_wait_not_busy(dev)) { + netdev_warn(dev->net, "Timed out reading MII reg %02X\n", idx); + return -EIO; + } + + smsc95xx_read_reg(dev, MII_DATA, &val); + + return val & 0xffff; +} + +static int smsc95xx_mdio_write(struct mii_device *mdev, int phy_id, int idx, + int regval) +{ + struct eth_device *eth = mdev->edev; + struct usbnet *dev = eth->priv; + u32 val, addr; + + /* confirm MII not busy */ + if (smsc95xx_phy_wait_not_busy(dev)) { + netdev_warn(dev->net, "MII is busy in smsc95xx_mdio_write\n"); + return -EBUSY; + } + + val = regval; + smsc95xx_write_reg(dev, MII_DATA, val); + + /* set the address, index & direction (write to PHY) */ + addr = (phy_id << 11) | (idx << 6) | MII_WRITE_; + smsc95xx_write_reg(dev, MII_ADDR, addr); + + if (smsc95xx_phy_wait_not_busy(dev)) + netdev_warn(dev->net, "Timed out writing MII reg %02X\n", idx); + + return 0; +} + +static int smsc95xx_wait_eeprom(struct usbnet *dev) +{ + int timeout = 1000; + u32 val; + + do { + smsc95xx_read_reg(dev, E2P_CMD, &val); + if (!(val & E2P_CMD_BUSY_) || (val & E2P_CMD_TIMEOUT_)) + break; + udelay(100); + } while (--timeout); + + if (val & (E2P_CMD_TIMEOUT_ | E2P_CMD_BUSY_)) { + netdev_warn(dev->net, "EEPROM read operation timeout\n"); + return -EIO; + } + + return 0; +} + +static int smsc95xx_eeprom_confirm_not_busy(struct usbnet *dev) +{ + int timeout = 1000; + u32 val; + + do { + smsc95xx_read_reg(dev, E2P_CMD, &val); + + if (!(val & E2P_CMD_BUSY_)) + return 0; + udelay(100); + } while (--timeout); + + netdev_warn(dev->net, "EEPROM is busy\n"); + return -EIO; +} + +static int smsc95xx_read_eeprom(struct usbnet *dev, u32 offset, u32 length, + u8 *data) +{ + u32 val; + int i, ret; + + ret = smsc95xx_eeprom_confirm_not_busy(dev); + if (ret) + return ret; + + for (i = 0; i < length; i++) { + val = E2P_CMD_BUSY_ | E2P_CMD_READ_ | (offset & E2P_CMD_ADDR_); + smsc95xx_write_reg(dev, E2P_CMD, val); + + ret = smsc95xx_wait_eeprom(dev); + if (ret < 0) + return ret; + + smsc95xx_read_reg(dev, E2P_DATA, &val); + + data[i] = val & 0xFF; + offset++; + } + + return 0; +} + +#define GPIO_CFG_GPEN_ (0xff000000) +#define GPIO_CFG_GPO0_EN_ (0x01000000) +#define GPIO_CFG_GPTYPE (0x00ff0000) +#define GPIO_CFG_GPO0_TYPE (0x00010000) +#define GPIO_CFG_GPDIR_ (0x0000ff00) +#define GPIO_CFG_GPO0_DIR_ (0x00000100) +#define GPIO_CFG_GPDATA_ (0x000000ff) +#define GPIO_CFG_GPO0_DATA_ (0x00000001) +#define LED_GPIO_CFG_FDX_LED (0x00010000) +#define LED_GPIO_CFG_GPBUF_08_ (0x00000100) +#define LED_GPIO_CFG_GPDIR_08_ (0x00000010) +#define LED_GPIO_CFG_GPDATA_08_ (0x00000001) +#define LED_GPIO_CFG_GPCTL_LED_ (0x00000001) + +#if 0 +static int smsc95xx_enable_gpio(struct usbnet *dev, int gpio, int type) +{ + int ret = -1; + u32 val, reg; + int dir_shift, enable_shift, type_shift; + + if (gpio < 8) { + reg = GPIO_CFG; + enable_shift = 24 + gpio; + type_shift = 16 + gpio; + dir_shift = 8 + gpio; + } else { + gpio -= 8; + reg = LED_GPIO_CFG; + enable_shift = 16 + gpio * 4; + type_shift = 8 + gpio; + dir_shift = 4 + gpio; + } + + ret = smsc95xx_read_reg(dev, reg, &val); + if (ret < 0) + return ret; + + val &= ~(1 << enable_shift); + + if (type) + val &= ~(1 << type_shift); + else + val |= (1 << type_shift); + + val |= (1 << dir_shift); + + ret = smsc95xx_write_reg(dev, reg, val); + + return ret < 0 ? ret : 0; +} + +static int smsc95xx_gpio_set_value(struct usbnet *dev, int gpio, int value) +{ + int ret = -1; + u32 tmp, reg; + + if (gpio > 10) + return -EINVAL; + + smsc95xx_enable_gpio(dev, gpio, 0); + + if (gpio < 8) { + reg = GPIO_CFG; + } else { + reg = LED_GPIO_CFG; + gpio -= 8; + } + + ret = smsc95xx_read_reg(dev, reg, &tmp); + if (ret < 0) + return ret; + + if (value) + tmp |= 1 << gpio; + else + tmp &= ~(1 << gpio); + + ret = smsc95xx_write_reg(dev, reg, tmp); + + return ret < 0 ? ret : 0; +} +#endif + +static void smsc95xx_set_multicast(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 hash_hi = 0; + u32 hash_lo = 0; + + netif_dbg(dev, drv, dev->net, "receive own packets only\n"); + pdata->mac_cr &= ~(MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + + /* Initiate async writes, as we can't wait for completion here */ + smsc95xx_write_reg(dev, HASHH, hash_hi); + smsc95xx_write_reg(dev, HASHL, hash_lo); + smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); +} + +/* Enable or disable Tx & Rx checksum offload engines */ +static int smsc95xx_set_csums(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 read_buf; + int ret = smsc95xx_read_reg(dev, COE_CR, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read COE_CR: %d\n", ret); + return ret; + } + + if (pdata->use_tx_csum) + read_buf |= Tx_COE_EN_; + else + read_buf &= ~Tx_COE_EN_; + + if (pdata->use_rx_csum) + read_buf |= Rx_COE_EN_; + else + read_buf &= ~Rx_COE_EN_; + + ret = smsc95xx_write_reg(dev, COE_CR, read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write COE_CR: %d\n", ret); + return ret; + } + + netif_dbg(dev, hw, dev->net, "COE_CR = 0x%08x\n", read_buf); + return 0; +} + +static int smsc95xx_set_ethaddr(struct eth_device *edev, unsigned char *adr) +{ + struct usbnet *udev = container_of(edev, struct usbnet, edev); + + u32 addr_lo = adr[0] | adr[1] << 8 | + adr[2] << 16 | adr[3] << 24; + u32 addr_hi = adr[4] | adr[5] << 8; + int ret; + + ret = smsc95xx_write_reg(udev, ADDRL, addr_lo); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write ADDRL: %d\n", ret); + return ret; + } + + ret = smsc95xx_write_reg(udev, ADDRH, addr_hi); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write ADDRH: %d\n", ret); + return ret; + } + + return 0; +} + +static int smsc95xx_get_ethaddr(struct eth_device *edev, unsigned char *adr) +{ + struct usbnet *udev = container_of(edev, struct usbnet, edev); + + /* try reading mac address from EEPROM */ + if (smsc95xx_read_eeprom(udev, EEPROM_MAC_OFFSET, ETH_ALEN, + adr) == 0) { + return 0; + } + + return -EINVAL; +} + +/* starts the TX path */ +static void smsc95xx_start_tx_path(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 reg_val; + + /* Enable Tx at MAC */ + pdata->mac_cr |= MAC_CR_TXEN_; + + smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); + + /* Enable Tx at SCSRs */ + reg_val = TX_CFG_ON_; + smsc95xx_write_reg(dev, TX_CFG, reg_val); +} + +/* Starts the Receive path */ +static void smsc95xx_start_rx_path(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + + pdata->mac_cr |= MAC_CR_RXEN_; + + smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); +} + +static int smsc95xx_phy_initialize(struct usbnet *dev) +{ + int timeout = 0; + int phy_id = 1; /* FIXME */ + uint16_t val, bmcr; + + /* Initialize MII structure */ + dev->miidev.read = smsc95xx_mdio_read; + dev->miidev.write = smsc95xx_mdio_write; + dev->miidev.address = 1; /* FIXME: asix_get_phy_addr(dev); */ + dev->miidev.flags = 0; + dev->miidev.edev = &dev->edev; +// dev->miidev.name = dev->edev.name; + + /* reset phy and wait for reset to complete */ + smsc95xx_mdio_write(&dev->miidev, phy_id, MII_BMCR, BMCR_RESET); + + do { + udelay(10 * 1000); + bmcr = smsc95xx_mdio_read(&dev->miidev, phy_id, MII_BMCR); + timeout++; + } while ((bmcr & MII_BMCR) && (timeout < 100)); + + if (timeout >= 100) { + netdev_warn(dev->net, "timeout on PHY Reset"); + return -EIO; + } + + smsc95xx_mdio_write(&dev->miidev, phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP | + ADVERTISE_PAUSE_ASYM); + + /* read to clear */ + val = smsc95xx_mdio_read(&dev->miidev, phy_id, PHY_INT_SRC); + + smsc95xx_mdio_write(&dev->miidev, phy_id, PHY_INT_MASK, + PHY_INT_MASK_DEFAULT_); + + netif_dbg(dev, ifup, dev->net, "phy initialised successfully\n"); + return 0; +} + +static int smsc95xx_reset(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 read_buf, write_buf, burst_cap = 0; + int ret = 0, timeout; + + netif_dbg(dev, ifup, dev->net, "entering %s\n", __func__); + + write_buf = HW_CFG_LRST_; + ret = smsc95xx_write_reg(dev, HW_CFG, write_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write HW_CFG_LRST_ bit in HW_CFG register, ret = %d\n", + ret); + return ret; + } + + timeout = 0; + do { + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + udelay(1000 * 10); + timeout++; + } while ((read_buf & HW_CFG_LRST_) && (timeout < 100)); + + if (timeout >= 100) { + netdev_warn(dev->net, "timeout waiting for completion of Lite Reset\n"); + return ret; + } + + write_buf = PM_CTL_PHY_RST_; + ret = smsc95xx_write_reg(dev, PM_CTRL, write_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write PM_CTRL: %d\n", ret); + return ret; + } + + timeout = 0; + do { + ret = smsc95xx_read_reg(dev, PM_CTRL, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read PM_CTRL: %d\n", ret); + return ret; + } + udelay(1000 * 10); + timeout++; + } while ((read_buf & PM_CTL_PHY_RST_) && (timeout < 100)); + + if (timeout >= 100) { + netdev_warn(dev->net, "timeout waiting for PHY Reset\n"); + return ret; + } + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, + "Read Value from HW_CFG : 0x%08x\n", read_buf); + + read_buf |= HW_CFG_BIR_; + + ret = smsc95xx_write_reg(dev, HW_CFG, read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write HW_CFG_BIR_ bit in HW_CFG register, ret = %d\n", + ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + netif_dbg(dev, ifup, dev->net, + "Read Value from HW_CFG after writing HW_CFG_BIR_: 0x%08x\n", + read_buf); + + if (!turbo_mode) { + burst_cap = 0; + dev->rx_urb_size = MAX_SINGLE_PACKET_SIZE; + } else if (0) { /* highspeed */ + burst_cap = DEFAULT_HS_BURST_CAP_SIZE / HS_USB_PKT_SIZE; + dev->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE; + } else { + burst_cap = DEFAULT_FS_BURST_CAP_SIZE / FS_USB_PKT_SIZE; + dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE; + } + + netif_dbg(dev, ifup, dev->net, + "rx_urb_size=%ld\n", (ulong)dev->rx_urb_size); + + ret = smsc95xx_write_reg(dev, BURST_CAP, burst_cap); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write BURST_CAP: %d\n", ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, BURST_CAP, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read BURST_CAP: %d\n", ret); + return ret; + } + netif_dbg(dev, ifup, dev->net, + "Read Value from BURST_CAP after writing: 0x%08x\n", + read_buf); + + read_buf = DEFAULT_BULK_IN_DELAY; + ret = smsc95xx_write_reg(dev, BULK_IN_DLY, read_buf); + if (ret < 0) { + netdev_warn(dev->net, "ret = %d\n", ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, BULK_IN_DLY, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read BULK_IN_DLY: %d\n", ret); + return ret; + } + netif_dbg(dev, ifup, dev->net, + "Read Value from BULK_IN_DLY after writing: 0x%08x\n", + read_buf); + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + netif_dbg(dev, ifup, dev->net, + "Read Value from HW_CFG: 0x%08x\n", read_buf); + + if (turbo_mode) + read_buf |= (HW_CFG_MEF_ | HW_CFG_BCE_); + + read_buf &= ~HW_CFG_RXDOFF_; + + /* set Rx data offset=2, Make IP header aligns on word boundary. */ + read_buf |= NET_IP_ALIGN << 9; + + ret = smsc95xx_write_reg(dev, HW_CFG, read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write HW_CFG register, ret=%d\n", + ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + netif_dbg(dev, ifup, dev->net, + "Read Value from HW_CFG after writing: 0x%08x\n", read_buf); + + write_buf = 0xFFFFFFFF; + ret = smsc95xx_write_reg(dev, INT_STS, write_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write INT_STS register, ret=%d\n", + ret); + return ret; + } + + ret = smsc95xx_read_reg(dev, ID_REV, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read ID_REV: %d\n", ret); + return ret; + } + netif_dbg(dev, ifup, dev->net, "ID_REV = 0x%08x\n", read_buf); + + /* Configure GPIO pins as LED outputs */ + write_buf = LED_GPIO_CFG_SPD_LED | LED_GPIO_CFG_LNK_LED | + LED_GPIO_CFG_FDX_LED; + ret = smsc95xx_write_reg(dev, LED_GPIO_CFG, write_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write LED_GPIO_CFG register, ret=%d\n", + ret); + return ret; + } + + /* Init Tx */ + write_buf = 0; + ret = smsc95xx_write_reg(dev, FLOW, write_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write FLOW: %d\n", ret); + return ret; + } + + read_buf = AFC_CFG_DEFAULT; + ret = smsc95xx_write_reg(dev, AFC_CFG, read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write AFC_CFG: %d\n", ret); + return ret; + } + + /* Don't need mac_cr_lock during initialisation */ + ret = smsc95xx_read_reg(dev, MAC_CR, &pdata->mac_cr); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read MAC_CR: %d\n", ret); + return ret; + } + + /* Init Rx */ + /* Set Vlan */ + write_buf = (u32)ETH_P_8021Q; + ret = smsc95xx_write_reg(dev, VLAN1, write_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write VAN1: %d\n", ret); + return ret; + } + + ret = smsc95xx_set_csums(dev); + if (ret < 0) { + netdev_warn(dev->net, "Failed to set csum offload: %d\n", ret); + return ret; + } + + smsc95xx_set_multicast(dev); + + if (smsc95xx_phy_initialize(dev) < 0) + return -EIO; + + ret = smsc95xx_read_reg(dev, INT_EP_CTL, &read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read INT_EP_CTL: %d\n", ret); + return ret; + } + + /* enable PHY interrupts */ + read_buf |= INT_EP_CTL_PHY_INT_; + + ret = smsc95xx_write_reg(dev, INT_EP_CTL, read_buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write INT_EP_CTL: %d\n", ret); + return ret; + } + + smsc95xx_start_tx_path(dev); + smsc95xx_start_rx_path(dev); + + netif_dbg(dev, ifup, dev->net, "%s: return 0\n", __func__); + return 0; +} + +static struct usbnet *usbnet_global; + +static int smsc95xx_bind(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = NULL; + int ret; + + printf(SMSC_CHIPNAME " v" SMSC_DRIVER_VERSION "\n"); + + ret = usbnet_get_endpoints(dev); + if (ret < 0) { + netdev_warn(dev->net, "usbnet_get_endpoints failed: %d\n", ret); + return ret; + } + + dev->data[0] = (unsigned long)malloc(sizeof(struct smsc95xx_priv)); + + pdata = (struct smsc95xx_priv *)(dev->data[0]); + if (!pdata) { + netdev_warn(dev->net, "Unable to allocate struct smsc95xx_priv\n"); + return -ENOMEM; + } + + pdata->use_tx_csum = DEFAULT_TX_CSUM_ENABLE; + pdata->use_rx_csum = DEFAULT_RX_CSUM_ENABLE; + + /* Init all registers */ + ret = smsc95xx_reset(dev); + + dev->edev.get_ethaddr = smsc95xx_get_ethaddr; + dev->edev.set_ethaddr = smsc95xx_set_ethaddr; + mii_register(&dev->miidev); + + return 0; +} + +static void smsc95xx_unbind(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + if (pdata) { + netif_dbg(dev, ifdown, dev->net, "free pdata\n"); + free(pdata); + pdata = NULL; + dev->data[0] = 0; + } + + usbnet_global = NULL; +} + +static int smsc95xx_rx_fixup(struct usbnet *dev, void *buf, int len) +{ + while (len > 0) { + u32 header, align_count; + unsigned char *packet; + u16 size; + + memcpy(&header, buf, sizeof(header)); + le32_to_cpus(&header); + buf += 4 + NET_IP_ALIGN; + len -= 4 + NET_IP_ALIGN; + packet = buf; + + /* get the packet length */ + size = (u16)((header & RX_STS_FL_) >> 16); + align_count = (4 - ((size + NET_IP_ALIGN) % 4)) % 4; + + if (header & RX_STS_ES_) { + netif_dbg(dev, rx_err, dev->net, + "Error header=0x%08x\n", header); + } else { + /* ETH_FRAME_LEN + 4(CRC) + 2(COE) + 4(Vlan) */ + if (size > (ETH_FRAME_LEN + 12)) { + netif_dbg(dev, rx_err, dev->net, + "size err header=0x%08x\n", header); + return 0; + } + + /* last frame in this batch */ + if (len == size) { + net_receive(buf, len - 4); + return 1; + } + + net_receive(packet, len - 4); + } + + len -= size; + + /* padding bytes before the next frame starts */ + if (len) + len -= align_count; + } + + if (len < 0) { + netdev_warn(dev->net, "invalid rx length<0 %d\n", len); + return 0; + } + + return 1; +} +#if 0 +static u32 smsc95xx_calc_csum_preamble(struct sk_buff *skb) +{ + int len = skb->data - skb->head; + u16 high_16 = (u16)(skb->csum_offset + skb->csum_start - len); + u16 low_16 = (u16)(skb->csum_start - len); + return (high_16 << 16) | low_16; +} +#endif +static int smsc95xx_tx_fixup(struct usbnet *dev, + void *buf, int len, + void *nbuf, int *nlen) +{ + u32 tx_cmd_a, tx_cmd_b; + + tx_cmd_a = (u32)(len) | TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + cpu_to_le32s(&tx_cmd_a); + memcpy(nbuf, &tx_cmd_a, 4); + + tx_cmd_b = (u32)(len); + cpu_to_le32s(&tx_cmd_b); + memcpy(nbuf + 4, &tx_cmd_b, 4); + + memcpy(nbuf + 8, buf, len); + + *nlen = len + 8; + + return 0; +} + +static struct driver_info smsc95xx_info = { + .description = "smsc95xx USB 2.0 Ethernet", + .bind = smsc95xx_bind, + .unbind = smsc95xx_unbind, + .rx_fixup = smsc95xx_rx_fixup, + .tx_fixup = smsc95xx_tx_fixup, +}; + +static const struct usb_device_id products[] = { + { + /* SMSC9500 USB Ethernet Device */ + USB_DEVICE(0x0424, 0x9500), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9505 USB Ethernet Device */ + USB_DEVICE(0x0424, 0x9505), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9500A USB Ethernet Device */ + USB_DEVICE(0x0424, 0x9E00), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9505A USB Ethernet Device */ + USB_DEVICE(0x0424, 0x9E01), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9512/9514 USB Hub & Ethernet Device */ + USB_DEVICE(0x0424, 0xec00), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9500 USB Ethernet Device (SAL10) */ + USB_DEVICE(0x0424, 0x9900), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9505 USB Ethernet Device (SAL10) */ + USB_DEVICE(0x0424, 0x9901), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9500A USB Ethernet Device (SAL10) */ + USB_DEVICE(0x0424, 0x9902), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9505A USB Ethernet Device (SAL10) */ + USB_DEVICE(0x0424, 0x9903), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9512/9514 USB Hub & Ethernet Device (SAL10) */ + USB_DEVICE(0x0424, 0x9904), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9500A USB Ethernet Device (HAL) */ + USB_DEVICE(0x0424, 0x9905), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9505A USB Ethernet Device (HAL) */ + USB_DEVICE(0x0424, 0x9906), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9500 USB Ethernet Device (Alternate ID) */ + USB_DEVICE(0x0424, 0x9907), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9500A USB Ethernet Device (Alternate ID) */ + USB_DEVICE(0x0424, 0x9908), + .driver_info = &smsc95xx_info, + }, { + /* SMSC9512/9514 USB Hub & Ethernet Device (Alternate ID) */ + USB_DEVICE(0x0424, 0x9909), + .driver_info = &smsc95xx_info, + }, + { }, /* END */ +}; + +static struct usb_driver smsc95xx_driver = { + .name = "smsc95xx", + .id_table = products, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, +}; + +static int __init smsc95xx_init(void) +{ + return usb_driver_register(&smsc95xx_driver); +} +device_initcall(smsc95xx_init); diff --git a/drivers/net/usb/smsc95xx.h b/drivers/net/usb/smsc95xx.h new file mode 100644 index 0000000000..86bc44977f --- /dev/null +++ b/drivers/net/usb/smsc95xx.h @@ -0,0 +1,256 @@ + /*************************************************************************** + * + * Copyright (C) 2007-2008 SMSC + * + * 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. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + *****************************************************************************/ + +#ifndef _SMSC95XX_H +#define _SMSC95XX_H + +/* Tx command words */ +#define TX_CMD_A_DATA_OFFSET_ (0x001F0000) +#define TX_CMD_A_FIRST_SEG_ (0x00002000) +#define TX_CMD_A_LAST_SEG_ (0x00001000) +#define TX_CMD_A_BUF_SIZE_ (0x000007FF) + +#define TX_CMD_B_CSUM_ENABLE (0x00004000) +#define TX_CMD_B_ADD_CRC_DISABLE_ (0x00002000) +#define TX_CMD_B_DISABLE_PADDING_ (0x00001000) +#define TX_CMD_B_PKT_BYTE_LENGTH_ (0x000007FF) + +/* Rx status word */ +#define RX_STS_FF_ (0x40000000) /* Filter Fail */ +#define RX_STS_FL_ (0x3FFF0000) /* Frame Length */ +#define RX_STS_ES_ (0x00008000) /* Error Summary */ +#define RX_STS_BF_ (0x00002000) /* Broadcast Frame */ +#define RX_STS_LE_ (0x00001000) /* Length Error */ +#define RX_STS_RF_ (0x00000800) /* Runt Frame */ +#define RX_STS_MF_ (0x00000400) /* Multicast Frame */ +#define RX_STS_TL_ (0x00000080) /* Frame too long */ +#define RX_STS_CS_ (0x00000040) /* Collision Seen */ +#define RX_STS_FT_ (0x00000020) /* Frame Type */ +#define RX_STS_RW_ (0x00000010) /* Receive Watchdog */ +#define RX_STS_ME_ (0x00000008) /* Mii Error */ +#define RX_STS_DB_ (0x00000004) /* Dribbling */ +#define RX_STS_CRC_ (0x00000002) /* CRC Error */ + +/* SCSRs */ +#define ID_REV (0x00) +#define ID_REV_CHIP_ID_MASK_ (0xFFFF0000) +#define ID_REV_CHIP_REV_MASK_ (0x0000FFFF) +#define ID_REV_CHIP_ID_9500_ (0x9500) + +#define INT_STS (0x08) +#define INT_STS_TX_STOP_ (0x00020000) +#define INT_STS_RX_STOP_ (0x00010000) +#define INT_STS_PHY_INT_ (0x00008000) +#define INT_STS_TXE_ (0x00004000) +#define INT_STS_TDFU_ (0x00002000) +#define INT_STS_TDFO_ (0x00001000) +#define INT_STS_RXDF_ (0x00000800) +#define INT_STS_GPIOS_ (0x000007FF) + +#define RX_CFG (0x0C) +#define RX_FIFO_FLUSH_ (0x00000001) + +#define TX_CFG (0x10) +#define TX_CFG_ON_ (0x00000004) +#define TX_CFG_STOP_ (0x00000002) +#define TX_CFG_FIFO_FLUSH_ (0x00000001) + +#define HW_CFG (0x14) +#define HW_CFG_BIR_ (0x00001000) +#define HW_CFG_LEDB_ (0x00000800) +#define HW_CFG_RXDOFF_ (0x00000600) +#define HW_CFG_DRP_ (0x00000040) +#define HW_CFG_MEF_ (0x00000020) +#define HW_CFG_LRST_ (0x00000008) +#define HW_CFG_PSEL_ (0x00000004) +#define HW_CFG_BCE_ (0x00000002) +#define HW_CFG_SRST_ (0x00000001) + +#define PM_CTRL (0x20) +#define PM_CTL_DEV_RDY_ (0x00000080) +#define PM_CTL_SUS_MODE_ (0x00000060) +#define PM_CTL_SUS_MODE_0 (0x00000000) +#define PM_CTL_SUS_MODE_1 (0x00000020) +#define PM_CTL_SUS_MODE_2 (0x00000060) +#define PM_CTL_PHY_RST_ (0x00000010) +#define PM_CTL_WOL_EN_ (0x00000008) +#define PM_CTL_ED_EN_ (0x00000004) +#define PM_CTL_WUPS_ (0x00000003) +#define PM_CTL_WUPS_NO_ (0x00000000) +#define PM_CTL_WUPS_ED_ (0x00000001) +#define PM_CTL_WUPS_WOL_ (0x00000002) +#define PM_CTL_WUPS_MULTI_ (0x00000003) + +#define LED_GPIO_CFG (0x24) +#define LED_GPIO_CFG_SPD_LED (0x01000000) +#define LED_GPIO_CFG_LNK_LED (0x00100000) +#define LED_GPIO_CFG_FDX_LED (0x00010000) + +#define GPIO_CFG (0x28) + +#define AFC_CFG (0x2C) + +/* Hi watermark = 15.5Kb (~10 mtu pkts) */ +/* low watermark = 3k (~2 mtu pkts) */ +/* backpressure duration = ~ 350us */ +/* Apply FC on any frame. */ +#define AFC_CFG_DEFAULT (0x00F830A1) + +#define E2P_CMD (0x30) +#define E2P_CMD_BUSY_ (0x80000000) +#define E2P_CMD_MASK_ (0x70000000) +#define E2P_CMD_READ_ (0x00000000) +#define E2P_CMD_EWDS_ (0x10000000) +#define E2P_CMD_EWEN_ (0x20000000) +#define E2P_CMD_WRITE_ (0x30000000) +#define E2P_CMD_WRAL_ (0x40000000) +#define E2P_CMD_ERASE_ (0x50000000) +#define E2P_CMD_ERAL_ (0x60000000) +#define E2P_CMD_RELOAD_ (0x70000000) +#define E2P_CMD_TIMEOUT_ (0x00000400) +#define E2P_CMD_LOADED_ (0x00000200) +#define E2P_CMD_ADDR_ (0x000001FF) + +#define MAX_EEPROM_SIZE (512) + +#define E2P_DATA (0x34) +#define E2P_DATA_MASK_ (0x000000FF) + +#define BURST_CAP (0x38) + +#define GPIO_WAKE (0x64) + +#define INT_EP_CTL (0x68) +#define INT_EP_CTL_INTEP_ (0x80000000) +#define INT_EP_CTL_MACRTO_ (0x00080000) +#define INT_EP_CTL_TX_STOP_ (0x00020000) +#define INT_EP_CTL_RX_STOP_ (0x00010000) +#define INT_EP_CTL_PHY_INT_ (0x00008000) +#define INT_EP_CTL_TXE_ (0x00004000) +#define INT_EP_CTL_TDFU_ (0x00002000) +#define INT_EP_CTL_TDFO_ (0x00001000) +#define INT_EP_CTL_RXDF_ (0x00000800) +#define INT_EP_CTL_GPIOS_ (0x000007FF) + +#define BULK_IN_DLY (0x6C) + +/* MAC CSRs */ +#define MAC_CR (0x100) +#define MAC_CR_RXALL_ (0x80000000) +#define MAC_CR_RCVOWN_ (0x00800000) +#define MAC_CR_LOOPBK_ (0x00200000) +#define MAC_CR_FDPX_ (0x00100000) +#define MAC_CR_MCPAS_ (0x00080000) +#define MAC_CR_PRMS_ (0x00040000) +#define MAC_CR_INVFILT_ (0x00020000) +#define MAC_CR_PASSBAD_ (0x00010000) +#define MAC_CR_HFILT_ (0x00008000) +#define MAC_CR_HPFILT_ (0x00002000) +#define MAC_CR_LCOLL_ (0x00001000) +#define MAC_CR_BCAST_ (0x00000800) +#define MAC_CR_DISRTY_ (0x00000400) +#define MAC_CR_PADSTR_ (0x00000100) +#define MAC_CR_BOLMT_MASK (0x000000C0) +#define MAC_CR_DFCHK_ (0x00000020) +#define MAC_CR_TXEN_ (0x00000008) +#define MAC_CR_RXEN_ (0x00000004) + +#define ADDRH (0x104) + +#define ADDRL (0x108) + +#define HASHH (0x10C) + +#define HASHL (0x110) + +#define MII_ADDR (0x114) +#define MII_WRITE_ (0x02) +#define MII_BUSY_ (0x01) +#define MII_READ_ (0x00) /* ~of MII Write bit */ + +#define MII_DATA (0x118) + +#define FLOW (0x11C) +#define FLOW_FCPT_ (0xFFFF0000) +#define FLOW_FCPASS_ (0x00000004) +#define FLOW_FCEN_ (0x00000002) +#define FLOW_FCBSY_ (0x00000001) + +#define VLAN1 (0x120) + +#define VLAN2 (0x124) + +#define WUFF (0x128) + +#define WUCSR (0x12C) + +#define COE_CR (0x130) +#define Tx_COE_EN_ (0x00010000) +#define Rx_COE_MODE_ (0x00000002) +#define Rx_COE_EN_ (0x00000001) + +/* Vendor-specific PHY Definitions */ + +/* Mode Control/Status Register */ +#define PHY_MODE_CTRL_STS (17) +#define MODE_CTRL_STS_EDPWRDOWN_ ((u16)0x2000) +#define MODE_CTRL_STS_ENERGYON_ ((u16)0x0002) + +#define SPECIAL_CTRL_STS (27) +#define SPECIAL_CTRL_STS_OVRRD_AMDIX_ ((u16)0x8000) +#define SPECIAL_CTRL_STS_AMDIX_ENABLE_ ((u16)0x4000) +#define SPECIAL_CTRL_STS_AMDIX_STATE_ ((u16)0x2000) + +#define PHY_INT_SRC (29) +#define PHY_INT_SRC_ENERGY_ON_ ((u16)0x0080) +#define PHY_INT_SRC_ANEG_COMP_ ((u16)0x0040) +#define PHY_INT_SRC_REMOTE_FAULT_ ((u16)0x0020) +#define PHY_INT_SRC_LINK_DOWN_ ((u16)0x0010) + +#define PHY_INT_MASK (30) +#define PHY_INT_MASK_ENERGY_ON_ ((u16)0x0080) +#define PHY_INT_MASK_ANEG_COMP_ ((u16)0x0040) +#define PHY_INT_MASK_REMOTE_FAULT_ ((u16)0x0020) +#define PHY_INT_MASK_LINK_DOWN_ ((u16)0x0010) +#define PHY_INT_MASK_DEFAULT_ (PHY_INT_MASK_ANEG_COMP_ | \ + PHY_INT_MASK_LINK_DOWN_) + +#define PHY_SPECIAL (31) +#define PHY_SPECIAL_SPD_ ((u16)0x001C) +#define PHY_SPECIAL_SPD_10HALF_ ((u16)0x0004) +#define PHY_SPECIAL_SPD_10FULL_ ((u16)0x0014) +#define PHY_SPECIAL_SPD_100HALF_ ((u16)0x0008) +#define PHY_SPECIAL_SPD_100FULL_ ((u16)0x0018) + +/* USB Vendor Requests */ +#define USB_VENDOR_REQUEST_WRITE_REGISTER 0xA0 +#define USB_VENDOR_REQUEST_READ_REGISTER 0xA1 +#define USB_VENDOR_REQUEST_GET_STATS 0xA2 + +/* Interrupt Endpoint status word bitfields */ +#define INT_ENP_TX_STOP_ ((u32)BIT(17)) +#define INT_ENP_RX_STOP_ ((u32)BIT(16)) +#define INT_ENP_PHY_INT_ ((u32)BIT(15)) +#define INT_ENP_TXE_ ((u32)BIT(14)) +#define INT_ENP_TDFU_ ((u32)BIT(13)) +#define INT_ENP_TDFO_ ((u32)BIT(12)) +#define INT_ENP_RXDF_ ((u32)BIT(11)) + +#endif /* _SMSC95XX_H */ -- cgit v1.2.3 From 102f1811d0d3450f57d060136813b507fedd5af4 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Mar 2011 18:55:19 +0100 Subject: net smc911x: Add LAN9221 support Signed-off-by: Sascha Hauer --- drivers/net/smc911x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/smc911x.c b/drivers/net/smc911x.c index e5c0e7e08e..dc5477b93c 100644 --- a/drivers/net/smc911x.c +++ b/drivers/net/smc911x.c @@ -365,6 +365,7 @@ #define CHIP_9216 0x116a #define CHIP_9217 0x117a #define CHIP_9218 0x118a +#define CHIP_9221 0x9221 struct smc911x_priv { struct mii_device miidev; @@ -385,6 +386,7 @@ static const struct chip_id chip_ids[] = { { CHIP_9216, "LAN9216" }, { CHIP_9217, "LAN9217" }, { CHIP_9218, "LAN9218" }, + { CHIP_9221, "LAN9221" }, { 0, NULL }, }; -- cgit v1.2.3 From 4cdf069d2ab0f8e591dc1a46add8fa984c82f944 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 18 Mar 2011 08:23:40 +0100 Subject: mci: Add omap hsmmc driver This driver is based on the U-Boot omap hsmmc driver by Sukumar Ghorai . Signed-off-by: Sascha Hauer --- drivers/mci/Kconfig | 7 + drivers/mci/Makefile | 1 + drivers/mci/omap_hsmmc.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 590 insertions(+) create mode 100644 drivers/mci/omap_hsmmc.c diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig index b11f2670f5..9cb7eb6f85 100644 --- a/drivers/mci/Kconfig +++ b/drivers/mci/Kconfig @@ -61,4 +61,11 @@ config MCI_IMX_ESDHC_PIO help mostly useful for debugging. Normally you should use DMA. +config MCI_OMAP_HSMMC + bool "OMAP HSMMC" + depends on ARCH_OMAP4 + help + Enable this entry to add support to read and write SD cards on a + OMAP4 based system. + endif diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile index f175bbac59..2bb9a93b9a 100644 --- a/drivers/mci/Makefile +++ b/drivers/mci/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_MCI_MXS) += mxs.o obj-$(CONFIG_MCI_S3C) += s3c.o obj-$(CONFIG_MCI_IMX) += imx.o obj-$(CONFIG_MCI_IMX_ESDHC) += imx-esdhc.o +obj-$(CONFIG_MCI_OMAP_HSMMC) += omap_hsmmc.o diff --git a/drivers/mci/omap_hsmmc.c b/drivers/mci/omap_hsmmc.c new file mode 100644 index 0000000000..ce4ea8ba88 --- /dev/null +++ b/drivers/mci/omap_hsmmc.c @@ -0,0 +1,582 @@ +/* + * (C) Copyright 2008 + * Texas Instruments, + * Sukumar Ghorai + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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's version 2 of + * the License. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +/* #define DEBUG */ +#include +#include +#include +#include +#include +#include +#include +#include + +struct hsmmc { + unsigned char res1[0x10]; + unsigned int sysconfig; /* 0x10 */ + unsigned int sysstatus; /* 0x14 */ + unsigned char res2[0x14]; + unsigned int con; /* 0x2C */ + unsigned char res3[0xD4]; + unsigned int blk; /* 0x104 */ + unsigned int arg; /* 0x108 */ + unsigned int cmd; /* 0x10C */ + unsigned int rsp10; /* 0x110 */ + unsigned int rsp32; /* 0x114 */ + unsigned int rsp54; /* 0x118 */ + unsigned int rsp76; /* 0x11C */ + unsigned int data; /* 0x120 */ + unsigned int pstate; /* 0x124 */ + unsigned int hctl; /* 0x128 */ + unsigned int sysctl; /* 0x12C */ + unsigned int stat; /* 0x130 */ + unsigned int ie; /* 0x134 */ + unsigned char res4[0x8]; + unsigned int capa; /* 0x140 */ +}; + +/* + * OMAP HS MMC Bit definitions + */ +#define MMC_SOFTRESET (0x1 << 1) +#define RESETDONE (0x1 << 0) +#define NOOPENDRAIN (0x0 << 0) +#define OPENDRAIN (0x1 << 0) +#define OD (0x1 << 0) +#define INIT_NOINIT (0x0 << 1) +#define INIT_INITSTREAM (0x1 << 1) +#define HR_NOHOSTRESP (0x0 << 2) +#define STR_BLOCK (0x0 << 3) +#define MODE_FUNC (0x0 << 4) +#define DW8_1_4BITMODE (0x0 << 5) +#define MIT_CTO (0x0 << 6) +#define CDP_ACTIVEHIGH (0x0 << 7) +#define WPP_ACTIVEHIGH (0x0 << 8) +#define RESERVED_MASK (0x3 << 9) +#define CTPL_MMC_SD (0x0 << 11) +#define BLEN_512BYTESLEN (0x200 << 0) +#define NBLK_STPCNT (0x0 << 16) +#define DE_DISABLE (0x0 << 0) +#define BCE_DISABLE (0x0 << 1) +#define BCE_ENABLE (0x1 << 1) +#define ACEN_DISABLE (0x0 << 2) +#define DDIR_OFFSET (4) +#define DDIR_MASK (0x1 << 4) +#define DDIR_WRITE (0x0 << 4) +#define DDIR_READ (0x1 << 4) +#define MSBS_SGLEBLK (0x0 << 5) +#define MSBS_MULTIBLK (0x1 << 5) +#define RSP_TYPE_OFFSET (16) +#define RSP_TYPE_MASK (0x3 << 16) +#define RSP_TYPE_NORSP (0x0 << 16) +#define RSP_TYPE_LGHT136 (0x1 << 16) +#define RSP_TYPE_LGHT48 (0x2 << 16) +#define RSP_TYPE_LGHT48B (0x3 << 16) +#define CCCE_NOCHECK (0x0 << 19) +#define CCCE_CHECK (0x1 << 19) +#define CICE_NOCHECK (0x0 << 20) +#define CICE_CHECK (0x1 << 20) +#define DP_OFFSET (21) +#define DP_MASK (0x1 << 21) +#define DP_NO_DATA (0x0 << 21) +#define DP_DATA (0x1 << 21) +#define CMD_TYPE_NORMAL (0x0 << 22) +#define INDEX_OFFSET (24) +#define INDEX_MASK (0x3f << 24) +#define INDEX(i) (i << 24) +#define DATI_MASK (0x1 << 1) +#define DATI_CMDDIS (0x1 << 1) +#define DTW_1_BITMODE (0x0 << 1) +#define DTW_4_BITMODE (0x1 << 1) +#define DTW_8_BITMODE (0x1 << 5) /* CON[DW8]*/ +#define SDBP_PWROFF (0x0 << 8) +#define SDBP_PWRON (0x1 << 8) +#define SDVS_1V8 (0x5 << 9) +#define SDVS_3V0 (0x6 << 9) +#define ICE_MASK (0x1 << 0) +#define ICE_STOP (0x0 << 0) +#define ICS_MASK (0x1 << 1) +#define ICS_NOTREADY (0x0 << 1) +#define ICE_OSCILLATE (0x1 << 0) +#define CEN_MASK (0x1 << 2) +#define CEN_DISABLE (0x0 << 2) +#define CEN_ENABLE (0x1 << 2) +#define CLKD_OFFSET (6) +#define CLKD_MASK (0x3FF << 6) +#define DTO_MASK (0xF << 16) +#define DTO_15THDTO (0xE << 16) +#define SOFTRESETALL (0x1 << 24) +#define CC_MASK (0x1 << 0) +#define TC_MASK (0x1 << 1) +#define BWR_MASK (0x1 << 4) +#define BRR_MASK (0x1 << 5) +#define ERRI_MASK (0x1 << 15) +#define IE_CC (0x01 << 0) +#define IE_TC (0x01 << 1) +#define IE_BWR (0x01 << 4) +#define IE_BRR (0x01 << 5) +#define IE_CTO (0x01 << 16) +#define IE_CCRC (0x01 << 17) +#define IE_CEB (0x01 << 18) +#define IE_CIE (0x01 << 19) +#define IE_DTO (0x01 << 20) +#define IE_DCRC (0x01 << 21) +#define IE_DEB (0x01 << 22) +#define IE_CERR (0x01 << 28) +#define IE_BADA (0x01 << 29) + +#define VS30_3V0SUP (1 << 25) +#define VS18_1V8SUP (1 << 26) + +/* Driver definitions */ +#define MMCSD_SECTOR_SIZE 512 +#define MMC_CARD 0 +#define SD_CARD 1 +#define BYTE_MODE 0 +#define SECTOR_MODE 1 +#define CLK_INITSEQ 0 +#define CLK_400KHZ 1 +#define CLK_MISC 2 + +#define RSP_TYPE_NONE (RSP_TYPE_NORSP | CCCE_NOCHECK | CICE_NOCHECK) +#define MMC_CMD0 (INDEX(0) | RSP_TYPE_NONE | DP_NO_DATA | DDIR_WRITE) + +/* Clock Configurations and Macros */ +#define MMC_CLOCK_REFERENCE 96 /* MHz */ + +#define mmc_reg_out(addr, mask, val)\ + writel((readl(addr) & (~(mask))) | ((val) & (mask)), (addr)) + +struct omap_hsmmc { + struct mci_host mci; + struct device_d *dev; + struct hsmmc *base; +}; + +#define to_hsmmc(mci) container_of(mci, struct omap_hsmmc, mci) + +static int mmc_init_stream(struct omap_hsmmc *hsmmc) +{ + uint64_t start; + struct hsmmc *mmc_base = hsmmc->base; + + writel(readl(&mmc_base->con) | INIT_INITSTREAM, &mmc_base->con); + + writel(MMC_CMD0, &mmc_base->cmd); + start = get_time_ns(); + while (!(readl(&mmc_base->stat) & CC_MASK)) { + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for cc!\n"); + return -ETIMEDOUT; + } + } + writel(CC_MASK, &mmc_base->stat); + writel(MMC_CMD0, &mmc_base->cmd); + + start = get_time_ns(); + while (!(readl(&mmc_base->stat) & CC_MASK)) { + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for cc2!\n"); + return -ETIMEDOUT; + } + } + writel(readl(&mmc_base->con) & ~INIT_INITSTREAM, &mmc_base->con); + + return 0; +} + +static int mmc_init_setup(struct mci_host *mci, struct device_d *dev) +{ + struct omap_hsmmc *hsmmc = to_hsmmc(mci); + struct hsmmc *mmc_base = hsmmc->base; + unsigned int reg_val; + unsigned int dsor; + uint64_t start; + + writel(readl(&mmc_base->sysconfig) | MMC_SOFTRESET, + &mmc_base->sysconfig); + + start = get_time_ns(); + while ((readl(&mmc_base->sysstatus) & RESETDONE) == 0) { + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for cc2!\n"); + return -ETIMEDOUT; + } + } + + writel(readl(&mmc_base->sysctl) | SOFTRESETALL, &mmc_base->sysctl); + + start = get_time_ns(); + while ((readl(&mmc_base->sysctl) & SOFTRESETALL) != 0x0) { + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for softresetall!\n"); + return -ETIMEDOUT; + } + } + + writel(DTW_1_BITMODE | SDBP_PWROFF | SDVS_3V0, &mmc_base->hctl); + writel(readl(&mmc_base->capa) | VS30_3V0SUP | VS18_1V8SUP, + &mmc_base->capa); + + reg_val = readl(&mmc_base->con) & RESERVED_MASK; + + writel(CTPL_MMC_SD | reg_val | WPP_ACTIVEHIGH | CDP_ACTIVEHIGH | + MIT_CTO | DW8_1_4BITMODE | MODE_FUNC | STR_BLOCK | + HR_NOHOSTRESP | INIT_NOINIT | NOOPENDRAIN, &mmc_base->con); + + dsor = 240; + mmc_reg_out(&mmc_base->sysctl, (ICE_MASK | DTO_MASK | CEN_MASK), + (ICE_STOP | DTO_15THDTO | CEN_DISABLE)); + mmc_reg_out(&mmc_base->sysctl, ICE_MASK | CLKD_MASK, + (dsor << CLKD_OFFSET) | ICE_OSCILLATE); + + start = get_time_ns(); + while ((readl(&mmc_base->sysctl) & ICS_MASK) == ICS_NOTREADY) { + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for ics!\n"); + return -ETIMEDOUT; + } + } + + writel(readl(&mmc_base->sysctl) | CEN_ENABLE, &mmc_base->sysctl); + + writel(readl(&mmc_base->hctl) | SDBP_PWRON, &mmc_base->hctl); + + writel(IE_BADA | IE_CERR | IE_DEB | IE_DCRC | IE_DTO | IE_CIE | + IE_CEB | IE_CCRC | IE_CTO | IE_BRR | IE_BWR | IE_TC | IE_CC, + &mmc_base->ie); + + return mmc_init_stream(hsmmc); +} + +static int mmc_read_data(struct omap_hsmmc *hsmmc, char *buf, unsigned int size) +{ + struct hsmmc *mmc_base = hsmmc->base; + unsigned int *output_buf = (unsigned int *)buf; + unsigned int mmc_stat; + unsigned int count; + + /* + * Start Polled Read + */ + count = (size > MMCSD_SECTOR_SIZE) ? MMCSD_SECTOR_SIZE : size; + count /= 4; + + while (size) { + uint64_t start = get_time_ns(); + do { + mmc_stat = readl(&mmc_base->stat); + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for status!\n"); + return -ETIMEDOUT; + } + } while (mmc_stat == 0); + + if ((mmc_stat & ERRI_MASK) != 0) + return 1; + + if (mmc_stat & BRR_MASK) { + unsigned int k; + + writel(readl(&mmc_base->stat) | BRR_MASK, + &mmc_base->stat); + for (k = 0; k < count; k++) { + *output_buf = readl(&mmc_base->data); + output_buf++; + } + size -= (count*4); + } + + if (mmc_stat & BWR_MASK) + writel(readl(&mmc_base->stat) | BWR_MASK, + &mmc_base->stat); + + if (mmc_stat & TC_MASK) { + writel(readl(&mmc_base->stat) | TC_MASK, + &mmc_base->stat); + break; + } + } + return 0; +} + +static int mmc_write_data(struct omap_hsmmc *hsmmc, const char *buf, unsigned int size) +{ + struct hsmmc *mmc_base = hsmmc->base; + unsigned int *input_buf = (unsigned int *)buf; + unsigned int mmc_stat; + unsigned int count; + + /* + * Start Polled Read + */ + count = (size > MMCSD_SECTOR_SIZE) ? MMCSD_SECTOR_SIZE : size; + count /= 4; + + while (size) { + uint64_t start = get_time_ns(); + do { + mmc_stat = readl(&mmc_base->stat); + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for status!\n"); + return -ETIMEDOUT; + } + } while (mmc_stat == 0); + + if ((mmc_stat & ERRI_MASK) != 0) + return 1; + + if (mmc_stat & BWR_MASK) { + unsigned int k; + + writel(readl(&mmc_base->stat) | BWR_MASK, + &mmc_base->stat); + for (k = 0; k < count; k++) { + writel(*input_buf, &mmc_base->data); + input_buf++; + } + size -= (count * 4); + } + + if (mmc_stat & BRR_MASK) + writel(readl(&mmc_base->stat) | BRR_MASK, + &mmc_base->stat); + + if (mmc_stat & TC_MASK) { + writel(readl(&mmc_base->stat) | TC_MASK, + &mmc_base->stat); + break; + } + } + return 0; +} + +static int mmc_send_cmd(struct mci_host *mci, struct mci_cmd *cmd, + struct mci_data *data) +{ + struct omap_hsmmc *hsmmc = to_hsmmc(mci); + struct hsmmc *mmc_base = hsmmc->base; + unsigned int flags, mmc_stat; + uint64_t start; + + start = get_time_ns(); + while ((readl(&mmc_base->pstate) & DATI_MASK) == DATI_CMDDIS) { + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for cmddis!\n"); + return -ETIMEDOUT; + } + } + + writel(0xFFFFFFFF, &mmc_base->stat); + start = get_time_ns(); + while (readl(&mmc_base->stat)) { + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for stat!\n"); + return -ETIMEDOUT; + } + } + + /* + * CMDREG + * CMDIDX[13:8] : Command index + * DATAPRNT[5] : Data Present Select + * ENCMDIDX[4] : Command Index Check Enable + * ENCMDCRC[3] : Command CRC Check Enable + * RSPTYP[1:0] + * 00 = No Response + * 01 = Length 136 + * 10 = Length 48 + * 11 = Length 48 Check busy after response + */ + /* Delay added before checking the status of frq change + * retry not supported by mmc.c(core file) + */ + if (cmd->cmdidx == SD_CMD_APP_SEND_SCR) + udelay(50000); /* wait 50 ms */ + + if (!(cmd->resp_type & MMC_RSP_PRESENT)) + flags = 0; + else if (cmd->resp_type & MMC_RSP_136) + flags = RSP_TYPE_LGHT136 | CICE_NOCHECK; + else if (cmd->resp_type & MMC_RSP_BUSY) + flags = RSP_TYPE_LGHT48B; + else + flags = RSP_TYPE_LGHT48; + + /* enable default flags */ + flags = flags | (CMD_TYPE_NORMAL | CICE_NOCHECK | CCCE_NOCHECK | + MSBS_SGLEBLK | ACEN_DISABLE | BCE_DISABLE | DE_DISABLE); + + if (cmd->resp_type & MMC_RSP_CRC) + flags |= CCCE_CHECK; + if (cmd->resp_type & MMC_RSP_OPCODE) + flags |= CICE_CHECK; + + if (data) { + if ((cmd->cmdidx == MMC_CMD_READ_MULTIPLE_BLOCK) || + (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)) { + flags |= (MSBS_MULTIBLK | BCE_ENABLE); + data->blocksize = 512; + writel(data->blocksize | (data->blocks << 16), + &mmc_base->blk); + } else + writel(data->blocksize | NBLK_STPCNT, &mmc_base->blk); + + if (data->flags & MMC_DATA_READ) + flags |= (DP_DATA | DDIR_READ); + else + flags |= (DP_DATA | DDIR_WRITE); + } + + writel(cmd->cmdarg, &mmc_base->arg); + writel((cmd->cmdidx << 24) | flags, &mmc_base->cmd); + + start = get_time_ns(); + do { + mmc_stat = readl(&mmc_base->stat); + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timeout: No status update\n"); + return -ETIMEDOUT; + } + } while (!mmc_stat); + + if ((mmc_stat & IE_CTO) != 0) + return -ETIMEDOUT; + else if ((mmc_stat & ERRI_MASK) != 0) + return -1; + + if (mmc_stat & CC_MASK) { + writel(CC_MASK, &mmc_base->stat); + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + /* response type 2 */ + cmd->response[3] = readl(&mmc_base->rsp10); + cmd->response[2] = readl(&mmc_base->rsp32); + cmd->response[1] = readl(&mmc_base->rsp54); + cmd->response[0] = readl(&mmc_base->rsp76); + } else + /* response types 1, 1b, 3, 4, 5, 6 */ + cmd->response[0] = readl(&mmc_base->rsp10); + } + } + + if (data && (data->flags & MMC_DATA_READ)) + mmc_read_data(hsmmc, data->dest, data->blocksize * data->blocks); + else if (data && (data->flags & MMC_DATA_WRITE)) + mmc_write_data(hsmmc, data->src, data->blocksize * data->blocks); + + return 0; +} + +static void mmc_set_ios(struct mci_host *mci, struct device_d *dev, + unsigned bus_width, unsigned clock) +{ + struct omap_hsmmc *hsmmc = to_hsmmc(mci); + struct hsmmc *mmc_base = hsmmc->base; + unsigned int dsor = 0; + uint64_t start; + + /* configue bus width */ + switch (bus_width) { + case 8: + writel(readl(&mmc_base->con) | DTW_8_BITMODE, + &mmc_base->con); + break; + + case 4: + writel(readl(&mmc_base->con) & ~DTW_8_BITMODE, + &mmc_base->con); + writel(readl(&mmc_base->hctl) | DTW_4_BITMODE, + &mmc_base->hctl); + break; + + case 1: + default: + writel(readl(&mmc_base->con) & ~DTW_8_BITMODE, + &mmc_base->con); + writel(readl(&mmc_base->hctl) & ~DTW_4_BITMODE, + &mmc_base->hctl); + break; + } + + /* configure clock with 96Mhz system clock. + */ + if (clock != 0) { + dsor = (MMC_CLOCK_REFERENCE * 1000000 / clock); + if ((MMC_CLOCK_REFERENCE * 1000000) / dsor > clock) + dsor++; + } + + mmc_reg_out(&mmc_base->sysctl, (ICE_MASK | DTO_MASK | CEN_MASK), + (ICE_STOP | DTO_15THDTO | CEN_DISABLE)); + + mmc_reg_out(&mmc_base->sysctl, ICE_MASK | CLKD_MASK, + (dsor << CLKD_OFFSET) | ICE_OSCILLATE); + + start = get_time_ns(); + while ((readl(&mmc_base->sysctl) & ICS_MASK) == ICS_NOTREADY) { + if (is_timeout(start, SECOND)) { + dev_dbg(hsmmc->dev, "timedout waiting for ics!\n"); + return; + } + } + writel(readl(&mmc_base->sysctl) | CEN_ENABLE, &mmc_base->sysctl); +} + +static int mxcmci_probe(struct device_d *dev) +{ + struct omap_hsmmc *hsmmc; + + hsmmc = xzalloc(sizeof(*hsmmc)); + + hsmmc->dev = dev; + hsmmc->mci.send_cmd = mmc_send_cmd; + hsmmc->mci.set_ios = mmc_set_ios; + hsmmc->mci.init = mmc_init_setup; + hsmmc->mci.host_caps = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS; + + hsmmc->base = (struct hsmmc *)dev->map_base; + + hsmmc->mci.voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + hsmmc->mci.f_min = 400000; + hsmmc->mci.f_max = 52000000; + + mci_register(&hsmmc->mci); + + return 0; +} + +static struct driver_d mxcmci_driver = { + .name = "omap-hsmmc", + .probe = mxcmci_probe, +}; + +static int mxcmci_init_driver(void) +{ + register_driver(&mxcmci_driver); + return 0; +} + +device_initcall(mxcmci_init_driver); + -- cgit v1.2.3 From 2a1db7330a37790348256a2dbe021c4e7e61020b Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 30 Mar 2011 23:56:58 +0200 Subject: mtd nand omap: Pass ecc mode from platform As we will get more ecc modes pass through the exact mode from the platform, not only a flag. Signed-off-by: Sascha Hauer --- arch/arm/boards/omap/board-beagle.c | 2 +- arch/arm/boards/omap/devices-gpmc-nand.c | 5 +++-- arch/arm/mach-omap/include/mach/gpmc_nand.h | 14 ++++++++------ drivers/mtd/nand/nand_omap_gpmc.c | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/arch/arm/boards/omap/board-beagle.c b/arch/arm/boards/omap/board-beagle.c index 6de2cce7d9..17a349c42a 100644 --- a/arch/arm/boards/omap/board-beagle.c +++ b/arch/arm/boards/omap/board-beagle.c @@ -332,7 +332,7 @@ static int beagle_devices_init(void) /* WP is made high and WAIT1 active Low */ gpmc_generic_init(0x10); #endif - gpmc_generic_nand_devices_init(0, 16, 1); + gpmc_generic_nand_devices_init(0, 16, OMAP_ECC_HAMMING_CODE_HW_ROMCODE); armlinux_add_dram(&sdram_dev); armlinux_set_bootparams((void *)0x80000100); diff --git a/arch/arm/boards/omap/devices-gpmc-nand.c b/arch/arm/boards/omap/devices-gpmc-nand.c index 4369aa0029..c2a2b0d1b5 100644 --- a/arch/arm/boards/omap/devices-gpmc-nand.c +++ b/arch/arm/boards/omap/devices-gpmc-nand.c @@ -84,7 +84,8 @@ static struct device_d gpmc_generic_nand_nand_device = { * * @return success/fail based on device funtion */ -int gpmc_generic_nand_devices_init(int cs, int width, int hwecc) +int gpmc_generic_nand_devices_init(int cs, int width, + enum gpmc_ecc_mode eccmode) { nand_plat.cs = cs; @@ -94,7 +95,7 @@ int gpmc_generic_nand_devices_init(int cs, int width, int hwecc) nand_cfg.cfg[0] = GPMC_CONF1_VALx8; nand_plat.device_width = width; - nand_plat.plat_options = hwecc ? NAND_HWECC_ENABLE : 0; + nand_plat.ecc_mode = eccmode; /* Configure GPMC CS before register */ gpmc_cs_config(nand_plat.cs, &nand_cfg); diff --git a/arch/arm/mach-omap/include/mach/gpmc_nand.h b/arch/arm/mach-omap/include/mach/gpmc_nand.h index e23faabcac..a57d2a9e19 100644 --- a/arch/arm/mach-omap/include/mach/gpmc_nand.h +++ b/arch/arm/mach-omap/include/mach/gpmc_nand.h @@ -33,6 +33,11 @@ #include #include +enum gpmc_ecc_mode { + OMAP_ECC_SOFT, + OMAP_ECC_HAMMING_CODE_HW_ROMCODE, +}; + /** omap nand platform data structure */ struct gpmc_nand_platform_data { /** Chip select you want to use */ @@ -46,6 +51,8 @@ struct gpmc_nand_platform_data { * platform specific configs here */ unsigned short plat_options; + /** ecc mode to use */ + enum gpmc_ecc_mode ecc_mode; /** setup any special options */ unsigned int options; /** set up device access as 8,16 as per GPMC config */ @@ -68,11 +75,6 @@ struct gpmc_nand_platform_data { #define NAND_WAITPOL_HIGH (1 << 0) #define NAND_WAITPOL_MASK (1 << 0) -/** plat_options: hw ecc enabled */ -#define NAND_HWECC_ENABLE (1 << 1) -/** plat_options: hw ecc disabled */ -#define NAND_HWECC_MASK (1 << 1) - -int gpmc_generic_nand_devices_init(int cs, int width, int hwecc); +int gpmc_generic_nand_devices_init(int cs, int width, enum gpmc_ecc_mode); #endif /* __ASM_OMAP_NAND_GPMC_H */ diff --git a/drivers/mtd/nand/nand_omap_gpmc.c b/drivers/mtd/nand/nand_omap_gpmc.c index 7c9bc3205a..86fe6b9f83 100644 --- a/drivers/mtd/nand/nand_omap_gpmc.c +++ b/drivers/mtd/nand/nand_omap_gpmc.c @@ -548,7 +548,7 @@ static int gpmc_nand_probe(struct device_d *pdev) goto out_release_mem; } - if (pdata->plat_options & NAND_HWECC_ENABLE) { + if (pdata->ecc_mode == OMAP_ECC_HAMMING_CODE_HW_ROMCODE) { nand->ecc.layout = layout; /* Program how many columns we expect+ -- cgit v1.2.3 From 694363574e802745089926fe786c27275acad954 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 30 Mar 2011 21:55:48 +0200 Subject: mtd nand omap: make ecc mode runtime configurable On omap we use different ecc modes for different purposes. The initial boot code has to be written with hardware ecc whereas Linux usually uses software ecc. To be able to write in both modes with a sinlge barebox image introduce a eccmode device parameter. Signed-off-by: Sascha Hauer --- arch/arm/mach-omap/include/mach/gpmc.h | 1 + drivers/mtd/nand/nand_omap_gpmc.c | 269 ++++++++++++++++++++++----------- 2 files changed, 182 insertions(+), 88 deletions(-) diff --git a/arch/arm/mach-omap/include/mach/gpmc.h b/arch/arm/mach-omap/include/mach/gpmc.h index a658cf00e7..3ddc5f5b87 100644 --- a/arch/arm/mach-omap/include/mach/gpmc.h +++ b/arch/arm/mach-omap/include/mach/gpmc.h @@ -66,6 +66,7 @@ #define GPMC_ECC7_RESULT (0x218) #define GPMC_ECC8_RESULT (0x21C) #define GPMC_ECC9_RESULT (0x220) +#define GPMC_ECC_BCH_RESULT_0 0x240 #define GPMC_CONFIG1_0 (0x60) #define GPMC_CONFIG1_1 (0x90) diff --git a/drivers/mtd/nand/nand_omap_gpmc.c b/drivers/mtd/nand/nand_omap_gpmc.c index 86fe6b9f83..889009bbf0 100644 --- a/drivers/mtd/nand/nand_omap_gpmc.c +++ b/drivers/mtd/nand/nand_omap_gpmc.c @@ -86,6 +86,13 @@ #endif #define gpmcnand_err(ARGS...) fprintf(stderr, "omapnand: " ARGS); +int decode_bch(int select_4_8, unsigned char *ecc, unsigned int *err_loc); + +static char *ecc_mode_strings[] = { + "software", + "hamming_hw_romcode", +}; + /** internal structure maintained for nand information */ struct gpmc_nand_info { struct nand_hw_control controller; @@ -103,10 +110,21 @@ struct gpmc_nand_info { unsigned inuse:1; unsigned wait_pol:1; unsigned char ecc_parity_pairs; - unsigned int ecc_config; + enum gpmc_ecc_mode ecc_mode; }; /* Typical BOOTROM oob layouts-requires hwecc **/ +static struct nand_ecclayout omap_oobinfo; +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks + */ +static uint8_t scan_ff_pattern[] = { 0xff }; +static struct nand_bbt_descr bb_descrip_flashbased = { + .options = NAND_BBT_SCANEMPTY | NAND_BBT_SCANALLPAGES, + .offs = 0, + .len = 1, + .pattern = scan_ff_pattern, +}; /** Large Page x8 NAND device Layout */ static struct nand_ecclayout ecc_lp_x8 = { @@ -288,38 +306,51 @@ static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat, unsigned char bit; struct nand_chip *nand = (struct nand_chip *)(mtd->priv); struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); + int blockCnt = 0; gpmcnand_dbg("mtd=%x dat=%x read_ecc=%x calc_ecc=%x", (unsigned int)mtd, (unsigned int)dat, (unsigned int)read_ecc, (unsigned int)calc_ecc); - /* Regenerate the orginal ECC */ - orig_ecc = gen_true_ecc(read_ecc); - new_ecc = gen_true_ecc(calc_ecc); - /* Get the XOR of real ecc */ - res = orig_ecc ^ new_ecc; - if (res) { - /* Get the hamming width */ - hm = hweight32(res); - /* Single bit errors can be corrected! */ - if (hm == oinfo->ecc_parity_pairs) { - /* Correctable data! */ - parity_bits = res >> 16; - bit = (parity_bits & 0x7); - byte = (parity_bits >> 3) & 0x1FF; - /* Flip the bit to correct */ - dat[byte] ^= (0x1 << bit); - - } else if (hm == 1) { - gpmcnand_err("Ecc is wrong\n"); - /* ECC itself is corrupted */ - return 2; - } else { - gpmcnand_err("bad compare! failed\n"); - /* detected 2 bit error */ - return -1; + if ((nand->ecc.mode == NAND_ECC_HW) && + (nand->ecc.size == 2048)) + blockCnt = 4; + else + blockCnt = 1; + + switch (oinfo->ecc_mode) { + case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: + /* Regenerate the orginal ECC */ + orig_ecc = gen_true_ecc(read_ecc); + new_ecc = gen_true_ecc(calc_ecc); + /* Get the XOR of real ecc */ + res = orig_ecc ^ new_ecc; + if (res) { + /* Get the hamming width */ + hm = hweight32(res); + /* Single bit errors can be corrected! */ + if (hm == oinfo->ecc_parity_pairs) { + /* Correctable data! */ + parity_bits = res >> 16; + bit = (parity_bits & 0x7); + byte = (parity_bits >> 3) & 0x1FF; + /* Flip the bit to correct */ + dat[byte] ^= (0x1 << bit); + } else if (hm == 1) { + gpmcnand_err("Ecc is wrong\n"); + /* ECC itself is corrupted */ + return 2; + } else { + gpmcnand_err("bad compare! failed\n"); + /* detected 2 bit error */ + return -1; + } } + break; + default: + return -EINVAL; } + return 0; } @@ -341,54 +372,136 @@ static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, { struct nand_chip *nand = (struct nand_chip *)(mtd->priv); struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); - unsigned int val; - gpmcnand_dbg("mtd=%x dat=%x ecc_code=%x", (unsigned int)mtd, - (unsigned int)dat, (unsigned int)ecc_code); - debug("ecc 0 1 2 = %x %x %x", ecc_code[0], ecc_code[1], ecc_code[2]); - - /* Since we smartly tell mtd driver to use eccsize of 512, only - * ECC Reg1 will be used.. we just read that */ - val = readl(oinfo->gpmc_base + GPMC_ECC1_RESULT); - ecc_code[0] = val & 0xFF; - ecc_code[1] = (val >> 16) & 0xFF; - ecc_code[2] = ((val >> 8) & 0x0f) | ((val >> 20) & 0xf0); - - /* Stop reading anymore ECC vals and clear old results - * enable will be called if more reads are required */ - writel(0x000, oinfo->gpmc_base + GPMC_ECC_CONFIG); + unsigned int val1 = 0x0; + + switch (oinfo->ecc_mode) { + case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: + /* read ecc result */ + val1 = readl(oinfo->gpmc_base + GPMC_ECC1_RESULT); + *ecc_code++ = val1; /* P128e, ..., P1e */ + *ecc_code++ = val1 >> 16; /* P128o, ..., P1o */ + /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ + *ecc_code++ = ((val1 >> 8) & 0x0f) | ((val1 >> 20) & 0xf0); + break; + default: + return -EINVAL; + } + return 0; } -/* - * omap_enable_ecc - This function enables the hardware ecc functionality - * @param mtd - mtd info structure - * @param mode - Read/Write mode - */ static void omap_enable_hwecc(struct mtd_info *mtd, int mode) { struct nand_chip *nand = (struct nand_chip *)(mtd->priv); struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); - gpmcnand_dbg("mtd=%x mode=%x", (unsigned int)mtd, mode); + unsigned int eccsize1 = 0; + unsigned int ecc_conf_val = 0, ecc_size_conf_val = 0; + int dev_width = 0; + int ecc_size = nand->ecc.size; + int cs = 0; + + switch (oinfo->ecc_mode) { + case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: + eccsize1 = ((ecc_size >> 1) - 1) << 22; + break; + case OMAP_ECC_SOFT: + return; + } + + writel(0x00000101, oinfo->gpmc_base + GPMC_ECC_CONTROL); + ecc_size_conf_val = (eccsize1 << 22) | 0x0000000F; + ecc_conf_val = (dev_width << 7) | (cs << 1) | (0x1); + + writel(ecc_size_conf_val, oinfo->gpmc_base + GPMC_ECC_SIZE_CONFIG); + writel(ecc_conf_val, oinfo->gpmc_base + GPMC_ECC_CONFIG); + writel(0x00000101, oinfo->gpmc_base + GPMC_ECC_CONTROL); +} + +static int omap_gpmc_eccmode(struct gpmc_nand_info *oinfo, + enum gpmc_ecc_mode mode) +{ + struct mtd_info *minfo = &oinfo->minfo; + struct nand_chip *nand = &oinfo->nand; + int offset; + int i; + + if (nand->options & NAND_BUSWIDTH_16) + nand->badblock_pattern = &bb_descrip_flashbased; + else + nand->badblock_pattern = NULL; + + if (oinfo->nand.options & NAND_BUSWIDTH_16) + offset = 2; + else + offset = 1; + + if (mode != OMAP_ECC_SOFT) { + nand->ecc.layout = &omap_oobinfo; + nand->ecc.calculate = omap_calculate_ecc; + nand->ecc.hwctl = omap_enable_hwecc; + nand->ecc.correct = omap_correct_data; + nand->ecc.read_page = NULL; + nand->ecc.write_page = NULL; + nand->ecc.read_oob = NULL; + nand->ecc.write_oob = NULL; + nand->ecc.mode = NAND_ECC_HW; + } + switch (mode) { - case NAND_ECC_READ: - case NAND_ECC_WRITE: - /* Clear the ecc result registers - * select ecc reg as 1 - */ - writel(0x101, oinfo->gpmc_base + GPMC_ECC_CONTROL); - /* Size 0 = 0xFF, Size1 is 0xFF - both are 512 bytes - * tell all regs to generate size0 sized regs - * we just have a single ECC engine for all CS - */ - writel(0x3FCFF000, oinfo->gpmc_base + - GPMC_ECC_SIZE_CONFIG); - writel(oinfo->ecc_config, oinfo->gpmc_base + - GPMC_ECC_CONFIG); + case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: + oinfo->nand.ecc.bytes = 3; + oinfo->nand.ecc.size = 512; + for (i = 0; i < omap_oobinfo.eccbytes; i++) + omap_oobinfo.eccpos[i] = i + offset; + omap_oobinfo.oobfree->offset = offset + omap_oobinfo.eccbytes; + omap_oobinfo.oobfree->length = minfo->oobsize - + offset - omap_oobinfo.eccbytes; break; - default: - gpmcnand_err("Error: Unrecognized Mode[%d]!\n", mode); + case OMAP_ECC_SOFT: + nand->ecc.layout = NULL; + nand->ecc.mode = NAND_ECC_SOFT; break; + default: + return -EINVAL; + } + + omap_oobinfo.eccbytes = oinfo->nand.ecc.bytes; + + oinfo->ecc_mode = mode; + + if (nand->buffers) + kfree(nand->buffers); + + /* second phase scan */ + if (nand_scan_tail(minfo)) + return -ENXIO; + + nand->options |= NAND_SKIP_BBTSCAN; + + return 0; +} + +static int omap_gpmc_eccmode_set(struct device_d *dev, struct param_d *param, const char *val) +{ + struct gpmc_nand_info *oinfo = dev->priv; + int i; + + if (!val) + return 0; + + for (i = 0; i < ARRAY_SIZE(ecc_mode_strings); i++) + if (!strcmp(ecc_mode_strings[i], val)) + break; + + if (i == ARRAY_SIZE(ecc_mode_strings)) { + dev_err(dev, "invalid ecc mode '%s'\n", val); + printf("valid modes:\n"); + for (i = 0; i < ARRAY_SIZE(ecc_mode_strings); i++) + printf("%s\n", ecc_mode_strings[i]); + return -EINVAL; } + + return omap_gpmc_eccmode(oinfo, i); } /** @@ -425,6 +538,7 @@ static int gpmc_nand_probe(struct device_d *pdev) oinfo->pdev = pdev; oinfo->pdata = pdata; pdev->platform_data = (void *)oinfo; + pdev->priv = oinfo; nand = &oinfo->nand; nand->priv = (void *)oinfo; @@ -548,31 +662,7 @@ static int gpmc_nand_probe(struct device_d *pdev) goto out_release_mem; } - if (pdata->ecc_mode == OMAP_ECC_HAMMING_CODE_HW_ROMCODE) { - nand->ecc.layout = layout; - - /* Program how many columns we expect+ - * enable the cs we want and enable the engine - */ - oinfo->ecc_config = (pdata->cs << 1) | - ((nand->options & NAND_BUSWIDTH_16) ? - (0x1 << 7) : 0x0) | 0x1; - nand->ecc.hwctl = omap_enable_hwecc; - nand->ecc.calculate = omap_calculate_ecc; - nand->ecc.correct = omap_correct_data; - nand->ecc.mode = NAND_ECC_HW; - nand->ecc.size = 512; - nand->ecc.bytes = 3; - nand->ecc.steps = nand->ecc.layout->eccbytes / nand->ecc.bytes; - oinfo->ecc_parity_pairs = 12; - } else - nand->ecc.mode = NAND_ECC_SOFT; - - /* second phase scan */ - if (nand_scan_tail(minfo)) { - err = -ENXIO; - goto out_release_mem; - } + omap_gpmc_eccmode(oinfo, pdata->ecc_mode); /* We are all set to register with the system now! */ err = add_mtd_device(minfo); @@ -580,6 +670,9 @@ static int gpmc_nand_probe(struct device_d *pdev) gpmcnand_err("device registration failed\n"); goto out_release_mem; } + + dev_add_param(pdev, "eccmode", omap_gpmc_eccmode_set, NULL, 0); + return 0; out_release_mem: -- cgit v1.2.3 From 6e7eba2c613ed1b92c288f36f15e985a5dc62ff1 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 31 Mar 2011 00:10:31 +0200 Subject: mtd nand omap: Add BCH ecc support This patch adds BCH ecc support to the omap nand driver. The BCH error correction allows for up to 8 bit error correction. It is also needed for booting from nand on omap4. This is based on code from Sukumar Ghorai : [PATCH] omap3: nand: bch ecc support added Signed-off-by: Sascha Hauer --- arch/arm/mach-omap/include/mach/gpmc_nand.h | 3 + drivers/mtd/nand/Makefile | 2 +- drivers/mtd/nand/nand_omap_bch_decoder.c | 389 ++++++++++++++++++++++++++++ drivers/mtd/nand/nand_omap_gpmc.c | 223 +++++++++++++--- 4 files changed, 579 insertions(+), 38 deletions(-) create mode 100644 drivers/mtd/nand/nand_omap_bch_decoder.c diff --git a/arch/arm/mach-omap/include/mach/gpmc_nand.h b/arch/arm/mach-omap/include/mach/gpmc_nand.h index a57d2a9e19..1bc52ffe00 100644 --- a/arch/arm/mach-omap/include/mach/gpmc_nand.h +++ b/arch/arm/mach-omap/include/mach/gpmc_nand.h @@ -36,6 +36,9 @@ enum gpmc_ecc_mode { OMAP_ECC_SOFT, OMAP_ECC_HAMMING_CODE_HW_ROMCODE, + OMAP_ECC_BCH4_CODE_HW, + OMAP_ECC_BCH8_CODE_HW, + OMAP_ECC_BCH8_CODE_HW_ROMCODE, }; /** omap nand platform data structure */ diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index bac39e7591..e9a94b9f5c 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -7,7 +7,7 @@ obj-$(CONFIG_NAND) += nand_base.o nand_bbt.o obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o obj-$(CONFIG_MTD_NAND_NOMADIK) += nomadik_nand.o obj-$(CONFIG_NAND_IMX) += nand_imx.o -obj-$(CONFIG_NAND_OMAP_GPMC) += nand_omap_gpmc.o +obj-$(CONFIG_NAND_OMAP_GPMC) += nand_omap_gpmc.o nand_omap_bch_decoder.o obj-$(CONFIG_NAND_ATMEL) += atmel_nand.o obj-$(CONFIG_NAND_S3C24X0) += nand_s3c2410.o #obj-$(CONFIG_NAND) += nand_util.o diff --git a/drivers/mtd/nand/nand_omap_bch_decoder.c b/drivers/mtd/nand/nand_omap_bch_decoder.c new file mode 100644 index 0000000000..356f71f0cd --- /dev/null +++ b/drivers/mtd/nand/nand_omap_bch_decoder.c @@ -0,0 +1,389 @@ +/* + * drivers/mtd/nand/omap_omap_bch_decoder.c + * + * Whole BCH ECC Decoder (Post hardware generated syndrome decoding) + * + * Copyright (c) 2007 Texas Instruments + * + * Author: Sukumar Ghorai + * + * 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 + +#define mm 13 +#define kk_shorten 4096 +#define nn 8191 /* Length of codeword, n = 2**mm - 1 */ + +#define PPP 0x201B /* Primary Polynomial : x^13 + x^4 + x^3 + x + 1 */ +#define P 0x001B /* With omitted x^13 */ +#define POLY 12 /* degree of the primary Polynomial less one */ + +/** + * mpy_mod_gf - GALOIS field multiplier + * Input : A(x), B(x) + * Output : A(x)*B(x) mod P(x) + */ +static unsigned int mpy_mod_gf(unsigned int a, unsigned int b) +{ + unsigned int R = 0; + unsigned int R1 = 0; + unsigned int k = 0; + + for (k = 0; k < mm; k++) { + + R = (R << 1) & 0x1FFE; + if (R1 == 1) + R ^= P; + + if (((a >> (POLY - k)) & 1) == 1) + R ^= b; + + if (k < POLY) + R1 = (R >> POLY) & 1; + } + return R; +} + +/** + * chien - CHIEN search + * + * @location - Error location vector pointer + * + * Inputs : ELP(z) + * No. of found errors + * Size of input codeword + * Outputs : Up to 8 locations + * No. of errors + */ +static int chien(unsigned int select_4_8, int err_nums, + unsigned int err[], unsigned int *location) +{ + int i, count; /* Number of dectected errors */ + /* Contains accumulation of evaluation at x^i (i:1->8) */ + unsigned int gammas[8] = {0}; + unsigned int alpha; + unsigned int bit, ecc_bits; + unsigned int elp_sum; + + ecc_bits = (select_4_8 == 0) ? 52 : 104; + + /* Start evaluation at Alpha**8192 and decreasing */ + for (i = 0; i < 8; i++) + gammas[i] = err[i]; + + count = 0; + for (i = 1; (i <= nn) && (count < err_nums); i++) { + + /* Result of evaluation at root */ + elp_sum = 1 ^ gammas[0] ^ gammas[1] ^ + gammas[2] ^ gammas[3] ^ + gammas[4] ^ gammas[5] ^ + gammas[6] ^ gammas[7]; + + alpha = PPP >> 1; + gammas[0] = mpy_mod_gf(gammas[0], alpha); + alpha = mpy_mod_gf(alpha, (PPP >> 1)); /* x alphha^-2 */ + gammas[1] = mpy_mod_gf(gammas[1], alpha); + alpha = mpy_mod_gf(alpha, (PPP >> 1)); /* x alphha^-2 */ + gammas[2] = mpy_mod_gf(gammas[2], alpha); + alpha = mpy_mod_gf(alpha, (PPP >> 1)); /* x alphha^-3 */ + gammas[3] = mpy_mod_gf(gammas[3], alpha); + alpha = mpy_mod_gf(alpha, (PPP >> 1)); /* x alphha^-4 */ + gammas[4] = mpy_mod_gf(gammas[4], alpha); + alpha = mpy_mod_gf(alpha, (PPP >> 1)); /* x alphha^-5 */ + gammas[5] = mpy_mod_gf(gammas[5], alpha); + alpha = mpy_mod_gf(alpha, (PPP >> 1)); /* x alphha^-6 */ + gammas[6] = mpy_mod_gf(gammas[6], alpha); + alpha = mpy_mod_gf(alpha, (PPP >> 1)); /* x alphha^-7 */ + gammas[7] = mpy_mod_gf(gammas[7], alpha); + + if (elp_sum == 0) { + /* calculate bit position in main data area */ + bit = ((i-1) & ~7)|(7-((i-1) & 7)); + if (i >= 2 * ecc_bits) + location[count++] = + kk_shorten - (bit - 2 * ecc_bits) - 1; + } + } + + /* Failure: No. of detected errors != No. or corrected errors */ + if (count != err_nums) { + count = -1; + printk(KERN_ERR "BCH decoding failed\n"); + } + for (i = 0; i < count; i++) + pr_debug("%d ", location[i]); + + return count; +} + +/* synd : 16 Syndromes + * return: gamaas - Coefficients to the error polynomial + * return: : Number of detected errors +*/ +static unsigned int berlekamp(unsigned int select_4_8, + unsigned int synd[], unsigned int err[]) +{ + int loop, iteration; + unsigned int LL = 0; /* Detected errors */ + unsigned int d = 0; /* Distance between Syndromes and ELP[n](z) */ + unsigned int invd = 0; /* Inverse of d */ + /* Intermediate ELP[n](z). + * Final ELP[n](z) is Error Location Polynomial + */ + unsigned int gammas[16] = {0}; + /* Intermediate normalized ELP[n](z) : D[n](z) */ + unsigned int D[16] = {0}; + /* Temporary value that holds an ELP[n](z) coefficient */ + unsigned int next_gamma = 0; + + int e = 0; + unsigned int sign = 0; + unsigned int u = 0; + unsigned int v = 0; + unsigned int C1 = 0, C2 = 0; + unsigned int ss = 0; + unsigned int tmp_v = 0, tmp_s = 0; + unsigned int tmp_poly; + + /*-------------- Step 0 ------------------*/ + for (loop = 0; loop < 16; loop++) + gammas[loop] = 0; + gammas[0] = 1; + D[1] = 1; + + iteration = 0; + LL = 0; + while ((iteration < ((select_4_8+1)*2*4)) && + (LL <= ((select_4_8+1)*4))) { + + pr_debug("\nIteration.............%d\n", iteration); + d = 0; + /* Step: 0 */ + for (loop = 0; loop <= LL; loop++) { + tmp_poly = mpy_mod_gf( + gammas[loop], synd[iteration - loop]); + d ^= tmp_poly; + pr_debug("%02d. s=0 LL=%x poly %x\n", + loop, LL, tmp_poly); + } + + /* Step 1: 1 cycle only to perform inversion */ + v = d << 1; + e = -1; + sign = 1; + ss = 0x2000; + invd = 0; + u = PPP; + for (loop = 0; (d != 0) && (loop <= (2 * POLY)); loop++) { + pr_debug("%02d. s=1 LL=%x poly NULL\n", + loop, LL); + C1 = (v >> 13) & 1; + C2 = C1 & sign; + + sign ^= C2 ^ (e == 0); + + tmp_v = v; + tmp_s = ss; + + if (C1 == 1) { + v ^= u; + ss ^= invd; + } + v = (v << 1) & 0x3FFF; + if (C2 == 1) { + u = tmp_v; + invd = tmp_s; + e = -e; + } + invd >>= 1; + e--; + } + + for (loop = 0; (d != 0) && (loop <= (iteration + 1)); loop++) { + /* Step 2 + * Interleaved with Step 3, if L<(n-k) + * invd: Update of ELP[n](z) = ELP[n-1](z) - d.D[n-1](z) + */ + + /* Holds value of ELP coefficient until precedent + * value does not have to be used anymore + */ + tmp_poly = mpy_mod_gf(d, D[loop]); + pr_debug("%02d. s=2 LL=%x poly %x\n", + loop, LL, tmp_poly); + + next_gamma = gammas[loop] ^ tmp_poly; + if ((2 * LL) < (iteration + 1)) { + /* Interleaving with Step 3 + * for parallelized update of ELP(z) and D(z) + */ + } else { + /* Update of ELP(z) only -> stay in Step 2 */ + gammas[loop] = next_gamma; + if (loop == (iteration + 1)) { + /* to step 4 */ + break; + } + } + + /* Step 3 + * Always interleaved with Step 2 (case when L<(n-k)) + * Update of D[n-1](z) = ELP[n-1](z)/d + */ + D[loop] = mpy_mod_gf(gammas[loop], invd); + pr_debug("%02d. s=3 LL=%x poly %x\n", + loop, LL, D[loop]); + + /* Can safely update ELP[n](z) */ + gammas[loop] = next_gamma; + + if (loop == (iteration + 1)) { + /* If update finished */ + LL = iteration - LL + 1; + /* to step 4 */ + break; + } + /* Else, interleaving to step 2*/ + } + + /* Step 4: Update D(z): i:0->L */ + /* Final update of D[n](z) = D[n](z).z*/ + for (loop = 0; loop < 15; loop++) /* Left Shift */ + D[15 - loop] = D[14 - loop]; + + D[0] = 0; + + iteration++; + } /* while */ + + /* Processing finished, copy ELP to final registers : 0->2t-1*/ + for (loop = 0; loop < 8; loop++) + err[loop] = gammas[loop+1]; + + pr_debug("\n Err poly:"); + for (loop = 0; loop < 8; loop++) + pr_debug("0x%x ", err[loop]); + + return LL; +} + +/* + * syndrome - Generate syndrome components from hw generate syndrome + * r(x) = c(x) + e(x) + * s(x) = c(x) mod g(x) + e(x) mod g(x) = e(x) mod g(x) + * so receiver checks if the syndrome s(x) = r(x) mod g(x) is equal to zero. + * unsigned int s[16]; - Syndromes + */ +static void syndrome(unsigned int select_4_8, + unsigned char *ecc, unsigned int syn[]) +{ + unsigned int k, l, t; + unsigned int alpha_bit, R_bit; + int ecc_pos, ecc_min; + + /* 2t-1 = 15 (for t=8) minimal polynomials of the first 15 powers of a + * primitive elemmants of GF(m); Even powers minimal polynomials are + * duplicate of odd powers' minimal polynomials. + * Odd powers of alpha (1 to 15) + */ + unsigned int pow_alpha[8] = {0x0002, 0x0008, 0x0020, 0x0080, + 0x0200, 0x0800, 0x001B, 0x006C}; + + pr_debug("\n ECC[0..n]: "); + for (k = 0; k < 13; k++) + pr_debug("0x%x ", ecc[k]); + + if (select_4_8 == 0) { + t = 4; + ecc_pos = 55; /* bits(52-bits): 55->4 */ + ecc_min = 4; + } else { + t = 8; + ecc_pos = 103; /* bits: 103->0 */ + ecc_min = 0; + } + + /* total numbber of syndrom to be used is 2t */ + /* Step1: calculate the odd syndrome(s) */ + R_bit = ((ecc[ecc_pos/8] >> (7 - ecc_pos%8)) & 1); + ecc_pos--; + for (k = 0; k < t; k++) + syn[2 * k] = R_bit; + + while (ecc_pos >= ecc_min) { + R_bit = ((ecc[ecc_pos/8] >> (7 - ecc_pos%8)) & 1); + ecc_pos--; + + for (k = 0; k < t; k++) { + /* Accumulate value of x^i at alpha^(2k+1) */ + if (R_bit == 1) + syn[2*k] ^= pow_alpha[k]; + + /* Compute a**(2k+1), using LSFR */ + for (l = 0; l < (2 * k + 1); l++) { + alpha_bit = (pow_alpha[k] >> POLY) & 1; + pow_alpha[k] = (pow_alpha[k] << 1) & 0x1FFF; + if (alpha_bit == 1) + pow_alpha[k] ^= P; + } + } + } + + /* Step2: calculate the even syndrome(s) + * Compute S(a), where a is an even power of alpha + * Evenry even power of primitive element has the same minimal + * polynomial as some odd power of elemets. + * And based on S(a^2) = S^2(a) + */ + for (k = 0; k < t; k++) + syn[2*k+1] = mpy_mod_gf(syn[k], syn[k]); + + pr_debug("\n Syndromes: "); + for (k = 0; k < 16; k++) + pr_debug("0x%x ", syn[k]); +} + +/** + * decode_bch - BCH decoder for 4- and 8-bit error correction + * + * @ecc - ECC syndrome generated by hw BCH engine + * @err_loc - pointer to error location array + * + * This function does post sydrome generation (hw generated) decoding + * for:- + * Dimension of Galoise Field: m = 13 + * Length of codeword: n = 2**m - 1 + * Number of errors that can be corrected: 4- or 8-bits + * Length of information bit: kk = nn - rr + */ +int decode_bch(int select_4_8, unsigned char *ecc, unsigned int *err_loc) +{ + int no_of_err; + unsigned int syn[16] = {0,}; /* 16 Syndromes */ + unsigned int err_poly[8] = {0,}; + /* Coefficients to the error polynomial + * ELP(x) = 1 + err0.x + err1.x^2 + ... + err7.x^8 + */ + + /* Decoding involes three steps + * 1. Compute the syndrom from the received codeword, + * 2. Find the error location polynomial from a set of equations + * derived from the syndrome, + * 3. Use the error location polynomial to identify errants bits, + * + * And correction done by bit flips using error location and expected + * to be outseide of this implementation. + */ + syndrome(select_4_8, ecc, syn); + no_of_err = berlekamp(select_4_8, syn, err_poly); + if (no_of_err <= (4 << select_4_8)) + no_of_err = chien(select_4_8, no_of_err, err_poly, err_loc); + + return no_of_err; +} diff --git a/drivers/mtd/nand/nand_omap_gpmc.c b/drivers/mtd/nand/nand_omap_gpmc.c index 889009bbf0..15996039e6 100644 --- a/drivers/mtd/nand/nand_omap_gpmc.c +++ b/drivers/mtd/nand/nand_omap_gpmc.c @@ -91,6 +91,9 @@ int decode_bch(int select_4_8, unsigned char *ecc, unsigned int *err_loc); static char *ecc_mode_strings[] = { "software", "hamming_hw_romcode", + "bch4_hw", + "bch8_hw", + "bch8_hw_romcode", }; /** internal structure maintained for nand information */ @@ -284,6 +287,66 @@ static unsigned int gen_true_ecc(u8 *ecc_buf) ((ecc_buf[2] & 0x0F) << 8); } +static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc_code) +{ + struct nand_chip *nand = (struct nand_chip *)(mtd->priv); + struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); + unsigned int reg; + unsigned int val1 = 0x0, val2 = 0x0; + unsigned int val3 = 0x0, val4 = 0x0; + int i; + int ecc_size = 8; + + switch (oinfo->ecc_mode) { + case OMAP_ECC_BCH4_CODE_HW: + ecc_size = 4; + /* fall through */ + case OMAP_ECC_BCH8_CODE_HW: + case OMAP_ECC_BCH8_CODE_HW_ROMCODE: + for (i = 0; i < 4; i++) { + /* + * Reading HW ECC_BCH_Results + * 0x240-0x24C, 0x250-0x25C, 0x260-0x26C, 0x270-0x27C + */ + reg = GPMC_ECC_BCH_RESULT_0 + (0x10 * i); + val1 = readl(oinfo->gpmc_base + reg); + val2 = readl(oinfo->gpmc_base + reg + 4); + if (ecc_size == 8) { + val3 = readl(oinfo->gpmc_base +reg + 8); + val4 = readl(oinfo->gpmc_base + reg + 12); + + *ecc_code++ = (val4 & 0xFF); + *ecc_code++ = ((val3 >> 24) & 0xFF); + *ecc_code++ = ((val3 >> 16) & 0xFF); + *ecc_code++ = ((val3 >> 8) & 0xFF); + *ecc_code++ = (val3 & 0xFF); + *ecc_code++ = ((val2 >> 24) & 0xFF); + } + *ecc_code++ = ((val2 >> 16) & 0xFF); + *ecc_code++ = ((val2 >> 8) & 0xFF); + *ecc_code++ = (val2 & 0xFF); + *ecc_code++ = ((val1 >> 24) & 0xFF); + *ecc_code++ = ((val1 >> 16) & 0xFF); + *ecc_code++ = ((val1 >> 8) & 0xFF); + *ecc_code++ = (val1 & 0xFF); + } + break; + case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: + /* read ecc result */ + val1 = readl(oinfo->gpmc_base + GPMC_ECC1_RESULT); + *ecc_code++ = val1; /* P128e, ..., P1e */ + *ecc_code++ = val1 >> 16; /* P128o, ..., P1o */ + /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ + *ecc_code++ = ((val1 >> 8) & 0x0f) | ((val1 >> 20) & 0xf0); + break; + default: + return -EINVAL; + } + + return 0; +} + /** * @brief Compares the ecc read from nand spare area with ECC * registers values and corrects one bit error if it has occured @@ -306,7 +369,11 @@ static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat, unsigned char bit; struct nand_chip *nand = (struct nand_chip *)(mtd->priv); struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); + int ecc_type = OMAP_ECC_BCH8_CODE_HW; + int i, j, eccsize, eccflag, count; + unsigned int err_loc[8]; int blockCnt = 0; + int select_4_8; gpmcnand_dbg("mtd=%x dat=%x read_ecc=%x calc_ecc=%x", (unsigned int)mtd, (unsigned int)dat, (unsigned int)read_ecc, @@ -347,41 +414,48 @@ static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat, } } break; - default: - return -EINVAL; - } + case OMAP_ECC_BCH8_CODE_HW: + case OMAP_ECC_BCH8_CODE_HW_ROMCODE: + eccsize = 13; + select_4_8 = 1; + /* fall through */ + case OMAP_ECC_BCH4_CODE_HW: + if (ecc_type == OMAP_ECC_BCH4_CODE_HW) { + eccsize = 7; + select_4_8 = 0; + } - return 0; -} + omap_calculate_ecc(mtd, dat, calc_ecc); + for (i = 0; i < blockCnt; i++) { + /* check if any ecc error */ + eccflag = 0; + for (j = 0; (j < eccsize) && (eccflag == 0); j++) + if (calc_ecc[j] != 0) + eccflag = 1; + + if (eccflag == 1) { + eccflag = 0; + for (j = 0; (j < eccsize) && + (eccflag == 0); j++) + if (read_ecc[j] != 0xFF) + eccflag = 1; + } -/** - * @brief Using noninverted ECC can be considered ugly since writing a blank - * page ie. padding will clear the ECC bytes. This is no problem as long - * nobody is trying to write data on the seemingly unused page. Reading - * an erased page will produce an ECC mismatch between generated and read - * ECC bytes that has to be dealt with separately. - * - * @param mtd - mtd info structure - * @param dat data being written - * @param ecc_code ecc code returned back to nand layer - * - * @return 0 - */ -static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, - uint8_t *ecc_code) -{ - struct nand_chip *nand = (struct nand_chip *)(mtd->priv); - struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); - unsigned int val1 = 0x0; + count = 0; + if (eccflag == 1) + count = decode_bch(select_4_8, calc_ecc, err_loc); - switch (oinfo->ecc_mode) { - case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: - /* read ecc result */ - val1 = readl(oinfo->gpmc_base + GPMC_ECC1_RESULT); - *ecc_code++ = val1; /* P128e, ..., P1e */ - *ecc_code++ = val1 >> 16; /* P128o, ..., P1o */ - /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ - *ecc_code++ = ((val1 >> 8) & 0x0f) | ((val1 >> 20) & 0xf0); + for (j = 0; j < count; j++) { + if (err_loc[j] < 4096) + dat[err_loc[j] >> 3] ^= + 1 << (err_loc[j] & 7); + /* else, not interested to correct ecc */ + } + + calc_ecc = calc_ecc + eccsize; + read_ecc = read_ecc + eccsize; + dat += 512; + } break; default: return -EINVAL; @@ -394,13 +468,36 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int mode) { struct nand_chip *nand = (struct nand_chip *)(mtd->priv); struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); - unsigned int eccsize1 = 0; + unsigned int bch_mod = 0, bch_wrapmode = 0, eccsize1 = 0, eccsize0 = 0; unsigned int ecc_conf_val = 0, ecc_size_conf_val = 0; int dev_width = 0; int ecc_size = nand->ecc.size; int cs = 0; switch (oinfo->ecc_mode) { + case OMAP_ECC_BCH4_CODE_HW: + if (mode == NAND_ECC_READ) { + eccsize1 = 0xD; eccsize0 = 0x48; + bch_mod = 0; + bch_wrapmode = 0x09; + } else { + eccsize1 = 0x20; eccsize0 = 0x00; + bch_mod = 0; + bch_wrapmode = 0x06; + } + break; + case OMAP_ECC_BCH8_CODE_HW: + case OMAP_ECC_BCH8_CODE_HW_ROMCODE: + if (mode == NAND_ECC_READ) { + eccsize1 = 0x1A; eccsize0 = 0x18; + bch_mod = 1; + bch_wrapmode = 0x04; + } else { + eccsize1 = 0x20; eccsize0 = 0x00; + bch_mod = 1; + bch_wrapmode = 0x06; + } + break; case OMAP_ECC_HAMMING_CODE_HW_ROMCODE: eccsize1 = ((ecc_size >> 1) - 1) << 22; break; @@ -408,9 +505,18 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int mode) return; } - writel(0x00000101, oinfo->gpmc_base + GPMC_ECC_CONTROL); - ecc_size_conf_val = (eccsize1 << 22) | 0x0000000F; - ecc_conf_val = (dev_width << 7) | (cs << 1) | (0x1); + /* clear ecc and enable bits */ + if (oinfo->ecc_mode == OMAP_ECC_HAMMING_CODE_HW_ROMCODE) { + writel(0x00000101, oinfo->gpmc_base + GPMC_ECC_CONTROL); + ecc_size_conf_val = (eccsize1 << 22) | 0x0000000F; + ecc_conf_val = (dev_width << 7) | (cs << 1) | (0x1); + } else { + writel(0x1, oinfo->gpmc_base + GPMC_ECC_CONTROL); + ecc_size_conf_val = (eccsize1 << 22) | (eccsize0 << 12); + ecc_conf_val = ((0x01 << 16) | (bch_mod << 12) + | (bch_wrapmode << 8) | (dev_width << 7) + | (0x03 << 4) | (cs << 1) | (0x1)); + } writel(ecc_size_conf_val, oinfo->gpmc_base + GPMC_ECC_SIZE_CONFIG); writel(ecc_conf_val, oinfo->gpmc_base + GPMC_ECC_CONFIG); @@ -423,7 +529,7 @@ static int omap_gpmc_eccmode(struct gpmc_nand_info *oinfo, struct mtd_info *minfo = &oinfo->minfo; struct nand_chip *nand = &oinfo->nand; int offset; - int i; + int i, j; if (nand->options & NAND_BUSWIDTH_16) nand->badblock_pattern = &bb_descrip_flashbased; @@ -457,6 +563,48 @@ static int omap_gpmc_eccmode(struct gpmc_nand_info *oinfo, omap_oobinfo.oobfree->length = minfo->oobsize - offset - omap_oobinfo.eccbytes; break; + case OMAP_ECC_BCH4_CODE_HW: + oinfo->nand.ecc.bytes = 4 * 7; + oinfo->nand.ecc.size = 4 * 512; + omap_oobinfo.oobfree->offset = offset; + omap_oobinfo.oobfree->length = minfo->oobsize - + offset - omap_oobinfo.eccbytes; + offset = minfo->oobsize - oinfo->nand.ecc.bytes; + for (i = 0; i < oinfo->nand.ecc.bytes; i++) + omap_oobinfo.eccpos[i] = i + offset; + break; + case OMAP_ECC_BCH8_CODE_HW: + oinfo->nand.ecc.bytes = 4 * 13; + oinfo->nand.ecc.size = 4 * 512; + omap_oobinfo.oobfree->offset = offset; + omap_oobinfo.oobfree->length = minfo->oobsize - + offset - omap_oobinfo.eccbytes; + offset = minfo->oobsize - oinfo->nand.ecc.bytes; + for (i = 0; i < oinfo->nand.ecc.bytes; i++) + omap_oobinfo.eccpos[i] = i + offset; + break; + case OMAP_ECC_BCH8_CODE_HW_ROMCODE: + /* + * Contradicting the datasheet the ecc checksum has to start + * at byte 2 in oob. I have no idea how the rom code can + * read this but it does. + */ + dev_warn(oinfo->pdev, "using rom loader ecc mode. " + "You can write properly but not read it back\n"); + + oinfo->nand.ecc.bytes = 4 * 13; + oinfo->nand.ecc.size = 4 * 512; + omap_oobinfo.oobfree->length = 0; + j = 0; + for (i = 2; i < 15; i++) + omap_oobinfo.eccpos[j++] = i; + for (i = 16; i < 29; i++) + omap_oobinfo.eccpos[j++] = i; + for (i = 30; i < 43; i++) + omap_oobinfo.eccpos[j++] = i; + for (i = 44; i < 57; i++) + omap_oobinfo.eccpos[j++] = i; + break; case OMAP_ECC_SOFT: nand->ecc.layout = NULL; nand->ecc.mode = NAND_ECC_SOFT; @@ -662,6 +810,7 @@ static int gpmc_nand_probe(struct device_d *pdev) goto out_release_mem; } + nand->options |= NAND_SKIP_BBTSCAN; omap_gpmc_eccmode(oinfo, pdata->ecc_mode); /* We are all set to register with the system now! */ -- cgit v1.2.3