diff options
Diffstat (limited to 'drivers/watchdog/ziirave_wdt.c')
-rw-r--r-- | drivers/watchdog/ziirave_wdt.c | 409 |
1 files changed, 404 insertions, 5 deletions
diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c index fa1efef3c96e..b4e0cea5a64e 100644 --- a/drivers/watchdog/ziirave_wdt.c +++ b/drivers/watchdog/ziirave_wdt.c @@ -18,7 +18,10 @@ * GNU General Public License for more details. */ +#include <linux/delay.h> #include <linux/i2c.h> +#include <linux/ihex.h> +#include <linux/firmware.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> @@ -36,6 +39,8 @@ #define ZIIRAVE_STATE_OFF 0x1 #define ZIIRAVE_STATE_ON 0x2 +#define ZIIRAVE_FW_NAME "ziirave_wdt.fw" + static char *ziirave_reasons[] = {"power cycle", "hw watchdog", NULL, NULL, "host request", NULL, "illegal configuration", "illegal instruction", "illegal trap", @@ -50,12 +55,35 @@ static char *ziirave_reasons[] = {"power cycle", "hw watchdog", NULL, NULL, #define ZIIRAVE_WDT_PING 0x9 #define ZIIRAVE_WDT_RESET_DURATION 0xa +#define ZIIRAVE_FIRM_PKT_TOTAL_SIZE 20 +#define ZIIRAVE_FIRM_PKT_DATA_SIZE 16 +#define ZIIRAVE_FIRM_FLASH_MEMORY_START 0x1600 +#define ZIIRAVE_FIRM_FLASH_MEMORY_END 0x2bbf + +/* Received and ready for next Download packet. */ +#define ZIIRAVE_FIRM_DOWNLOAD_ACK 1 +/* Currently writing to flash. Retry Download status in a moment! */ +#define ZIIRAVE_FIRM_DOWNLOAD_BUSY 2 + +/* Wait for ACK timeout in ms */ +#define ZIIRAVE_FIRM_WAIT_FOR_ACK_TIMEOUT 50 + +/* Firmware commands */ +#define ZIIRAVE_CMD_DOWNLOAD_START 0x10 +#define ZIIRAVE_CMD_DOWNLOAD_END 0x11 +#define ZIIRAVE_CMD_DOWNLOAD_SET_READ_ADDR 0x12 +#define ZIIRAVE_CMD_DOWNLOAD_READ_BYTE 0x13 +#define ZIIRAVE_CMD_RESET_PROCESSOR 0x0b +#define ZIIRAVE_CMD_JUMP_TO_BOOTLOADER 0x0c +#define ZIIRAVE_CMD_DOWNLOAD_PACKET 0x0e + struct ziirave_wdt_rev { unsigned char major; unsigned char minor; }; struct ziirave_wdt_data { + struct mutex sysfs_mutex; struct watchdog_device wdd; struct ziirave_wdt_rev bootloader_rev; struct ziirave_wdt_rev firmware_rev; @@ -146,6 +174,293 @@ static unsigned int ziirave_wdt_get_timeleft(struct watchdog_device *wdd) return ret; } +static int ziirave_firm_wait_for_ack(struct watchdog_device *wdd) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + int ret; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(ZIIRAVE_FIRM_WAIT_FOR_ACK_TIMEOUT); + do { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + usleep_range(5000, 10000); + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&client->dev, "Failed to read byte\n"); + return ret; + } + } while (ret == ZIIRAVE_FIRM_DOWNLOAD_BUSY); + + return ret == ZIIRAVE_FIRM_DOWNLOAD_ACK ? 0 : -EIO; +} + +static int ziirave_firm_set_read_addr(struct watchdog_device *wdd, u16 addr) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + u8 address[2]; + + address[0] = addr & 0xff; + address[1] = (addr >> 8) & 0xff; + + return i2c_smbus_write_block_data(client, + ZIIRAVE_CMD_DOWNLOAD_SET_READ_ADDR, + ARRAY_SIZE(address), address); +} + +static int ziirave_firm_write_block_data(struct watchdog_device *wdd, + u8 command, u8 length, const u8 *data, + bool wait_for_ack) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + int ret; + + ret = i2c_smbus_write_block_data(client, command, length, data); + if (ret) { + dev_err(&client->dev, + "Failed to send command 0x%02x: %d\n", command, ret); + return ret; + } + + if (wait_for_ack) + ret = ziirave_firm_wait_for_ack(wdd); + + return ret; +} + +static int ziirave_firm_write_byte(struct watchdog_device *wdd, u8 command, + u8 byte, bool wait_for_ack) +{ + return ziirave_firm_write_block_data(wdd, command, 1, &byte, + wait_for_ack); +} + +/* + * ziirave_firm_write_pkt() - Build and write a firmware packet + * + * A packet to send to the firmware is composed by following bytes: + * Length | Addr0 | Addr1 | Data0 .. Data15 | Checksum | + * Where, + * Length: A data byte containing the length of the data. + * Addr0: Low byte of the address. + * Addr1: High byte of the address. + * Data0 .. Data15: Array of 16 bytes of data. + * Checksum: Checksum byte to verify data integrity. + */ +static int ziirave_firm_write_pkt(struct watchdog_device *wdd, + const struct ihex_binrec *rec) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + u8 i, checksum = 0, packet[ZIIRAVE_FIRM_PKT_TOTAL_SIZE]; + int ret; + u16 addr; + + memset(packet, 0, ARRAY_SIZE(packet)); + + /* Packet length */ + packet[0] = (u8)be16_to_cpu(rec->len); + /* Packet address */ + addr = (be32_to_cpu(rec->addr) & 0xffff) >> 1; + packet[1] = addr & 0xff; + packet[2] = (addr & 0xff00) >> 8; + + /* Packet data */ + if (be16_to_cpu(rec->len) > ZIIRAVE_FIRM_PKT_DATA_SIZE) + return -EMSGSIZE; + memcpy(packet + 3, rec->data, be16_to_cpu(rec->len)); + + /* Packet checksum */ + for (i = 0; i < ZIIRAVE_FIRM_PKT_TOTAL_SIZE - 1; i++) + checksum += packet[i]; + packet[ZIIRAVE_FIRM_PKT_TOTAL_SIZE - 1] = checksum; + + ret = ziirave_firm_write_block_data(wdd, ZIIRAVE_CMD_DOWNLOAD_PACKET, + ARRAY_SIZE(packet), packet, true); + if (ret) + dev_err(&client->dev, + "Failed to write firmware packet at address 0x%04x: %d\n", + addr, ret); + + return ret; +} + +static int ziirave_firm_verify(struct watchdog_device *wdd, + const struct firmware *fw) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + const struct ihex_binrec *rec; + int i, ret; + u8 data[ZIIRAVE_FIRM_PKT_DATA_SIZE]; + u16 addr; + + for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) { + /* Zero length marks end of records */ + if (!be16_to_cpu(rec->len)) + break; + + addr = (be32_to_cpu(rec->addr) & 0xffff) >> 1; + if (addr < ZIIRAVE_FIRM_FLASH_MEMORY_START || + addr > ZIIRAVE_FIRM_FLASH_MEMORY_END) + continue; + + ret = ziirave_firm_set_read_addr(wdd, addr); + if (ret) { + dev_err(&client->dev, + "Failed to send SET_READ_ADDR command: %d\n", + ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(data); i++) { + ret = i2c_smbus_read_byte_data(client, + ZIIRAVE_CMD_DOWNLOAD_READ_BYTE); + if (ret < 0) { + dev_err(&client->dev, + "Failed to READ DATA: %d\n", ret); + return ret; + } + data[i] = ret; + } + + if (memcmp(data, rec->data, be16_to_cpu(rec->len))) { + dev_err(&client->dev, + "Firmware mismatch at address 0x%04x\n", addr); + return -EINVAL; + } + } + + return 0; +} + +static int ziirave_firm_upload(struct watchdog_device *wdd, + const struct firmware *fw) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + int ret, words_till_page_break; + const struct ihex_binrec *rec; + struct ihex_binrec *rec_new; + + ret = ziirave_firm_write_byte(wdd, ZIIRAVE_CMD_JUMP_TO_BOOTLOADER, 1, + false); + if (ret) + return ret; + + msleep(500); + + ret = ziirave_firm_write_byte(wdd, ZIIRAVE_CMD_DOWNLOAD_START, 1, true); + if (ret) + return ret; + + msleep(500); + + for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) { + /* Zero length marks end of records */ + if (!be16_to_cpu(rec->len)) + break; + + /* Check max data size */ + if (be16_to_cpu(rec->len) > ZIIRAVE_FIRM_PKT_DATA_SIZE) { + dev_err(&client->dev, "Firmware packet too long (%d)\n", + be16_to_cpu(rec->len)); + return -EMSGSIZE; + } + + /* Calculate words till page break */ + words_till_page_break = (64 - ((be32_to_cpu(rec->addr) >> 1) & + 0x3f)); + if ((be16_to_cpu(rec->len) >> 1) > words_till_page_break) { + /* + * Data in passes page boundary, so we need to split in + * two blocks of data. Create a packet with the first + * block of data. + */ + rec_new = kzalloc(sizeof(struct ihex_binrec) + + (words_till_page_break << 1), + GFP_KERNEL); + if (!rec_new) + return -ENOMEM; + + rec_new->len = cpu_to_be16(words_till_page_break << 1); + rec_new->addr = rec->addr; + memcpy(rec_new->data, rec->data, + be16_to_cpu(rec_new->len)); + + ret = ziirave_firm_write_pkt(wdd, rec_new); + kfree(rec_new); + if (ret) + return ret; + + /* Create a packet with the second block of data */ + rec_new = kzalloc(sizeof(struct ihex_binrec) + + be16_to_cpu(rec->len) - + (words_till_page_break << 1), + GFP_KERNEL); + if (!rec_new) + return -ENOMEM; + + /* Remaining bytes */ + rec_new->len = rec->len - + cpu_to_be16(words_till_page_break << 1); + + rec_new->addr = cpu_to_be32(be32_to_cpu(rec->addr) + + (words_till_page_break << 1)); + + memcpy(rec_new->data, + rec->data + (words_till_page_break << 1), + be16_to_cpu(rec_new->len)); + + ret = ziirave_firm_write_pkt(wdd, rec_new); + kfree(rec_new); + if (ret) + return ret; + } else { + ret = ziirave_firm_write_pkt(wdd, rec); + if (ret) + return ret; + } + } + + /* For end of download, the length field will be set to 0 */ + rec_new = kzalloc(sizeof(struct ihex_binrec) + 1, GFP_KERNEL); + if (!rec_new) + return -ENOMEM; + + ret = ziirave_firm_write_pkt(wdd, rec_new); + kfree(rec_new); + if (ret) { + dev_err(&client->dev, "Failed to send EMPTY packet: %d\n", ret); + return ret; + } + + /* This sleep seems to be required */ + msleep(20); + + /* Start firmware verification */ + ret = ziirave_firm_verify(wdd, fw); + if (ret) { + dev_err(&client->dev, + "Failed to verify firmware: %d\n", ret); + return ret; + } + + /* End download operation */ + ret = ziirave_firm_write_byte(wdd, ZIIRAVE_CMD_DOWNLOAD_END, 1, false); + if (ret) + return ret; + + /* Reset the processor */ + ret = ziirave_firm_write_byte(wdd, ZIIRAVE_CMD_RESET_PROCESSOR, 1, + false); + if (ret) + return ret; + + msleep(500); + + return 0; +} + static const struct watchdog_info ziirave_wdt_info = { .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, .identity = "Zodiac RAVE Watchdog", @@ -166,9 +481,18 @@ static ssize_t ziirave_wdt_sysfs_show_firm(struct device *dev, { struct i2c_client *client = to_i2c_client(dev->parent); struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + int ret; + + ret = mutex_lock_interruptible(&w_priv->sysfs_mutex); + if (ret) + return ret; + + ret = sprintf(buf, "02.%02u.%02u", w_priv->firmware_rev.major, + w_priv->firmware_rev.minor); - return sprintf(buf, "02.%02u.%02u", w_priv->firmware_rev.major, - w_priv->firmware_rev.minor); + mutex_unlock(&w_priv->sysfs_mutex); + + return ret; } static DEVICE_ATTR(firmware_version, S_IRUGO, ziirave_wdt_sysfs_show_firm, @@ -180,9 +504,18 @@ static ssize_t ziirave_wdt_sysfs_show_boot(struct device *dev, { struct i2c_client *client = to_i2c_client(dev->parent); struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + int ret; - return sprintf(buf, "01.%02u.%02u", w_priv->bootloader_rev.major, - w_priv->bootloader_rev.minor); + ret = mutex_lock_interruptible(&w_priv->sysfs_mutex); + if (ret) + return ret; + + ret = sprintf(buf, "01.%02u.%02u", w_priv->bootloader_rev.major, + w_priv->bootloader_rev.minor); + + mutex_unlock(&w_priv->sysfs_mutex); + + return ret; } static DEVICE_ATTR(bootloader_version, S_IRUGO, ziirave_wdt_sysfs_show_boot, @@ -194,17 +527,81 @@ static ssize_t ziirave_wdt_sysfs_show_reason(struct device *dev, { struct i2c_client *client = to_i2c_client(dev->parent); struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + int ret; + + ret = mutex_lock_interruptible(&w_priv->sysfs_mutex); + if (ret) + return ret; + + ret = sprintf(buf, "%s", ziirave_reasons[w_priv->reset_reason]); - return sprintf(buf, "%s", ziirave_reasons[w_priv->reset_reason]); + mutex_unlock(&w_priv->sysfs_mutex); + + return ret; } static DEVICE_ATTR(reset_reason, S_IRUGO, ziirave_wdt_sysfs_show_reason, NULL); +static ssize_t ziirave_wdt_sysfs_store_firm(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + const struct firmware *fw; + int err; + + err = request_ihex_firmware(&fw, ZIIRAVE_FW_NAME, dev); + if (err) { + dev_err(&client->dev, "Failed to request ihex firmware\n"); + return err; + } + + err = mutex_lock_interruptible(&w_priv->sysfs_mutex); + if (err) + goto release_firmware; + + err = ziirave_firm_upload(&w_priv->wdd, fw); + if (err) { + dev_err(&client->dev, "The firmware update failed: %d\n", err); + goto unlock_mutex; + } + + /* Update firmware version */ + err = ziirave_wdt_revision(client, &w_priv->firmware_rev, + ZIIRAVE_WDT_FIRM_VER_MAJOR); + if (err) { + dev_err(&client->dev, "Failed to read firmware version: %d\n", + err); + goto unlock_mutex; + } + + dev_info(&client->dev, "Firmware updated to version 02.%02u.%02u\n", + w_priv->firmware_rev.major, w_priv->firmware_rev.minor); + + /* Restore the watchdog timeout */ + err = ziirave_wdt_set_timeout(&w_priv->wdd, w_priv->wdd.timeout); + if (err) + dev_err(&client->dev, "Failed to set timeout: %d\n", err); + +unlock_mutex: + mutex_unlock(&w_priv->sysfs_mutex); + +release_firmware: + release_firmware(fw); + + return err ? err : count; +} + +static DEVICE_ATTR(update_firmware, S_IWUSR, NULL, + ziirave_wdt_sysfs_store_firm); + static struct attribute *ziirave_wdt_attrs[] = { &dev_attr_firmware_version.attr, &dev_attr_bootloader_version.attr, &dev_attr_reset_reason.attr, + &dev_attr_update_firmware.attr, NULL }; ATTRIBUTE_GROUPS(ziirave_wdt); @@ -252,6 +649,8 @@ static int ziirave_wdt_probe(struct i2c_client *client, if (!w_priv) return -ENOMEM; + mutex_init(&w_priv->sysfs_mutex); + w_priv->wdd.info = &ziirave_wdt_info; w_priv->wdd.ops = &ziirave_wdt_ops; w_priv->wdd.min_timeout = ZIIRAVE_TIMEOUT_MIN; |