elphel393-init.c 13.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/*!***************************************************************************
 *! FILE NAME  : elphel393-init.c
 *! DESCRIPTION: * Unlock rootfs NAND flash partition
 *!              * Read MAC and other useful info from NAND flash OTP area
 *!                and put to sysfs
 *!
 *! E-mail: oleg@elphel.com, support-list@elphel.com
 *!
 *! Copyright (C) 2016 Elphel, Inc.
 *! -----------------------------------------------------------------------------**
 *!
 *!  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
Andrey Filippov's avatar
Andrey Filippov committed
14
 *!  the Free Software Foundation, either version 2 of the License, or
15 16 17 18 19 20 21 22 23 24
 *!  (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, see <http://www.gnu.org/licenses/>.
 *!****************************************************************************/
25 26 27

#define DRV_NAME "elphel393-init"
#define pr_fmt(fmt) DRV_NAME": " fmt
28 29

#include <asm/page.h>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
30
#include <asm/io.h>
31 32

#include <linux/init.h>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
33
#include <linux/delay.h>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
34
#include <linux/io.h>
35 36 37 38 39
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
40
#include <linux/of_net.h>
41 42 43 44 45 46 47 48 49 50 51 52
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/sysfs.h>

#include <elphel/elphel393-init.h>

/* from elphel393-mem.c */
#define SYSFS_PERMISSIONS         0644 /* default permissions for sysfs files */
#define SYSFS_READONLY            0444
#define SYSFS_WRITEONLY           0222

53
#define NAND_FLASH_OTP_PAGE_OFFSET	0*2048
54

55 56 57 58 59 60
/*
 * Read and parse bootargs parameter in the device tree
 * This driver is run in the last place - at least after NAND driver is probed
 */
static struct mtd_info *mtd;

61 62
//surprise size
static char *bootargs;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
63
//known size, all should be zero filled
64
static char boardinfo[2048];
65 66 67
static char serial[13];
static char revision[8];

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
68 69 70
static int setup_mio_pin_and_reset_usb(void);

static int __init elphel393_early_initialize(void){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
71
	pr_info("Set up the mio pin 49 and reset USB\n");
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
72 73 74
	setup_mio_pin_and_reset_usb();
	return 0;
}
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
/*
static int __init elphel393_early_initialize(void){

	struct device_node *node;
	struct property *newproperty;
	u8 *macaddr;

	pr_info("VERY EARLY CALL TO UPDATE DEVICE TREE");

	node = of_find_node_by_name(NULL, "ps7-ethernet");
	if (!node){
		pr_err("Device tree node 'ps7-ethernet' not found.");
		return -ENODEV;
	}

	newproperty = kzalloc(sizeof(*newproperty) + 6, GFP_KERNEL);
	if (!newproperty)
		return -ENOMEM;
	newproperty->value = newproperty + 1;
	newproperty->length = 6;
	newproperty->name = kstrdup("local-mac-address", GFP_KERNEL);
	if (!newproperty->name) {
		kfree(newproperty);
		return -ENOMEM;
	}
	macaddr = newproperty->value;
	macaddr[0] = 0x02;
	macaddr[1] = 0x03;
	macaddr[2] = 0x04;
	macaddr[3] = 0x05;
	macaddr[4] = 0x06;
	macaddr[5] = 0x07;

	of_update_property(node,newproperty);
	return 0;
}
*/
112 113 114 115 116 117
static int __init elphel393_init_init (void)
{
	struct device_node *node;

	node = of_find_node_by_name(NULL, "chosen");
	//just throw an error
118
	if (!node){
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
		pr_err("Device tree node 'chosen' not found.");
		return -ENODEV;
	}
	of_property_read_string(node, "bootargs", &bootargs);
	if (bootargs!=NULL) {
		pr_debug("bootargs line from device tree is %s",bootargs);
	}
	return 0;
}

static void __exit elphel393_init_exit(void)
{
    printk("Exit\n");
}

// SYSFS

