summaryrefslogtreecommitdiff
path: root/drivers/watchdog/ziirave_wdt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog/ziirave_wdt.c')
-rw-r--r--drivers/watchdog/ziirave_wdt.c409
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;