Skip to content
Snippets Groups Projects
cros_ec.c 37.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    {
    	if (argc > 0) {
    		if (0 == strcmp(*argv, "rw"))
    			return EC_FLASH_REGION_RW;
    		else if (0 == strcmp(*argv, "ro"))
    			return EC_FLASH_REGION_RO;
    
    		debug("%s: Invalid region '%s'\n", __func__, *argv);
    	} else {
    		debug("%s: Missing region parameter\n", __func__);
    	}
    
    	return -1;
    }
    
    
    int cros_ec_decode_ec_flash(const void *blob, int node,
    			    struct fdt_cros_ec *config)
    
    	int flash_node;
    
    
    	flash_node = fdt_subnode_offset(blob, node, "flash");
    	if (flash_node < 0) {
    		debug("Failed to find flash node\n");
    		return -1;
    	}
    
    	if (fdtdec_read_fmap_entry(blob, flash_node, "flash",
    				   &config->flash)) {
    		debug("Failed to decode flash node in chrome-ec'\n");
    		return -1;
    	}
    
    	config->flash_erase_value = fdtdec_get_int(blob, flash_node,
    						    "erase-value", -1);
    	for (node = fdt_first_subnode(blob, flash_node); node >= 0;
    	     node = fdt_next_subnode(blob, node)) {
    		const char *name = fdt_get_name(blob, node, NULL);
    		enum ec_flash_region region;
    
    		if (0 == strcmp(name, "ro")) {
    			region = EC_FLASH_REGION_RO;
    		} else if (0 == strcmp(name, "rw")) {
    			region = EC_FLASH_REGION_RW;
    		} else if (0 == strcmp(name, "wp-ro")) {
    			region = EC_FLASH_REGION_WP_RO;
    		} else {
    			debug("Unknown EC flash region name '%s'\n", name);
    			return -1;
    		}
    
    		if (fdtdec_read_fmap_entry(blob, node, "reg",
    					   &config->region[region])) {
    			debug("Failed to decode flash region in chrome-ec'\n");
    			return -1;
    		}
    	}
    
    	return 0;
    }
    
    
    int cros_ec_i2c_tunnel(struct udevice *dev, int port, struct i2c_msg *in,
    		       int nmsgs)
    
    {
    	struct cros_ec_dev *cdev = dev_get_uclass_priv(dev);
    	union {
    		struct ec_params_i2c_passthru p;
    		uint8_t outbuf[EC_PROTO2_MAX_PARAM_SIZE];
    	} params;
    	union {
    		struct ec_response_i2c_passthru r;
    		uint8_t inbuf[EC_PROTO2_MAX_PARAM_SIZE];
    	} response;
    	struct ec_params_i2c_passthru *p = &params.p;
    	struct ec_response_i2c_passthru *r = &response.r;
    	struct ec_params_i2c_passthru_msg *msg;
    	uint8_t *pdata, *read_ptr = NULL;
    	int read_len;
    	int size;
    	int rv;
    	int i;
    
    
    
    	p->num_msgs = nmsgs;
    	size = sizeof(*p) + p->num_msgs * sizeof(*msg);
    
    	/* Create a message to write the register address and optional data */
    	pdata = (uint8_t *)p + size;
    
    	read_len = 0;
    	for (i = 0, msg = p->msg; i < nmsgs; i++, msg++, in++) {
    		bool is_read = in->flags & I2C_M_RD;
    
    		msg->addr_flags = in->addr;
    		msg->len = in->len;
    		if (is_read) {
    			msg->addr_flags |= EC_I2C_FLAG_READ;
    			read_len += in->len;
    			read_ptr = in->buf;
    			if (sizeof(*r) + read_len > sizeof(response)) {
    				puts("Read length too big for buffer\n");
    				return -1;
    			}
    		} else {
    			if (pdata - (uint8_t *)p + in->len > sizeof(params)) {
    				puts("Params too large for buffer\n");
    				return -1;
    			}
    			memcpy(pdata, in->buf, in->len);
    			pdata += in->len;
    		}
    	}
    
    	rv = ec_command(cdev, EC_CMD_I2C_PASSTHRU, 0, p, pdata - (uint8_t *)p,
    			r, sizeof(*r) + read_len);
    	if (rv < 0)
    		return rv;
    
    	/* Parse response */
    	if (r->i2c_status & EC_I2C_STATUS_ERROR) {
    		printf("Transfer failed with status=0x%x\n", r->i2c_status);
    		return -1;
    	}
    
    	if (rv < sizeof(*r) + read_len) {
    		puts("Truncated read response\n");
    		return -1;
    	}
    
    	/* We only support a single read message for each transfer */
    	if (read_len)
    		memcpy(read_ptr, r->data, read_len);
    
    	return 0;
    }
    
    
    #ifdef CONFIG_CMD_CROS_EC
    
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    /**
     * Perform a flash read or write command
     *
     * @param dev		CROS-EC device to read/write
     * @param is_write	1 do to a write, 0 to do a read
     * @param argc		Number of arguments
     * @param argv		Arguments (2 is region, 3 is address)
     * @return 0 for ok, 1 for a usage error or -ve for ec command error
     *	(negative EC_RES_...)
     */
    static int do_read_write(struct cros_ec_dev *dev, int is_write, int argc,
    			 char * const argv[])
    {
    	uint32_t offset, size = -1U, region_size;
    	unsigned long addr;
    	char *endp;
    	int region;
    	int ret;
    
    	region = cros_ec_decode_region(argc - 2, argv + 2);
    	if (region == -1)
    		return 1;
    	if (argc < 4)
    		return 1;
    	addr = simple_strtoul(argv[3], &endp, 16);
    	if (*argv[3] == 0 || *endp != 0)
    		return 1;
    	if (argc > 4) {
    		size = simple_strtoul(argv[4], &endp, 16);
    		if (*argv[4] == 0 || *endp != 0)
    			return 1;
    	}
    
    	ret = cros_ec_flash_offset(dev, region, &offset, &region_size);
    	if (ret) {
    		debug("%s: Could not read region info\n", __func__);
    		return ret;
    	}
    	if (size == -1U)
    		size = region_size;
    
    	ret = is_write ?
    		cros_ec_flash_write(dev, (uint8_t *)addr, offset, size) :
    		cros_ec_flash_read(dev, (uint8_t *)addr, offset, size);
    	if (ret) {
    		debug("%s: Could not %s region\n", __func__,
    		      is_write ? "write" : "read");
    		return ret;
    	}
    
    	return 0;
    }
    
    static int do_cros_ec(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
    {
    
    	struct cros_ec_dev *dev;
    	struct udevice *udev;
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    	const char *cmd;
    	int ret = 0;
    
    	if (argc < 2)
    		return CMD_RET_USAGE;
    
    	cmd = argv[1];
    	if (0 == strcmp("init", cmd)) {
    
    		/* Remove any existing device */
    		ret = uclass_find_device(UCLASS_CROS_EC, 0, &udev);
    		if (!ret)
    			device_remove(udev);
    		ret = uclass_get_device(UCLASS_CROS_EC, 0, &udev);
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    		if (ret) {
    			printf("Could not init cros_ec device (err %d)\n", ret);
    			return 1;
    		}
    		return 0;
    	}
    
    
    	ret = uclass_get_device(UCLASS_CROS_EC, 0, &udev);
    	if (ret) {
    		printf("Cannot get cros-ec device (err=%d)\n", ret);
    		return 1;
    	}
    
    	dev = dev_get_uclass_priv(udev);
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    	if (0 == strcmp("id", cmd)) {
    		char id[MSG_BYTES];
    
    		if (cros_ec_read_id(dev, id, sizeof(id))) {
    			debug("%s: Could not read KBC ID\n", __func__);
    			return 1;
    		}
    		printf("%s\n", id);
    	} else if (0 == strcmp("info", cmd)) {
    
    		struct ec_response_mkbp_info info;
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    
    		if (cros_ec_info(dev, &info)) {
    			debug("%s: Could not read KBC info\n", __func__);
    			return 1;
    		}
    		printf("rows     = %u\n", info.rows);
    		printf("cols     = %u\n", info.cols);
    		printf("switches = %#x\n", info.switches);
    	} else if (0 == strcmp("curimage", cmd)) {
    		enum ec_current_image image;
    
    		if (cros_ec_read_current_image(dev, &image)) {
    			debug("%s: Could not read KBC image\n", __func__);
    			return 1;
    		}
    		printf("%d\n", image);
    	} else if (0 == strcmp("hash", cmd)) {
    		struct ec_response_vboot_hash hash;
    		int i;
    
    		if (cros_ec_read_hash(dev, &hash)) {
    			debug("%s: Could not read KBC hash\n", __func__);
    			return 1;
    		}
    
    		if (hash.hash_type == EC_VBOOT_HASH_TYPE_SHA256)
    			printf("type:    SHA-256\n");
    		else
    			printf("type:    %d\n", hash.hash_type);
    
    		printf("offset:  0x%08x\n", hash.offset);
    		printf("size:    0x%08x\n", hash.size);
    
    		printf("digest:  ");
    		for (i = 0; i < hash.digest_size; i++)
    			printf("%02x", hash.hash_digest[i]);
    		printf("\n");
    	} else if (0 == strcmp("reboot", cmd)) {
    		int region;
    		enum ec_reboot_cmd cmd;
    
    		if (argc >= 3 && !strcmp(argv[2], "cold"))
    			cmd = EC_REBOOT_COLD;
    		else {
    			region = cros_ec_decode_region(argc - 2, argv + 2);
    			if (region == EC_FLASH_REGION_RO)
    				cmd = EC_REBOOT_JUMP_RO;
    			else if (region == EC_FLASH_REGION_RW)
    				cmd = EC_REBOOT_JUMP_RW;
    			else
    				return CMD_RET_USAGE;
    		}
    
    		if (cros_ec_reboot(dev, cmd, 0)) {
    			debug("%s: Could not reboot KBC\n", __func__);
    			return 1;
    		}
    	} else if (0 == strcmp("events", cmd)) {
    		uint32_t events;
    
    		if (cros_ec_get_host_events(dev, &events)) {
    			debug("%s: Could not read host events\n", __func__);
    			return 1;
    		}
    		printf("0x%08x\n", events);
    	} else if (0 == strcmp("clrevents", cmd)) {
    		uint32_t events = 0x7fffffff;
    
    		if (argc >= 3)
    			events = simple_strtol(argv[2], NULL, 0);
    
    		if (cros_ec_clear_host_events(dev, events)) {
    			debug("%s: Could not clear host events\n", __func__);
    			return 1;
    		}
    	} else if (0 == strcmp("read", cmd)) {
    		ret = do_read_write(dev, 0, argc, argv);
    		if (ret > 0)
    			return CMD_RET_USAGE;
    	} else if (0 == strcmp("write", cmd)) {
    		ret = do_read_write(dev, 1, argc, argv);
    		if (ret > 0)
    			return CMD_RET_USAGE;
    	} else if (0 == strcmp("erase", cmd)) {
    		int region = cros_ec_decode_region(argc - 2, argv + 2);
    		uint32_t offset, size;
    
    		if (region == -1)
    			return CMD_RET_USAGE;
    		if (cros_ec_flash_offset(dev, region, &offset, &size)) {
    			debug("%s: Could not read region info\n", __func__);
    			ret = -1;
    		} else {
    			ret = cros_ec_flash_erase(dev, offset, size);
    			if (ret) {
    				debug("%s: Could not erase region\n",
    				      __func__);
    			}
    		}
    	} else if (0 == strcmp("regioninfo", cmd)) {
    		int region = cros_ec_decode_region(argc - 2, argv + 2);
    		uint32_t offset, size;
    
    		if (region == -1)
    			return CMD_RET_USAGE;
    		ret = cros_ec_flash_offset(dev, region, &offset, &size);
    		if (ret) {
    			debug("%s: Could not read region info\n", __func__);
    		} else {
    			printf("Region: %s\n", region == EC_FLASH_REGION_RO ?
    					"RO" : "RW");
    			printf("Offset: %x\n", offset);
    			printf("Size:   %x\n", size);
    		}
    	} else if (0 == strcmp("vbnvcontext", cmd)) {
    		uint8_t block[EC_VBNV_BLOCK_SIZE];
    		char buf[3];
    		int i, len;
    		unsigned long result;
    
    		if (argc <= 2) {
    			ret = cros_ec_read_vbnvcontext(dev, block);
    			if (!ret) {
    				printf("vbnv_block: ");
    				for (i = 0; i < EC_VBNV_BLOCK_SIZE; i++)
    					printf("%02x", block[i]);
    				putc('\n');
    			}
    		} else {
    			/*
    			 * TODO(clchiou): Move this to a utility function as
    			 * cmd_spi might want to call it.
    			 */
    			memset(block, 0, EC_VBNV_BLOCK_SIZE);
    			len = strlen(argv[2]);
    			buf[2] = '\0';
    			for (i = 0; i < EC_VBNV_BLOCK_SIZE; i++) {
    				if (i * 2 >= len)
    					break;
    				buf[0] = argv[2][i * 2];
    				if (i * 2 + 1 >= len)
    					buf[1] = '0';
    				else
    					buf[1] = argv[2][i * 2 + 1];
    				strict_strtoul(buf, 16, &result);
    				block[i] = result;
    			}
    			ret = cros_ec_write_vbnvcontext(dev, block);
    		}
    		if (ret) {
    			debug("%s: Could not %s VbNvContext\n", __func__,
    					argc <= 2 ?  "read" : "write");
    		}
    	} else if (0 == strcmp("test", cmd)) {
    		int result = cros_ec_test(dev);
    
    		if (result)
    			printf("Test failed with error %d\n", result);
    		else
    			puts("Test passed\n");
    	} else if (0 == strcmp("version", cmd)) {
    		struct ec_response_get_version *p;
    		char *build_string;
    
    		ret = cros_ec_read_version(dev, &p);
    		if (!ret) {
    			/* Print versions */
    			printf("RO version:    %1.*s\n",
    
    			       (int)sizeof(p->version_string_ro),
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    			       p->version_string_ro);
    			printf("RW version:    %1.*s\n",
    
    			       (int)sizeof(p->version_string_rw),
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    			       p->version_string_rw);
    			printf("Firmware copy: %s\n",
    				(p->current_image <
    					ARRAY_SIZE(ec_current_image_name) ?
    				ec_current_image_name[p->current_image] :
    				"?"));
    			ret = cros_ec_read_build_info(dev, &build_string);
    			if (!ret)
    				printf("Build info:    %s\n", build_string);
    		}
    	} else if (0 == strcmp("ldo", cmd)) {
    		uint8_t index, state;
    		char *endp;
    
    		if (argc < 3)
    			return CMD_RET_USAGE;
    		index = simple_strtoul(argv[2], &endp, 10);
    		if (*argv[2] == 0 || *endp != 0)
    			return CMD_RET_USAGE;
    		if (argc > 3) {
    			state = simple_strtoul(argv[3], &endp, 10);
    			if (*argv[3] == 0 || *endp != 0)
    				return CMD_RET_USAGE;
    
    			ret = cros_ec_set_ldo(udev, index, state);
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    		} else {
    
    			ret = cros_ec_get_ldo(udev, index, &state);
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    			if (!ret) {
    				printf("LDO%d: %s\n", index,
    					state == EC_LDO_STATE_ON ?
    					"on" : "off");
    			}
    		}
    
    		if (ret) {
    			debug("%s: Could not access LDO%d\n", __func__, index);
    			return ret;
    		}
    	} else {
    		return CMD_RET_USAGE;
    	}
    
    	if (ret < 0) {
    		printf("Error: CROS-EC command failed (error %d)\n", ret);
    		ret = 1;
    	}
    
    	return ret;
    }
    
    U_BOOT_CMD(
    
    	crosec,	6,	1,	do_cros_ec,
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    	"CROS-EC utility command",
    	"init                Re-init CROS-EC (done on startup automatically)\n"
    	"crosec id                  Read CROS-EC ID\n"
    	"crosec info                Read CROS-EC info\n"
    	"crosec curimage            Read CROS-EC current image\n"
    	"crosec hash                Read CROS-EC hash\n"
    	"crosec reboot [rw | ro | cold]  Reboot CROS-EC\n"
    	"crosec events              Read CROS-EC host events\n"
    	"crosec clrevents [mask]    Clear CROS-EC host events\n"
    	"crosec regioninfo <ro|rw>  Read image info\n"
    	"crosec erase <ro|rw>       Erase EC image\n"
    	"crosec read <ro|rw> <addr> [<size>]   Read EC image\n"
    	"crosec write <ro|rw> <addr> [<size>]  Write EC image\n"
    	"crosec vbnvcontext [hexstring]        Read [write] VbNvContext from EC\n"
    	"crosec ldo <idx> [<state>] Switch/Read LDO state\n"
    	"crosec test                run tests on cros_ec\n"
    
    	"crosec version             Read CROS-EC version"
    
    Hung-ying Tyan's avatar
    Hung-ying Tyan committed
    );
    #endif
    
    
    UCLASS_DRIVER(cros_ec) = {
    	.id		= UCLASS_CROS_EC,
    	.name		= "cros_ec",
    	.per_device_auto_alloc_size = sizeof(struct cros_ec_dev),
    
    	.post_bind	= dm_scan_fdt_dev,