diff --git a/Kconfig b/Kconfig old mode 100755 new mode 100644 index 9f14796..f6c62f0 --- a/Kconfig +++ b/Kconfig @@ -1,13 +1,13 @@ # # MCP2210 # - + config MCP2210 - tristate "MCP2210 usb to spi master" - depends on HID && SPI - help - If you say yes here you get support for the Microchip - MCP2210 usb to spi master. + tristate "MCP2210 usb to spi master" + depends on HID && SPI + help + If you say yes here you get support for the Microchip + MCP2210 usb to spi master. - To compile this driver as a module, choose M here: the - module will be called mcp2210. + To compile this driver as a module, choose M here: the + module will be called mcp2210. diff --git a/Makefile b/Makefile old mode 100755 new mode 100644 index 5b09647..2ce652d --- a/Makefile +++ b/Makefile @@ -2,6 +2,35 @@ # Makefile for mcp2210 # -mcp2210-objs := mcp2210-core.o mcp2210-spi.o +EXTRA_CFLAGS += -Werror -Wall -obj-$(CONFIG_MCP2210) += mcp2210.o +# To build modules outside of the kernel tree, we run "make" +# in the kernel source tree; the Makefile these then includes this +# Makefile once again. +# This conditional selects whether we are being included from the +# kernel Makefile or not. +ifeq ($(KERNELRELEASE),) + # Assume the source tree is where the running kernel was built + # You should set KERNELDIR in the environment if it's elsewhere + KERNELDIR ?= /lib/modules/$(shell uname -r)/build + # The current directory is passed to sub-makes as argument + PWD := $(shell pwd) + +modules: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +modules_install: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c *.mod .tmp_versions modules.order Module.symvers + +.PHONY: modules modules_install clean + +else + # called from kernel build system: just declare what our modules are + CONFIG_MCP2210 ?= m + mcp2210-objs := mcp2210-core.o mcp2210-spi.o + + obj-$(CONFIG_MCP2210) += mcp2210.o +endif diff --git a/mcp2210-core.c b/mcp2210-core.c old mode 100755 new mode 100644 index efc1475..32ade26 --- a/mcp2210-core.c +++ b/mcp2210-core.c @@ -1,407 +1,427 @@ -/* - * MCP 2210 driver for linux - * - * Copyright (c) 2013 Mathew King for Trilithic, Inc - * - */ - -/* - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - */ - -#include -#include "mcp2210.h" -#include "../../hid/usbhid/usbhid.h" - -struct info_command { - int id; - int count; - int response_count; - int done; -}; - -int mcp2210_spi_probe(struct mcp2210_device *dev); -void mcp2210_spi_remove(struct mcp2210_device *dev); -static void mcp2210_output_command_atomic(struct mcp2210_device *dev); - -int next_mcp2210_info_command(void *data, u8 *request) { - struct info_command *cmd_data = data; - - if(cmd_data->done) { - kfree(cmd_data); - return 0; - } - - if(cmd_data->count >= 10) { - return 0; - } - - cmd_data->count++; - request[0] = 0x41; - - return 1; -} - -void next_mcp2210_info_command_data(void *data, u8 *response) { - struct info_command *cmd_data = data; - - cmd_data->response_count++; - if(cmd_data->response_count >= 10) { - cmd_data->done = 1; - printk("next_mcp2210_info_command done %d: %d\n", cmd_data->id, cmd_data->response_count); - } - - if(response[0] != 0x41) { - printk("wrong command \n"); - } -} - -void mcp2210_info_command_interrupted(void *data) { - - printk("mcp2210_info_command_interrupted\n"); -} - -static void mcp2210_process_commnds(struct mcp2210_device *dev) { - int ret = 0; - //printk("mcp2210_process_commnds\n"); - - // Get the next request from the current command - if(dev->current_command) { - mcp2210_output_command_atomic(dev); - } - - if(!dev->current_command && !list_empty(&dev->command_list)) { - dev->current_command = list_first_entry(&dev->command_list, struct mcp2210_command, node); - mcp2210_process_commnds(dev); - } -} - -static void mcp2210_output_command(struct work_struct *work) { - struct mcp2210_device *dev = container_of(work, struct mcp2210_device, command_work); -} - -static void mcp2210_output_command_atomic(struct mcp2210_device *dev) { - struct hid_report *report; - struct hid_field *field; - struct mcp2210_request_list *list_node; - int cnt, pending; - - int num; - //printk("mcp2210_output_command\n"); - - if(!dev->current_command) { - printk("Error: no current command\n"); - return; - } - - memset(dev->requeust_buffer, 0, MCP2210_BUFFER_SIZE); - num = dev->current_command->next_request(dev->current_command->data, dev->requeust_buffer); - while(num > 0) { - dev->current_command->requests_pending++; - - report = kzalloc(sizeof(struct hid_report), GFP_ATOMIC); - if(!report) - goto err_free_report; - - field = kzalloc(sizeof(struct hid_field), GFP_ATOMIC); - if(!field) - goto err_free_field; - - field->value = kzalloc((MCP2210_BUFFER_SIZE - 1) * sizeof(__s32), GFP_ATOMIC); - if(!field->value) - goto err_free_value; - - list_node = kzalloc(sizeof(struct mcp2210_request_list), GFP_ATOMIC); - if(!list_node) - goto err_free_node; - - report->id = dev->requeust_buffer[0]; - report->type = HID_OUTPUT_REPORT; - report->field[0] = field; - report->maxfield = 1; - report->size = (MCP2210_BUFFER_SIZE - 1) << 3; - report->device = dev->hid; - - field->logical_minimum = 0; - field->logical_maximum = 255; - field->report_count = MCP2210_BUFFER_SIZE - 1; - field->report_size = 8; - - list_node->report = report; - list_add_tail(&list_node->node, &dev->current_command->request_list); - - for(cnt = 0; cnt < (MCP2210_BUFFER_SIZE - 1); cnt++) { - field->value[cnt] = dev->requeust_buffer[cnt + 1]; - } - - usbhid_submit_report(dev->hid, report, USB_DIR_OUT); - //dev->hid->hiddev_report_event(dev->hid, report); - //dev->hid->hid_output_raw_report(dev->hid, dev->requeust_buffer, MCP2210_BUFFER_SIZE, HID_OUTPUT_REPORT); - - if(dev->current_command->requests_pending >= 63) - break; - - memset(dev->requeust_buffer, 0, MCP2210_BUFFER_SIZE); - num = dev->current_command->next_request(dev->current_command->data, dev->requeust_buffer); - } - - //printk("Sending %d commands\n", dev->current_command->requests_pending); - if(dev->current_command->requests_pending == 0) { - // The command has finished so remove it - list_del(&dev->current_command->node); - - kfree(dev->current_command); - dev->current_command = NULL; - mcp2210_process_commnds(dev); - } - - return; - -err_free_node: - kfree(list_node); -err_free_value: - kfree(field->value); -err_free_field: - kfree(field); -err_free_report: - kfree(report); -err: - pending = dev->current_command->requests_pending; - if(pending == 0) { - if(dev->current_command->interrupted) - dev->current_command->interrupted(dev->current_command->data); - - list_del(&dev->current_command->node); - - kfree(dev->current_command); - dev->current_command = NULL; - mcp2210_process_commnds(dev); - } -} - -int mcp2210_add_command(struct mcp2210_device *dev, void *cmd_data, - int (*next_request)(void *command_data, u8 *request), - void (*data_received)(void *command_data, u8 *response), - void (*interrupted)(void *command_data)) { - - struct mcp2210_command *cmd; - - spin_lock(&dev->command_lock); - //printk("mcp2210_add_command lock\n"); - - cmd = kzalloc(sizeof(struct mcp2210_command), GFP_KERNEL); - if (!cmd) - return -ENOMEM; - - cmd->data = cmd_data; - cmd->dev = dev; - cmd->next_request = next_request; - cmd->data_received = data_received; - cmd->interrupted = interrupted; - - INIT_LIST_HEAD(&cmd->request_list); - - list_add_tail(&cmd->node, &dev->command_list); - - - if(!dev->current_command) { - mcp2210_process_commnds(dev); - } - - //printk("mcp2210_add_command unlock\n"); - spin_unlock(&dev->command_lock); - - return 0; -} - -static void mcp2210_free_request(struct mcp2210_request_list *list_node) { - kfree(list_node->report->field[0]->value); - kfree(list_node->report->field[0]); - kfree(list_node->report); - list_del(&list_node->node); - kfree(list_node); -} - -// Called when data is received from the mcp2210 -static int mcp2210_raw_event(struct hid_device *hdev, struct hid_report *report, - u8 *data, int size) -{ - struct mcp2210_device *dev = hid_get_drvdata(hdev); - struct mcp2210_request_list *list_node; - - if(size == MCP2210_BUFFER_SIZE && dev->current_command) { - spin_lock(&dev->command_lock); - //printk("mcp2210_raw_event lock\n"); - list_node = list_first_entry(&dev->current_command->request_list, struct mcp2210_request_list, node); - mcp2210_free_request(list_node); - - dev->current_command->data_received(dev->current_command->data, data); - dev->current_command->requests_pending--; - - mcp2210_process_commnds(dev); - //printk("mcp2210_raw_event unlock\n"); - spin_unlock(&dev->command_lock); - return 1; - } - - return 0; -} - -// Called when a new mcp2210 device is added -static int mcp2210_probe(struct hid_device *hdev, - const struct hid_device_id *id) -{ - int ret; - struct mcp2210_device *dev; - struct info_command *cmd_data; - - dev = kzalloc(sizeof(struct mcp2210_device), GFP_KERNEL); - if (!dev) - return -ENOMEM; - - printk("mcp2210_probe\n"); - - hid_set_drvdata(hdev, dev); - dev->hid = hdev; - INIT_LIST_HEAD(&dev->command_list); - mutex_init(&dev->command_mutex); - spin_lock_init(&dev->command_lock); - INIT_WORK(&dev->command_work, mcp2210_output_command); - - ret = hid_parse(hdev); - if (ret) { - dev_err(&hdev->dev, "parse failed\n"); - goto err_free; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (ret) { - dev_err(&hdev->dev, "hw start failed\n"); - goto err_free; - } - - if (hdev->ll_driver->power) { - ret = hdev->ll_driver->power(hdev, PM_HINT_FULLON); - if (ret < 0) - goto err_free; - } - ret = hdev->ll_driver->open(hdev); - if (ret < 0) { - if (hdev->ll_driver->power) - hdev->ll_driver->power(hdev, PM_HINT_NORMAL); - - goto err_free; - } - - ret = mcp2210_spi_probe(dev); - if(ret < 0) { - goto err_power; - } - - /* - cmd_data = kzalloc(sizeof(struct info_command), GFP_KERNEL); - if (!cmd_data) - return -ENOMEM; - - cmd_data->id = 1; - mcp2210_add_command(dev, cmd_data, next_mcp2210_info_command, next_mcp2210_info_command_data, mcp2210_info_command_interrupted); - - - cmd_data = kzalloc(sizeof(struct info_command), GFP_KERNEL); - if (!cmd_data) - return -ENOMEM; - - cmd_data->id = 2; - mcp2210_add_command(dev, cmd_data, next_mcp2210_info_command, next_mcp2210_info_command_data, mcp2210_info_command_interrupted); - - - cmd_data = kzalloc(sizeof(struct info_command), GFP_KERNEL); - if (!cmd_data) - return -ENOMEM; - - cmd_data->id = 3; - mcp2210_add_command(dev, cmd_data, next_mcp2210_info_command, next_mcp2210_info_command_data, mcp2210_info_command_interrupted); - - */ - return 0; - -err_power: - if (hdev->ll_driver->power) - hdev->ll_driver->power(hdev, PM_HINT_NORMAL); - hdev->ll_driver->close(hdev); -err_free: - kfree(dev); - return ret; -} - -// Called when a new mcp2210 device is removed -static void mcp2210_remove(struct hid_device *hdev) -{ - struct mcp2210_device *dev = hid_get_drvdata(hdev); - struct list_head *pos, *q, *request_pos, *request_q; - struct mcp2210_command *tmp; - - list_for_each_safe(pos, q, &dev->command_list){ - tmp = list_entry(pos, struct mcp2210_command, node); - list_del(pos); - - if(tmp->interrupted) - tmp->interrupted(tmp->data); - - list_for_each_safe(request_pos, request_q, &tmp->request_list) { - mcp2210_free_request(list_entry(request_pos, struct mcp2210_request_list, node)); - } - - kfree(tmp); - } - - mcp2210_spi_remove(dev); - - if (hdev->ll_driver->power) - hdev->ll_driver->power(hdev, PM_HINT_NORMAL); - hdev->ll_driver->close(hdev); - - kfree(dev); - - printk("mcp2210_remove\n"); - hid_hw_stop(hdev); -} - -static const struct hid_device_id mcp2210_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2210), - .driver_data = 0 }, - { } -}; - -static struct hid_driver mcp2210_driver = { - .name = "mcp2210", - .id_table = mcp2210_devices, - .raw_event = mcp2210_raw_event, - .probe = mcp2210_probe, - .remove = mcp2210_remove -}; - -static int __init mcp2210_init(void) -{ - int ret; - printk("mcp2210_init\n"); - - ret = hid_register_driver(&mcp2210_driver); - if (ret) - printk(KERN_ERR "can't register mcp2210 driver\n"); - - return ret; -} - -static void __exit mcp2210_exit(void) -{ - printk("mcp2210_exit\n"); - hid_unregister_driver(&mcp2210_driver); -} - -module_init(mcp2210_init); -module_exit(mcp2210_exit); -MODULE_LICENSE("GPL"); \ No newline at end of file +/* + * MCP 2210 driver for linux + * + * Copyright (c) 2013 Mathew King for Trilithic, Inc + * 2020 Cristian Balint + * + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include +#include + +#include "mcp2210.h" + +struct info_command { + int id; + int count; + int response_count; + int done; + struct mcp2210_dev_cfg *cfg; +}; + +int mcp2210_spi_probe(struct mcp2210_device *dev); +void mcp2210_spi_remove(struct mcp2210_device *dev); +static void mcp2210_output_command_atomic(struct mcp2210_device *dev); + +int next_mcp2210_info_command(void *data, u8 *request) { + struct info_command *cmd_data = data; + // last info command + if(cmd_data->count >= 1) { + return 0; + } + + cmd_data->count++; + request[0] = MCP2210_CMD_GET_SPI_CONFIG; + + return 1; +} + +void next_mcp2210_info_command_response(void *data, u8 *response) { + struct info_command *cmd_data = data; + char idle_cs[10], active_cs[10]; + + cmd_data->response_count++; + // last info command response + if(cmd_data->response_count >= 1) { + cmd_data->done = 1; + + verbose_debug(KERN_DEBUG "next_mcp2210_info_command done %d: %d\n", cmd_data->id, cmd_data->response_count); + print_msg(response, MCP2210_BUFFER_SIZE); + + // parse configuration + cmd_data->cfg->bitrate = *(u32*)&response[4]; + cmd_data->cfg->idle_cs = *(u16*)&response[8]; + cmd_data->cfg->active_cs = *(u16*)&response[10]; + cmd_data->cfg->cs_ds_delay = *(u16*)&response[12]; + cmd_data->cfg->cs_ls_delay = *(u16*)&response[14]; + cmd_data->cfg->byte_delay = *(u16*)&response[16]; + cmd_data->cfg->byte_per_spi = *(u16*)&response[18]; + cmd_data->cfg->spi_mode = response[20]; + + // activate cfg + cmd_data->cfg->active = 1; + + print_binary(idle_cs, *(u16*)&response[8], 9); + print_binary(active_cs, *(u16*)&response[10], 9); + printk(KERN_INFO "MCP2210 SPI settings: bitrate=%i(bit/s) idle_cs=%s active_cs=%s cs_ds_delay=%i(us) cs_ls_delay=%i(us) byte_delay=%i(us) byte_per_spi=%i spi_mode=0x%02x\n", + cmd_data->cfg->bitrate, idle_cs, active_cs, + cmd_data->cfg->cs_ds_delay * 100, + cmd_data->cfg->cs_ls_delay * 100, + cmd_data->cfg->byte_delay * 100, + cmd_data->cfg->byte_per_spi, + cmd_data->cfg->spi_mode); + } + + if(response[0] != MCP2210_CMD_GET_SPI_CONFIG) { + printk(KERN_ERR "wrong command response: 0x%02x\n", response[0]); + } +} + +void mcp2210_info_command_interrupted(void *data) { + + printk(KERN_INFO "mcp2210_info_command_interrupted\n"); +} + +static void mcp2210_process_commands(struct mcp2210_device *dev) { + verbose_debug(KERN_DEBUG "mcp2210_process_commands\n"); + + // Get the next request from the current command + if(dev->current_command) { + mcp2210_output_command_atomic(dev); + } + + if(!dev->current_command && !list_empty(&dev->command_list)) { + dev->current_command = list_first_entry(&dev->command_list, struct mcp2210_command, node); + mcp2210_process_commands(dev); + } +} + +static void mcp2210_output_command(struct work_struct *work) { + //struct mcp2210_device *dev = container_of(work, struct mcp2210_device, command_work); +} + +static void mcp2210_output_command_atomic(struct mcp2210_device *dev) { + struct hid_report *report; + struct hid_field *field; + struct mcp2210_request_list *list_node; + int cnt, pending; + + int num; + verbose_debug(KERN_DEBUG "mcp2210_output_command\n"); + + if(!dev->current_command) { + printk(KERN_ERR "Error: no current command\n"); + return; + } + + memset(dev->requeust_buffer, 0, MCP2210_BUFFER_SIZE); + num = dev->current_command->next_request(dev->current_command->data, dev->requeust_buffer); + while(num > 0) { + dev->current_command->requests_pending++; + + report = kzalloc(sizeof(struct hid_report), GFP_ATOMIC); + if(!report) + goto err_free_report; + + field = kzalloc(sizeof(struct hid_field), GFP_ATOMIC); + if(!field) + goto err_free_field; + + field->value = kzalloc((MCP2210_BUFFER_SIZE - 1) * sizeof(__s32), GFP_ATOMIC); + if(!field->value) + goto err_free_value; + + list_node = kzalloc(sizeof(struct mcp2210_request_list), GFP_ATOMIC); + if(!list_node) + goto err_free_node; + + report->id = dev->requeust_buffer[0]; + report->type = HID_OUTPUT_REPORT; + report->field[0] = field; + report->maxfield = 1; + report->size = (MCP2210_BUFFER_SIZE - 1) << 3; + report->device = dev->hid; + + field->logical_minimum = 0; + field->logical_maximum = 255; + field->report_count = MCP2210_BUFFER_SIZE - 1; + field->report_size = 8; + + list_node->report = report; + list_add_tail(&list_node->node, &dev->current_command->request_list); + + for(cnt = 0; cnt < (MCP2210_BUFFER_SIZE - 1); cnt++) { + field->value[cnt] = dev->requeust_buffer[cnt + 1]; + } + + hid_hw_request(dev->hid, report, HID_REQ_SET_REPORT); + + if(dev->current_command->requests_pending >= 63) + break; + + memset(dev->requeust_buffer, 0, MCP2210_BUFFER_SIZE); + num = dev->current_command->next_request(dev->current_command->data, dev->requeust_buffer); + } + + verbose_debug(KERN_DEBUG "Sending %d commands\n", dev->current_command->requests_pending); + if(dev->current_command->requests_pending == 0) { + // The command has finished so remove it + list_del(&dev->current_command->node); + + kfree(dev->current_command); + dev->current_command = NULL; + mcp2210_process_commands(dev); + } + + return; + +err_free_node: + kfree(list_node); +err_free_value: + kfree(field->value); +err_free_field: + kfree(field); +err_free_report: + kfree(report); + + pending = dev->current_command->requests_pending; + if(pending == 0) { + if(dev->current_command->interrupted) + dev->current_command->interrupted(dev->current_command->data); + list_del(&dev->current_command->node); + + kfree(dev->current_command); + dev->current_command = NULL; + mcp2210_process_commands(dev); + } +} + +int mcp2210_add_command(struct mcp2210_device *dev, void *cmd_data, + int (*next_request)(void *command_data, u8 *request), + void (*data_received)(void *command_data, u8 *response), + void (*interrupted)(void *command_data)) { + + struct mcp2210_command *cmd; + + spin_lock(&dev->command_lock); + verbose_debug(KERN_DEBUG "mcp2210_add_command lock\n"); + + cmd = kzalloc(sizeof(struct mcp2210_command), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->data = cmd_data; + cmd->dev = dev; + cmd->next_request = next_request; + cmd->data_received = data_received; + cmd->interrupted = interrupted; + + INIT_LIST_HEAD(&cmd->request_list); + + list_add_tail(&cmd->node, &dev->command_list); + + + if(!dev->current_command) { + mcp2210_process_commands(dev); + } + + verbose_debug(KERN_DEBUG "mcp2210_add_command unlock\n"); + spin_unlock(&dev->command_lock); + + return 0; +} + +static void mcp2210_free_request(struct mcp2210_request_list *list_node) { + kfree(list_node->report->field[0]->value); + kfree(list_node->report->field[0]); + kfree(list_node->report); + list_del(&list_node->node); + kfree(list_node); +} + +// Called when data is received from the mcp2210 +static int mcp2210_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct mcp2210_device *dev = hid_get_drvdata(hdev); + struct mcp2210_request_list *list_node; + + if(size == MCP2210_BUFFER_SIZE && dev->current_command) { + spin_lock(&dev->command_lock); + verbose_debug(KERN_DEBUG "mcp2210_raw_event lock\n"); + list_node = list_first_entry(&dev->current_command->request_list, struct mcp2210_request_list, node); + mcp2210_free_request(list_node); + + dev->current_command->data_received(dev->current_command->data, data); + dev->current_command->requests_pending--; + + mcp2210_process_commands(dev); + verbose_debug(KERN_DEBUG "mcp2210_raw_event unlock\n"); + spin_unlock(&dev->command_lock); + return 1; + } + + return 0; +} + +// Called when a new mcp2210 device is added +static int mcp2210_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + struct mcp2210_device *dev; + struct mcp2210_dev_cfg *cfg; + struct info_command *cmd_data; + + dev = kzalloc(sizeof(struct mcp2210_device), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + cfg = kzalloc(sizeof(struct mcp2210_dev_cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + cfg->active = 0; + + dev->cfg = cfg; + + verbose_debug(KERN_DEBUG "mcp2210_probe\n"); + + hid_set_drvdata(hdev, dev); + dev->hid = hdev; + INIT_LIST_HEAD(&dev->command_list); + mutex_init(&dev->command_mutex); + spin_lock_init(&dev->command_lock); + INIT_WORK(&dev->command_work, mcp2210_output_command); + + ret = hid_parse(hdev); + if (ret) { + dev_err(&hdev->dev, "parse failed\n"); + goto err_free; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + dev_err(&hdev->dev, "hw start failed\n"); + goto err_free; + } + + if (hdev->ll_driver->power) { + ret = hdev->ll_driver->power(hdev, PM_HINT_FULLON); + if (ret < 0) + goto err_free; + } + ret = hdev->ll_driver->open(hdev); + if (ret < 0) { + if (hdev->ll_driver->power) + hdev->ll_driver->power(hdev, PM_HINT_NORMAL); + + goto err_free; + } + + ret = mcp2210_spi_probe(dev); + if(ret < 0) { + goto err_power; + } + + cmd_data = kzalloc(sizeof(struct info_command), GFP_KERNEL); + if (!cmd_data) + return -ENOMEM; + + + cmd_data->id = 1; + cmd_data->cfg = dev->cfg; + // Query current SPI settings from IC + mcp2210_add_command(dev, cmd_data, next_mcp2210_info_command, next_mcp2210_info_command_response, mcp2210_info_command_interrupted); + + return 0; + +err_power: + if (hdev->ll_driver->power) + hdev->ll_driver->power(hdev, PM_HINT_NORMAL); + hdev->ll_driver->close(hdev); +err_free: + kfree(dev); + + return ret; +} + +// Called when a new mcp2210 device is removed +static void mcp2210_remove(struct hid_device *hdev) +{ + struct mcp2210_device *dev = hid_get_drvdata(hdev); + struct list_head *pos, *q, *request_pos, *request_q; + struct mcp2210_command *tmp; + + list_for_each_safe(pos, q, &dev->command_list){ + tmp = list_entry(pos, struct mcp2210_command, node); + list_del(pos); + + if(tmp->interrupted) + tmp->interrupted(tmp->data); + + list_for_each_safe(request_pos, request_q, &tmp->request_list) { + mcp2210_free_request(list_entry(request_pos, struct mcp2210_request_list, node)); + } + + kfree(tmp); + } + + mcp2210_spi_remove(dev); + + if (hdev->ll_driver->power) + hdev->ll_driver->power(hdev, PM_HINT_NORMAL); + hdev->ll_driver->close(hdev); + + kfree(dev->cfg); + kfree(dev); + + verbose_debug(KERN_DEBUG "mcp2210_remove\n"); + hid_hw_stop(hdev); +} + +static const struct hid_device_id mcp2210_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2210), + .driver_data = 0 }, + { } +}; + +static struct hid_driver mcp2210_driver = { + .name = "mcp2210", + .id_table = mcp2210_devices, + .raw_event = mcp2210_raw_event, + .probe = mcp2210_probe, + .remove = mcp2210_remove +}; + +static int __init mcp2210_init(void) +{ + int ret; + verbose_debug(KERN_DEBUG "mcp2210_init\n"); + + ret = hid_register_driver(&mcp2210_driver); + if (ret) + printk(KERN_ERR "can't register mcp2210 driver\n"); + + return ret; +} + +static void __exit mcp2210_exit(void) +{ + verbose_debug(KERN_DEBUG "mcp2210_exit\n"); + hid_unregister_driver(&mcp2210_driver); +} + +int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Enable debug messages"); + +module_init(mcp2210_init); +module_exit(mcp2210_exit); +MODULE_LICENSE("GPL"); diff --git a/mcp2210-spi.c b/mcp2210-spi.c old mode 100755 new mode 100644 index 8a84e55..05f0906 --- a/mcp2210-spi.c +++ b/mcp2210-spi.c @@ -1,324 +1,319 @@ -/* - * MCP 2210 driver for linux - * - * Copyright (c) 2013 Mathew King for Trilithic, Inc - * - */ - -/* - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - */ - -#include - -#include "mcp2210.h" - -struct mcp2210_spi { - struct mcp2210_device *dev; - struct spi_master *master; -}; - -struct mcp2210_spi_message { - struct spi_device *spi; - struct spi_message *msg; - struct spi_transfer *current_transfer; - unsigned int tx_pos; - unsigned int rx_pos; - unsigned int settings_set; - unsigned int tx_in_process; - unsigned int tx_bytes_in_process; - unsigned int kill; - struct list_head *next; -}; - -static void fill_u32(u8 *ptr, u32 data) { - ptr[0] = data & 0xFF; - ptr[1] = (data >> 8) & 0xFF; - ptr[2] = (data >> 16) & 0xFF; - ptr[3] = (data >> 24) & 0xFF; -} - - -static void fill_u16(u8 *ptr, u16 data) { - ptr[0] = data & 0xFF; - ptr[1] = (data >> 8) & 0xFF; -} - -static void mcp2210_spi_populate_transfer_settings(u8 *request, struct mcp2210_spi_message *mcp_msg) -{ - u32 bit_rate = mcp_msg->spi->max_speed_hz; - u16 idle_cs = ~(0x01 << mcp_msg->spi->chip_select); - u16 active_cs = 0xff; - u16 cs_to_data_delay = 5; // 5 * 100us delay - u16 last_byte_to_cs = 5; - u16 delay_between_bytes = 0; - u16 tansaction_bytes = mcp_msg->current_transfer->len; - - if(mcp_msg->current_transfer->speed_hz) - bit_rate = mcp_msg->current_transfer->speed_hz; - - if(bit_rate > MCP2210_MAX_SPEED) - bit_rate = MCP2210_MAX_SPEED; - - // Set SPI Transfer Settings - request[0] = 0x40; - - fill_u32(request + 4, bit_rate); - fill_u16(request + 8, idle_cs); - fill_u16(request + 10, active_cs); - fill_u16(request + 12, cs_to_data_delay); - fill_u16(request + 14, last_byte_to_cs); - fill_u16(request + 16, delay_between_bytes); - fill_u16(request + 18, tansaction_bytes); - - request[20] = mcp_msg->spi->mode & 0x03; -} - -static int next_mcp2210_spi_request(void *data, u8 *request) -{ - struct mcp2210_spi_message *mcp_msg = data; - u32 len; - - // Only send one command at a time - if((mcp_msg->current_transfer && !mcp_msg->settings_set) || mcp_msg->tx_in_process) { - return 0; - } - - // Check for done - if(mcp_msg->next == &mcp_msg->msg->transfers || mcp_msg->msg->status) { - //printk("mcp2210_spi Message done %d\n", mcp_msg->msg->status); - mcp_msg->msg->complete(mcp_msg->msg->context); - kfree(mcp_msg); - return 0; - } - - // Check for start of transfer - if(!mcp_msg->current_transfer) { - //printk("mcp2210_spi Start new transfer \n"); - mcp_msg->current_transfer = list_entry(mcp_msg->next, struct spi_transfer, transfer_list); - mcp_msg->tx_pos = 0; - mcp_msg->rx_pos = 0; - mcp_msg->tx_in_process = 0; - mcp_msg->tx_bytes_in_process = 0; - mcp_msg->settings_set = 0; - mcp2210_spi_populate_transfer_settings(request, mcp_msg); - print_msg(request); - return 1; - } - - // Write spi transfer command - if(mcp_msg->tx_pos < mcp_msg->current_transfer->len) { - len = mcp_msg->current_transfer->len - mcp_msg->tx_pos; - - if(len > MCP2210_BUFFER_SIZE - 4) - len = MCP2210_BUFFER_SIZE - 4; - - request[0] = 0x42; - request[1] = len & 0xff; - - if(mcp_msg->current_transfer->tx_buf) - memcpy(request + 4, mcp_msg->current_transfer->tx_buf + mcp_msg->tx_pos, len); - - mcp_msg->tx_pos += len; - mcp_msg->tx_bytes_in_process = len; - mcp_msg->tx_in_process++; - - if(len == 0) { - printk("ERROR: we are sending 0 bytes %d \n", mcp_msg->current_transfer->len); - } - - //printk("mcp2210_spi send %d bytes \n", len); - - print_msg(request); - return 1; - } - - if(mcp_msg->rx_pos < mcp_msg->current_transfer->len) { - // All data is sent but we need to wait and receive the rest - request[0] = 0x42; - request[1] = 0; - - mcp_msg->tx_in_process++; - mcp_msg->kill++; - - if(mcp_msg->kill > 50) - { - mcp_msg->msg->status = -EIO; - request[0] = 0x11; - //printk("mcp2210_spi kill the transfer \n"); - } - - //printk("mcp2210_spi request rx bytes \n"); - - print_msg(request); - return 1; - } - - if(mcp_msg->tx_pos == mcp_msg->current_transfer->len) { - //printk("mcp2210_spi All tx bytes sent\n"); - } - - return 0; -} - -static void mcp2210_spi_response(void *data, u8 *response) -{ - struct mcp2210_spi_message *mcp_msg = data; - int x; - u8 len; - - //printk("Received data\n\n"); - - print_msg(response); - - // This is the settings response - if(!mcp_msg->settings_set) { - //printk("mcp2210_spi Sent spi settings\n"); - mcp_msg->settings_set = 1; - return; - } - - // This is a transfer response - mcp_msg->tx_in_process--; - - // The message has an error just return - if(mcp_msg->msg->status) - return; - - if(response[1] == 0xf8) { - // data not accepted we need to resend - mcp_msg->tx_pos -= mcp_msg->tx_bytes_in_process; - mcp_msg->tx_bytes_in_process = 0; - return; - } - - if(response[1]) { - // Error response - mcp_msg->msg->status = -response[1]; - return; - } - - len = response[2]; - - if(len) { - // There is data to receive - if(mcp_msg->current_transfer->rx_buf) - memcpy(mcp_msg->current_transfer->rx_buf + mcp_msg->rx_pos, response + 4, len); - - mcp_msg->rx_pos += len; - mcp_msg->msg->actual_length += len; - } - - // Data was accepted so clear in process - mcp_msg->tx_bytes_in_process = 0; - - if(mcp_msg->rx_pos == mcp_msg->current_transfer->len) { - // Transfer is done move next - //printk("mcp2210_spi All rx bytes read\n"); - mcp_msg->current_transfer = NULL; - mcp_msg->next = mcp_msg->next->next; - } -} - -static void mcp2210_spi_interrupted(void *data) -{ - struct mcp2210_spi_message *mcp_msg = data; - - mcp_msg->msg->status = -ESHUTDOWN; - mcp_msg->msg->complete(mcp_msg->msg->context); - kfree(mcp_msg); -} - -static int mcp2210_spi_setup(struct spi_device *spi) -{ - struct mcp2210_spi *ms; - ms = spi_master_get_devdata(spi->master); - - return 0; -} - -static int mcp2210_spi_transfer(struct spi_device *spi, struct spi_message *msg) -{ - struct mcp2210_spi *ms; - struct mcp2210_spi_message *mcp_msg; - ms = spi_master_get_devdata(spi->master); - - mcp_msg = kzalloc(sizeof(struct mcp2210_spi_message), GFP_ATOMIC); - if(!mcp_msg) - return -ENOMEM; - - mcp_msg->spi = spi; - mcp_msg->msg = msg; - mcp_msg->next = msg->transfers.next; - msg->status = 0; - - mcp2210_add_command(ms->dev, mcp_msg, next_mcp2210_spi_request, mcp2210_spi_response, mcp2210_spi_interrupted); - - return 0; -} - -static void mcp2210_spi_cleanup(struct spi_device *spi) -{ - struct mcp2210_spi *ms; - ms = spi_master_get_devdata(spi->master); -} - -static struct spi_board_info demo_spi_devices[] = { - { - .modalias = "spidev", - .chip_select = 0, - .max_speed_hz = MCP2210_MAX_SPEED, - .bus_num = 0, - .mode = SPI_MODE_3, - } -}; - -int mcp2210_spi_probe(struct mcp2210_device *dev) { - int ret; - struct spi_master *master; - struct mcp2210_spi *ms; - - ret = -ENOMEM; - master = spi_alloc_master(&dev->hid->dev, sizeof *ms); - if (!master) - goto out_free; - - master->bus_num = -1; - master->num_chipselect = 4; - master->setup = mcp2210_spi_setup; - master->transfer = mcp2210_spi_transfer; - master->cleanup = mcp2210_spi_cleanup; - master->mode_bits = SPI_CPOL | SPI_CPHA; - - ms = spi_master_get_devdata(master); - ms->dev = dev; - ms->master = master; - dev->spi_data = ms; - - ret = spi_register_master(master); - - demo_spi_devices[0].bus_num = master->bus_num; - printk("mcp2210 spi master registered bus number %d\n", demo_spi_devices[0].bus_num); - - spi_new_device(master, demo_spi_devices); - - if (ret) - goto out_free; - - return 0; - -out_free: - spi_master_put(master); - return ret; -} - -void mcp2210_spi_remove(struct mcp2210_device *dev) -{ - struct mcp2210_spi *ms = dev->spi_data; - struct spi_master *master = ms->master; - - spi_unregister_master(master); -} \ No newline at end of file +/* + * MCP 2210 driver for linux + * + * Copyright (c) 2013 Mathew King for Trilithic, Inc + * 2020 Cristian Balint + * + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include + +#include "mcp2210.h" + +struct mcp2210_spi { + struct mcp2210_device *dev; + struct spi_master *master; +}; + +struct mcp2210_spi_message { + struct spi_device *spi; + struct spi_message *msg; + struct mcp2210_dev_cfg *cfg; + struct spi_transfer *current_transfer; + unsigned int tx_pos; + unsigned int rx_pos; + unsigned int settings_set; + unsigned int tx_in_process; + unsigned int tx_bytes_in_process; + unsigned int kill; + struct list_head *next; +}; + +static void fill_u32(u8 *ptr, u32 data) { + ptr[0] = data & 0xFF; + ptr[1] = (data >> 8) & 0xFF; + ptr[2] = (data >> 16) & 0xFF; + ptr[3] = (data >> 24) & 0xFF; +} + + +static void fill_u16(u8 *ptr, u16 data) { + ptr[0] = data & 0xFF; + ptr[1] = (data >> 8) & 0xFF; +} + +static void mcp2210_spi_populate_transfer_settings(u8 *request, struct mcp2210_spi_message *mcp_msg) +{ + u32 bit_rate = mcp_msg->cfg->bitrate; + u16 idle_cs = mcp_msg->cfg->idle_cs; + u16 active_cs = mcp_msg->cfg->active_cs; + u16 cs_to_data_delay = mcp_msg->cfg->cs_ds_delay; + u16 last_byte_to_cs = mcp_msg->cfg->cs_ls_delay; + u16 delay_between_bytes = mcp_msg->cfg->byte_delay; + u16 tansaction_bytes = mcp_msg->cfg->byte_per_spi; + + // Set SPI Transfer Settings + request[0] = MCP2210_CMD_SET_SPI_CONFIG; + + fill_u32(request + 4, bit_rate); + fill_u16(request + 8, idle_cs); + fill_u16(request + 10, active_cs); + fill_u16(request + 12, cs_to_data_delay); + fill_u16(request + 14, last_byte_to_cs); + fill_u16(request + 16, delay_between_bytes); + fill_u16(request + 18, tansaction_bytes); + + request[20] = mcp_msg->cfg->spi_mode; +} + +static int next_mcp2210_spi_request(void *data, u8 *request) +{ + struct mcp2210_spi_message *mcp_msg = data; + u32 len; + + // Only send one command at a time + if((mcp_msg->current_transfer && !mcp_msg->settings_set) || mcp_msg->tx_in_process) { + return 0; + } + + // Check for done + if(mcp_msg->next == &mcp_msg->msg->transfers || mcp_msg->msg->status) { + verbose_debug(KERN_DEBUG "mcp2210_spi Message done %d\n", mcp_msg->msg->status); + mcp_msg->msg->complete(mcp_msg->msg->context); + kfree(mcp_msg); + return 0; + } + + // Check for start of transfer + if(!mcp_msg->current_transfer) { + verbose_debug(KERN_DEBUG "mcp2210_spi Start new transfer \n"); + mcp_msg->current_transfer = list_entry(mcp_msg->next, struct spi_transfer, transfer_list); + mcp_msg->tx_pos = 0; + mcp_msg->rx_pos = 0; + mcp_msg->tx_in_process = 0; + mcp_msg->tx_bytes_in_process = 0; + mcp_msg->settings_set = 0; + mcp2210_spi_populate_transfer_settings(request, mcp_msg); + print_msg(request, MCP2210_BUFFER_SIZE); + return 1; + } + + // Write spi transfer command + if(mcp_msg->tx_pos < mcp_msg->current_transfer->len) { + len = mcp_msg->current_transfer->len - mcp_msg->tx_pos; + + if(len > MCP2210_BUFFER_SIZE - 4) + len = MCP2210_BUFFER_SIZE - 4; + + request[0] = MCP2210_CMD_SPI_TRANSFER; + request[1] = len & 0xff; + + if(mcp_msg->current_transfer->tx_buf) + memcpy(request + 4, mcp_msg->current_transfer->tx_buf + mcp_msg->tx_pos, len); + + mcp_msg->tx_pos += len; + mcp_msg->tx_bytes_in_process = len; + mcp_msg->tx_in_process++; + + if(len == 0) { + printk(KERN_ERR "ERROR: we are sending 0 bytes %d \n", mcp_msg->current_transfer->len); + } + + verbose_debug(KERN_DEBUG "mcp2210_spi send %d bytes \n", len); + + print_msg(request, MCP2210_BUFFER_SIZE); + return 1; + } + + if(mcp_msg->rx_pos < mcp_msg->current_transfer->len) { + // All data is sent but we need to wait and receive the rest + request[0] = MCP2210_CMD_SPI_TRANSFER; + request[1] = 0; + + mcp_msg->tx_in_process++; + mcp_msg->kill++; + + if(mcp_msg->kill > 50) + { + mcp_msg->msg->status = -EIO; + request[0] = MCP2210_CMD_SPI_CANCEL; + verbose_debug(KERN_DEBUG "mcp2210_spi kill the transfer \n"); + } + + verbose_debug(KERN_DEBUG "mcp2210_spi request rx bytes \n"); + + print_msg(request, MCP2210_BUFFER_SIZE); + return 1; + } + + if(mcp_msg->tx_pos == mcp_msg->current_transfer->len) { + verbose_debug(KERN_DEBUG "mcp2210_spi All tx bytes sent\n"); + } + + return 0; +} + +static void mcp2210_spi_response(void *data, u8 *response) +{ + struct mcp2210_spi_message *mcp_msg = data; + u8 len; + + verbose_debug(KERN_DEBUG "Received data\n\n"); + + print_msg(response, MCP2210_BUFFER_SIZE); + + // This is the settings response + if(!mcp_msg->settings_set) { + verbose_debug(KERN_DEBUG "mcp2210_spi Sent spi settings\n"); + mcp_msg->settings_set = 1; + return; + } + + // This is a transfer response + mcp_msg->tx_in_process--; + + // The message has an error just return + if(mcp_msg->msg->status) + return; + + if(response[1] == MCP2210_STATUS_BUSY) { + // data not accepted we need to resend + mcp_msg->tx_pos -= mcp_msg->tx_bytes_in_process; + mcp_msg->tx_bytes_in_process = 0; + return; + } + + if(response[1]) { + // Error response + mcp_msg->msg->status = -response[1]; + return; + } + + len = response[2]; + + if(len) { + // There is data to receive + if(mcp_msg->current_transfer->rx_buf) + memcpy(mcp_msg->current_transfer->rx_buf + mcp_msg->rx_pos, response + 4, len); + + mcp_msg->rx_pos += len; + mcp_msg->msg->actual_length += len; + } + + // Data was accepted so clear in process + mcp_msg->tx_bytes_in_process = 0; + + if(mcp_msg->rx_pos == mcp_msg->current_transfer->len) { + // Transfer is done move next + verbose_debug(KERN_DEBUG "mcp2210_spi All rx bytes read\n"); + mcp_msg->current_transfer = NULL; + mcp_msg->next = mcp_msg->next->next; + } +} + +static void mcp2210_spi_interrupted(void *data) +{ + struct mcp2210_spi_message *mcp_msg = data; + + mcp_msg->msg->status = -ESHUTDOWN; + mcp_msg->msg->complete(mcp_msg->msg->context); + kfree(mcp_msg); +} + +static int mcp2210_spi_setup(struct spi_device *spi) +{ + struct mcp2210_spi *ms; + ms = spi_master_get_devdata(spi->master); + + return 0; +} + +static int mcp2210_spi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct mcp2210_spi *ms; + struct mcp2210_spi_message *mcp_msg; + ms = spi_master_get_devdata(spi->master); + + mcp_msg = kzalloc(sizeof(struct mcp2210_spi_message), GFP_ATOMIC); + if(!mcp_msg) + return -ENOMEM; + + mcp_msg->spi = spi; + mcp_msg->msg = msg; + mcp_msg->cfg = ms->dev->cfg; + mcp_msg->next = msg->transfers.next; + msg->status = 0; + mcp2210_add_command(ms->dev, mcp_msg, next_mcp2210_spi_request, mcp2210_spi_response, mcp2210_spi_interrupted); + + return 0; +} + +static void mcp2210_spi_cleanup(struct spi_device *spi) +{ + struct mcp2210_spi *ms; + ms = spi_master_get_devdata(spi->master); +} + +static struct spi_board_info demo_spi_devices[] = { + { + .modalias = "spidev", + .chip_select = 0, + .max_speed_hz = MCP2210_MAX_SPEED, + .bus_num = 0, + .mode = SPI_MODE_0, + } +}; + +int mcp2210_spi_probe(struct mcp2210_device *dev) { + int ret; + struct spi_master *master; + struct mcp2210_spi *ms; + + ret = -ENOMEM; + master = spi_alloc_master(&dev->hid->dev, sizeof *ms); + if (!master) + goto out_free; + + master->bus_num = -1; + master->num_chipselect = MCP2210_MAX_CS; + master->setup = mcp2210_spi_setup; + master->transfer = mcp2210_spi_transfer; + master->cleanup = mcp2210_spi_cleanup; + master->mode_bits = SPI_CPOL | SPI_CPHA; + + ms = spi_master_get_devdata(master); + ms->dev = dev; + ms->master = master; + dev->spi_data = ms; + + ret = spi_register_master(master); + + demo_spi_devices[0].bus_num = master->bus_num; + printk(KERN_INFO "mcp2210 spi master registered bus number %d\n", demo_spi_devices[0].bus_num); + + spi_new_device(master, demo_spi_devices); + + if (ret) + goto out_free; + + return 0; + +out_free: + spi_master_put(master); + return ret; +} + +void mcp2210_spi_remove(struct mcp2210_device *dev) +{ + struct mcp2210_spi *ms = dev->spi_data; + struct spi_master *master = ms->master; + + spi_unregister_master(master); +} diff --git a/mcp2210.h b/mcp2210.h old mode 100755 new mode 100644 index feda958..57c0cef --- a/mcp2210.h +++ b/mcp2210.h @@ -1,81 +1,128 @@ -/* - * MCP 2210 driver for linux - * - * Copyright (c) 2013 Mathew King for Trilithic, Inc - * - */ - -/* - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - */ - -#ifndef _MCP2210_H -#define _MCP2210_H - -#include -#include -#include -#include - -#define USB_VENDOR_ID_MICROCHIP 0x04d8 -#define USB_DEVICE_ID_MCP2210 0x00de - -#define MCP2210_BUFFER_SIZE 64 -#define MCP2210_MAX_SPEED (12 * 1000 * 1000) - -struct mcp2210_device { - struct hid_device *hid; - wait_queue_head_t wait; - struct mcp2210_command *current_command; - //struct mcp2210_command_request *current_request; - struct list_head command_list; - struct mutex command_mutex; - spinlock_t command_lock; - struct work_struct command_work; - u8 requeust_buffer[MCP2210_BUFFER_SIZE]; - void *spi_data; -}; - -struct mcp2210_command { - void *data; - struct mcp2210_device *dev; - int requests_pending; - int processing; - int (*next_request)(void *command_data, u8 *request); - void (*data_received)(void *command_data, u8 *response); - void (*interrupted)(void *command_data); - struct list_head node; - struct list_head request_list; -}; - -struct mcp2210_request_list { - struct hid_report *report; - struct list_head node; -}; - -int mcp2210_add_command(struct mcp2210_device *dev, void *cmd_data, - int (*next_request)(void *command_data, u8 *request), - void (*data_received)(void *command_data, u8 *response), - void (*interrupted)(void *command_data)); - - -static inline void print_msg(u8 *data) { - return; - int x; - for(x = 0; x < 64; x++) { - printk("%02x ", data[x]); - if((x + 1) % 16 == 0) { - printk("\n"); - } - else if((x + 1) % 8 == 0) { - printk(" "); - } - } - - printk("\n"); -} - -#endif // _MCP2210_H \ No newline at end of file +/* + * MCP 2210 driver for linux + * + * Copyright (c) 2013 Mathew King for Trilithic, Inc + * 2020 Cristian Balint + * + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#ifndef _MCP2210_H +#define _MCP2210_H + +#include +#include +#include +#include + +extern int debug; +#define verbose_debug(fmt, args...) \ + if (debug != 0) printk(fmt, ##args) + +#define USB_VENDOR_ID_MICROCHIP 0x04d8 +#define USB_DEVICE_ID_MCP2210 0x00de + +#define MCP2210_BUFFER_SIZE 64 +#define MCP2210_MAX_CS 8 +#define MCP2210_MAX_SPEED (12 * 1000 * 1000) + +// MCP2210 commands +#define MCP2210_CMD_GET_STATUS 0x10 +#define MCP2210_CMD_SPI_CANCEL 0x11 +#define MCP2210_CMD_GET_INTERRUPTS 0x12 +#define MCP2210_CMD_GET_CHIP_CONFIG 0x20 +#define MCP2210_CMD_SET_CHIP_CONFIG 0x21 +#define MCP2210_CMD_SET_PIN_VALUE 0x30 +#define MCP2210_CMD_GET_PIN_VALUE 0x31 +#define MCP2210_CMD_SET_PIN_DIR 0x32 +#define MCP2210_CMD_GET_PIN_DIR 0x33 +#define MCP2210_CMD_SET_SPI_CONFIG 0x40 +#define MCP2210_CMD_GET_SPI_CONFIG 0x41 +#define MCP2210_CMD_SPI_TRANSFER 0x42 +#define MCP2210_CMD_READ_EEPROM 0x50 +#define MCP2210_CMD_WRITE_EEPROM 0x51 +#define MCP2210_CMD_SET_NVRAM 0x60 +#define MCP2210_CMD_GET_NVRAM 0x61 +#define MCP2210_CMD_SEND_PASSWD 0x70 +#define MCP2210_CMD_SPI_RELEASE 0x80 + +// MCP2210 status +#define MCP2210_STATUS_SUCCESS 0x00 +#define MCP2210_STATUS_SPI_NOT_OWNED 0xf7 +#define MCP2210_STATUS_BUSY 0xf8 +#define MCP2210_STATUS_UNKNOWN_CMD 0xf9 +#define MCP2210_STATUS_WRITE_FAIL 0xfa +#define MCP2210_STATUS_NO_ACCESS 0xfb +#define MCP2210_STATUS_PERM_LOCKED 0xfc +#define MCP2210_STATUS_BAD_PASSWD 0xfd + +struct mcp2210_dev_cfg { + u8 active; + u32 bitrate; + u16 idle_cs; + u16 active_cs; + u16 cs_ds_delay; + u16 cs_ls_delay; + u16 byte_delay; + u16 byte_per_spi; + u8 spi_mode; +}; + +struct mcp2210_device { + struct hid_device *hid; + struct mcp2210_dev_cfg *cfg; + wait_queue_head_t wait; + struct mcp2210_command *current_command; + struct list_head command_list; + struct mutex command_mutex; + spinlock_t command_lock; + struct work_struct command_work; + u8 requeust_buffer[MCP2210_BUFFER_SIZE]; + void *spi_data; +}; + +struct mcp2210_command { + void *data; + struct mcp2210_device *dev; + int requests_pending; + int processing; + int (*next_request)(void *command_data, u8 *request); + void (*data_received)(void *command_data, u8 *response); + void (*interrupted)(void *command_data); + struct list_head node; + struct list_head request_list; +}; + +struct mcp2210_request_list { + struct hid_report *report; + struct list_head node; +}; + +int mcp2210_add_command(struct mcp2210_device *dev, void *cmd_data, + int (*next_request)(void *command_data, u8 *request), + void (*data_received)(void *command_data, u8 *response), + void (*interrupted)(void *command_data)); + + +static inline void print_msg(u8 *data, unsigned int len) { + if (debug) + print_hex_dump(KERN_DEBUG, "message: ", DUMP_PREFIX_ADDRESS, 16, 1, data, len, 1); + return; +} + +static inline int print_binary(char *buf, unsigned long x, int nbits) { + unsigned long mask = 1UL << (nbits - 1); + while (mask != 0) { + *buf++ = (mask & x ? '1' : '0'); + mask >>= 1; + } + *buf = '\0'; + return nbits; +} +#endif // _MCP2210_H