Commit 51b7b1c3 authored by Ben Dooks's avatar Ben Dooks Committed by David S. Miller

KSZ8851-SNL: Add ethtool support for EEPROM via eeprom_93cx6

Add ethtool EEPROM read/write support using the eeprom_93cx6
library instead of open-coding the functions.

Depends on eeprom_93cx6 driver getting EEPROM write support.
Signed-off-by: default avatarBen Dooks <ben@simtec.co.uk>
Signed-off-by: default avatarSimtec Linux Team <linux@simtec.co.uk>
[sboyd@codeaurora.org: Removed previous eeprom implementation]
Signed-off-by: default avatarStephen Boyd <sboyd@codeaurora.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 32f160d9
...@@ -42,6 +42,8 @@ config KS8851 ...@@ -42,6 +42,8 @@ config KS8851
select NET_CORE select NET_CORE
select MII select MII
select CRC32 select CRC32
select MISC_DEVICES
select EEPROM_93CX6
---help--- ---help---
SPI driver for Micrel KS8851 SPI attached network chip. SPI driver for Micrel KS8851 SPI attached network chip.
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <linux/cache.h> #include <linux/cache.h>
#include <linux/crc32.h> #include <linux/crc32.h>
#include <linux/mii.h> #include <linux/mii.h>
#include <linux/eeprom_93cx6.h>
#include <linux/spi/spi.h> #include <linux/spi/spi.h>
...@@ -82,6 +83,7 @@ union ks8851_tx_hdr { ...@@ -82,6 +83,7 @@ union ks8851_tx_hdr {
* @rc_ccr: Cached copy of KS_CCR. * @rc_ccr: Cached copy of KS_CCR.
* @rc_rxqcr: Cached copy of KS_RXQCR. * @rc_rxqcr: Cached copy of KS_RXQCR.
* @eeprom_size: Companion eeprom size in Bytes, 0 if no eeprom * @eeprom_size: Companion eeprom size in Bytes, 0 if no eeprom
* @eeprom: 93CX6 EEPROM state for accessing on-board EEPROM.
* *
* The @lock ensures that the chip is protected when certain operations are * The @lock ensures that the chip is protected when certain operations are
* in progress. When the read or write packet transfer is in progress, most * in progress. When the read or write packet transfer is in progress, most
...@@ -128,6 +130,8 @@ struct ks8851_net { ...@@ -128,6 +130,8 @@ struct ks8851_net {
struct spi_message spi_msg2; struct spi_message spi_msg2;
struct spi_transfer spi_xfer1; struct spi_transfer spi_xfer1;
struct spi_transfer spi_xfer2[2]; struct spi_transfer spi_xfer2[2];
struct eeprom_93cx6 eeprom;
}; };
static int msg_enable; static int msg_enable;
...@@ -1071,234 +1075,6 @@ static const struct net_device_ops ks8851_netdev_ops = { ...@@ -1071,234 +1075,6 @@ static const struct net_device_ops ks8851_netdev_ops = {
.ndo_validate_addr = eth_validate_addr, .ndo_validate_addr = eth_validate_addr,
}; };
/* Companion eeprom access */
enum { /* EEPROM programming states */
EEPROM_CONTROL,
EEPROM_ADDRESS,
EEPROM_DATA,
EEPROM_COMPLETE
};
/**
* ks8851_eeprom_read - read a 16bits word in ks8851 companion EEPROM
* @dev: The network device the PHY is on.
* @addr: EEPROM address to read
*
* eeprom_size: used to define the data coding length. Can be changed
* through debug-fs.
*
* Programs a read on the EEPROM using ks8851 EEPROM SW access feature.
* Warning: The READ feature is not supported on ks8851 revision 0.
*
* Rough programming model:
* - on period start: set clock high and read value on bus
* - on period / 2: set clock low and program value on bus
* - start on period / 2
*/
unsigned int ks8851_eeprom_read(struct net_device *dev, unsigned int addr)
{
struct ks8851_net *ks = netdev_priv(dev);
int eepcr;
int ctrl = EEPROM_OP_READ;
int state = EEPROM_CONTROL;
int bit_count = EEPROM_OP_LEN - 1;
unsigned int data = 0;
int dummy;
unsigned int addr_len;
addr_len = (ks->eeprom_size == 128) ? 6 : 8;
/* start transaction: chip select high, authorize write */
mutex_lock(&ks->lock);
eepcr = EEPCR_EESA | EEPCR_EESRWA;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
eepcr |= EEPCR_EECS;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
mutex_unlock(&ks->lock);
while (state != EEPROM_COMPLETE) {
/* falling clock period starts... */
/* set EED_IO pin for control and address */
eepcr &= ~EEPCR_EEDO;
switch (state) {
case EEPROM_CONTROL:
eepcr |= ((ctrl >> bit_count) & 1) << 2;
if (bit_count-- <= 0) {
bit_count = addr_len - 1;
state = EEPROM_ADDRESS;
}
break;
case EEPROM_ADDRESS:
eepcr |= ((addr >> bit_count) & 1) << 2;
bit_count--;
break;
case EEPROM_DATA:
/* Change to receive mode */
eepcr &= ~EEPCR_EESRWA;
break;
}
/* lower clock */
eepcr &= ~EEPCR_EESCK;
mutex_lock(&ks->lock);
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
mutex_unlock(&ks->lock);
/* waitread period / 2 */
udelay(EEPROM_SK_PERIOD / 2);
/* rising clock period starts... */
/* raise clock */
mutex_lock(&ks->lock);
eepcr |= EEPCR_EESCK;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
mutex_unlock(&ks->lock);
/* Manage read */
switch (state) {
case EEPROM_ADDRESS:
if (bit_count < 0) {
bit_count = EEPROM_DATA_LEN - 1;
state = EEPROM_DATA;
}
break;
case EEPROM_DATA:
mutex_lock(&ks->lock);
dummy = ks8851_rdreg16(ks, KS_EEPCR);
mutex_unlock(&ks->lock);
data |= ((dummy >> EEPCR_EESB_OFFSET) & 1) << bit_count;
if (bit_count-- <= 0)
state = EEPROM_COMPLETE;
break;
}
/* wait period / 2 */
udelay(EEPROM_SK_PERIOD / 2);
}
/* close transaction */
mutex_lock(&ks->lock);
eepcr &= ~EEPCR_EECS;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
eepcr = 0;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
mutex_unlock(&ks->lock);
return data;
}
/**
* ks8851_eeprom_write - write a 16bits word in ks8851 companion EEPROM
* @dev: The network device the PHY is on.
* @op: operand (can be WRITE, EWEN, EWDS)
* @addr: EEPROM address to write
* @data: data to write
*
* eeprom_size: used to define the data coding length. Can be changed
* through debug-fs.
*
* Programs a write on the EEPROM using ks8851 EEPROM SW access feature.
*
* Note that a write enable is required before writing data.
*
* Rough programming model:
* - on period start: set clock high
* - on period / 2: set clock low and program value on bus
* - start on period / 2
*/
void ks8851_eeprom_write(struct net_device *dev, unsigned int op,
unsigned int addr, unsigned int data)
{
struct ks8851_net *ks = netdev_priv(dev);
int eepcr;
int state = EEPROM_CONTROL;
int bit_count = EEPROM_OP_LEN - 1;
unsigned int addr_len;
addr_len = (ks->eeprom_size == 128) ? 6 : 8;
switch (op) {
case EEPROM_OP_EWEN:
addr = 0x30;
break;
case EEPROM_OP_EWDS:
addr = 0;
break;
}
/* start transaction: chip select high, authorize write */
mutex_lock(&ks->lock);
eepcr = EEPCR_EESA | EEPCR_EESRWA;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
eepcr |= EEPCR_EECS;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
mutex_unlock(&ks->lock);
while (state != EEPROM_COMPLETE) {
/* falling clock period starts... */
/* set EED_IO pin for control and address */
eepcr &= ~EEPCR_EEDO;
switch (state) {
case EEPROM_CONTROL:
eepcr |= ((op >> bit_count) & 1) << 2;
if (bit_count-- <= 0) {
bit_count = addr_len - 1;
state = EEPROM_ADDRESS;
}
break;
case EEPROM_ADDRESS:
eepcr |= ((addr >> bit_count) & 1) << 2;
if (bit_count-- <= 0) {
if (op == EEPROM_OP_WRITE) {
bit_count = EEPROM_DATA_LEN - 1;
state = EEPROM_DATA;
} else {
state = EEPROM_COMPLETE;
}
}
break;
case EEPROM_DATA:
eepcr |= ((data >> bit_count) & 1) << 2;
if (bit_count-- <= 0)
state = EEPROM_COMPLETE;
break;
}
/* lower clock */
eepcr &= ~EEPCR_EESCK;
mutex_lock(&ks->lock);
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
mutex_unlock(&ks->lock);
/* wait period / 2 */
udelay(EEPROM_SK_PERIOD / 2);
/* rising clock period starts... */
/* raise clock */
eepcr |= EEPCR_EESCK;
mutex_lock(&ks->lock);
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
mutex_unlock(&ks->lock);
/* wait period / 2 */
udelay(EEPROM_SK_PERIOD / 2);
}
/* close transaction */
mutex_lock(&ks->lock);
eepcr &= ~EEPCR_EECS;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
eepcr = 0;
ks8851_wrreg16(ks, KS_EEPCR, eepcr);
mutex_unlock(&ks->lock);
}
/* ethtool support */ /* ethtool support */
static void ks8851_get_drvinfo(struct net_device *dev, static void ks8851_get_drvinfo(struct net_device *dev,
...@@ -1345,115 +1121,141 @@ static int ks8851_nway_reset(struct net_device *dev) ...@@ -1345,115 +1121,141 @@ static int ks8851_nway_reset(struct net_device *dev)
return mii_nway_restart(&ks->mii); return mii_nway_restart(&ks->mii);
} }
static int ks8851_get_eeprom_len(struct net_device *dev) /* EEPROM support */
{
struct ks8851_net *ks = netdev_priv(dev);
return ks->eeprom_size;
}
static int ks8851_get_eeprom(struct net_device *dev, static void ks8851_eeprom_regread(struct eeprom_93cx6 *ee)
struct ethtool_eeprom *eeprom, u8 *bytes)
{ {
struct ks8851_net *ks = netdev_priv(dev); struct ks8851_net *ks = ee->data;
u16 *eeprom_buff; unsigned val;
int first_word;
int last_word;
int ret_val = 0;
u16 i;
if (eeprom->len == 0)
return -EINVAL;
if (eeprom->len > ks->eeprom_size) val = ks8851_rdreg16(ks, KS_EEPCR);
return -EINVAL;
eeprom->magic = ks8851_rdreg16(ks, KS_CIDER); ee->reg_data_out = (val & EEPCR_EESB) ? 1 : 0;
ee->reg_data_clock = (val & EEPCR_EESCK) ? 1 : 0;
ee->reg_chip_select = (val & EEPCR_EECS) ? 1 : 0;
}
first_word = eeprom->offset >> 1; static void ks8851_eeprom_regwrite(struct eeprom_93cx6 *ee)
last_word = (eeprom->offset + eeprom->len - 1) >> 1; {
struct ks8851_net *ks = ee->data;
unsigned val = EEPCR_EESA; /* default - eeprom access on */
if (ee->drive_data)
val |= EEPCR_EESRWA;
if (ee->reg_data_in)
val |= EEPCR_EEDO;
if (ee->reg_data_clock)
val |= EEPCR_EESCK;
if (ee->reg_chip_select)
val |= EEPCR_EECS;
ks8851_wrreg16(ks, KS_EEPCR, val);
}
eeprom_buff = kmalloc(sizeof(u16) * /**
(last_word - first_word + 1), GFP_KERNEL); * ks8851_eeprom_claim - claim device EEPROM and activate the interface
if (!eeprom_buff) * @ks: The network device state.
return -ENOMEM; *
* Check for the presence of an EEPROM, and then activate software access
* to the device.
*/
static int ks8851_eeprom_claim(struct ks8851_net *ks)
{
if (!(ks->rc_ccr & CCR_EEPROM))
return -ENOENT;
for (i = 0; i < last_word - first_word + 1; i++) mutex_lock(&ks->lock);
eeprom_buff[i] = ks8851_eeprom_read(dev, first_word + 1);
/* Device's eeprom is little-endian, word addressable */ /* start with clock low, cs high */
for (i = 0; i < last_word - first_word + 1; i++) ks8851_wrreg16(ks, KS_EEPCR, EEPCR_EESA | EEPCR_EECS);
le16_to_cpus(&eeprom_buff[i]); return 0;
}
memcpy(bytes, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len); /**
kfree(eeprom_buff); * ks8851_eeprom_release - release the EEPROM interface
* @ks: The device state
*
* Release the software access to the device EEPROM
*/
static void ks8851_eeprom_release(struct ks8851_net *ks)
{
unsigned val = ks8851_rdreg16(ks, KS_EEPCR);
return ret_val; ks8851_wrreg16(ks, KS_EEPCR, val & ~EEPCR_EESA);
mutex_unlock(&ks->lock);
} }
#define KS_EEPROM_MAGIC (0x00008851)
static int ks8851_set_eeprom(struct net_device *dev, static int ks8851_set_eeprom(struct net_device *dev,
struct ethtool_eeprom *eeprom, u8 *bytes) struct ethtool_eeprom *ee, u8 *data)
{ {
struct ks8851_net *ks = netdev_priv(dev); struct ks8851_net *ks = netdev_priv(dev);
u16 *eeprom_buff; int offset = ee->offset;
void *ptr; int len = ee->len;
int max_len; u16 tmp;
int first_word;
int last_word; /* currently only support byte writing */
int ret_val = 0; if (len != 1)
u16 i; return -EINVAL;
if (eeprom->len == 0) if (ee->magic != KS_EEPROM_MAGIC)
return -EOPNOTSUPP;
if (eeprom->len > ks->eeprom_size)
return -EINVAL; return -EINVAL;
if (eeprom->magic != ks8851_rdreg16(ks, KS_CIDER)) if (ks8851_eeprom_claim(ks))
return -EFAULT; return -ENOENT;
first_word = eeprom->offset >> 1; eeprom_93cx6_wren(&ks->eeprom, true);
last_word = (eeprom->offset + eeprom->len - 1) >> 1;
max_len = (last_word - first_word + 1) * 2;
eeprom_buff = kmalloc(max_len, GFP_KERNEL);
if (!eeprom_buff)
return -ENOMEM;
ptr = (void *)eeprom_buff; /* ethtool currently only supports writing bytes, which means
* we have to read/modify/write our 16bit EEPROMs */
if (eeprom->offset & 1) { eeprom_93cx6_read(&ks->eeprom, offset/2, &tmp);
/* need read/modify/write of first changed EEPROM word */
/* only the second byte of the word is being modified */ if (offset & 1) {
eeprom_buff[0] = ks8851_eeprom_read(dev, first_word); tmp &= 0xff;
ptr++; tmp |= *data << 8;
} else {
tmp &= 0xff00;
tmp |= *data;
} }
if ((eeprom->offset + eeprom->len) & 1)
/* need read/modify/write of last changed EEPROM word */
/* only the first byte of the word is being modified */
eeprom_buff[last_word - first_word] =
ks8851_eeprom_read(dev, last_word);
eeprom_93cx6_write(&ks->eeprom, offset/2, tmp);
eeprom_93cx6_wren(&ks->eeprom, false);
/* Device's eeprom is little-endian, word addressable */ ks8851_eeprom_release(ks);
le16_to_cpus(&eeprom_buff[0]);
le16_to_cpus(&eeprom_buff[last_word - first_word]);
memcpy(ptr, bytes, eeprom->len); return 0;
}
for (i = 0; i < last_word - first_word + 1; i++) static int ks8851_get_eeprom(struct net_device *dev,
eeprom_buff[i] = cpu_to_le16(eeprom_buff[i]); struct ethtool_eeprom *ee, u8 *data)
{
struct ks8851_net *ks = netdev_priv(dev);
int offset = ee->offset;
int len = ee->len;
/* must be 2 byte aligned */
if (len & 1 || offset & 1)
return -EINVAL;
ks8851_eeprom_write(dev, EEPROM_OP_EWEN, 0, 0); if (ks8851_eeprom_claim(ks))
return -ENOENT;
for (i = 0; i < last_word - first_word + 1; i++) { ee->magic = KS_EEPROM_MAGIC;
ks8851_eeprom_write(dev, EEPROM_OP_WRITE, first_word + i,
eeprom_buff[i]);
mdelay(EEPROM_WRITE_TIME);
}
ks8851_eeprom_write(dev, EEPROM_OP_EWDS, 0, 0); eeprom_93cx6_multiread(&ks->eeprom, offset/2, (__le16 *)data, len/2);
ks8851_eeprom_release(ks);
kfree(eeprom_buff); return 0;
return ret_val; }
static int ks8851_get_eeprom_len(struct net_device *dev)
{
struct ks8851_net *ks = netdev_priv(dev);
/* currently, we assume it is an 93C46 attached, so return 128 */
return ks->rc_ccr & CCR_EEPROM ? 128 : 0;
} }
static const struct ethtool_ops ks8851_ethtool_ops = { static const struct ethtool_ops ks8851_ethtool_ops = {
...@@ -1646,6 +1448,13 @@ static int __devinit ks8851_probe(struct spi_device *spi) ...@@ -1646,6 +1448,13 @@ static int __devinit ks8851_probe(struct spi_device *spi)
spi_message_add_tail(&ks->spi_xfer2[0], &ks->spi_msg2); spi_message_add_tail(&ks->spi_xfer2[0], &ks->spi_msg2);
spi_message_add_tail(&ks->spi_xfer2[1], &ks->spi_msg2); spi_message_add_tail(&ks->spi_xfer2[1], &ks->spi_msg2);
/* setup EEPROM state */
ks->eeprom.data = ks;
ks->eeprom.width = PCI_EEPROM_WIDTH_93C46;
ks->eeprom.register_read = ks8851_eeprom_regread;
ks->eeprom.register_write = ks8851_eeprom_regwrite;
/* setup mii state */ /* setup mii state */
ks->mii.dev = ndev; ks->mii.dev = ndev;
ks->mii.phy_id = 1, ks->mii.phy_id = 1,
......
...@@ -27,22 +27,11 @@ ...@@ -27,22 +27,11 @@
#define KS_EEPCR 0x22 #define KS_EEPCR 0x22
#define EEPCR_EESRWA (1 << 5) #define EEPCR_EESRWA (1 << 5)
#define EEPCR_EESA (1 << 4) #define EEPCR_EESA (1 << 4)
#define EEPCR_EESB_OFFSET 3 #define EEPCR_EESB (1 << 3)
#define EEPCR_EESB (1 << EEPCR_EESB_OFFSET)
#define EEPCR_EEDO (1 << 2) #define EEPCR_EEDO (1 << 2)
#define EEPCR_EESCK (1 << 1) #define EEPCR_EESCK (1 << 1)
#define EEPCR_EECS (1 << 0) #define EEPCR_EECS (1 << 0)
#define EEPROM_OP_LEN 3 /* bits:*/
#define EEPROM_OP_READ 0x06
#define EEPROM_OP_EWEN 0x04
#define EEPROM_OP_WRITE 0x05
#define EEPROM_OP_EWDS 0x14
#define EEPROM_DATA_LEN 16 /* 16 bits EEPROM */
#define EEPROM_WRITE_TIME 4 /* wrt ack time in ms */
#define EEPROM_SK_PERIOD 400 /* in us */
#define KS_MBIR 0x24 #define KS_MBIR 0x24
#define MBIR_TXMBF (1 << 12) #define MBIR_TXMBF (1 << 12)
#define MBIR_TXMBFA (1 << 11) #define MBIR_TXMBFA (1 << 11)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment