Message ID | 1324229621-2054-1-git-send-email-frdrc66@gmail.com |
---|---|
State | Accepted, archived |
Delegated to: | David Miller |
Headers | show |
Hi, Is something still wrong with this driver or is it acceptable in this state, now? 2011/12/18 Frederic LAMBERT <frdrc66@gmail.com> > > Signed-off-by: Gabor Juhos <juhosg@openwrt.org> > Signed-off-by: Frederic Lambert <frdrc66@gmail.com> > --- > drivers/net/phy/Kconfig | 5 + > drivers/net/phy/Makefile | 1 + > drivers/net/phy/spi_ks8995.c | 376 ++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 382 insertions(+), 0 deletions(-) > create mode 100644 drivers/net/phy/spi_ks8995.c > > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig > index a702443..050f069 100644 > --- a/drivers/net/phy/Kconfig > +++ b/drivers/net/phy/Kconfig > @@ -131,3 +131,8 @@ config MDIO_OCTEON > If in doubt, say Y. > > endif # PHYLIB > + > +config MICREL_KS8995MA > + tristate "Micrel KS8995MA 5-ports 10/100 managed Ethernet switch" > + depends on SPI > + > diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile > index 2333215..e15c83f 100644 > --- a/drivers/net/phy/Makefile > +++ b/drivers/net/phy/Makefile > @@ -23,3 +23,4 @@ obj-$(CONFIG_DP83640_PHY) += dp83640.o > obj-$(CONFIG_STE10XP) += ste10Xp.o > obj-$(CONFIG_MICREL_PHY) += micrel.o > obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o > +obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o > diff --git a/drivers/net/phy/spi_ks8995.c b/drivers/net/phy/spi_ks8995.c > new file mode 100644 > index 0000000..e9ce085 > --- /dev/null > +++ b/drivers/net/phy/spi_ks8995.c > @@ -0,0 +1,376 @@ > +/* > + * SPI driver for Micrel/Kendin KS8995M ethernet switch > + * > + * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org> > + * > + * This file was based on: drivers/spi/at25.c > + * Copyright (C) 2006 David Brownell > + * > + * 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 <linux/types.h> > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/module.h> > +#include <linux/delay.h> > +#include <linux/device.h> > + > +#include <linux/spi/spi.h> > + > +#define DRV_VERSION "0.1.1" > +#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver" > + > +/* ------------------------------------------------------------------------ */ > + > +#define KS8995_REG_ID0 0x00 /* Chip ID0 */ > +#define KS8995_REG_ID1 0x01 /* Chip ID1 */ > + > +#define KS8995_REG_GC0 0x02 /* Global Control 0 */ > +#define KS8995_REG_GC1 0x03 /* Global Control 1 */ > +#define KS8995_REG_GC2 0x04 /* Global Control 2 */ > +#define KS8995_REG_GC3 0x05 /* Global Control 3 */ > +#define KS8995_REG_GC4 0x06 /* Global Control 4 */ > +#define KS8995_REG_GC5 0x07 /* Global Control 5 */ > +#define KS8995_REG_GC6 0x08 /* Global Control 6 */ > +#define KS8995_REG_GC7 0x09 /* Global Control 7 */ > +#define KS8995_REG_GC8 0x0a /* Global Control 8 */ > +#define KS8995_REG_GC9 0x0b /* Global Control 9 */ > + > +#define KS8995_REG_PC(p, r) ((0x10 * p) + r) /* Port Control */ > +#define KS8995_REG_PS(p, r) ((0x10 * p) + r + 0xe) /* Port Status */ > + > +#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */ > +#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */ > +#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */ > +#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */ > +#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */ > +#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */ > +#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */ > +#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */ > + > +#define KS8995_REG_MAC0 0x68 /* MAC address 0 */ > +#define KS8995_REG_MAC1 0x69 /* MAC address 1 */ > +#define KS8995_REG_MAC2 0x6a /* MAC address 2 */ > +#define KS8995_REG_MAC3 0x6b /* MAC address 3 */ > +#define KS8995_REG_MAC4 0x6c /* MAC address 4 */ > +#define KS8995_REG_MAC5 0x6d /* MAC address 5 */ > + > +#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */ > +#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */ > +#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */ > +#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */ > +#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */ > +#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */ > +#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */ > +#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */ > +#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */ > +#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */ > + > +#define KS8995_REGS_SIZE 0x80 > + > +#define ID1_CHIPID_M 0xf > +#define ID1_CHIPID_S 4 > +#define ID1_REVISION_M 0x7 > +#define ID1_REVISION_S 1 > +#define ID1_START_SW 1 /* start the switch */ > + > +#define FAMILY_KS8995 0x95 > +#define CHIPID_M 0 > + > +#define KS8995_CMD_WRITE 0x02U > +#define KS8995_CMD_READ 0x03U > + > +#define KS8995_RESET_DELAY 10 /* usec */ > + > +struct ks8995_pdata { > + /* not yet implemented */ > +}; > + > +struct ks8995_switch { > + struct spi_device *spi; > + struct mutex lock; > + struct ks8995_pdata *pdata; > +}; > + > +static inline u8 get_chip_id(u8 val) > +{ > + return (val >> ID1_CHIPID_S) & ID1_CHIPID_M; > +} > + > +static inline u8 get_chip_rev(u8 val) > +{ > + return (val >> ID1_REVISION_S) & ID1_REVISION_M; > +} > + > +/* ------------------------------------------------------------------------ */ > +static int ks8995_read(struct ks8995_switch *ks, char *buf, > + unsigned offset, size_t count) > +{ > + u8 cmd[2]; > + struct spi_transfer t[2]; > + struct spi_message m; > + int err; > + > + spi_message_init(&m); > + > + memset(&t, 0, sizeof(t)); > + > + t[0].tx_buf = cmd; > + t[0].len = sizeof(cmd); > + spi_message_add_tail(&t[0], &m); > + > + t[1].rx_buf = buf; > + t[1].len = count; > + spi_message_add_tail(&t[1], &m); > + > + cmd[0] = KS8995_CMD_READ; > + cmd[1] = offset; > + > + mutex_lock(&ks->lock); > + err = spi_sync(ks->spi, &m); > + mutex_unlock(&ks->lock); > + > + return err ? err : count; > +} > + > + > +static int ks8995_write(struct ks8995_switch *ks, char *buf, > + unsigned offset, size_t count) > +{ > + u8 cmd[2]; > + struct spi_transfer t[2]; > + struct spi_message m; > + int err; > + > + spi_message_init(&m); > + > + memset(&t, 0, sizeof(t)); > + > + t[0].tx_buf = cmd; > + t[0].len = sizeof(cmd); > + spi_message_add_tail(&t[0], &m); > + > + t[1].tx_buf = buf; > + t[1].len = count; > + spi_message_add_tail(&t[1], &m); > + > + cmd[0] = KS8995_CMD_WRITE; > + cmd[1] = offset; > + > + mutex_lock(&ks->lock); > + err = spi_sync(ks->spi, &m); > + mutex_unlock(&ks->lock); > + > + return err ? err : count; > +} > + > +static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf) > +{ > + return (ks8995_read(ks, buf, addr, 1) != 1); > +} > + > +static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val) > +{ > + char buf = val; > + > + return (ks8995_write(ks, &buf, addr, 1) != 1); > +} > + > +/* ------------------------------------------------------------------------ */ > + > +static int ks8995_stop(struct ks8995_switch *ks) > +{ > + return ks8995_write_reg(ks, KS8995_REG_ID1, 0); > +} > + > +static int ks8995_start(struct ks8995_switch *ks) > +{ > + return ks8995_write_reg(ks, KS8995_REG_ID1, 1); > +} > + > +static int ks8995_reset(struct ks8995_switch *ks) > +{ > + int err; > + > + err = ks8995_stop(ks); > + if (err) > + return err; > + > + udelay(KS8995_RESET_DELAY); > + > + return ks8995_start(ks); > +} > + > +/* ------------------------------------------------------------------------ */ > + > +static ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj, > + struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) > +{ > + struct device *dev; > + struct ks8995_switch *ks8995; > + > + dev = container_of(kobj, struct device, kobj); > + ks8995 = dev_get_drvdata(dev); > + > + if (unlikely(off > KS8995_REGS_SIZE)) > + return 0; > + > + if ((off + count) > KS8995_REGS_SIZE) > + count = KS8995_REGS_SIZE - off; > + > + if (unlikely(!count)) > + return count; > + > + return ks8995_read(ks8995, buf, off, count); > +} > + > + > +static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj, > + struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) > +{ > + struct device *dev; > + struct ks8995_switch *ks8995; > + > + dev = container_of(kobj, struct device, kobj); > + ks8995 = dev_get_drvdata(dev); > + > + if (unlikely(off >= KS8995_REGS_SIZE)) > + return -EFBIG; > + > + if ((off + count) > KS8995_REGS_SIZE) > + count = KS8995_REGS_SIZE - off; > + > + if (unlikely(!count)) > + return count; > + > + return ks8995_write(ks8995, buf, off, count); > +} > + > + > +static struct bin_attribute ks8995_registers_attr = { > + .attr = { > + .name = "registers", > + .mode = S_IRUSR | S_IWUSR, > + }, > + .size = KS8995_REGS_SIZE, > + .read = ks8995_registers_read, > + .write = ks8995_registers_write, > +}; > + > +/* ------------------------------------------------------------------------ */ > + > +static int __devinit ks8995_probe(struct spi_device *spi) > +{ > + struct ks8995_switch *ks; > + struct ks8995_pdata *pdata; > + u8 ids[2]; > + int err; > + > + /* Chip description */ > + pdata = spi->dev.platform_data; > + > + ks = kzalloc(sizeof(*ks), GFP_KERNEL); > + if (!ks) { > + dev_err(&spi->dev, "no memory for private data\n"); > + return -ENOMEM; > + } > + > + mutex_init(&ks->lock); > + ks->pdata = pdata; > + ks->spi = spi_dev_get(spi); > + dev_set_drvdata(&spi->dev, ks); > + > + spi->mode = SPI_MODE_0; > + spi->bits_per_word = 8; > + err = spi_setup(spi); > + if (err) { > + dev_err(&spi->dev, "spi_setup failed, err=%d\n", err); > + goto err_drvdata; > + } > + > + err = ks8995_read(ks, ids, KS8995_REG_ID0, sizeof(ids)); > + if (err < 0) { > + dev_err(&spi->dev, "unable to read id registers, err=%d\n", > + err); > + goto err_drvdata; > + } > + > + switch (ids[0]) { > + case FAMILY_KS8995: > + break; > + default: > + dev_err(&spi->dev, "unknown family id:%02x\n", ids[0]); > + err = -ENODEV; > + goto err_drvdata; > + } > + > + err = ks8995_reset(ks); > + if (err) > + goto err_drvdata; > + > + err = sysfs_create_bin_file(&spi->dev.kobj, &ks8995_registers_attr); > + if (err) { > + dev_err(&spi->dev, "unable to create sysfs file, err=%d\n", > + err); > + goto err_drvdata; > + } > + > + dev_info(&spi->dev, "KS89%02X device found, Chip ID:%01x, " > + "Revision:%01x\n", ids[0], > + get_chip_id(ids[1]), get_chip_rev(ids[1])); > + > + return 0; > + > +err_drvdata: > + dev_set_drvdata(&spi->dev, NULL); > + kfree(ks); > + return err; > +} > + > +static int __devexit ks8995_remove(struct spi_device *spi) > +{ > + struct ks8995_data *ks8995; > + > + ks8995 = dev_get_drvdata(&spi->dev); > + sysfs_remove_bin_file(&spi->dev.kobj, &ks8995_registers_attr); > + > + dev_set_drvdata(&spi->dev, NULL); > + kfree(ks8995); > + > + return 0; > +} > + > +/* ------------------------------------------------------------------------ */ > + > +static struct spi_driver ks8995_driver = { > + .driver = { > + .name = "spi-ks8995", > + .bus = &spi_bus_type, > + .owner = THIS_MODULE, > + }, > + .probe = ks8995_probe, > + .remove = __devexit_p(ks8995_remove), > +}; > + > +static int __init ks8995_init(void) > +{ > + printk(KERN_INFO DRV_DESC " version " DRV_VERSION"\n"); > + > + return spi_register_driver(&ks8995_driver); > +} > +module_init(ks8995_init); > + > +static void __exit ks8995_exit(void) > +{ > + spi_unregister_driver(&ks8995_driver); > +} > +module_exit(ks8995_exit); > + > +MODULE_DESCRIPTION(DRV_DESC); > +MODULE_VERSION(DRV_VERSION); > +MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>"); > +MODULE_LICENSE("GPL v2"); > + > -- > 1.7.4.1 > BR, -- Fred -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index a702443..050f069 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -131,3 +131,8 @@ config MDIO_OCTEON If in doubt, say Y. endif # PHYLIB + +config MICREL_KS8995MA + tristate "Micrel KS8995MA 5-ports 10/100 managed Ethernet switch" + depends on SPI + diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 2333215..e15c83f 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -23,3 +23,4 @@ obj-$(CONFIG_DP83640_PHY) += dp83640.o obj-$(CONFIG_STE10XP) += ste10Xp.o obj-$(CONFIG_MICREL_PHY) += micrel.o obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o +obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o diff --git a/drivers/net/phy/spi_ks8995.c b/drivers/net/phy/spi_ks8995.c new file mode 100644 index 0000000..e9ce085 --- /dev/null +++ b/drivers/net/phy/spi_ks8995.c @@ -0,0 +1,376 @@ +/* + * SPI driver for Micrel/Kendin KS8995M ethernet switch + * + * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org> + * + * This file was based on: drivers/spi/at25.c + * Copyright (C) 2006 David Brownell + * + * 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 <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/device.h> + +#include <linux/spi/spi.h> + +#define DRV_VERSION "0.1.1" +#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver" + +/* ------------------------------------------------------------------------ */ + +#define KS8995_REG_ID0 0x00 /* Chip ID0 */ +#define KS8995_REG_ID1 0x01 /* Chip ID1 */ + +#define KS8995_REG_GC0 0x02 /* Global Control 0 */ +#define KS8995_REG_GC1 0x03 /* Global Control 1 */ +#define KS8995_REG_GC2 0x04 /* Global Control 2 */ +#define KS8995_REG_GC3 0x05 /* Global Control 3 */ +#define KS8995_REG_GC4 0x06 /* Global Control 4 */ +#define KS8995_REG_GC5 0x07 /* Global Control 5 */ +#define KS8995_REG_GC6 0x08 /* Global Control 6 */ +#define KS8995_REG_GC7 0x09 /* Global Control 7 */ +#define KS8995_REG_GC8 0x0a /* Global Control 8 */ +#define KS8995_REG_GC9 0x0b /* Global Control 9 */ + +#define KS8995_REG_PC(p, r) ((0x10 * p) + r) /* Port Control */ +#define KS8995_REG_PS(p, r) ((0x10 * p) + r + 0xe) /* Port Status */ + +#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */ +#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */ +#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */ +#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */ +#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */ +#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */ +#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */ +#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */ + +#define KS8995_REG_MAC0 0x68 /* MAC address 0 */ +#define KS8995_REG_MAC1 0x69 /* MAC address 1 */ +#define KS8995_REG_MAC2 0x6a /* MAC address 2 */ +#define KS8995_REG_MAC3 0x6b /* MAC address 3 */ +#define KS8995_REG_MAC4 0x6c /* MAC address 4 */ +#define KS8995_REG_MAC5 0x6d /* MAC address 5 */ + +#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */ +#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */ +#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */ +#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */ +#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */ +#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */ +#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */ +#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */ +#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */ +#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */ + +#define KS8995_REGS_SIZE 0x80 + +#define ID1_CHIPID_M 0xf +#define ID1_CHIPID_S 4 +#define ID1_REVISION_M 0x7 +#define ID1_REVISION_S 1 +#define ID1_START_SW 1 /* start the switch */ + +#define FAMILY_KS8995 0x95 +#define CHIPID_M 0 + +#define KS8995_CMD_WRITE 0x02U +#define KS8995_CMD_READ 0x03U + +#define KS8995_RESET_DELAY 10 /* usec */ + +struct ks8995_pdata { + /* not yet implemented */ +}; + +struct ks8995_switch { + struct spi_device *spi; + struct mutex lock; + struct ks8995_pdata *pdata; +}; + +static inline u8 get_chip_id(u8 val) +{ + return (val >> ID1_CHIPID_S) & ID1_CHIPID_M; +} + +static inline u8 get_chip_rev(u8 val) +{ + return (val >> ID1_REVISION_S) & ID1_REVISION_M; +} + +/* ------------------------------------------------------------------------ */ +static int ks8995_read(struct ks8995_switch *ks, char *buf, + unsigned offset, size_t count) +{ + u8 cmd[2]; + struct spi_transfer t[2]; + struct spi_message m; + int err; + + spi_message_init(&m); + + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = count; + spi_message_add_tail(&t[1], &m); + + cmd[0] = KS8995_CMD_READ; + cmd[1] = offset; + + mutex_lock(&ks->lock); + err = spi_sync(ks->spi, &m); + mutex_unlock(&ks->lock); + + return err ? err : count; +} + + +static int ks8995_write(struct ks8995_switch *ks, char *buf, + unsigned offset, size_t count) +{ + u8 cmd[2]; + struct spi_transfer t[2]; + struct spi_message m; + int err; + + spi_message_init(&m); + + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = buf; + t[1].len = count; + spi_message_add_tail(&t[1], &m); + + cmd[0] = KS8995_CMD_WRITE; + cmd[1] = offset; + + mutex_lock(&ks->lock); + err = spi_sync(ks->spi, &m); + mutex_unlock(&ks->lock); + + return err ? err : count; +} + +static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf) +{ + return (ks8995_read(ks, buf, addr, 1) != 1); +} + +static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val) +{ + char buf = val; + + return (ks8995_write(ks, &buf, addr, 1) != 1); +} + +/* ------------------------------------------------------------------------ */ + +static int ks8995_stop(struct ks8995_switch *ks) +{ + return ks8995_write_reg(ks, KS8995_REG_ID1, 0); +} + +static int ks8995_start(struct ks8995_switch *ks) +{ + return ks8995_write_reg(ks, KS8995_REG_ID1, 1); +} + +static int ks8995_reset(struct ks8995_switch *ks) +{ + int err; + + err = ks8995_stop(ks); + if (err) + return err; + + udelay(KS8995_RESET_DELAY); + + return ks8995_start(ks); +} + +/* ------------------------------------------------------------------------ */ + +static ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) +{ + struct device *dev; + struct ks8995_switch *ks8995; + + dev = container_of(kobj, struct device, kobj); + ks8995 = dev_get_drvdata(dev); + + if (unlikely(off > KS8995_REGS_SIZE)) + return 0; + + if ((off + count) > KS8995_REGS_SIZE) + count = KS8995_REGS_SIZE - off; + + if (unlikely(!count)) + return count; + + return ks8995_read(ks8995, buf, off, count); +} + + +static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) +{ + struct device *dev; + struct ks8995_switch *ks8995; + + dev = container_of(kobj, struct device, kobj); + ks8995 = dev_get_drvdata(dev); + + if (unlikely(off >= KS8995_REGS_SIZE)) + return -EFBIG; + + if ((off + count) > KS8995_REGS_SIZE) + count = KS8995_REGS_SIZE - off; + + if (unlikely(!count)) + return count; + + return ks8995_write(ks8995, buf, off, count); +} + + +static struct bin_attribute ks8995_registers_attr = { + .attr = { + .name = "registers", + .mode = S_IRUSR | S_IWUSR, + }, + .size = KS8995_REGS_SIZE, + .read = ks8995_registers_read, + .write = ks8995_registers_write, +}; + +/* ------------------------------------------------------------------------ */ + +static int __devinit ks8995_probe(struct spi_device *spi) +{ + struct ks8995_switch *ks; + struct ks8995_pdata *pdata; + u8 ids[2]; + int err; + + /* Chip description */ + pdata = spi->dev.platform_data; + + ks = kzalloc(sizeof(*ks), GFP_KERNEL); + if (!ks) { + dev_err(&spi->dev, "no memory for private data\n"); + return -ENOMEM; + } + + mutex_init(&ks->lock); + ks->pdata = pdata; + ks->spi = spi_dev_get(spi); + dev_set_drvdata(&spi->dev, ks); + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "spi_setup failed, err=%d\n", err); + goto err_drvdata; + } + + err = ks8995_read(ks, ids, KS8995_REG_ID0, sizeof(ids)); + if (err < 0) { + dev_err(&spi->dev, "unable to read id registers, err=%d\n", + err); + goto err_drvdata; + } + + switch (ids[0]) { + case FAMILY_KS8995: + break; + default: + dev_err(&spi->dev, "unknown family id:%02x\n", ids[0]); + err = -ENODEV; + goto err_drvdata; + } + + err = ks8995_reset(ks); + if (err) + goto err_drvdata; + + err = sysfs_create_bin_file(&spi->dev.kobj, &ks8995_registers_attr); + if (err) { + dev_err(&spi->dev, "unable to create sysfs file, err=%d\n", + err); + goto err_drvdata; + } + + dev_info(&spi->dev, "KS89%02X device found, Chip ID:%01x, " + "Revision:%01x\n", ids[0], + get_chip_id(ids[1]), get_chip_rev(ids[1])); + + return 0; + +err_drvdata: + dev_set_drvdata(&spi->dev, NULL); + kfree(ks); + return err; +} + +static int __devexit ks8995_remove(struct spi_device *spi) +{ + struct ks8995_data *ks8995; + + ks8995 = dev_get_drvdata(&spi->dev); + sysfs_remove_bin_file(&spi->dev.kobj, &ks8995_registers_attr); + + dev_set_drvdata(&spi->dev, NULL); + kfree(ks8995); + + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static struct spi_driver ks8995_driver = { + .driver = { + .name = "spi-ks8995", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ks8995_probe, + .remove = __devexit_p(ks8995_remove), +}; + +static int __init ks8995_init(void) +{ + printk(KERN_INFO DRV_DESC " version " DRV_VERSION"\n"); + + return spi_register_driver(&ks8995_driver); +} +module_init(ks8995_init); + +static void __exit ks8995_exit(void) +{ + spi_unregister_driver(&ks8995_driver); +} +module_exit(ks8995_exit); + +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>"); +MODULE_LICENSE("GPL v2"); +