fp@2589: /** fp@2589: Network Driver for Beckhoff CCAT communication controller fp@2589: Copyright (C) 2014 Beckhoff Automation GmbH fp@2589: Author: Patrick Bruenn fp@2589: fp@2589: This program is free software; you can redistribute it and/or modify fp@2589: it under the terms of the GNU General Public License as published by fp@2589: the Free Software Foundation; either version 2 of the License, or fp@2589: (at your option) any later version. fp@2589: fp@2589: This program is distributed in the hope that it will be useful, fp@2589: but WITHOUT ANY WARRANTY; without even the implied warranty of fp@2589: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fp@2589: GNU General Public License for more details. fp@2589: fp@2589: You should have received a copy of the GNU General Public License along fp@2589: with this program; if not, write to the Free Software Foundation, Inc., fp@2589: 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. fp@2589: */ fp@2589: fp@2589: #include fp@2589: #include fp@2589: #include fp@2589: #include fp@2589: #include fp@2589: #include "module.h" fp@2589: #include "update.h" fp@2589: fp@2589: #define CCAT_DATA_IN_4 0x038 fp@2589: #define CCAT_DATA_IN_N 0x7F0 fp@2589: #define CCAT_DATA_OUT_4 0x030 fp@2589: #define CCAT_DATA_BLOCK_SIZE (size_t)((CCAT_DATA_IN_N - CCAT_DATA_IN_4)/8) fp@2589: #define CCAT_WRITE_BLOCK_SIZE 128 fp@2589: #define CCAT_FLASH_SIZE (size_t)0xE0000 fp@2589: fp@2589: /** FUNCTION_NAME CMD, CLOCKS */ fp@2589: #define CCAT_BULK_ERASE 0xE3, 8 fp@2589: #define CCAT_GET_PROM_ID 0xD5, 40 fp@2589: #define CCAT_READ_FLASH 0xC0, 32 fp@2589: #define CCAT_READ_STATUS 0xA0, 16 fp@2589: #define CCAT_WRITE_ENABLE 0x60, 8 fp@2589: #define CCAT_WRITE_FLASH 0x40, 32 fp@2589: fp@2589: /* from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits */ fp@2589: #define SWAP_BITS(B) \ fp@2589: ((((B) * 0x0802LU & 0x22110LU) | ((B) * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16) fp@2589: fp@2589: /** fp@2589: * struct update_buffer - keep track of a CCAT FPGA update fp@2589: * @update: pointer to a valid ccat_update object fp@2589: * @data: buffer used for write operations fp@2589: * @size: number of bytes written to the data buffer, if 0 on ccat_update_release() no data will be written to FPGA fp@2589: */ fp@2589: struct update_buffer { fp@2589: struct ccat_update *update; fp@2589: char data[CCAT_FLASH_SIZE]; fp@2589: size_t size; fp@2589: }; fp@2589: fp@2589: /** fp@2589: * wait_until_busy_reset() - wait until the busy flag was reset fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: */ fp@2589: static inline void wait_until_busy_reset(void __iomem * const ioaddr) fp@2589: { fp@2589: wmb(); fp@2589: while (ioread8(ioaddr + 1)) { fp@2589: schedule(); fp@2589: } fp@2589: } fp@2589: fp@2589: /** fp@2589: * __ccat_update_cmd() - Helper to issue a FPGA flash command fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * @cmd: the command identifier fp@2589: * @clocks: the number of clocks associated with the specified command fp@2589: * fp@2589: * no write memory barrier is called and the busy flag is not evaluated fp@2589: */ fp@2589: static inline void __ccat_update_cmd(void __iomem * const ioaddr, u8 cmd, fp@2589: u16 clocks) fp@2589: { fp@2589: iowrite8((0xff00 & clocks) >> 8, ioaddr); fp@2589: iowrite8(0x00ff & clocks, ioaddr + 0x8); fp@2589: iowrite8(cmd, ioaddr + 0x10); fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_update_cmd() - Helper to issue a FPGA flash command fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * @cmd: the command identifier fp@2589: * @clocks: the number of clocks associated with the specified command fp@2589: * fp@2589: * Triggers a full flash command cycle with write memory barrier and fp@2589: * command activate. This call blocks until the busy flag is reset. fp@2589: */ fp@2589: static inline void ccat_update_cmd(void __iomem * const ioaddr, u8 cmd, fp@2589: u16 clocks) fp@2589: { fp@2589: __ccat_update_cmd(ioaddr, cmd, clocks); fp@2589: wmb(); fp@2589: iowrite8(0xff, ioaddr + 0x7f8); fp@2589: wait_until_busy_reset(ioaddr); fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_update_cmd_addr() - Helper to issue a FPGA flash command with address parameter fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * @cmd: the command identifier fp@2589: * @clocks: the number of clocks associated with the specified command fp@2589: * @addr: 24 bit address associated with the specified command fp@2589: * fp@2589: * Triggers a full flash command cycle with write memory barrier and fp@2589: * command activate. This call blocks until the busy flag is reset. fp@2589: */ fp@2589: static inline void ccat_update_cmd_addr(void __iomem * const ioaddr, fp@2589: u8 cmd, u16 clocks, u32 addr) fp@2589: { fp@2589: const u8 addr_0 = SWAP_BITS(addr & 0xff); fp@2589: const u8 addr_1 = SWAP_BITS((addr & 0xff00) >> 8); fp@2589: const u8 addr_2 = SWAP_BITS((addr & 0xff0000) >> 16); fp@2589: fp@2589: __ccat_update_cmd(ioaddr, cmd, clocks); fp@2589: iowrite8(addr_2, ioaddr + 0x18); fp@2589: iowrite8(addr_1, ioaddr + 0x20); fp@2589: iowrite8(addr_0, ioaddr + 0x28); fp@2589: wmb(); fp@2589: iowrite8(0xff, ioaddr + 0x7f8); fp@2589: wait_until_busy_reset(ioaddr); fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_get_status() - Read CCAT Update status fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * fp@2589: * Return: the current status of the CCAT Update function fp@2589: */ fp@2589: static u8 ccat_get_status(void __iomem * const ioaddr) fp@2589: { fp@2589: ccat_update_cmd(ioaddr, CCAT_READ_STATUS); fp@2589: return ioread8(ioaddr + 0x20); fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_read_flash_block() - Read a block of CCAT configuration data from flash fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * @addr: 24 bit address of the block to read fp@2589: * @len: number of bytes to read from this block, len <= CCAT_DATA_BLOCK_SIZE fp@2589: * @buf: output buffer in user space fp@2589: * fp@2589: * Copies one block of configuration data from the CCAT FPGA's flash to fp@2589: * the user space buffer. fp@2589: * Note that the size of the FPGA's firmware is not known exactly so it fp@2589: * is very possible that the overall buffer ends with a lot of 0xff. fp@2589: * fp@2589: * Return: the number of bytes copied fp@2589: */ fp@2589: static int ccat_read_flash_block(void __iomem * const ioaddr, fp@2589: const u32 addr, const u16 len, fp@2589: char __user * const buf) fp@2589: { fp@2589: u16 i; fp@2589: const u16 clocks = 8 * len; fp@2589: fp@2589: ccat_update_cmd_addr(ioaddr, CCAT_READ_FLASH + clocks, addr); fp@2589: for (i = 0; i < len; i++) { fp@2589: put_user(ioread8(ioaddr + CCAT_DATA_IN_4 + 8 * i), buf + i); fp@2589: } fp@2589: return len; fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_read_flash() - Read a chunk of CCAT configuration data from flash fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * @buf: output buffer in user space fp@2589: * @len: number of bytes to read fp@2589: * @off: offset in the configuration data fp@2589: * fp@2589: * Copies multiple blocks of configuration data from the CCAT FPGA's fp@2589: * flash to the user space buffer. fp@2589: * fp@2589: * Return: the number of bytes copied fp@2589: */ fp@2589: static int ccat_read_flash(void __iomem * const ioaddr, char __user * buf, fp@2589: u32 len, loff_t * off) fp@2589: { fp@2589: const loff_t start = *off; fp@2589: fp@2589: while (len > CCAT_DATA_BLOCK_SIZE) { fp@2589: *off += fp@2589: ccat_read_flash_block(ioaddr, *off, CCAT_DATA_BLOCK_SIZE, fp@2589: buf); fp@2589: buf += CCAT_DATA_BLOCK_SIZE; fp@2589: len -= CCAT_DATA_BLOCK_SIZE; fp@2589: } fp@2589: *off += ccat_read_flash_block(ioaddr, *off, len, buf); fp@2589: return *off - start; fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_wait_status_cleared() - wait until CCAT status is cleared fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * fp@2589: * Blocks until bit 7 of the CCAT Update status is reset fp@2589: */ fp@2589: static void ccat_wait_status_cleared(void __iomem * const ioaddr) fp@2589: { fp@2589: u8 status; fp@2589: fp@2589: do { fp@2589: status = ccat_get_status(ioaddr); fp@2589: } while (status & (1 << 7)); fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_write_flash_block() - Write a block of CCAT configuration data to flash fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * @addr: 24 bit start address in the CCAT FPGA's flash fp@2589: * @len: number of bytes to write in this block, len <= CCAT_WRITE_BLOCK_SIZE fp@2589: * @buf: input buffer fp@2589: * fp@2589: * Copies one block of configuration data to the CCAT FPGA's flash fp@2589: * fp@2589: * Return: the number of bytes copied fp@2589: */ fp@2589: static int ccat_write_flash_block(void __iomem * const ioaddr, fp@2589: const u32 addr, const u16 len, fp@2589: const char *const buf) fp@2589: { fp@2589: const u16 clocks = 8 * len; fp@2589: u16 i; fp@2589: fp@2589: ccat_update_cmd(ioaddr, CCAT_WRITE_ENABLE); fp@2589: for (i = 0; i < len; i++) { fp@2589: iowrite8(buf[i], ioaddr + CCAT_DATA_OUT_4 + 8 * i); fp@2589: } fp@2589: ccat_update_cmd_addr(ioaddr, CCAT_WRITE_FLASH + clocks, addr); fp@2589: ccat_wait_status_cleared(ioaddr); fp@2589: return len; fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_write_flash() - Write a new CCAT configuration to FPGA's flash fp@2589: * @update: a CCAT Update buffer containing the new FPGA configuration fp@2589: */ fp@2589: static void ccat_write_flash(const struct update_buffer *const update) fp@2589: { fp@2589: const char *buf = update->data; fp@2589: u32 off = 0; fp@2589: size_t len = update->size; fp@2589: fp@2589: while (len > CCAT_WRITE_BLOCK_SIZE) { fp@2589: ccat_write_flash_block(update->update->ioaddr, off, fp@2589: (u16) CCAT_WRITE_BLOCK_SIZE, buf); fp@2589: off += CCAT_WRITE_BLOCK_SIZE; fp@2589: buf += CCAT_WRITE_BLOCK_SIZE; fp@2589: len -= CCAT_WRITE_BLOCK_SIZE; fp@2589: } fp@2589: ccat_write_flash_block(update->update->ioaddr, off, (u16) len, buf); fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_update_destroy() - Cleanup the CCAT Update function fp@2589: * @ref: pointer to a struct kref embedded into a struct ccat_update, which we intend to destroy fp@2589: * fp@2589: * Retrieves the parent struct ccat_update and destroys it. fp@2589: */ fp@2589: static void ccat_update_destroy(struct kref *ref) fp@2589: { fp@2589: struct ccat_update *update = fp@2589: container_of(ref, struct ccat_update, refcount); fp@2589: fp@2589: cdev_del(&update->cdev); fp@2589: device_destroy(update->class, update->dev); fp@2589: class_destroy(update->class); fp@2589: unregister_chrdev_region(update->dev, 1); fp@2589: kfree(update); fp@2589: pr_debug("%s(): done\n", __FUNCTION__); fp@2589: } fp@2589: fp@2589: static int ccat_update_open(struct inode *const i, struct file *const f) fp@2589: { fp@2589: struct ccat_update *update = fp@2589: container_of(i->i_cdev, struct ccat_update, cdev); fp@2589: struct update_buffer *buf; fp@2589: fp@2589: kref_get(&update->refcount); fp@2589: if (atomic_read(&update->refcount.refcount) > 2) { fp@2589: kref_put(&update->refcount, ccat_update_destroy); fp@2589: return -EBUSY; fp@2589: } fp@2589: fp@2589: buf = kzalloc(sizeof(*buf), GFP_KERNEL); fp@2589: if (!buf) { fp@2589: kref_put(&update->refcount, ccat_update_destroy); fp@2589: return -ENOMEM; fp@2589: } fp@2589: fp@2589: buf->update = update; fp@2589: f->private_data = buf; fp@2589: return 0; fp@2589: } fp@2589: fp@2589: static int ccat_update_release(struct inode *const i, struct file *const f) fp@2589: { fp@2589: const struct update_buffer *const buf = f->private_data; fp@2589: struct ccat_update *const update = buf->update; fp@2589: fp@2589: if (buf->size > 0) { fp@2589: ccat_update_cmd(update->ioaddr, CCAT_WRITE_ENABLE); fp@2589: ccat_update_cmd(update->ioaddr, CCAT_BULK_ERASE); fp@2589: ccat_wait_status_cleared(update->ioaddr); fp@2589: ccat_write_flash(buf); fp@2589: } fp@2589: kfree(f->private_data); fp@2589: kref_put(&update->refcount, ccat_update_destroy); fp@2589: return 0; fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_update_read() - Read CCAT configuration data from flash fp@2589: * @f: file handle previously initialized with ccat_update_open() fp@2589: * @buf: buffer in user space provided for our data fp@2589: * @len: length of the user space buffer fp@2589: * @off: current offset of our file operation fp@2589: * fp@2589: * Copies data from the CCAT FPGA's configuration flash to user space. fp@2589: * Note that the size of the FPGA's firmware is not known exactly so it fp@2589: * is very possible that the overall buffer ends with a lot of 0xff. fp@2589: * fp@2589: * Return: the number of bytes written, or 0 if EOF reached fp@2589: */ fp@2589: static ssize_t ccat_update_read(struct file *const f, char __user * buf, fp@2589: size_t len, loff_t * off) fp@2589: { fp@2589: struct update_buffer *update = f->private_data; fp@2589: fp@2589: if (!buf || !off) { fp@2589: return -EINVAL; fp@2589: } fp@2589: if (*off >= CCAT_FLASH_SIZE) { fp@2589: return 0; fp@2589: } fp@2589: if (*off + len >= CCAT_FLASH_SIZE) { fp@2589: len = CCAT_FLASH_SIZE - *off; fp@2589: } fp@2589: return ccat_read_flash(update->update->ioaddr, buf, len, off); fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_update_write() - Write data to the CCAT FPGA's configuration flash fp@2589: * @f: file handle previously initialized with ccat_update_open() fp@2589: * @buf: buffer in user space providing the new configuration data (from *.rbf) fp@2589: * @len: length of the user space buffer fp@2589: * @off: current offset in the configuration data fp@2589: * fp@2589: * Copies data from user space (possibly a *.rbf) to the CCAT FPGA's fp@2589: * configuration flash to user space. fp@2589: * fp@2589: * Return: the number of bytes written, or 0 if flash end is reached fp@2589: */ fp@2589: fp@2589: static ssize_t ccat_update_write(struct file *const f, const char __user * buf, fp@2589: size_t len, loff_t * off) fp@2589: { fp@2589: struct update_buffer *const update = f->private_data; fp@2589: fp@2589: if (*off + len > sizeof(update->data)) fp@2589: return 0; fp@2589: fp@2589: if (copy_from_user(update->data + *off, buf, len)) { fp@2589: return -EFAULT; fp@2589: } fp@2589: fp@2589: *off += len; fp@2589: update->size = *off; fp@2589: return len; fp@2589: } fp@2589: fp@2589: static struct file_operations update_ops = { fp@2589: .owner = THIS_MODULE, fp@2589: .open = ccat_update_open, fp@2589: .release = ccat_update_release, fp@2589: .read = ccat_update_read, fp@2589: .write = ccat_update_write, fp@2589: }; fp@2589: fp@2589: /** fp@2589: * ccat_get_prom_id() - Read CCAT PROM ID fp@2589: * @ioaddr: address of the CCAT Update function in PCI config space fp@2589: * fp@2589: * Return: the CCAT FPGA's PROM identifier fp@2589: */ fp@2589: u8 ccat_get_prom_id(void __iomem * const ioaddr) fp@2589: { fp@2589: ccat_update_cmd(ioaddr, CCAT_GET_PROM_ID); fp@2589: return ioread8(ioaddr + 0x38); fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_update_init() - Initialize the CCAT Update function fp@2589: */ fp@2589: struct ccat_update *ccat_update_init(const struct ccat_device *const ccatdev, fp@2589: void __iomem * const addr) fp@2589: { fp@2589: struct ccat_update *const update = kzalloc(sizeof(*update), GFP_KERNEL); fp@2589: fp@2589: if (!update) { fp@2589: return NULL; fp@2589: } fp@2589: kref_init(&update->refcount); fp@2589: update->ioaddr = ccatdev->bar[0].ioaddr + ioread32(addr + 0x8); fp@2589: memcpy_fromio(&update->info, addr, sizeof(update->info)); fp@2589: fp@2589: if (0x00 != update->info.rev) { fp@2589: pr_warn("CCAT Update rev. %d not supported\n", fp@2589: update->info.rev); fp@2589: goto cleanup; fp@2589: } fp@2589: fp@2589: if (alloc_chrdev_region(&update->dev, 0, 1, KBUILD_MODNAME)) { fp@2589: pr_warn("alloc_chrdev_region() failed\n"); fp@2589: goto cleanup; fp@2589: } fp@2589: fp@2589: update->class = class_create(THIS_MODULE, "ccat_update"); fp@2589: if (NULL == update->class) { fp@2589: pr_warn("Create device class failed\n"); fp@2589: goto cleanup; fp@2589: } fp@2589: fp@2589: if (NULL == fp@2589: device_create(update->class, NULL, update->dev, NULL, fp@2589: "ccat_update")) { fp@2589: pr_warn("device_create() failed\n"); fp@2589: goto cleanup; fp@2589: } fp@2589: fp@2589: cdev_init(&update->cdev, &update_ops); fp@2589: update->cdev.owner = THIS_MODULE; fp@2589: update->cdev.ops = &update_ops; fp@2589: if (cdev_add(&update->cdev, update->dev, 1)) { fp@2589: pr_warn("add update device failed\n"); fp@2589: goto cleanup; fp@2589: } fp@2589: return update; fp@2589: cleanup: fp@2589: kref_put(&update->refcount, ccat_update_destroy); fp@2589: return NULL; fp@2589: } fp@2589: fp@2589: /** fp@2589: * ccat_update_remove() - Prepare the CCAT Update function for removal fp@2589: */ fp@2589: void ccat_update_remove(struct ccat_update *update) fp@2589: { fp@2589: kref_put(&update->refcount, ccat_update_destroy); fp@2589: pr_debug("%s(): done\n", __FUNCTION__); fp@2589: }