static ssize_t get_bootargs(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf,"%s\n",bootargs);
}
static ssize_t get_boardinfo(struct device *dev, struct device_attribute *attr, char *buf)
{
142
	return sprintf(buf,"%s\n",boardinfo);
143 144 145
}
static ssize_t get_revision(struct device *dev, struct device_attribute *attr, char *buf)
{
146
	return sprintf(buf,"%s\n",revision);
147 148 149
}
static ssize_t get_serial(struct device *dev, struct device_attribute *attr, char *buf)
{
150
	return sprintf(buf,"%s\n",serial);
151 152
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
153 154
static int get_factory_info(void);

155 156
static ssize_t set_boardinfo(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
157 158 159 160 161
	int ret;
	size_t retlen;

	char wbuf[2048];

162 163 164
	if (IS_ERR(mtd)){
		pr_err("Get MTD device error, code:%d\n",-(u32)mtd);
		return -ENODEV;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
165
	}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
166
	//need all 0xff bytes
167
	memset(wbuf,0xff,2048);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
168 169 170
	//too much of a trouble to read from flash again
	if(!strnstr(boardinfo,"<board>",sizeof(boardinfo))){
		pr_info("Factory Info record is clean.\n");
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
171
		pr_info("Data to be written: %s\n",buf);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
172
		// I got some buf, unknown size- should be limited to 2048? ok
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
173
		if (strlen(buf)>2047) {
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
174
			pr_err("Data > 2KiB. Abort.\n");
175
			return -EFBIG;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
176
		}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
177 178
		// Not strict check, just look for opening tags.
		if(!strstr(buf,"<board>")||!strstr(buf,"<serial>")||!strstr(buf,"<rev>")){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
179
			pr_err("Bad data format\n");
180
			return -EINVAL;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
181
		}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
182 183
		//copy to wbuf, buf is null terminated and is <=2047
		strcpy(wbuf,buf);
184
		//pr_info("BUFFER: %s\n",wbuf);
185
		ret = mtd_write_user_prot_reg(mtd, NAND_FLASH_OTP_PAGE_OFFSET, 2048, &retlen, wbuf);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
186
		if (ret){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
187
			pr_err("Flash page write, code %d\n",ret);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
188 189
			return ret;
		}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
190
		pr_info("Data is successfully written and cannot be overwritten anymore\n");
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
191
		get_factory_info();
192
	}else{
193 194
		pr_err("Factory Info record (serial='%s' revision='%s') can not be overwritten\n",serial,revision);
		return -EPERM;
195 196 197 198
	}
    return count;
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
199 200 201 202 203 204 205 206 207 208 209
static ssize_t perform_usbreset(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){
	pr_info("EMIO pin 49: USB reset\n");
	setup_mio_pin_and_reset_usb();
	return count;
}

static DEVICE_ATTR(bootargs  ,  SYSFS_PERMISSIONS & SYSFS_READONLY,  get_bootargs ,   NULL);
static DEVICE_ATTR(boardinfo ,  SYSFS_PERMISSIONS                 ,  get_boardinfo,   set_boardinfo);
static DEVICE_ATTR(revision  ,  SYSFS_PERMISSIONS & SYSFS_READONLY,  get_revision ,   NULL);
static DEVICE_ATTR(serial    ,  SYSFS_PERMISSIONS & SYSFS_READONLY,    get_serial ,   NULL);
static DEVICE_ATTR(usbreset  ,  SYSFS_PERMISSIONS                 ,           NULL,   perform_usbreset);
210 211 212

static struct attribute *root_dev_attrs[] = {
        &dev_attr_bootargs.attr,
213
        &dev_attr_boardinfo.attr,
214 215
        &dev_attr_revision.attr,
        &dev_attr_serial.attr,
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
216
		&dev_attr_usbreset.attr,
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
        NULL
};

static const struct attribute_group dev_attr_root_group = {
	.attrs = root_dev_attrs,
	.name  = NULL,
};

static int elphel393_init_sysfs_register(struct platform_device *pdev)
{
	int retval=0;
	struct device *dev = &pdev->dev;
	if (&dev->kobj) {
		if (((retval = sysfs_create_group(&dev->kobj, &dev_attr_root_group)))<0) return retval;
	}
	return retval;
}

static int elphel393_init_probe(struct platform_device *pdev)
{
	char *bootargs_copy;

	char *token;
	char *token_copy;

	char *param;
	char *value;

	//mtd device number to unlock
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
246
	u8 devnum= 0;
247 248
	u8 unlock= 0;

249
	pr_debug("Probing\n");
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
	//copy bootargs string
	bootargs_copy = kstrdup(bootargs,GFP_KERNEL);
	//find out which partition to unlock if any
	//parse bootargs
	do {
		token = strsep(&bootargs_copy," ");
		if (token) {
			//if '=' is found - split by '='
			token_copy = token;
			if (strchr(token_copy,'=')){
				if (!strcmp(token_copy,"rootfstype=ubifs")){
					unlock=1;
				}
				param = strsep(&token_copy,"=");
	            //if "ubi.mtd" then get the partition number and unlock /dev/mtdN - if not found then don't care
				if (!strcmp(param,"ubi.mtd")){
					value = strsep(&token_copy,",");
					if (kstrtou8(value,10,&devnum)){
						pr_err("Partition number str to u8 conversion.");
					}
				}
			}
		}
	} while (token);

	kfree(bootargs_copy);

277 278
	// unlock /dev/mtdN partition
	// if there's no need to unlock, get /dev/mtd0
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
279
	if (devnum>=0){
280
		mtd = get_mtd_device(NULL,devnum);
281 282 283
		if (IS_ERR(mtd)){
			pr_err("Get MTD device error, code:%d\n",-(u32)mtd);
			return -ENODEV;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
284 285 286 287 288
		}
		if (unlock){
			mtd_unlock(mtd,0,mtd->size);
			pr_info("/dev/mtd%d: unlocked",devnum);
		}
289
	}
290 291 292 293 294 295 296
	// read boardinfo record
	// * device number is not important
	// * no need to unlock

	// page size
	BUG_ON(mtd->writesize > 2048);

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
297
	get_factory_info();
298 299 300 301
	elphel393_init_sysfs_register(pdev);
	return 0;
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
302
static int get_factory_info(void){
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
303 304
	char regvalh[]="0000";
	char regvall[]="00000000";
305 306
	u16 hwaddrh0, hwaddrh1;
	u32 hwaddrl0, hwaddrl1;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
307 308 309 310 311

	size_t retlen;
	//size of nand flash page
	char kbuf[2048];
	size_t size = mtd->writesize;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
312
	size_t min_size;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
313 314 315
	int ret;
	char *ps,*pe;

316 317 318 319 320
	struct device_node *node;
	struct property *newproperty;

	u32 *mac32; //  = (u32*) mac_address;
	u8 *macaddr;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
321

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
	u8 block_factory_mac = 0;

	node = of_find_node_by_name(NULL, "ps7-ethernet");
	if (!node){
		pr_err("Device tree node 'ps7-ethernet' not found.");
		return -ENODEV;
	}

	// check if MAC address was overridden in u-boot
	mac32 = (u32 *) of_get_mac_address(node);
	hwaddrl0 = cpu_to_le32(mac32[0]);
	hwaddrh0 = cpu_to_le16(mac32[1]);
	if ((hwaddrh0!=0x0000)||(hwaddrl0!=0x10640E00)){
		block_factory_mac = 1;
	}
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
337

338
	ret = mtd_read_user_prot_reg(mtd, NAND_FLASH_OTP_PAGE_OFFSET, size, &retlen, kbuf);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
339 340 341 342
	if (ret){
		pr_err("Flash page read, code %d",ret);
		return ret;
	}
343 344 345

	pr_debug("buf: %s\n",kbuf);

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
346 347 348 349 350 351 352
	// do whatever we like with the kbuf
	// search for "<board>"
	// expecting to find it somewhere...
	if(strnstr(kbuf,"<board>",size)){
		//...right in the beginning or error
		ps = strnstr(kbuf,"<serial>",size);
		pe = strnstr(kbuf,"</serial>",size);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
353 354
		min_size = min(pe-ps-(sizeof("<serial>")-1),sizeof(serial)-1);
		strncpy(serial,ps+sizeof("<serial>")-1,min_size);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
355 356 357 358 359

		strncpy(regvalh,serial,4);
		strncpy(regvall,serial+4,8);

		//there is kstrtou64 but it doesn't work?
360 361
		kstrtou16(regvalh,16,&hwaddrh1);
		kstrtou32(regvall,16,&hwaddrl1);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
362

363
		pr_debug("MAC from flash: %02x:%02x:%02x:%02x:%02x:%02x\n",
364 365 366
			 (hwaddrl1 & 0xff), ((hwaddrl1 >> 8) & 0xff),
			((hwaddrl1 >> 16) & 0xff), (hwaddrl1 >> 24),
			 (hwaddrh1 & 0xff), (hwaddrh1 >> 8));
367 368 369 370 371 372 373 374 375 376 377 378

		newproperty = kzalloc(sizeof(*newproperty) + 6, GFP_KERNEL);
		if (!newproperty)
			return -ENOMEM;
		newproperty->value = newproperty + 1;
		newproperty->length = 6;
		newproperty->name = kstrdup("local-mac-address", GFP_KERNEL);
		if (!newproperty->name) {
			kfree(newproperty);
			return -ENOMEM;
		}
		macaddr = newproperty->value;
379 380 381 382 383 384 385 386 387 388 389 390 391
		macaddr[0] = (hwaddrh1 >> 8) & 0xff;
		macaddr[1] =  hwaddrh1 & 0xff;
		macaddr[2] = (hwaddrl1 >> 24) & 0xff;
		macaddr[3] = (hwaddrl1 >> 16) & 0xff;
		macaddr[4] = (hwaddrl1 >> 8) & 0xff;
		macaddr[5] =  hwaddrl1 & 0xff;

		if (!block_factory_mac)
			of_update_property(node,newproperty);
		else
			pr_info("Factory MAC: %02x:%02x:%02x:%02x:%02x:%02x, u-boot overridden MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
					(hwaddrl1 & 0xff),((hwaddrl1 >> 8) & 0xff),((hwaddrl1 >> 16) & 0xff),(hwaddrl1 >> 24),(hwaddrh1 & 0xff),(hwaddrh1 >> 8),
					(hwaddrl0 & 0xff),((hwaddrl0 >> 8) & 0xff),((hwaddrl0 >> 16) & 0xff),(hwaddrl0 >> 24),(hwaddrh0 & 0xff),(hwaddrh0 >> 8));
392 393

		mac32 = (u32 *) of_get_mac_address(node);
394 395
		hwaddrl1 = cpu_to_le32(mac32[0]);
		hwaddrh1 = cpu_to_le16(mac32[1]);
396

397 398 399 400
		pr_debug("new MAC from device tree: %02x:%02x:%02x:%02x:%02x:%02x\n",
			(hwaddrl1 & 0xff), ((hwaddrl1 >> 8) & 0xff),
			((hwaddrl1 >> 16) & 0xff), (hwaddrl1 >> 24),
			(hwaddrh1 & 0xff), (hwaddrh1 >> 8));
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
401 402 403 404 405 406 407

		//write hwaddr to zynq reg
		//kstrtou16(serial,16,&regvalh);
		//serial

		ps = strnstr(kbuf,"<rev>",size);
		pe = strnstr(kbuf,"</rev>",size);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
408 409
		min_size = min(pe-ps-(sizeof("<rev>")-1),sizeof(revision)-1);
		strncpy(revision,ps+sizeof("<rev>")-1,min_size);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
410 411
		ps = strnstr(kbuf,"<board>",size);
		pe = strnstr(kbuf,"</board>",size);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
412 413
		min_size = min(pe-ps+(sizeof("</board>")-1),sizeof(boardinfo)-1);
		strncpy(boardinfo,ps,min_size);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
414 415 416 417
	}
	return 0;
}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
418 419
static int setup_mio_pin_and_reset_usb(void){
	u32 reg_value;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
420 421 422 423 424 425
	void __iomem* emio_ptr = ioremap(0xe000a000, 0x00000300);
	if (!emio_ptr) {
		pr_err("Failed to get a pointer to the EMIO registers");
		return -1;
	}

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
426
	const u32 newvalue = 0x1<<17;
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
427 428 429
	reg_value = readl(emio_ptr+0x244);
	pr_debug("read DIR reg: 0x%08x\n",reg_value);
	writel(reg_value|newvalue,emio_ptr+0x244);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
430
	udelay(10);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
431 432 433
	reg_value = readl(emio_ptr+0x248);
	pr_debug("read EN reg: 0x%08x\n",reg_value);
	writel(reg_value|newvalue,emio_ptr+0x248);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
434
	udelay(10);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
435 436 437
	reg_value = readl(emio_ptr+0x44);
	pr_debug("read OUT reg: 0x%08x\n",reg_value);
	writel(reg_value&(~newvalue),emio_ptr+0x44);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
438
	udelay(10);
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
439 440 441 442 443 444
	writel(reg_value|newvalue,emio_ptr+0x44);
	reg_value = readl(emio_ptr+0x64);
	pr_debug("read IN reg (correct value: 0x00020000): 0x%08x\n",reg_value);

	iounmap(emio_ptr);

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
445 446 447
	return 0;
}

448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
static int elphel393_init_remove(struct platform_device *pdev)
{
	pr_info("Remove");
	return 0;
}

static struct of_device_id elphel393_init_of_match[] = {
	{ .compatible = "elphel,elphel393-init-1.00", },
	{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, elphel393_init_of_match);

static struct platform_driver elphel393_initialize = {
	.probe   = elphel393_init_probe,
	.remove  = elphel393_init_remove,
	.driver  = {
464
		.name  = DRV_NAME,
465 466 467 468 469 470
		.owner = THIS_MODULE,
		.of_match_table = elphel393_init_of_match,
		.pm = NULL, /* power management */
	},
};

Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
471
early_initcall(elphel393_early_initialize);
472

473 474 475 476
module_platform_driver(elphel393_initialize);
module_init(elphel393_init_init);
module_exit(elphel393_init_exit);
MODULE_LICENSE("GPL");
477 478
MODULE_AUTHOR("Elphel, Inc.");
MODULE_DESCRIPTION("Unlock rootfs flash partition and read/write board info: serial and revision");