diff options
Diffstat (limited to 'drivers/misc/eeprom')
| -rw-r--r-- | drivers/misc/eeprom/Kconfig | 51 | ||||
| -rw-r--r-- | drivers/misc/eeprom/Makefile | 3 | ||||
| -rw-r--r-- | drivers/misc/eeprom/at24.c | 92 | ||||
| -rw-r--r-- | drivers/misc/eeprom/at25.c | 160 | ||||
| -rw-r--r-- | drivers/misc/eeprom/digsy_mtc_eeprom.c | 85 | ||||
| -rw-r--r-- | drivers/misc/eeprom/eeprom.c | 17 | ||||
| -rw-r--r-- | drivers/misc/eeprom/eeprom_93cx6.c | 88 | ||||
| -rw-r--r-- | drivers/misc/eeprom/eeprom_93xx46.c | 398 | ||||
| -rw-r--r-- | drivers/misc/eeprom/max6875.c | 15 | ||||
| -rw-r--r-- | drivers/misc/eeprom/sunxi_sid.c | 157 | 
10 files changed, 946 insertions, 120 deletions
diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 9118613af32..9536852fd4c 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -1,13 +1,14 @@  menu "EEPROM support"  config EEPROM_AT24 -	tristate "I2C EEPROMs from most vendors" +	tristate "I2C EEPROMs / RAMs / ROMs from most vendors"  	depends on I2C && SYSFS  	help -	  Enable this driver to get read/write support to most I2C EEPROMs, -	  after you configure the driver to know about each EEPROM on -	  your target board.  Use these generic chip names, instead of -	  vendor-specific ones like at24c64 or 24lc02: +	  Enable this driver to get read/write support to most I2C EEPROMs +	  and compatible devices like FRAMs, SRAMs, ROMs etc. After you +	  configure the driver to know about each chip on your target +	  board.  Use these generic chip names, instead of vendor-specific +	  ones like at24c64, 24lc02 or fm24c04:  	     24c00, 24c01, 24c02, spd (readonly 24c02), 24c04, 24c08,  	     24c16, 24c32, 24c64, 24c128, 24c256, 24c512, 24c1024 @@ -50,7 +51,7 @@ config EEPROM_LEGACY  config EEPROM_MAX6875  	tristate "Maxim MAX6874/5 power supply supervisor" -	depends on I2C && EXPERIMENTAL +	depends on I2C  	help  	  If you say yes here you get read-only support for the user EEPROM of  	  the Maxim MAX6874/5 EEPROM-programmable, quad power-supply @@ -70,4 +71,42 @@ config EEPROM_93CX6  	  If unsure, say N. +config EEPROM_93XX46 +	tristate "Microwire EEPROM 93XX46 support" +	depends on SPI && SYSFS +	help +	  Driver for the microwire EEPROM chipsets 93xx46x. The driver +	  supports both read and write commands and also the command to +	  erase the whole EEPROM. + +	  This driver can also be built as a module.  If so, the module +	  will be called eeprom_93xx46. + +	  If unsure, say N. + +config EEPROM_DIGSY_MTC_CFG +	bool "DigsyMTC display configuration EEPROMs device" +	depends on GPIO_MPC5200 && SPI_GPIO +	help +	  This option enables access to display configuration EEPROMs +	  on digsy_mtc board. You have to additionally select Microwire +	  EEPROM 93XX46 driver. sysfs entries will be created for that +	  EEPROM allowing to read/write the configuration data or to +	  erase the whole EEPROM. + +	  If unsure, say N. + +config EEPROM_SUNXI_SID +	tristate "Allwinner sunxi security ID support" +	depends on ARCH_SUNXI && SYSFS +	help +	  This is a driver for the 'security ID' available on various Allwinner +	  devices. + +	  Due to the potential risks involved with changing e-fuses, +	  this driver is read-only. + +	  This driver can also be built as a module. If so, the module +	  will be called sunxi_sid. +  endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile index df3d68ffa9d..9507aec95e9 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile @@ -3,3 +3,6 @@ obj-$(CONFIG_EEPROM_AT25)	+= at25.o  obj-$(CONFIG_EEPROM_LEGACY)	+= eeprom.o  obj-$(CONFIG_EEPROM_MAX6875)	+= max6875.o  obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o +obj-$(CONFIG_EEPROM_93XX46)	+= eeprom_93xx46.o +obj-$(CONFIG_EEPROM_SUNXI_SID)	+= sunxi_sid.o +obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 559b0b3c16c..d87f77f790d 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -20,8 +20,9 @@  #include <linux/log2.h>  #include <linux/bitops.h>  #include <linux/jiffies.h> +#include <linux/of.h>  #include <linux/i2c.h> -#include <linux/i2c/at24.h> +#include <linux/platform_data/at24.h>  /*   * I2C EEPROMs from most vendors are inexpensive and mostly interchangeable. @@ -427,6 +428,9 @@ static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,  {  	struct at24_data *at24; +	if (unlikely(off >= attr->size)) +		return -EFBIG; +  	at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));  	return at24_write(at24, buf, off, count);  } @@ -457,6 +461,27 @@ static ssize_t at24_macc_write(struct memory_accessor *macc, const char *buf,  /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_OF +static void at24_get_ofdata(struct i2c_client *client, +		struct at24_platform_data *chip) +{ +	const __be32 *val; +	struct device_node *node = client->dev.of_node; + +	if (node) { +		if (of_get_property(node, "read-only", NULL)) +			chip->flags |= AT24_FLAG_READONLY; +		val = of_get_property(node, "pagesize", NULL); +		if (val) +			chip->page_size = be32_to_cpup(val); +	} +} +#else +static void at24_get_ofdata(struct i2c_client *client, +		struct at24_platform_data *chip) +{ } +#endif /* CONFIG_OF */ +  static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  {  	struct at24_platform_data chip; @@ -470,10 +495,9 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  	if (client->dev.platform_data) {  		chip = *(struct at24_platform_data *)client->dev.platform_data;  	} else { -		if (!id->driver_data) { -			err = -ENODEV; -			goto err_out; -		} +		if (!id->driver_data) +			return -ENODEV; +  		magic = id->driver_data;  		chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));  		magic >>= AT24_SIZE_BYTELEN; @@ -485,6 +509,9 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  		 */  		chip.page_size = 1; +		/* update chipdata if OF is present */ +		at24_get_ofdata(client, &chip); +  		chip.setup = NULL;  		chip.context = NULL;  	} @@ -492,16 +519,19 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  	if (!is_power_of_2(chip.byte_len))  		dev_warn(&client->dev,  			"byte_len looks suspicious (no power of 2)!\n"); +	if (!chip.page_size) { +		dev_err(&client->dev, "page_size must not be 0!\n"); +		return -EINVAL; +	}  	if (!is_power_of_2(chip.page_size))  		dev_warn(&client->dev,  			"page_size looks suspicious (no power of 2)!\n");  	/* Use I2C operations unless we're stuck with SMBus extensions. */  	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { -		if (chip.flags & AT24_FLAG_ADDR16) { -			err = -EPFNOSUPPORT; -			goto err_out; -		} +		if (chip.flags & AT24_FLAG_ADDR16) +			return -EPFNOSUPPORT; +  		if (i2c_check_functionality(client->adapter,  				I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {  			use_smbus = I2C_SMBUS_I2C_BLOCK_DATA; @@ -512,8 +542,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  				I2C_FUNC_SMBUS_READ_BYTE_DATA)) {  			use_smbus = I2C_SMBUS_BYTE_DATA;  		} else { -			err = -EPFNOSUPPORT; -			goto err_out; +			return -EPFNOSUPPORT;  		}  	} @@ -523,12 +552,10 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  		num_addresses =	DIV_ROUND_UP(chip.byte_len,  			(chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256); -	at24 = kzalloc(sizeof(struct at24_data) + +	at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) +  		num_addresses * sizeof(struct i2c_client *), GFP_KERNEL); -	if (!at24) { -		err = -ENOMEM; -		goto err_out; -	} +	if (!at24) +		return -ENOMEM;  	mutex_init(&at24->lock);  	at24->use_smbus = use_smbus; @@ -566,11 +593,10 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  			at24->write_max = write_max;  			/* buffer (data + address at the beginning) */ -			at24->writebuf = kmalloc(write_max + 2, GFP_KERNEL); -			if (!at24->writebuf) { -				err = -ENOMEM; -				goto err_struct; -			} +			at24->writebuf = devm_kzalloc(&client->dev, +				write_max + 2, GFP_KERNEL); +			if (!at24->writebuf) +				return -ENOMEM;  		} else {  			dev_warn(&client->dev,  				"cannot write due to controller restrictions."); @@ -597,19 +623,15 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)  	i2c_set_clientdata(client, at24); -	dev_info(&client->dev, "%zu byte %s EEPROM %s\n", +	dev_info(&client->dev, "%zu byte %s EEPROM, %s, %u bytes/write\n",  		at24->bin.size, client->name, -		writable ? "(writable)" : "(read-only)"); +		writable ? "writable" : "read-only", at24->write_max);  	if (use_smbus == I2C_SMBUS_WORD_DATA ||  	    use_smbus == I2C_SMBUS_BYTE_DATA) {  		dev_notice(&client->dev, "Falling back to %s reads, "  			   "performance will suffer\n", use_smbus ==  			   I2C_SMBUS_WORD_DATA ? "word" : "byte");  	} -	dev_dbg(&client->dev, -		"page_size %d, num_addresses %d, write_max %d, use_smbus %d\n", -		chip.page_size, num_addresses, -		at24->write_max, use_smbus);  	/* export data to kernel code */  	if (chip.setup) @@ -622,15 +644,10 @@ err_clients:  		if (at24->client[i])  			i2c_unregister_device(at24->client[i]); -	kfree(at24->writebuf); -err_struct: -	kfree(at24); -err_out: -	dev_dbg(&client->dev, "probe error %d\n", err);  	return err;  } -static int __devexit at24_remove(struct i2c_client *client) +static int at24_remove(struct i2c_client *client)  {  	struct at24_data *at24;  	int i; @@ -641,8 +658,6 @@ static int __devexit at24_remove(struct i2c_client *client)  	for (i = 1; i < at24->num_addresses; i++)  		i2c_unregister_device(at24->client[i]); -	kfree(at24->writebuf); -	kfree(at24);  	return 0;  } @@ -654,12 +669,17 @@ static struct i2c_driver at24_driver = {  		.owner = THIS_MODULE,  	},  	.probe = at24_probe, -	.remove = __devexit_p(at24_remove), +	.remove = at24_remove,  	.id_table = at24_ids,  };  static int __init at24_init(void)  { +	if (!io_limit) { +		pr_err("at24: io_limit must not be 0!\n"); +		return -EINVAL; +	} +  	io_limit = rounddown_pow_of_two(io_limit);  	return i2c_add_driver(&at24_driver);  } diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index c627e4174cc..634f72929e1 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -10,7 +10,6 @@   */  #include <linux/kernel.h> -#include <linux/init.h>  #include <linux/module.h>  #include <linux/slab.h>  #include <linux/delay.h> @@ -19,7 +18,7 @@  #include <linux/spi/spi.h>  #include <linux/spi/eeprom.h> - +#include <linux/of.h>  /*   * NOTE: this is an *EEPROM* driver.  The vagaries of product naming @@ -50,6 +49,7 @@ struct at25_data {  #define	AT25_SR_BP1	0x08  #define	AT25_SR_WPEN	0x80		/* writeprotect enable */ +#define	AT25_INSTR_BIT3	0x08		/* Additional address bit in instr */  #define EE_MAXADDRLEN	3		/* 24 bit addresses, up to 2 MBytes */ @@ -75,6 +75,7 @@ at25_ee_read(  	ssize_t			status;  	struct spi_transfer	t[2];  	struct spi_message	m; +	u8			instr;  	if (unlikely(offset >= at25->bin.size))  		return 0; @@ -84,7 +85,12 @@ at25_ee_read(  		return count;  	cp = command; -	*cp++ = AT25_READ; + +	instr = AT25_READ; +	if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR) +		if (offset >= (1U << (at25->addrlen * 8))) +			instr |= AT25_INSTR_BIT3; +	*cp++ = instr;  	/* 8/16/24-bit address is written MSB first */  	switch (at25->addrlen) { @@ -167,14 +173,14 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,  	/* For write, rollover is within the page ... so we write at  	 * most one page, then manually roll over to the next page.  	 */ -	bounce[0] = AT25_WRITE;  	mutex_lock(&at25->lock);  	do {  		unsigned long	timeout, retries;  		unsigned	segment;  		unsigned	offset = (unsigned) off; -		u8		*cp = bounce + 1; +		u8		*cp = bounce;  		int		sr; +		u8		instr;  		*cp = AT25_WREN;  		status = spi_write(at25->spi, cp, 1); @@ -184,6 +190,12 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,  			break;  		} +		instr = AT25_WRITE; +		if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR) +			if (offset >= (1U << (at25->addrlen * 8))) +				instr |= AT25_INSTR_BIT3; +		*cp++ = instr; +  		/* 8/16/24-bit address is written MSB first */  		switch (at25->addrlen) {  		default:	/* case 3 */ @@ -289,33 +301,93 @@ static ssize_t at25_mem_write(struct memory_accessor *mem, const char *buf,  /*-------------------------------------------------------------------------*/ +static int at25_np_to_chip(struct device *dev, +			   struct device_node *np, +			   struct spi_eeprom *chip) +{ +	u32 val; + +	memset(chip, 0, sizeof(*chip)); +	strncpy(chip->name, np->name, sizeof(chip->name)); + +	if (of_property_read_u32(np, "size", &val) == 0 || +	    of_property_read_u32(np, "at25,byte-len", &val) == 0) { +		chip->byte_len = val; +	} else { +		dev_err(dev, "Error: missing \"size\" property\n"); +		return -ENODEV; +	} + +	if (of_property_read_u32(np, "pagesize", &val) == 0 || +	    of_property_read_u32(np, "at25,page-size", &val) == 0) { +		chip->page_size = (u16)val; +	} else { +		dev_err(dev, "Error: missing \"pagesize\" property\n"); +		return -ENODEV; +	} + +	if (of_property_read_u32(np, "at25,addr-mode", &val) == 0) { +		chip->flags = (u16)val; +	} else { +		if (of_property_read_u32(np, "address-width", &val)) { +			dev_err(dev, +				"Error: missing \"address-width\" property\n"); +			return -ENODEV; +		} +		switch (val) { +		case 8: +			chip->flags |= EE_ADDR1; +			break; +		case 16: +			chip->flags |= EE_ADDR2; +			break; +		case 24: +			chip->flags |= EE_ADDR3; +			break; +		default: +			dev_err(dev, +				"Error: bad \"address-width\" property: %u\n", +				val); +			return -ENODEV; +		} +		if (of_find_property(np, "read-only", NULL)) +			chip->flags |= EE_READONLY; +	} +	return 0; +} +  static int at25_probe(struct spi_device *spi)  {  	struct at25_data	*at25 = NULL; -	const struct spi_eeprom *chip; +	struct spi_eeprom	chip; +	struct device_node	*np = spi->dev.of_node;  	int			err;  	int			sr;  	int			addrlen;  	/* Chip description */ -	chip = spi->dev.platform_data; -	if (!chip) { -		dev_dbg(&spi->dev, "no chip description\n"); -		err = -ENODEV; -		goto fail; -	} +	if (!spi->dev.platform_data) { +		if (np) { +			err = at25_np_to_chip(&spi->dev, np, &chip); +			if (err) +				return err; +		} else { +			dev_err(&spi->dev, "Error: no chip description\n"); +			return -ENODEV; +		} +	} else +		chip = *(struct spi_eeprom *)spi->dev.platform_data;  	/* For now we only support 8/16/24 bit addressing */ -	if (chip->flags & EE_ADDR1) +	if (chip.flags & EE_ADDR1)  		addrlen = 1; -	else if (chip->flags & EE_ADDR2) +	else if (chip.flags & EE_ADDR2)  		addrlen = 2; -	else if (chip->flags & EE_ADDR3) +	else if (chip.flags & EE_ADDR3)  		addrlen = 3;  	else {  		dev_dbg(&spi->dev, "unsupported address type\n"); -		err = -EINVAL; -		goto fail; +		return -EINVAL;  	}  	/* Ping the chip ... the status register is pretty portable, @@ -325,19 +397,17 @@ static int at25_probe(struct spi_device *spi)  	sr = spi_w8r8(spi, AT25_RDSR);  	if (sr < 0 || sr & AT25_SR_nRDY) {  		dev_dbg(&spi->dev, "rdsr --> %d (%02x)\n", sr, sr); -		err = -ENXIO; -		goto fail; +		return -ENXIO;  	} -	if (!(at25 = kzalloc(sizeof *at25, GFP_KERNEL))) { -		err = -ENOMEM; -		goto fail; -	} +	at25 = devm_kzalloc(&spi->dev, sizeof(struct at25_data), GFP_KERNEL); +	if (!at25) +		return -ENOMEM;  	mutex_init(&at25->lock); -	at25->chip = *chip; +	at25->chip = chip;  	at25->spi = spi_dev_get(spi); -	dev_set_drvdata(&spi->dev, at25); +	spi_set_drvdata(spi, at25);  	at25->addrlen = addrlen;  	/* Export the EEPROM bytes through sysfs, since that's convenient. @@ -356,7 +426,7 @@ static int at25_probe(struct spi_device *spi)  	at25->mem.read = at25_mem_read;  	at25->bin.size = at25->chip.byte_len; -	if (!(chip->flags & EE_READONLY)) { +	if (!(chip.flags & EE_READONLY)) {  		at25->bin.write = at25_bin_write;  		at25->bin.attr.mode |= S_IWUSR;  		at25->mem.write = at25_mem_write; @@ -364,10 +434,10 @@ static int at25_probe(struct spi_device *spi)  	err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin);  	if (err) -		goto fail; +		return err; -	if (chip->setup) -		chip->setup(&at25->mem, chip->context); +	if (chip.setup) +		chip.setup(&at25->mem, chip.context);  	dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",  		(at25->bin.size < 1024) @@ -375,47 +445,39 @@ static int at25_probe(struct spi_device *spi)  			: (at25->bin.size / 1024),  		(at25->bin.size < 1024) ? "Byte" : "KByte",  		at25->chip.name, -		(chip->flags & EE_READONLY) ? " (readonly)" : "", +		(chip.flags & EE_READONLY) ? " (readonly)" : "",  		at25->chip.page_size);  	return 0; -fail: -	dev_dbg(&spi->dev, "probe err %d\n", err); -	kfree(at25); -	return err;  } -static int __devexit at25_remove(struct spi_device *spi) +static int at25_remove(struct spi_device *spi)  {  	struct at25_data	*at25; -	at25 = dev_get_drvdata(&spi->dev); +	at25 = spi_get_drvdata(spi);  	sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin); -	kfree(at25);  	return 0;  }  /*-------------------------------------------------------------------------*/ +static const struct of_device_id at25_of_match[] = { +	{ .compatible = "atmel,at25", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, at25_of_match); +  static struct spi_driver at25_driver = {  	.driver = {  		.name		= "at25",  		.owner		= THIS_MODULE, +		.of_match_table = at25_of_match,  	},  	.probe		= at25_probe, -	.remove		= __devexit_p(at25_remove), +	.remove		= at25_remove,  }; -static int __init at25_init(void) -{ -	return spi_register_driver(&at25_driver); -} -module_init(at25_init); - -static void __exit at25_exit(void) -{ -	spi_unregister_driver(&at25_driver); -} -module_exit(at25_exit); +module_spi_driver(at25_driver);  MODULE_DESCRIPTION("Driver for most SPI EEPROMs");  MODULE_AUTHOR("David Brownell"); diff --git a/drivers/misc/eeprom/digsy_mtc_eeprom.c b/drivers/misc/eeprom/digsy_mtc_eeprom.c new file mode 100644 index 00000000000..66d9e1baeae --- /dev/null +++ b/drivers/misc/eeprom/digsy_mtc_eeprom.c @@ -0,0 +1,85 @@ +/* + * EEPROMs access control driver for display configuration EEPROMs + * on DigsyMTC board. + * + * (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de> + * + * 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/gpio.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi_gpio.h> +#include <linux/eeprom_93xx46.h> + +#define GPIO_EEPROM_CLK		216 +#define GPIO_EEPROM_CS		210 +#define GPIO_EEPROM_DI		217 +#define GPIO_EEPROM_DO		249 +#define GPIO_EEPROM_OE		255 +#define EE_SPI_BUS_NUM	1 + +static void digsy_mtc_op_prepare(void *p) +{ +	/* enable */ +	gpio_set_value(GPIO_EEPROM_OE, 0); +} + +static void digsy_mtc_op_finish(void *p) +{ +	/* disable */ +	gpio_set_value(GPIO_EEPROM_OE, 1); +} + +struct eeprom_93xx46_platform_data digsy_mtc_eeprom_data = { +	.flags		= EE_ADDR8, +	.prepare	= digsy_mtc_op_prepare, +	.finish		= digsy_mtc_op_finish, +}; + +static struct spi_gpio_platform_data eeprom_spi_gpio_data = { +	.sck		= GPIO_EEPROM_CLK, +	.mosi		= GPIO_EEPROM_DI, +	.miso		= GPIO_EEPROM_DO, +	.num_chipselect	= 1, +}; + +static struct platform_device digsy_mtc_eeprom = { +	.name	= "spi_gpio", +	.id	= EE_SPI_BUS_NUM, +	.dev	= { +		.platform_data	= &eeprom_spi_gpio_data, +	}, +}; + +static struct spi_board_info digsy_mtc_eeprom_info[] __initdata = { +	{ +		.modalias		= "93xx46", +		.max_speed_hz		= 1000000, +		.bus_num		= EE_SPI_BUS_NUM, +		.chip_select		= 0, +		.mode			= SPI_MODE_0, +		.controller_data	= (void *)GPIO_EEPROM_CS, +		.platform_data		= &digsy_mtc_eeprom_data, +	}, +}; + +static int __init digsy_mtc_eeprom_devices_init(void) +{ +	int ret; + +	ret = gpio_request_one(GPIO_EEPROM_OE, GPIOF_OUT_INIT_HIGH, +				"93xx46 EEPROMs OE"); +	if (ret) { +		pr_err("can't request gpio %d\n", GPIO_EEPROM_OE); +		return ret; +	} +	spi_register_board_info(digsy_mtc_eeprom_info, +				ARRAY_SIZE(digsy_mtc_eeprom_info)); +	return platform_device_register(&digsy_mtc_eeprom); +} +device_initcall(digsy_mtc_eeprom_devices_init); diff --git a/drivers/misc/eeprom/eeprom.c b/drivers/misc/eeprom/eeprom.c index 45060ddc4e5..33f8673d23a 100644 --- a/drivers/misc/eeprom/eeprom.c +++ b/drivers/misc/eeprom/eeprom.c @@ -3,7 +3,7 @@   *                           Philip Edelbrock <phil@netroedge.com>   * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>   * Copyright (C) 2003 IBM Corp. - * Copyright (C) 2004 Jean Delvare <khali@linux-fr.org> + * Copyright (C) 2004 Jean Delvare <jdelvare@suse.de>   *   * 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 @@ -17,7 +17,6 @@   */  #include <linux/kernel.h> -#include <linux/init.h>  #include <linux/module.h>  #include <linux/slab.h>  #include <linux/jiffies.h> @@ -229,22 +228,10 @@ static struct i2c_driver eeprom_driver = {  	.address_list	= normal_i2c,  }; -static int __init eeprom_init(void) -{ -	return i2c_add_driver(&eeprom_driver); -} - -static void __exit eeprom_exit(void) -{ -	i2c_del_driver(&eeprom_driver); -} - +module_i2c_driver(eeprom_driver);  MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and "  		"Philip Edelbrock <phil@netroedge.com> and "  		"Greg Kroah-Hartman <greg@kroah.com>");  MODULE_DESCRIPTION("I2C EEPROM driver");  MODULE_LICENSE("GPL"); - -module_init(eeprom_init); -module_exit(eeprom_exit); diff --git a/drivers/misc/eeprom/eeprom_93cx6.c b/drivers/misc/eeprom/eeprom_93cx6.c index 7b33de95c4b..0ff4b02177b 100644 --- a/drivers/misc/eeprom/eeprom_93cx6.c +++ b/drivers/misc/eeprom/eeprom_93cx6.c @@ -63,6 +63,7 @@ static void eeprom_93cx6_startup(struct eeprom_93cx6 *eeprom)  	eeprom->reg_data_out = 0;  	eeprom->reg_data_clock = 0;  	eeprom->reg_chip_select = 1; +	eeprom->drive_data = 1;  	eeprom->register_write(eeprom);  	/* @@ -101,6 +102,7 @@ static void eeprom_93cx6_write_bits(struct eeprom_93cx6 *eeprom,  	 */  	eeprom->reg_data_in = 0;  	eeprom->reg_data_out = 0; +	eeprom->drive_data = 1;  	/*  	 * Start writing all bits. @@ -140,6 +142,7 @@ static void eeprom_93cx6_read_bits(struct eeprom_93cx6 *eeprom,  	 */  	eeprom->reg_data_in = 0;  	eeprom->reg_data_out = 0; +	eeprom->drive_data = 0;  	/*  	 * Start reading all bits. @@ -231,3 +234,88 @@ void eeprom_93cx6_multiread(struct eeprom_93cx6 *eeprom, const u8 word,  }  EXPORT_SYMBOL_GPL(eeprom_93cx6_multiread); +/** + * eeprom_93cx6_wren - set the write enable state + * @eeprom: Pointer to eeprom structure + * @enable: true to enable writes, otherwise disable writes + * + * Set the EEPROM write enable state to either allow or deny + * writes depending on the @enable value. + */ +void eeprom_93cx6_wren(struct eeprom_93cx6 *eeprom, bool enable) +{ +	u16 command; + +	/* start the command */ +	eeprom_93cx6_startup(eeprom); + +	/* create command to enable/disable */ + +	command = enable ? PCI_EEPROM_EWEN_OPCODE : PCI_EEPROM_EWDS_OPCODE; +	command <<= (eeprom->width - 2); + +	eeprom_93cx6_write_bits(eeprom, command, +				PCI_EEPROM_WIDTH_OPCODE + eeprom->width); + +	eeprom_93cx6_cleanup(eeprom); +} +EXPORT_SYMBOL_GPL(eeprom_93cx6_wren); + +/** + * eeprom_93cx6_write - write data to the EEPROM + * @eeprom: Pointer to eeprom structure + * @addr: Address to write data to. + * @data: The data to write to address @addr. + * + * Write the @data to the specified @addr in the EEPROM and + * waiting for the device to finish writing. + * + * Note, since we do not expect large number of write operations + * we delay in between parts of the operation to avoid using excessive + * amounts of CPU time busy waiting. + */ +void eeprom_93cx6_write(struct eeprom_93cx6 *eeprom, u8 addr, u16 data) +{ +	int timeout = 100; +	u16 command; + +	/* start the command */ +	eeprom_93cx6_startup(eeprom); + +	command = PCI_EEPROM_WRITE_OPCODE << eeprom->width; +	command |= addr; + +	/* send write command */ +	eeprom_93cx6_write_bits(eeprom, command, +				PCI_EEPROM_WIDTH_OPCODE + eeprom->width); + +	/* send data */ +	eeprom_93cx6_write_bits(eeprom, data, 16); + +	/* get ready to check for busy */ +	eeprom->drive_data = 0; +	eeprom->reg_chip_select = 1; +	eeprom->register_write(eeprom); + +	/* wait at-least 250ns to get DO to be the busy signal */ +	usleep_range(1000, 2000); + +	/* wait for DO to go high to signify finish */ + +	while (true) { +		eeprom->register_read(eeprom); + +		if (eeprom->reg_data_out) +			break; + +		usleep_range(1000, 2000); + +		if (--timeout <= 0) { +			printk(KERN_ERR "%s: timeout\n", __func__); +			break; +		} +	} + +	eeprom_93cx6_cleanup(eeprom); +} +EXPORT_SYMBOL_GPL(eeprom_93cx6_write); diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c new file mode 100644 index 00000000000..9ebeacdb8ec --- /dev/null +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -0,0 +1,398 @@ +/* + * Driver for 93xx46 EEPROMs + * + * (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de> + * + * 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/delay.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/eeprom_93xx46.h> + +#define OP_START	0x4 +#define OP_WRITE	(OP_START | 0x1) +#define OP_READ		(OP_START | 0x2) +#define ADDR_EWDS	0x00 +#define ADDR_ERAL	0x20 +#define ADDR_EWEN	0x30 + +struct eeprom_93xx46_dev { +	struct spi_device *spi; +	struct eeprom_93xx46_platform_data *pdata; +	struct bin_attribute bin; +	struct mutex lock; +	int addrlen; +}; + +static ssize_t +eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj, +		       struct bin_attribute *bin_attr, +		       char *buf, loff_t off, size_t count) +{ +	struct eeprom_93xx46_dev *edev; +	struct device *dev; +	struct spi_message m; +	struct spi_transfer t[2]; +	int bits, ret; +	u16 cmd_addr; + +	dev = container_of(kobj, struct device, kobj); +	edev = dev_get_drvdata(dev); + +	if (unlikely(off >= edev->bin.size)) +		return 0; +	if ((off + count) > edev->bin.size) +		count = edev->bin.size - off; +	if (unlikely(!count)) +		return count; + +	cmd_addr = OP_READ << edev->addrlen; + +	if (edev->addrlen == 7) { +		cmd_addr |= off & 0x7f; +		bits = 10; +	} else { +		cmd_addr |= off & 0x3f; +		bits = 9; +	} + +	dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", +		cmd_addr, edev->spi->max_speed_hz); + +	spi_message_init(&m); +	memset(t, 0, sizeof(t)); + +	t[0].tx_buf = (char *)&cmd_addr; +	t[0].len = 2; +	t[0].bits_per_word = bits; +	spi_message_add_tail(&t[0], &m); + +	t[1].rx_buf = buf; +	t[1].len = count; +	t[1].bits_per_word = 8; +	spi_message_add_tail(&t[1], &m); + +	mutex_lock(&edev->lock); + +	if (edev->pdata->prepare) +		edev->pdata->prepare(edev); + +	ret = spi_sync(edev->spi, &m); +	/* have to wait at least Tcsl ns */ +	ndelay(250); +	if (ret) { +		dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n", +			count, (int)off, ret); +	} + +	if (edev->pdata->finish) +		edev->pdata->finish(edev); + +	mutex_unlock(&edev->lock); +	return ret ? : count; +} + +static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) +{ +	struct spi_message m; +	struct spi_transfer t; +	int bits, ret; +	u16 cmd_addr; + +	cmd_addr = OP_START << edev->addrlen; +	if (edev->addrlen == 7) { +		cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << 1; +		bits = 10; +	} else { +		cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS); +		bits = 9; +	} + +	dev_dbg(&edev->spi->dev, "ew cmd 0x%04x\n", cmd_addr); + +	spi_message_init(&m); +	memset(&t, 0, sizeof(t)); + +	t.tx_buf = &cmd_addr; +	t.len = 2; +	t.bits_per_word = bits; +	spi_message_add_tail(&t, &m); + +	mutex_lock(&edev->lock); + +	if (edev->pdata->prepare) +		edev->pdata->prepare(edev); + +	ret = spi_sync(edev->spi, &m); +	/* have to wait at least Tcsl ns */ +	ndelay(250); +	if (ret) +		dev_err(&edev->spi->dev, "erase/write %sable error %d\n", +			is_on ? "en" : "dis", ret); + +	if (edev->pdata->finish) +		edev->pdata->finish(edev); + +	mutex_unlock(&edev->lock); +	return ret; +} + +static ssize_t +eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev, +			 const char *buf, unsigned off) +{ +	struct spi_message m; +	struct spi_transfer t[2]; +	int bits, data_len, ret; +	u16 cmd_addr; + +	cmd_addr = OP_WRITE << edev->addrlen; + +	if (edev->addrlen == 7) { +		cmd_addr |= off & 0x7f; +		bits = 10; +		data_len = 1; +	} else { +		cmd_addr |= off & 0x3f; +		bits = 9; +		data_len = 2; +	} + +	dev_dbg(&edev->spi->dev, "write cmd 0x%x\n", cmd_addr); + +	spi_message_init(&m); +	memset(t, 0, sizeof(t)); + +	t[0].tx_buf = (char *)&cmd_addr; +	t[0].len = 2; +	t[0].bits_per_word = bits; +	spi_message_add_tail(&t[0], &m); + +	t[1].tx_buf = buf; +	t[1].len = data_len; +	t[1].bits_per_word = 8; +	spi_message_add_tail(&t[1], &m); + +	ret = spi_sync(edev->spi, &m); +	/* have to wait program cycle time Twc ms */ +	mdelay(6); +	return ret; +} + +static ssize_t +eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj, +			struct bin_attribute *bin_attr, +			char *buf, loff_t off, size_t count) +{ +	struct eeprom_93xx46_dev *edev; +	struct device *dev; +	int i, ret, step = 1; + +	dev = container_of(kobj, struct device, kobj); +	edev = dev_get_drvdata(dev); + +	if (unlikely(off >= edev->bin.size)) +		return -EFBIG; +	if ((off + count) > edev->bin.size) +		count = edev->bin.size - off; +	if (unlikely(!count)) +		return count; + +	/* only write even number of bytes on 16-bit devices */ +	if (edev->addrlen == 6) { +		step = 2; +		count &= ~1; +	} + +	/* erase/write enable */ +	ret = eeprom_93xx46_ew(edev, 1); +	if (ret) +		return ret; + +	mutex_lock(&edev->lock); + +	if (edev->pdata->prepare) +		edev->pdata->prepare(edev); + +	for (i = 0; i < count; i += step) { +		ret = eeprom_93xx46_write_word(edev, &buf[i], off + i); +		if (ret) { +			dev_err(&edev->spi->dev, "write failed at %d: %d\n", +				(int)off + i, ret); +			break; +		} +	} + +	if (edev->pdata->finish) +		edev->pdata->finish(edev); + +	mutex_unlock(&edev->lock); + +	/* erase/write disable */ +	eeprom_93xx46_ew(edev, 0); +	return ret ? : count; +} + +static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) +{ +	struct eeprom_93xx46_platform_data *pd = edev->pdata; +	struct spi_message m; +	struct spi_transfer t; +	int bits, ret; +	u16 cmd_addr; + +	cmd_addr = OP_START << edev->addrlen; +	if (edev->addrlen == 7) { +		cmd_addr |= ADDR_ERAL << 1; +		bits = 10; +	} else { +		cmd_addr |= ADDR_ERAL; +		bits = 9; +	} + +	spi_message_init(&m); +	memset(&t, 0, sizeof(t)); + +	t.tx_buf = &cmd_addr; +	t.len = 2; +	t.bits_per_word = bits; +	spi_message_add_tail(&t, &m); + +	mutex_lock(&edev->lock); + +	if (edev->pdata->prepare) +		edev->pdata->prepare(edev); + +	ret = spi_sync(edev->spi, &m); +	if (ret) +		dev_err(&edev->spi->dev, "erase error %d\n", ret); +	/* have to wait erase cycle time Tec ms */ +	mdelay(6); + +	if (pd->finish) +		pd->finish(edev); + +	mutex_unlock(&edev->lock); +	return ret; +} + +static ssize_t eeprom_93xx46_store_erase(struct device *dev, +					 struct device_attribute *attr, +					 const char *buf, size_t count) +{ +	struct eeprom_93xx46_dev *edev = dev_get_drvdata(dev); +	int erase = 0, ret; + +	sscanf(buf, "%d", &erase); +	if (erase) { +		ret = eeprom_93xx46_ew(edev, 1); +		if (ret) +			return ret; +		ret = eeprom_93xx46_eral(edev); +		if (ret) +			return ret; +		ret = eeprom_93xx46_ew(edev, 0); +		if (ret) +			return ret; +	} +	return count; +} +static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_93xx46_store_erase); + +static int eeprom_93xx46_probe(struct spi_device *spi) +{ +	struct eeprom_93xx46_platform_data *pd; +	struct eeprom_93xx46_dev *edev; +	int err; + +	pd = spi->dev.platform_data; +	if (!pd) { +		dev_err(&spi->dev, "missing platform data\n"); +		return -ENODEV; +	} + +	edev = kzalloc(sizeof(*edev), GFP_KERNEL); +	if (!edev) +		return -ENOMEM; + +	if (pd->flags & EE_ADDR8) +		edev->addrlen = 7; +	else if (pd->flags & EE_ADDR16) +		edev->addrlen = 6; +	else { +		dev_err(&spi->dev, "unspecified address type\n"); +		err = -EINVAL; +		goto fail; +	} + +	mutex_init(&edev->lock); + +	edev->spi = spi_dev_get(spi); +	edev->pdata = pd; + +	sysfs_bin_attr_init(&edev->bin); +	edev->bin.attr.name = "eeprom"; +	edev->bin.attr.mode = S_IRUSR; +	edev->bin.read = eeprom_93xx46_bin_read; +	edev->bin.size = 128; +	if (!(pd->flags & EE_READONLY)) { +		edev->bin.write = eeprom_93xx46_bin_write; +		edev->bin.attr.mode |= S_IWUSR; +	} + +	err = sysfs_create_bin_file(&spi->dev.kobj, &edev->bin); +	if (err) +		goto fail; + +	dev_info(&spi->dev, "%d-bit eeprom %s\n", +		(pd->flags & EE_ADDR8) ? 8 : 16, +		(pd->flags & EE_READONLY) ? "(readonly)" : ""); + +	if (!(pd->flags & EE_READONLY)) { +		if (device_create_file(&spi->dev, &dev_attr_erase)) +			dev_err(&spi->dev, "can't create erase interface\n"); +	} + +	spi_set_drvdata(spi, edev); +	return 0; +fail: +	kfree(edev); +	return err; +} + +static int eeprom_93xx46_remove(struct spi_device *spi) +{ +	struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi); + +	if (!(edev->pdata->flags & EE_READONLY)) +		device_remove_file(&spi->dev, &dev_attr_erase); + +	sysfs_remove_bin_file(&spi->dev.kobj, &edev->bin); +	kfree(edev); +	return 0; +} + +static struct spi_driver eeprom_93xx46_driver = { +	.driver = { +		.name	= "93xx46", +		.owner	= THIS_MODULE, +	}, +	.probe		= eeprom_93xx46_probe, +	.remove		= eeprom_93xx46_remove, +}; + +module_spi_driver(eeprom_93xx46_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for 93xx46 EEPROMs"); +MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>"); +MODULE_ALIAS("spi:93xx46"); diff --git a/drivers/misc/eeprom/max6875.c b/drivers/misc/eeprom/max6875.c index 5653a3ce051..580ff9df552 100644 --- a/drivers/misc/eeprom/max6875.c +++ b/drivers/misc/eeprom/max6875.c @@ -27,7 +27,6 @@   */  #include <linux/kernel.h> -#include <linux/init.h>  #include <linux/module.h>  #include <linux/slab.h>  #include <linux/i2c.h> @@ -208,20 +207,8 @@ static struct i2c_driver max6875_driver = {  	.id_table	= max6875_id,  }; -static int __init max6875_init(void) -{ -	return i2c_add_driver(&max6875_driver); -} - -static void __exit max6875_exit(void) -{ -	i2c_del_driver(&max6875_driver); -} - +module_i2c_driver(max6875_driver);  MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>");  MODULE_DESCRIPTION("MAX6875 driver");  MODULE_LICENSE("GPL"); - -module_init(max6875_init); -module_exit(max6875_exit); diff --git a/drivers/misc/eeprom/sunxi_sid.c b/drivers/misc/eeprom/sunxi_sid.c new file mode 100644 index 00000000000..3f2b625b203 --- /dev/null +++ b/drivers/misc/eeprom/sunxi_sid.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> + * http://www.linux-sunxi.org + * + * 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. + * + * This driver exposes the Allwinner security ID, efuses exported in byte- + * sized chunks. + */ + +#include <linux/compiler.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/random.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/sysfs.h> +#include <linux/types.h> + +#define DRV_NAME "sunxi-sid" + +struct sunxi_sid_data { +	void __iomem *reg_base; +	unsigned int keysize; +}; + +/* We read the entire key, due to a 32 bit read alignment requirement. Since we + * want to return the requested byte, this results in somewhat slower code and + * uses 4 times more reads as needed but keeps code simpler. Since the SID is + * only very rarely probed, this is not really an issue. + */ +static u8 sunxi_sid_read_byte(const struct sunxi_sid_data *sid_data, +			      const unsigned int offset) +{ +	u32 sid_key; + +	if (offset >= sid_data->keysize) +		return 0; + +	sid_key = ioread32be(sid_data->reg_base + round_down(offset, 4)); +	sid_key >>= (offset % 4) * 8; + +	return sid_key; /* Only return the last byte */ +} + +static ssize_t sid_read(struct file *fd, struct kobject *kobj, +			struct bin_attribute *attr, char *buf, +			loff_t pos, size_t size) +{ +	struct platform_device *pdev; +	struct sunxi_sid_data *sid_data; +	int i; + +	pdev = to_platform_device(kobj_to_dev(kobj)); +	sid_data = platform_get_drvdata(pdev); + +	if (pos < 0 || pos >= sid_data->keysize) +		return 0; +	if (size > sid_data->keysize - pos) +		size = sid_data->keysize - pos; + +	for (i = 0; i < size; i++) +		buf[i] = sunxi_sid_read_byte(sid_data, pos + i); + +	return i; +} + +static struct bin_attribute sid_bin_attr = { +	.attr = { .name = "eeprom", .mode = S_IRUGO, }, +	.read = sid_read, +}; + +static int sunxi_sid_remove(struct platform_device *pdev) +{ +	device_remove_bin_file(&pdev->dev, &sid_bin_attr); +	dev_dbg(&pdev->dev, "driver unloaded\n"); + +	return 0; +} + +static const struct of_device_id sunxi_sid_of_match[] = { +	{ .compatible = "allwinner,sun4i-a10-sid", .data = (void *)16}, +	{ .compatible = "allwinner,sun7i-a20-sid", .data = (void *)512}, +	{/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); + +static int sunxi_sid_probe(struct platform_device *pdev) +{ +	struct sunxi_sid_data *sid_data; +	struct resource *res; +	const struct of_device_id *of_dev_id; +	u8 *entropy; +	unsigned int i; + +	sid_data = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_sid_data), +				GFP_KERNEL); +	if (!sid_data) +		return -ENOMEM; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	sid_data->reg_base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(sid_data->reg_base)) +		return PTR_ERR(sid_data->reg_base); + +	of_dev_id = of_match_device(sunxi_sid_of_match, &pdev->dev); +	if (!of_dev_id) +		return -ENODEV; +	sid_data->keysize = (int)of_dev_id->data; + +	platform_set_drvdata(pdev, sid_data); + +	sid_bin_attr.size = sid_data->keysize; +	if (device_create_bin_file(&pdev->dev, &sid_bin_attr)) +		return -ENODEV; + +	entropy = kzalloc(sizeof(u8) * sid_data->keysize, GFP_KERNEL); +	for (i = 0; i < sid_data->keysize; i++) +		entropy[i] = sunxi_sid_read_byte(sid_data, i); +	add_device_randomness(entropy, sid_data->keysize); +	kfree(entropy); + +	dev_dbg(&pdev->dev, "loaded\n"); + +	return 0; +} + +static struct platform_driver sunxi_sid_driver = { +	.probe = sunxi_sid_probe, +	.remove = sunxi_sid_remove, +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.of_match_table = sunxi_sid_of_match, +	}, +}; +module_platform_driver(sunxi_sid_driver); + +MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); +MODULE_DESCRIPTION("Allwinner sunxi security id driver"); +MODULE_LICENSE("GPL");  | 
