input: touchscreen: import CY8CTMA395 touchscreen driver
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index e77f3dc..12bbe8c 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -1026,4 +1026,15 @@
help
This enables support for Synaptics RMI4 over I2C based touchscreens.
+config TOUCHSCREEN_CY8CTMA395
+ tristate "Cypress TMA395 capacitive touchscreen controller"
+ default n
+ help
+ Say Y here if you have a CY8CTMA395 based touchscreen controller
+
+ If unsure, say N.
+
+ To comile this driver as a module, choose M here: the module
+ will be called cy8ctma395
+
endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index ba0e55a..ed27d41 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -89,3 +89,4 @@
obj-$(CONFIG_TOUCHSCREEN_ATMEL_224E) += atmel_224e.o
obj-$(CONFIG_TOUCHSCREEN_CYPRESS_CS) += cy8c_cs.o
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_3K) += synaptics_3200.o rmi_dev.o
+obj-$(CONFIG_TOUCHSCREEN_CY8CTMA395) += cy8ctma395.o
diff --git a/drivers/input/touchscreen/cy8ctma395.c b/drivers/input/touchscreen/cy8ctma395.c
new file mode 100644
index 0000000..0500e9a
--- /dev/null
+++ b/drivers/input/touchscreen/cy8ctma395.c
@@ -0,0 +1,1190 @@
+//#define DEBUG
+
+#include <linux/cy8ctma395.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/ihex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#define BLOCK_LEN 256
+#define ECC_LEN 32
+#define DEVICE_CONFIG_ECCEN 0x08
+
+#define DATA_RECORD_ADDR 0x00000000
+#define DATA_RECORD_LEN 64
+#define ECC_RECORD_ADDR 0x80000000
+#define ECC_RECORD_LEN 64
+#define NVL_RECORD_ADDR 0x90000000
+#define NVL_RECORD_LEN 4
+
+#define APACC_ADDR_WRITE 0x8b
+#define APACC_DATA_READ 0x9f
+#define APACC_DATA_WRITE 0xbb
+#define DPACC_DATA_WRITE 0x99
+#define DPACC_DP_CONFIG_WRITE 0xa9
+#define DPACC_IDCODE_READ 0xa5
+
+#define RESPONSE_OK 0x1
+#define RESPONSE_WAIT 0x2
+#define RESPONSE_FAULT 0x4
+
+struct addr_data_pair {
+ u32 addr;
+ u32 data;
+};
+
+struct program_row {
+ const struct ihex_binrec *dat_rec;
+ u16 dat_rec_off;
+ const struct ihex_binrec *ecc_rec;
+ u16 ecc_rec_off;
+ const struct ihex_binrec *nvl_rec;
+ int ecc_enabled;
+ u32 sram_addr;
+ u32 temp[2];
+ u32 nr;
+ u32 phub_ch_status_addr;
+ u32 phub_ch_status_data;
+ u32 phub_ch_basic_cfg_addr;
+ u32 phub_cfgmem_cfg0_addr;
+ u32 phub_cfgmem_cfg1_addr;
+ u32 phub_tdmem_orig_td0_addr;
+ u32 phub_tdmem_orig_td1_addr;
+ u32 phub_ch_action_addr;
+};
+
+struct cy8ctma395_device_data {
+ int last_swdio_bit;
+};
+
+static int swd_read_bit(struct device *dev, int fast)
+{
+ int bit;
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ gpio_set_value(pdat->swdck, 0);
+ if (!fast) udelay(1);
+ bit = gpio_get_value(pdat->swdio);
+ gpio_set_value(pdat->swdck, 1);
+
+ return (bit);
+}
+
+static void swd_write_bit(struct device *dev, int bit, int fast)
+{
+ struct cy8ctma395_device_data *dat = dev_get_drvdata(dev);
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ if (bit == dat->last_swdio_bit) {
+ gpio_set_value(pdat->swdck, 0);
+ gpio_set_value(pdat->swdck, 1);
+ }
+
+ else {
+ gpio_set_value(pdat->swdck, 0);
+ gpio_set_value(pdat->swdio, dat->last_swdio_bit = bit);
+ if (!fast) udelay(1);
+ gpio_set_value(pdat->swdck, 1);
+ }
+}
+
+static void swd_turnaround(struct device *dev, int out)
+{
+ struct cy8ctma395_device_data *dat = dev_get_drvdata(dev);
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ if (out)
+ gpio_direction_output(pdat->swdio, dat->last_swdio_bit = 1);
+
+ else
+ gpio_direction_input(pdat->swdio);
+
+ gpio_set_value(pdat->swdck, 0);
+ gpio_set_value(pdat->swdck, 1);
+}
+
+static u8 swd_read_response(struct device *dev, int fast)
+{
+ u8 response;
+
+ response = swd_read_bit(dev, fast);
+ response |= swd_read_bit(dev, fast) << 1;
+ response |= swd_read_bit(dev, fast) << 2;
+
+ return (response);
+}
+
+static u8 swd_read_byte(struct device *dev)
+{
+ u8 byte;
+
+ byte = swd_read_bit(dev, 0);
+ byte |= swd_read_bit(dev, 0) << 1;
+ byte |= swd_read_bit(dev, 0) << 2;
+ byte |= swd_read_bit(dev, 0) << 3;
+ byte |= swd_read_bit(dev, 0) << 4;
+ byte |= swd_read_bit(dev, 0) << 5;
+ byte |= swd_read_bit(dev, 0) << 6;
+ byte |= swd_read_bit(dev, 0) << 7;
+
+ return (byte);
+}
+
+static void swd_write_byte(struct device *dev, u8 byte, int fast)
+{
+ swd_write_bit(dev, (byte >> 0) & 0x01, fast);
+ swd_write_bit(dev, (byte >> 1) & 0x01, fast);
+ swd_write_bit(dev, (byte >> 2) & 0x01, fast);
+ swd_write_bit(dev, (byte >> 3) & 0x01, fast);
+ swd_write_bit(dev, (byte >> 4) & 0x01, fast);
+ swd_write_bit(dev, (byte >> 5) & 0x01, fast);
+ swd_write_bit(dev, (byte >> 6) & 0x01, fast);
+ swd_write_bit(dev, (byte >> 7) & 0x01, fast);
+}
+
+static int even_parity(u32 data)
+{
+ int parity = 0;
+
+ for (; data; data >>= 1)
+ parity ^= data;
+
+ return (parity & 0x1);
+}
+
+static int swd_read_data(struct device *dev, u32 *data)
+{
+ int rc;
+ int parity;
+
+ *data = swd_read_byte(dev);
+ *data |= (u32)swd_read_byte(dev) << 8;
+ *data |= (u32)swd_read_byte(dev) << 16;
+ *data |= (u32)swd_read_byte(dev) << 24;
+ parity = swd_read_bit(dev, 0);
+
+ if (parity != even_parity(*data)) {
+ dev_err(dev, "swd data parity error, data=0x%08x parity=%x\n",
+ *data, parity);
+ rc = -EIO;
+ goto exit;
+ }
+
+ rc = 0;
+exit:
+ return (rc);
+}
+
+static void swd_write_data(struct device *dev, u32 data, int fast)
+{
+ swd_write_byte(dev, data >> 0, fast);
+ swd_write_byte(dev, data >> 8, fast);
+ swd_write_byte(dev, data >> 16, fast);
+ swd_write_byte(dev, data >> 24, fast);
+ swd_write_bit(dev, even_parity(data), fast);
+}
+
+static int swd_read(struct device *dev, u8 command, u32 *data)
+{
+ u8 response;
+ int rc;
+ int retries = 0;
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+retry:
+ swd_write_byte(dev, command, 0);
+ swd_turnaround(dev, 0);
+ response = swd_read_response(dev, 0);
+
+ rc = swd_read_data(dev, data);
+ if (rc < 0)
+ goto exit;
+
+ swd_turnaround(dev, 1);
+
+ if ((response == RESPONSE_WAIT) && (retries++ < pdat->swd_wait_retries))
+ goto retry;
+
+ if (response != RESPONSE_OK) {
+ dev_err(dev, "swd read failed, command=%02x response=%x\n",
+ command, response);
+ rc = -EIO;
+ goto exit;
+ }
+
+ rc = 0;
+exit:
+ return (rc);
+}
+
+static u8 __swd_write(struct device *dev, u8 command, u32 data, int fast)
+{
+ u8 response;
+
+ swd_write_byte(dev, command, fast);
+ swd_turnaround(dev, 0);
+ response = swd_read_response(dev, fast);
+ swd_turnaround(dev, 1);
+ swd_write_data(dev, data, fast);
+
+ return (response);
+}
+
+static int swd_write(struct device *dev, u8 command, u32 data)
+{
+ u8 response;
+ int rc;
+ int retries = 0;
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+retry:
+ response = __swd_write(dev, command, data, 0);
+
+ if ((response == RESPONSE_WAIT) && (retries++ < pdat->swd_wait_retries))
+ goto retry;
+
+ if (response != RESPONSE_OK) {
+ dev_err(dev, "swd write failed, command=%02x data=%08x response=%x\n",
+ command, data, response);
+ rc = -EIO;
+ goto exit;
+ }
+
+ rc = 0;
+exit:
+ return (rc);
+}
+
+static int apacc_addr_write(struct device *dev, u32 addr)
+{
+ dev_dbg(dev, "apacc addr write [%08x]\n", addr);
+
+ return (swd_write(dev, APACC_ADDR_WRITE, addr));
+}
+
+static int apacc_data_read(struct device *dev, u32 *data, int nr)
+{
+ int i;
+ int rc;
+ u32 unused;
+
+ rc = swd_read(dev, APACC_DATA_READ, &unused);
+ if (rc < 0)
+ goto exit;
+
+ dev_dbg(dev, "apacc data read (dummy) [%08x]\n", unused);
+
+ for (i = 0; i < nr; i++) {
+ rc = swd_read(dev, APACC_DATA_READ, &data[i]);
+ if (rc < 0)
+ goto exit;
+
+ dev_dbg(dev, "apacc data read [%08x]\n", data[i]);
+ }
+
+exit:
+ return (rc);
+}
+
+static int apacc_addr_write_data_read(struct device *dev, u32 addr, u32 *data,
+ int nr)
+{
+ int rc;
+
+ rc = apacc_addr_write(dev, addr);
+ if (rc < 0)
+ goto exit;
+
+ rc = apacc_data_read(dev, data, nr);
+exit:
+ return (rc);
+}
+
+static inline int apacc_data_write(struct device *dev, u32 data)
+{
+ dev_dbg(dev, "apacc data write [%08x]\n", data);
+
+ return (swd_write(dev, APACC_DATA_WRITE, data));
+}
+
+static int apacc_addr_data_write(struct device *dev, u32 addr, u32 data)
+{
+ int rc;
+
+ rc = apacc_addr_write(dev, addr);
+ if (rc < 0)
+ goto exit;
+
+ rc = apacc_data_write(dev, data);
+exit:
+ return (rc);
+}
+
+static int apacc_addr_data_write_seq(struct device *dev,
+ struct addr_data_pair *seq, int nr)
+{
+ int i;
+ int rc;
+
+ for (i = 0; i < nr; i++) {
+ rc = apacc_addr_data_write(dev, seq[i].addr, seq[i].data);
+ if (rc < 0)
+ goto exit;
+ }
+
+ rc = 0;
+exit:
+ return (rc);
+}
+
+static int port_acquire(struct device *dev, u32 *id, u8 *rev)
+{
+ int rc;
+ u32 data[2];
+ struct cy8ctma395_device_data *dat = dev_get_drvdata(dev);
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ rc = pdat->swdck_request(1);
+ if (rc < 0)
+ goto request_swdck_failed;
+
+ rc = pdat->swdio_request(1);
+ if (rc < 0)
+ goto request_swdio_failed;
+
+ gpio_set_value(pdat->swdck, 1);
+ gpio_set_value(pdat->swdio, dat->last_swdio_bit = 1);
+ gpio_set_value(pdat->xres, 0);
+ usleep(pdat->xres_us);
+
+ local_irq_disable();
+ {
+ u8 response;
+ int retries = 0;
+
+ gpio_set_value(pdat->xres, 1);
+retry:
+ response = __swd_write(dev, DPACC_DATA_WRITE, 0x7B0C06DB, 1);
+ if (response != RESPONSE_OK) {
+ if (retries++ < pdat->port_acquire_retries)
+ goto retry;
+
+ rc = -EIO;
+ goto enable;
+ }
+
+ rc = apacc_addr_data_write(dev, 0x00050210, 0xEA7E30A9);
+ }
+
+enable:
+ local_irq_enable();
+
+ if (rc < 0) {
+ dev_err(dev, "failed to acquire port\n");
+ goto acquire_failed;
+ }
+
+ {
+ struct addr_data_pair seq[] = {
+ {0x00050220, 0x000000B3},
+ {0x000046EA, 0x00000001},
+ {0x000043A0, 0x000000BF},
+ {0x00004200, 0x00000000},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto acquire_failed;
+ }
+
+ dev_dbg(dev, "dpacc idcode read\n");
+ rc = swd_read(dev, DPACC_IDCODE_READ, &data[0]);
+ if (rc < 0)
+ goto acquire_failed;
+
+ rc = apacc_addr_write_data_read(dev, 0x000046EC, &data[1], 1);
+ if (rc < 0)
+ goto acquire_failed;
+
+ if (id)
+ *id = data[0];
+
+ if (rev)
+ *rev = data[1];
+
+ return (0);
+
+acquire_failed:
+ gpio_set_value(pdat->xres, 0);
+ usleep(pdat->xres_us);
+ gpio_set_value(pdat->xres, 1);
+ (void)pdat->swdio_request(0);
+request_swdio_failed:
+ (void)pdat->swdck_request(0);
+request_swdck_failed:
+
+ return (rc);
+}
+
+static void port_release(struct device *dev)
+{
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ gpio_set_value(pdat->xres, 0);
+ usleep(pdat->xres_us);
+ gpio_set_value(pdat->xres, 1);
+ (void)pdat->swdio_request(0);
+ (void)pdat->swdck_request(0);
+}
+
+static int poll_status_reg(struct device *dev, u8 expected, const char *step)
+{
+ int rc;
+#ifdef DEBUG
+ u64 ms;
+#endif /* DEBUG */
+ u32 data;
+ struct timespec now;
+ struct timespec expiry;
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ ktime_get_ts(&expiry);
+ timespec_add_ns(&expiry, pdat->status_reg_timeout_ms * NSEC_PER_MSEC);
+
+ for (;;) {
+ rc = apacc_addr_write_data_read(dev, 0x00004722, &data, 1);
+ if (rc < 0)
+ goto exit;
+
+ /* only low byte is relevant in status reg */
+ if ((data & 0xff) == expected)
+ break;
+
+ ktime_get_ts(&now);
+ if (timespec_compare(&now, &expiry) >= 0) {
+ dev_err(dev, "timed out waiting for '%s'\n", step);
+ rc = -ETIME;
+ goto exit;
+ }
+
+ msleep(jiffies_to_msecs(1));
+ }
+
+#ifdef DEBUG
+ ktime_get_ts(&now);
+ timespec_add_ns(&now, NSEC_PER_SEC);
+ ms = timespec_to_ns(&now) - timespec_to_ns(&expiry);
+ do_div(ms, NSEC_PER_MSEC);
+ dev_dbg(dev, "'%s' time=%lldms\n", step, ms);
+#endif /* DEBUG */
+
+ rc = 0;
+exit:
+ return (rc);
+}
+
+static int find_record(struct device *dev, const struct ihex_binrec *beg,
+ u32 addr, u16 len, const struct ihex_binrec **rec)
+{
+ int rc;
+
+ for (*rec = beg; be32_to_cpu((*rec)->addr) != addr;) {
+ *rec = ihex_next_binrec(*rec);
+ if (!*rec) {
+ dev_err(dev, "no record with address 0x%08x\n", addr);
+ rc = -EINVAL;
+ goto exit;
+ }
+ }
+
+ if (be16_to_cpu((*rec)->len) != len) {
+ dev_err(dev, "record 0x%08x is %hu bytes, expected %hu\n", addr,
+ be16_to_cpu((*rec)->len), len);
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ rc = 0;
+exit:
+ return (rc);
+}
+
+static int load_row(struct device *dev, const struct ihex_binrec **rec,
+ u16 *rec_off, u32 beg, u32 end)
+{
+ int rc;
+ u32 data;
+
+ for (; beg < end; beg += 4) {
+ if (*rec_off >= be16_to_cpu((*rec)->len)) {
+ rc = find_record(dev, *rec, be32_to_cpu((*rec)->addr)
+ + be16_to_cpu((*rec)->len),
+ be16_to_cpu((*rec)->len), rec);
+ if (rc < 0)
+ goto exit;
+
+ *rec_off = 0;
+ }
+
+ data = (*rec)->data[*rec_off] << 0;
+ data |= (*rec)->data[*rec_off+1] << 8;
+ data |= (*rec)->data[*rec_off+2] << 16;
+ data |= (*rec)->data[*rec_off+3] << 24;
+ rc = apacc_addr_data_write(dev, beg+4, data);
+ if (rc < 0)
+ goto exit;
+
+ *rec_off += 4;
+ }
+
+ rc = 0;
+exit:
+ return (rc);
+}
+
+static int program_row(struct device *dev, struct program_row *row)
+{
+ int rc;
+ int len = BLOCK_LEN;
+
+ rc = apacc_addr_data_write(dev, row->sram_addr, 0x0002D5B6);
+ if (rc < 0)
+ goto exit;
+
+ rc = load_row(dev, &row->dat_rec, &row->dat_rec_off, row->sram_addr,
+ row->sram_addr + BLOCK_LEN);
+ if (rc < 0)
+ goto exit;
+
+ if (!row->ecc_enabled) {
+ rc = load_row(dev, &row->ecc_rec, &row->ecc_rec_off,
+ row->sram_addr + BLOCK_LEN,
+ row->sram_addr + BLOCK_LEN + ECC_LEN);
+ if (rc < 0)
+ goto exit;
+
+ len += ECC_LEN;
+ }
+
+ {
+ struct addr_data_pair seq[] = {
+ {row->sram_addr + len + 0x4, 0xB6000000},
+ {row->sram_addr + len + 0x8, 0x000007DA},
+ {row->sram_addr + len + 0xC,
+ ((row->temp[1] & 0xff) << 16)
+ | ((row->temp[0] & 0xff) << 8)
+ | (row->nr & 0xff)},
+ {row->phub_ch_status_addr, row->phub_ch_status_data},
+ {row->phub_ch_basic_cfg_addr, 0x00000021},
+ {row->phub_cfgmem_cfg0_addr, 0x00000080},
+ {row->phub_cfgmem_cfg1_addr, 0x00000000},
+ {row->phub_tdmem_orig_td0_addr, 0x01FF0000 + len + 0xf},
+ {row->phub_tdmem_orig_td1_addr, 0x47200000 + row->sram_addr},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto exit;
+ }
+
+ rc = poll_status_reg(dev, 0x2, "dma");
+ if (rc < 0)
+ goto exit;
+
+ rc = apacc_addr_data_write(dev, row->phub_ch_action_addr, 0x00000001);
+exit:
+ if (rc < 0)
+ dev_err(dev, "failure programming row %u\n", row->nr);
+
+ return (rc);
+}
+
+static int checksum(struct device *dev, u16 *sum)
+{
+ int rc;
+ u32 data[4];
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ {
+ struct addr_data_pair seq[] = {
+ {0x00004720, 0x000000B6},
+ {0x00004720, 0x000000DF},
+ {0x00004720, 0x0000000C},
+ {0x00004720, 0x00000000},
+ {0x00004720, 0x00000000},
+ {0x00004720, 0x00000000},
+ {0x00004720, 0x00000000},
+ {0x00004720, pdat->nr_blocks - 1},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto exit;
+ }
+
+ rc = poll_status_reg(dev, 0x1, "checksum");
+ if (rc < 0)
+ goto exit;
+
+ rc = apacc_addr_write_data_read(dev, 0x00004720, data, 4);
+ if (rc < 0)
+ goto exit;
+
+ rc = poll_status_reg(dev, 0x2, "idle");
+ if (rc < 0)
+ goto exit;
+
+ *sum = ((data[2] & 0xff) << 8) | (data[3] & 0xff);
+exit:
+ return (rc);
+}
+
+static ssize_t cy8ctma395_attr_checksum_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u16 sum;
+ ssize_t rc;
+
+ rc = port_acquire(dev, NULL, NULL);
+ if (rc < 0)
+ goto exit;
+
+ rc = checksum(dev, &sum);
+ if (rc < 0)
+ goto release;
+
+ rc = snprintf(buf, PAGE_SIZE, "%04x\n", sum);
+release:
+ port_release(dev);
+exit:
+ return (rc);
+}
+
+static struct device_attribute cy8ctma395_attr_checksum = {
+ .attr = {
+ .name = "checksum",
+ .mode = S_IRUSR,
+ },
+ .show = cy8ctma395_attr_checksum_show,
+};
+
+static ssize_t cy8ctma395_attr_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 rev;
+ u32 id;
+ ssize_t rc;
+
+ rc = port_acquire(dev, &id, &rev);
+ if (rc < 0)
+ goto exit;
+
+ rc = snprintf(buf, PAGE_SIZE, "%08x %02x\n", id, rev);
+ port_release(dev);
+exit:
+ return (rc);
+}
+
+static struct device_attribute cy8ctma395_attr_id = {
+ .attr = {
+ .name = "id",
+ .mode = S_IRUSR,
+ },
+ .show = cy8ctma395_attr_id_show,
+};
+
+static int read_device_config(struct device *dev, u8 *regs)
+{
+ int i;
+ int rc;
+ u32 data[4];
+
+ {
+ struct addr_data_pair seq[] = {
+ {0x00005112, 0x00000000},
+ {0x00005113, 0x00000004},
+ {0x00005114, 0x00000000},
+ {0x00005110, 0x00000004},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto exit;
+ }
+
+ for (i = 0; i < 4; i++) {
+ struct addr_data_pair seq[] = {
+ {0x00004720, 0x000000B6},
+ {0x00004720, 0x000000D6},
+ {0x00004720, 0x00000003},
+ {0x00004720, 0x00000080},
+ {0x00004720, i},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto exit;
+
+ rc = poll_status_reg(dev, 0x1, "device config");
+ if (rc < 0)
+ goto exit;
+
+ rc = apacc_addr_write_data_read(dev, 0x00004720, &data[i], 1);
+ if (rc < 0)
+ goto exit;
+
+ regs[i] = data[i];
+
+ rc = poll_status_reg(dev, 0x2, "idle");
+ if (rc < 0)
+ goto exit;
+ }
+
+exit:
+ return (rc);
+}
+
+static int write_device_config(struct device *dev, u8 *regs)
+{
+ int i;
+ int rc;
+
+ for (i = 0; i < 4; i++) {
+ struct addr_data_pair seq[] = {
+ {0x00004720, 0x000000B6},
+ {0x00004720, 0x000000D3},
+ {0x00004720, 0x00000000},
+ {0x00004720, 0x00000080},
+ {0x00004720, i},
+ {0x00004720, regs[i]},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto exit;
+
+ rc = poll_status_reg(dev, 0x2, "device config");
+ if (rc < 0)
+ goto exit;
+ }
+
+ {
+ struct addr_data_pair seq[] = {
+ {0x00004720, 0x000000B6},
+ {0x00004720, 0x000000D9},
+ {0x00004720, 0x00000006},
+ {0x00004720, 0x00000080},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto exit;
+ }
+
+ rc = poll_status_reg(dev, 0x2, "device config");
+ if (rc < 0)
+ goto exit;
+
+ exit:
+ return (rc);
+
+}
+
+static ssize_t cy8ctma395_attr_device_config_show(
+ struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ u8 regs[4];
+ ssize_t rc;
+
+ rc = port_acquire(dev, NULL, NULL);
+ if (rc < 0)
+ goto exit;
+
+ rc = read_device_config(dev, regs);
+ if (rc < 0)
+ goto release;
+
+ rc = snprintf(buf, PAGE_SIZE, "%02x %02x %02x %02x\n", regs[0], regs[1],
+ regs[2], regs[3]);
+release:
+ port_release(dev);
+exit:
+ return (rc);
+}
+
+static ssize_t cy8ctma395_attr_device_config_store(
+ struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u8 regs[4];
+ ssize_t rc;
+
+ rc = sscanf(buf, "%hhx %hhx %hhx %hhx", ®s[0], ®s[1], ®s[2],
+ ®s[3]);
+ if (rc < 4) {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ rc = port_acquire(dev, NULL, NULL);
+ if (rc < 0)
+ goto exit;
+
+ rc = write_device_config(dev, regs);
+ if (rc < 0)
+ goto release;
+
+ rc = count;
+release:
+ port_release(dev);
+exit:
+ return (rc);
+}
+
+static struct device_attribute cy8ctma395_attr_device_config = {
+ .attr = {
+ .name = "device_config",
+ .mode = S_IRUSR|S_IWUSR,
+ },
+ .show = cy8ctma395_attr_device_config_show,
+ .store = cy8ctma395_attr_device_config_store,
+};
+
+static ssize_t cy8ctma395_attr_flash_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u8 rev;
+ u8 regs[4];
+ int i;
+ u16 sum;
+ u32 id;
+ ssize_t rc;
+ struct program_row row;
+ const struct firmware *fw = NULL;
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ rc = request_ihex_firmware(&fw, buf, dev);
+ if (rc < 0) {
+ dev_err(dev, "error %d requesting firmware %s\n", rc, buf);
+ goto exit;
+ }
+
+ row.dat_rec_off = 0;
+ rc = find_record(dev, (struct ihex_binrec *)fw->data, DATA_RECORD_ADDR,
+ DATA_RECORD_LEN, &row.dat_rec);
+ if (rc < 0)
+ goto release_firmware;
+
+ row.ecc_rec_off = 0;
+ rc = find_record(dev, (struct ihex_binrec *)fw->data, ECC_RECORD_ADDR,
+ ECC_RECORD_LEN, &row.ecc_rec);
+ if (rc < 0)
+ goto release_firmware;
+
+ rc = find_record(dev, (struct ihex_binrec *)fw->data, NVL_RECORD_ADDR,
+ NVL_RECORD_LEN, &row.nvl_rec);
+ if (rc < 0)
+ goto release_firmware;
+
+ rc = port_acquire(dev, &id, &rev);
+ if (rc < 0)
+ goto release_firmware;
+
+ dev_info(dev, "jtag id=%08x revision=%08x\n", id, rev);
+
+ /* erase all */
+ {
+ struct addr_data_pair seq[] = {
+ {0x00004720, 0x000000B6},
+ {0x00004720, 0x000000DC},
+ {0x00004720, 0x00000009},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto port_release;
+ }
+
+ rc = poll_status_reg(dev, 0x2, "erase all");
+ if (rc < 0)
+ goto port_release;
+
+ /* read_device config */
+ rc = read_device_config(dev, regs);
+ if (rc < 0)
+ goto port_release;
+
+ /* check if we need to flash device_config by comparing */
+ /* the NVL bytes from target device with those in hex file */
+ if (memcmp(regs, row.nvl_rec->data, NVL_RECORD_LEN) != 0) {
+ rc = write_device_config(dev, (u8 *) row.nvl_rec->data);
+ if (rc < 0)
+ goto port_release;
+ }
+
+ /* program */
+ for (i = 0; i < 2; i++) {
+ struct addr_data_pair seq[] = {
+ {0x00004720, 0x000000B6},
+ {0x00004720, 0x000000E1},
+ {0x00004720, 0x0000000E},
+ {0x00004720, 0x00000003},
+ };
+
+ rc = apacc_addr_data_write_seq(dev, seq, ARRAY_SIZE(seq));
+ if (rc < 0)
+ goto port_release;
+
+ rc = poll_status_reg(dev, 0x1, "temperature data");
+ if (rc < 0)
+ goto port_release;
+
+ rc = apacc_addr_write_data_read(dev, 0x00004720, row.temp, 2);
+ if (rc < 0)
+ goto port_release;
+
+ rc = poll_status_reg(dev, 0x2, "idle");
+ if (rc < 0)
+ goto port_release;
+ }
+
+ dev_info(dev, "temperature sign=%08x magnitude=%08x\n", row.temp[0],
+ row.temp[1]);
+
+ dev_dbg(dev, "dpacc dp config write [%08x]\n", 0x00000004);
+ rc = swd_write(dev, DPACC_DP_CONFIG_WRITE, 0x00000004);
+ if (rc < 0)
+ goto port_release;
+
+ row.ecc_enabled = !!(regs[3] & DEVICE_CONFIG_ECCEN);
+ dev_info(dev, "ecc is %s\n", row.ecc_enabled ? "enabled" : "disabled");
+
+ for (i = 0; i < pdat->nr_blocks;) {
+ row.sram_addr = 0x000;
+ row.nr = i++;
+ row.phub_ch_status_addr = 0x00007018;
+ row.phub_ch_status_data = 0x00000000;
+ row.phub_ch_basic_cfg_addr = 0x00007010;
+ row.phub_cfgmem_cfg0_addr = 0x00007600;
+ row.phub_cfgmem_cfg1_addr = 0x00007604;
+ row.phub_tdmem_orig_td0_addr = 0x00007800;
+ row.phub_tdmem_orig_td1_addr = 0x00007804;
+ row.phub_ch_action_addr = 0x00007014;
+ rc = program_row(dev, &row);
+ if (rc < 0)
+ goto port_release;
+
+ row.sram_addr = 0x200;
+ row.nr = i++;
+ row.phub_ch_status_addr = 0x00007028;
+ row.phub_ch_status_data = 0x00000100;
+ row.phub_ch_basic_cfg_addr = 0x00007020;
+ row.phub_cfgmem_cfg0_addr = 0x00007608;
+ row.phub_cfgmem_cfg1_addr = 0x0000760C;
+ row.phub_tdmem_orig_td0_addr = 0x00007808;
+ row.phub_tdmem_orig_td1_addr = 0x0000780C;
+ row.phub_ch_action_addr = 0x00007024;
+ rc = program_row(dev, &row);
+ if (rc < 0)
+ goto port_release;
+ }
+
+ rc = poll_status_reg(dev, 0x2, "idle");
+ if (rc < 0)
+ goto port_release;
+
+ rc = checksum(dev, &sum);
+ if (rc < 0)
+ goto port_release;
+
+ dev_info(dev, "checksum %04x\n", sum);
+ rc = count;
+port_release:
+ port_release(dev);
+release_firmware:
+ release_firmware(fw);
+exit:
+ return (rc);
+}
+
+static struct device_attribute cy8ctma395_attr_flash = {
+ .attr = {
+ .name = "flash",
+ .mode = S_IWUSR,
+ },
+ .store = cy8ctma395_attr_flash_store,
+};
+
+static ssize_t cy8ctma395_attr_vdd_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ pdat->vdd_enable(!!simple_strtoul(buf, NULL, 10));
+
+ return (count);
+}
+
+static struct device_attribute cy8ctma395_attr_vdd = {
+ .attr = {
+ .name = "vdd",
+ .mode = S_IWUSR,
+ },
+ .store = cy8ctma395_attr_vdd_store,
+};
+
+static void cy8ctma395_xres_assert(struct cy8ctma395_platform_data *pdat, int assert)
+{
+ if (assert) {
+ gpio_set_value(pdat->xres, 0);
+ udelay(pdat->xres_us);
+ }
+ else
+ gpio_set_value(pdat->xres, 1);
+
+}
+
+static ssize_t cy8ctma395_attr_xres_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int assert = !!simple_strtoul(buf, NULL, 10);
+ struct cy8ctma395_platform_data *pdat = dev->platform_data;
+
+ cy8ctma395_xres_assert(pdat, assert);
+
+ return (count);
+}
+
+static struct device_attribute cy8ctma395_attr_xres = {
+ .attr = {
+ .name = "xres",
+ .mode = S_IWUGO,
+ },
+ .store = cy8ctma395_attr_xres_store,
+};
+
+static int cy8ctma395_device_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct cy8ctma395_device_data *dat;
+ struct cy8ctma395_platform_data *pdat = pdev->dev.platform_data;
+
+ if (!pdat) {
+ rc = -ENODEV;
+ goto failed;
+ }
+
+ dat = kzalloc(sizeof(*dat), 0);
+ if (!dat) {
+ rc = -ENOMEM;
+ goto failed;
+ }
+
+ dev_set_drvdata(&pdev->dev, dat);
+
+ rc = device_create_file(&pdev->dev, &cy8ctma395_attr_checksum);
+ if (rc < 0)
+ goto attr_checksum_failed;
+
+ rc = device_create_file(&pdev->dev, &cy8ctma395_attr_flash);
+ if (rc < 0)
+ goto attr_flash_failed;
+
+ rc = device_create_file(&pdev->dev, &cy8ctma395_attr_id);
+ if (rc < 0)
+ goto attr_id_failed;
+
+ rc = device_create_file(&pdev->dev, &cy8ctma395_attr_device_config);
+ if (rc < 0)
+ goto attr_device_config_failed;
+
+ rc = device_create_file(&pdev->dev, &cy8ctma395_attr_xres);
+ if (rc < 0)
+ goto attr_xres_failed;
+
+ if (pdat->vdd_enable) {
+ cy8ctma395_xres_assert(pdat, 1);
+ pdat->vdd_enable(1);
+ cy8ctma395_xres_assert(pdat, 0);
+
+ rc = device_create_file(&pdev->dev, &cy8ctma395_attr_vdd);
+ if (rc < 0)
+ goto attr_vdd_failed;
+ }
+
+ rc = 0;
+ goto exit;
+
+attr_vdd_failed:
+ pdat->vdd_enable(0);
+
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_xres);
+attr_xres_failed:
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_device_config);
+attr_device_config_failed:
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_id);
+attr_id_failed:
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_flash);
+attr_flash_failed:
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_checksum);
+attr_checksum_failed:
+ kfree(dat);
+failed:
+ dev_err(&pdev->dev, "probe failed with %d\n", rc);
+exit:
+ return (rc);
+}
+
+static int cy8ctma395_device_remove(struct platform_device *pdev)
+{
+ struct cy8ctma395_device_data *dat = dev_get_drvdata(&pdev->dev);
+ struct cy8ctma395_platform_data *pdat = pdev->dev.platform_data;
+
+ if (pdat->vdd_enable)
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_vdd);
+
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_xres);
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_device_config);
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_id);
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_flash);
+ device_remove_file(&pdev->dev, &cy8ctma395_attr_checksum);
+ kfree(dat);
+
+ return (0);
+}
+
+static struct platform_driver cy8ctma395_driver = {
+ .driver = {
+ .name = CY8CTMA395_DRIVER,
+ },
+ .probe = cy8ctma395_device_probe,
+ .remove = __devexit_p(cy8ctma395_device_remove),
+};
+
+static int __init cy8ctma395_module_init(void)
+{
+ int rc;
+
+ rc = platform_driver_register(&cy8ctma395_driver);
+
+ return (rc);
+}
+
+static void __exit cy8ctma395_module_exit(void)
+{
+ platform_driver_unregister(&cy8ctma395_driver);
+}
+
+module_init(cy8ctma395_module_init);
+module_exit(cy8ctma395_module_exit);
+
+MODULE_DESCRIPTION("cy8ctma395 driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/cy8ctma395.h b/include/linux/cy8ctma395.h
new file mode 100644
index 0000000..2ab4afa
--- /dev/null
+++ b/include/linux/cy8ctma395.h
@@ -0,0 +1,16 @@
+#define CY8CTMA395_DEVICE "cy8ctma395"
+#define CY8CTMA395_DRIVER "cy8ctma395"
+
+struct cy8ctma395_platform_data {
+ int (*swdck_request)(int request);
+ int (*swdio_request)(int request);
+ void (*vdd_enable)(int enable);
+ unsigned xres;
+ unsigned long xres_us;
+ unsigned swdck;
+ unsigned swdio;
+ int swd_wait_retries;
+ int port_acquire_retries;
+ int status_reg_timeout_ms;
+ int nr_blocks;
+};