├── .gitignore ├── Kconfig ├── Makefile ├── README.md ├── mcp2210-core.c ├── mcp2210-creek.h ├── mcp2210-ctl.c ├── mcp2210-debug.h ├── mcp2210-eeprom.c ├── mcp2210-gpio.c ├── mcp2210-ioctl.c ├── mcp2210-irq.c ├── mcp2210-lib.c ├── mcp2210-spi.c ├── mcp2210.h ├── out-of-tree-autoconf.h.template ├── udev ├── 99_mcp2210.rules ├── README └── rebind_sysfs_driver.sh └── user ├── Makefile ├── mcp2210-user.c ├── mcp2210-user.h ├── mcp2210.c ├── mcp2210_bind.sh ├── select-gpio.c ├── settings-example.h └── tests.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Output files 2 | mcp2210.ko 3 | Module.symvers 4 | user/libmcp2210.so 5 | user/mcp2210-util 6 | 7 | # Symlinks & per-user settings 8 | user/mcp2210-lib.c 9 | user/settings.h 10 | out-of-tree-autoconf.h 11 | 12 | # Temporary files & directories 13 | modules.order 14 | .tmp_versions 15 | *.o 16 | *.mod.c 17 | *.o.cmd 18 | *.ko.cmd 19 | *.gcno 20 | 21 | # IDE files 22 | .kdev4 23 | .kdev_include_paths 24 | *.kdev4 25 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | # 2 | # MCP2210 3 | # 4 | 5 | config MCP2210 6 | tristate "MCP2210 USB-to-SPI bridge" 7 | depends on USB_SUPPORT 8 | help 9 | If you say yes here you get (HID-free) support for the Microchip 10 | MCP2210 USB-to-SPI protocol converter with GPIO. This device 11 | advertises its self as a generic HID device and will normally be 12 | probed by hid-generic unless you (TODO). 13 | 14 | To compile this driver as a module, choose M here: the 15 | module will be called mcp2210. 16 | 17 | If unsure, say n. 18 | 19 | config MCP2210_IOCTL 20 | bool "Enable ioctl control support" 21 | depends on MCP2210 22 | default y 23 | help 24 | Say yes here to build support for an ioctl control interface. This 25 | creates a node under /dev named usb2spi_bridge. See 26 | Documentation/mfd/mcp2210 (which doesn't yet exist) to enter 27 | alternate dimension. 28 | 29 | If unsure, say y. 30 | 31 | config MCP2210_SPI 32 | bool "Enable SPI support" 33 | depends on MCP2210 34 | select SPI 35 | default y 36 | help 37 | Say yes here to build support for using the MCP2210 as an SPI master, 38 | This options exists for those who just want to use it as a gpio 39 | expander and saves you (TODO)kb. 40 | 41 | If unsure, say y. 42 | 43 | config MCP2210_GPIO 44 | bool "Enable GPIO support" 45 | depends on MCP2210 46 | select GPIOLIB 47 | default y 48 | help 49 | Say yes here to build support for using the device's GPIO lines. This 50 | option is not required to use gpio from userspace via the ioctl 51 | interface. Disabling saves (TODO)kb. 52 | 53 | If unsure, say y. 54 | 55 | config MCP2210_EEPROM 56 | bool "Enable user-EEPROM support" 57 | depends on MCP2210 58 | default y 59 | help 60 | The MCP2210 has 256 bytes of user-EEPROM. Enabling this builds 61 | support for reading & writing this memory via ioctls and is required 62 | to use the Creek auto-configuration. 63 | 64 | If unsure, say y. 65 | 66 | config MCP2210_CREEK 67 | bool "Creek auto-configuration support" 68 | depends on MCP2210 69 | select MCP2210_SPI 70 | select MCP2210_GPIO 71 | select MCP2210_EEPROM 72 | default y 73 | help 74 | Creek is an auto-configuration scheme that allows you to write the 75 | wiring configuration of your board to the MCP2210's user-EEPROM area 76 | (in a compressed format), which the driver will be able to auto- 77 | detect and read during the USB interface probe and automatically 78 | probe the attached SPI devices, gpio lines, etc. 79 | (see Documentation/mfd/mcp2210). 80 | 81 | If unsure, say y. 82 | 83 | config MCP2210_IRQ 84 | bool "Interrupt controller support for MCP2210" 85 | depends on MCP2210 && GENERIC_HARDIRQS 86 | default n 87 | select GPIOLIB_IRQCHIP 88 | help 89 | Say yes here to enable the MCP2210 to be used as a psudo-interrupt 90 | controller, generating local interrupts as a response to changes 91 | on the remote device. 92 | 93 | If unsure, say n. 94 | 95 | config MCP2210_DEBUG 96 | bool "Enable debug messages" 97 | depends on MCP2210 98 | help 99 | Saying no here removes all debug printk messages from the driver 100 | reducing saving (TODO) bytes. If enabled, you will still have to 101 | turn them on by either setting the module debug parameter or 102 | MCP2210_DEBUG_INITIAL to 7. 103 | 104 | If unsure, say n. 105 | 106 | config MCP2210_DEBUG_VERBOSE 107 | bool "Enable verbose debug messages" 108 | depends on MCP2210 && MCP2210_DEBUG 109 | help 110 | If you say yes here you enable verbose debug messages (including dump 111 | of structs & messages). This will increase the data & text by about 112 | (TODO)kb. Unless you are debugging the MCP2210 driver, you probably 113 | don't need or want this. 114 | 115 | If unsure, say n. 116 | 117 | config MCP2210_DEBUG_INITIAL 118 | int "Initial debug level" 119 | depends on MCP2210 120 | default 7 121 | help 122 | The initial debug level for this driver. You can change via 123 | /sys/module/mcp2210/parameters/debug_level. 124 | 125 | config MCP2210_LOGGING_FAST_PATH 126 | bool "Build logging code into fast path" 127 | depends on MCP2210 128 | help 129 | Saying yes here prevents all logging code paths from being marked as 130 | unlikely, and will result in better optimization of logging at the 131 | cost of slower code when logging is not enabled. This should ONLY 132 | be used when debugging timing-sensitive problems as it eats icache 133 | unneccessarily otherwise. 134 | 135 | If unsure, just say no. 136 | 137 | config MCP2210_USB_QUIRKS 138 | bool "Enable work-arounds for quirky USB host drivers" 139 | depends on MCP2210 140 | help 141 | Builds countermeasures for quirky USB host drivers like the BCM2835 142 | used on the Raspberry Pi to help it function better. 143 | 144 | If unsure, say n. 145 | 146 | config MCP2210_AUTOPM 147 | bool "Enable autosuspend via pm runtime (in development)" 148 | depends on MCP2210 149 | select PM 150 | help 151 | Saying yes here enables autosuspend support via the pm runtime 152 | core. This feature is currently in development and known to be 153 | non-working. (Enables PM Runtime) 154 | 155 | If unsure, say n. 156 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for mcp2210 3 | # 4 | # Derived from the good-ole LDD3 Makefile 5 | # 6 | 7 | ccflags-y += -Wall -Werror -Wunused-macros 8 | 9 | # To build modules outside of the kernel tree, we run "make" 10 | # in the kernel source tree; the Makefile these then includes this 11 | # Makefile once again. 12 | # This conditional selects whether we are being included from the 13 | # kernel Makefile or not. 14 | ifeq ($(KERNELRELEASE),) 15 | # Assume the source tree is where the running kernel was built 16 | # You should set KERNELDIR in the environment if it's elsewhere 17 | KERNELDIR ?= /lib/modules/$(shell uname -r)/build 18 | # The current directory is passed to sub-makes as argument 19 | PWD := $(shell pwd) 20 | 21 | all: modules user 22 | 23 | user: 24 | $(MAKE) -C user 25 | 26 | # Temporary measure for building out-of-tree 27 | out-of-tree-autoconf.h: 28 | cp -n out-of-tree-autoconf.h.template out-of-tree-autoconf.h 29 | 30 | modules: out-of-tree-autoconf.h 31 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 32 | 33 | modules_install: 34 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 35 | 36 | clean: 37 | rm -rf *.o *.gcno *~ core .depend .*.cmd *.ko *.mod.c *.dwo .*.dwo \ 38 | mcp2210.s .tmp_versions modules.order Module.symvers 39 | $(MAKE) -C user clean 40 | 41 | mcp2210.s: modules 42 | $(CROSS_COMPILE)objdump -dSr --prefix-addresses --line-numbers \ 43 | mcp2210.ko > mcp2210.s 44 | 45 | .PHONY: modules modules_install user clean 46 | 47 | else 48 | # called from kernel build system: just declare what our modules are 49 | 50 | # When building out-of-tree, we need a way to get our config macros 51 | KBUILD_CPPFLAGS += -include $(PWD)/out-of-tree-autoconf.h 52 | 53 | # Many kernels < v3.0 will produce loads of spam with 54 | # -Wunused-but-set-variable set 55 | ifeq ($(VERSION),2) 56 | ccflags-y += -Wno-unused-but-set-variable 57 | endif 58 | 59 | CONFIG_MCP2210 ?= m 60 | mcp2210-objs := mcp2210-core.o mcp2210-ioctl.o mcp2210-ctl.o \ 61 | mcp2210-spi.o mcp2210-eeprom.o mcp2210-lib.o \ 62 | mcp2210-gpio.o mcp2210-irq.o 63 | 64 | obj-$(CONFIG_MCP2210) += mcp2210.o 65 | endif 66 | 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MCP2210 driver for Linux 2 | ======================== 3 | 4 | This is a Linux device driver for the [Microchip mcp2210 USB-to-SPI bridge](http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en556614). It currently builds as an out-of-tree module and includes a userland utility for testing, configuration, control and spi messaging via spidev. (There are other userland utilities for doing spi through [spidev](https://www.kernel.org/doc/Documentation/spi/spidev)). 5 | 6 | This is a USB interface driver, NOT a usbhid driver! The MCP2210 isn't a device that interfaces with humans, and they only choose the HID interface to avoid the hassle of writing and maintaining drivers for every other platform. However, it means that you use this whole layer of software, protocols, drivers, etc that you don't really need. Therefore, this driver skips that layer and communicates directly with the device via standard interrupt URBs (not hid reports). 7 | 8 | Unlike other libraries, this driver connects the SPI devices directly to the spi sub-system, allowing you to choose whatever spi protocol driver you wish, with the default being spidev (which allows you to interact with the SPI device from userspace). 9 | 10 | RTFM 11 | ---- 12 | God loves people who rtfm. It's fun, healthy and besides, everybody's doing it. 13 | 14 | * [MCP2210 Product page](http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en556614) 15 | * [MCP2210 Datasheet](http://ww1.microchip.com/downloads/en/DeviceDoc/22288A.pdf) 16 | * [spidev](https://www.kernel.org/doc/Documentation/spi/spidev) 17 | 18 | 19 | Configuring, Building and Installing 20 | ==================================== 21 | 22 | If you have your kernel sources installed and all is right with the world, building and installing (with a default configuration) is as simple as: 23 | 24 | ``` 25 | make all && make modules_install 26 | ``` 27 | 28 | To explicitly say where your sources are, use: 29 | 30 | ``` 31 | KERNELDIR=/usr/scr/abcdefg make 32 | ``` 33 | 34 | This is an out-of-tree module, so if you don't fancy the default configuration, run `make out-of-tree-autoconf.h` to have it generated from the [template](out-of-tree-autoconf.h.template) and edit to your needs. You can refer to [`Kconfig`](Kconfig) for descriptions of the various config options, but there is currently no mechanism to use the `Kconfig` for a `make menuconfig`; the `Kconfig` exists primarily for mainline integration. 35 | 36 | Note that at this time, there is no `install` target for the userland utilities and to run `mcp2210-util`, you must explicitly specify an `LD_LIBRARY_PATH`. Example: 37 | 38 | ```bash 39 | /home/daniel/proj/mcp2210$ make -j4 40 | .... make output..... 41 | /home/daniel/proj/mcp2210$ LD_LIBRARY_PATH=`pwd`/user user/mcp2210-util --help 42 | ``` 43 | 44 | Cross-Compiling 45 | --------------- 46 | 47 | I'm not going to tell you how to setup a cross-compiling toolchain, but here is how I build for my Raspberry Pi. Please note that the USB drivers for the Pi are currently problematic, especially prior the commit "[USB fix using a FIQ to implement split transactions](https://github.com/raspberrypi/linux/commit/db4fad7380c83b6e1b62dfb3fd61e55d031a04fc)", which has greatly improved things. So the below `MCP2210_QUIRKS=1` cpp variable should be omitted if you aren't using a pi. 48 | 49 | ```bash 50 | #!/bin/bash 51 | 52 | export KERNELDIR=/home/daniel/proj/kernel/rpi.3.10 53 | export ARCH=arm 54 | export CROSS_COMPILE=/usr/bin/armv6j-hardfloat-linux-gnueabi- 55 | export CPPFLAGS="-DMCP2210_QUIRKS=1" 56 | 57 | CFLAGS="-O2 -g3 -pipe -march=armv6j -mfpu=vfp -mfloat-abi=hard" make -j4 -C user && 58 | make -j4 "$@" && 59 | make mcp2210.s && 60 | scp -p mcp2210.ko user/libmcp2210.so user/mcp2210 root@pi:bin/ 61 | ``` 62 | 63 | At the very least, your `KERNELDIR` should have a valid `.config` and you should run `make modules_prepare`. You'll need to enable the following in your .config (either modules or built-ins will do): 64 | 65 | * `CONFIG_SPI` - if you want to use the MCP2210 as an spi master 66 | * `CONFIG_SPI_SPIDEV` - if you want to use the kernel's handy-dandy spidev driver and the userspace utility's spi functionality. If you will only be using spi protocol drivers specific to your peripherals, then you do not need this option. 67 | * `CONFIG_GPIOLIB` - if you want to use the gpio interface or use gpios for chip selects (as opposed to Microchip's mechanism, which is faster if it serves your needs) 68 | 69 | You don't need to make `mcp2210.s` if you don't care to examine the disassembly. The `CFLAGS` supplied when building the userspace program aren't inferred if not supplied (although it really doesn't contain any floating point calculations at the moment). I use `-j4` because I have a quad core processor, tune to your preferences. 70 | 71 | Installing the Driver 72 | ===================== 73 | Aside from the usual modprobe/insmod, this part is unfortunately a pain in the ass. This is because the hid-generic driver uses these values to select its devices: 74 | 75 | ``` 76 | static const struct hid_device_id hid_table[] = { 77 | { HID_DEVICE(HID_BUS_ANY, HID_GROUP_GENERIC, HID_ANY_ID, HID_ANY_ID) }, 78 | { } 79 | }; 80 | ``` 81 | 82 | This prevents the mcp2210 driver from being selected as a candidate, even if the vid/pid explicitly match. Currently, the work-around is to run the script [`user/mcp2210_bind.sh`](user/mcp2210_bind.sh) as root (you must have sysfs mounted). This uses sysfs files to tell the usbhid driver to unbind from the mcp2210 device so that the mcp2210 driver can probe it. If you know of a way to do this via udev rules, please notify me! (just create an issue via the issue tracker). 83 | 84 | Configuration & Setup 85 | ===================== 86 | As you may be aware, SPI does not offer a mechanism to automatically detect and configure peripherals. Typically, a particular chip is hard-wired to an SPI master and the driver for that master knows whats conntect to it. In Linux, SPI slave devices are configred via [`struct spi_board_info`](https://www.kernel.org/doc/htmldocs/device-drivers/API-struct-spi-board-info.html) objects. But for USB-to-SPI protocol bridges like the MCP2210, we can't know how its board is wired -- we need that information to come from somewhere else. 87 | 88 | The MCP2210 has an embedded EEPROM (most likely 512 bytes in size), part of which it uses for its own internal settings and 256 bytes which it exposes as "user-EEPROM". In its internal settings (3.1.1 through 3.1.11 in the datasheet), the MCP2210 stores its inital "power up" values for all parameters: 89 | 90 | * Mode of each pin (GPIO, SPI or dedicated) 91 | * Direction and value (for outputs) of each GPIO 92 | * The idle and active values of all chip select lines (only affects pins configured as SPI) 93 | * SPI mode, bitrate, and timing parameters 94 | * Some access control settings (for changing the persistent settings) 95 | * USB power control: self or host powered, requested power 96 | * USB vid, pid, mfg name, product name, etc. 97 | 98 | While this is a pleasntly comprehensive feature set, it is still not enough to fully configure & probe any board wiring because: 99 | 100 | 1. We only have transfer settings for a single SPI device. 101 | 2. We still aren't telling the USB host what chip is connected to each chip select. 102 | 103 | The mcp2210 driver offers two separate mechanisms to provide that information and configure the device: one from userland and an auto-configuration mechanism that utilizes the user area of the on-chip EEPROM. 104 | 105 | Configuring from Userland (Manual) 106 | ---------------------------------- 107 | When ioctl control is enabled (`CONFIG_MCP2210_IOCTL`), the mcp2210 driver will create a `/dev` node upon a successful probe with the name `usb2spi_bridge` (where x is a number) which can be used to interact with the driver. Userspace configuration is fairly straight-forward: 108 | 109 | ``` 110 | user/mcp2210-util set config 111 | ``` 112 | 113 | For mask, you OR together the values for the configuration option(s) you want to set: 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
BitOptionDatasheet Sectionstruct
1chip settings (current)3.2.4struct mcp2210_chip_settings
2chip settings (power-up)3.1.1struct mcp2210_chip_settings
4spi transfer settings (current)3.2.2struct mcp2210_spi_xfer_settings
8spi transfer settings (power-up)3.1.2struct mcp2210_spi_xfer_settings
16key parameters (power-up)3.1.3struct mcp2210_usb_key_params
32board confign/astruct mcp2210_board_config
153 | 154 | "But where the hell do you specify the settings!?" I hear you ask. Yeah, well currently, you have to modify `user/settings.h`, which is auto-generated from [`user/settings-example.h`](user/settings-example.h) the first time you run make and recompile. (If you don't like this, then please write an xml / json / something interface and send me a patch or pull request!) This is pretty straight forward, consisting of simple static const structs initialized with named (C99) initializers. 155 | 156 | WARNING: Modifying `nvram_access_control` can permanently lock you chip! Don't change it unless you really mean to do this and understand what you're doing. 157 | 158 | Automatic Configuration 159 | ----------------------- 160 | When the driver probes, it queries the device for (among other things) its power-up chip settings (section 3.1.1) and the power-up spi transfer settings (section 3.1.2). If Creek support is enabled (via `CONFIG_MCP2210_CREEK`), the first four bytes of the user EEPROM area are also read. If these match a "magic number", then the remainder of the user-EEPROM is read and decoded into a [`struct mcp2210_board_config`](mcp2210.h#L546) object which contains all of the information (timings for each SPI device, name of the spi protocol driver, label, etc.) to allow probing the spi_master. 161 | 162 | For details on the encoding format, see the comments in [`mcp2210-creek.h`](mcp2210-creek.h#L29) (see also [`creek_encode()`](mcp2210-lib.c#L618) and [`creek_decode()`](mcp2210-lib.c#L373)) 163 | 164 | NOTICE: At this time, the Creek binary format is unstable and subject to change without a version bump. 165 | 166 | Storing and Viewing Auto-Configure Data 167 | --------------------------------------- 168 | All support for creating this encoding from your [`struct mcp2210_board_config`](mcp2210.h#L546) in `user/settings.h` is in the userspace utility program. 169 | 170 | 1. Edit `user/settings.h` to your needs and recomple `mcp2210-util` 171 | 2. Run the following command to encode to config.dat: 172 | 173 | ``` 174 | mcp2210-util encode > config.dat 175 | ``` 176 | 177 | 3. `ls -l config.dat` to determine its size (yes, the utility should eventually do this for you) 178 | 4. Run the following command to store it to the user-EEPROM (replace size with the size of the file) 179 | 180 | ``` 181 | mcp2210-util eeprom write size= addr=0 < config.dat 182 | ``` 183 | 184 | Decoding is the inverse. However, the creek data doesn't include any data available in the "chip settings" (because this would be a redundant waste of limited space) so your chip settings in settings.h (or at least the pin modes) must match what was used when the encoding was generated. 185 | 186 | 1. Make sure my_chip_settings in `user/settings.h` is correct. 187 | 2. Run the following command: 188 | 189 | ``` 190 | mcp2210-util decode < config.dat 191 | ``` 192 | 193 | -------------------------------------------------------------------------------- /mcp2210-creek.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 Creek auto-configure support 3 | * 4 | * Copyright (c) 2013-2017 Daniel Santos 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | /** 22 | * @file 23 | * Creek is just another home-grown hardware configuration scheme where data is 24 | * stored in a compact fashion on a device (in this case, the MCP2210's usable 25 | * EEPROM) and used to configure a device. 26 | * 27 | * Creek -- because if it doesn't work, you're up one. 28 | * 29 | * Format of Creek Encoding 30 | * 31 | * Start End Descrption Size In Bits 32 | * ------------------------------------------------------------- 33 | * 0 31 magic (0xc01df00d) 32 34 | * 32 35 version 4 35 | * 36 53 9x Presence of Strings 18 36 | * 54 ? 3-Wire Capability 1 to 5 37 | * ? ? IRQs 1 to 63 38 | * ? ? Polling Data (only if IRQs are used) 0 to 52 39 | * ? ? 9x SPI Data 0 to 648 40 | * ? ? Strings arbitrary 41 | * Total 56 to 2048 42 | * 43 | * Polling Data and SPI data use an encoding where where each field is 44 | * compared to a common or standard value. If it matches that value, then a 45 | * single 0 bit is written and nothing else. If it doesn't match, then a 46 | * single 1 bit is written followed by the value as a floating point unsigned 47 | * interger (see pack_uint_opt() and unpack_uint_opt() for details). In the 48 | * tables below, the type "uint_opt" indicates this type of storage (not an 49 | * actual C type). 50 | * 51 | * Presence of Strings 52 | * Field Num Bits Presence 53 | * ---------------------------------------------------------------------- 54 | * has_name 1 always 55 | * has_modalias 1 always 56 | * 57 | * 58 | * 3-Wire Capability 59 | * Field Num Bits Presence 60 | * ---------------------------------------------------------------------- 61 | * _3wire_capable 1 always 62 | * _3wire_tx_enable_active_high 63 | * 1 only if _3wire_capable 64 | * _3wire_tx_enable_pin 3 only if _3wire_capable 65 | * 66 | * 67 | * IRQs 68 | * Field Num Bits Presence 69 | * ---------------------------------------------------------------------- 70 | * has_irq 1 only if spi, gpio or dedicated pin 6 71 | * irq.num 3 only if has_irq 72 | * irq.threaded 1 only if has_irq 73 | * irq.type 4 only if has_irq and gpio 74 | * 75 | * 76 | * Polling Data 77 | * This section is only present if one or more pins are configured to trigger 78 | * an IRQ. 79 | * 80 | * Field Type Default Value Precision Magnitude 81 | * ---------------------------------------------------------------------- 82 | * poll_gpio_usecs (1) uint 10 3 83 | * stale_gpio_usecs (1) uint_opt poll_gpio_usecs 10 3 84 | * same_as_gpio (2) bit 85 | * poll_intr_usecs (3) uint 10 3 86 | * stale_intr_usecs (3) uint_opt poll_intr_usecs 10 3 87 | * 88 | * Footnotes: 89 | * 1. Only if a gpio pin exists that uses an IRQ, otherwise not written. 90 | * 2. Only if pin 6 is dedicated and has an IRQ and a gpio with an irq exists, otherwise not written. 91 | * 3. Only if requirements for both (1) and (2) are met and same_as_gpio is 92 | * set, otherwise not written. 93 | * 94 | * 95 | * SPI Data 96 | * Field Type Presence Default Value Precision Mag. 97 | * --------------------------------------------------------------------------- 98 | * max_speed_hz (1) uint_opt always MCP2210_MAX_SPEED 10 3 99 | * min_speed_hz (1) uint_opt always MCP2210_MIN_SPEED 10 3 100 | * mode 8 bits always 101 | * use_cs_gpio 1 bit always 102 | * cs_gpio 3 bits, only if use_cs_gpio 103 | * cs_to_data_delay uint_opt always 0 7 2 104 | * last_byte_to_cs_delay uint_opt always 0 7 2 105 | * delay_between_bytes uint_opt always 0 7 2 106 | * delay_between_xfers uint_opt always 0 7 2 107 | * 108 | * Strings are encoded as 7 bit ASCII with bit 7 terminating the string. (We 109 | * could get better compression by writing string sizes first and then limiting 110 | * them to 6 bits, but we seem to have plenty of room so far.) 111 | * 112 | * Footnotes: 113 | * 1. Magnitide is 2 prior to v 114 | * 115 | */ 116 | 117 | #ifndef _MCP2210_CREEK_H 118 | #define _MCP2210_CREEK_H 119 | 120 | #ifdef __cplusplus 121 | extern "C" { 122 | #endif /* __cplusplus */ 123 | 124 | static const u8 CREEK_CONFIG_MAGIC[4] = {0xc0, 0x1d, 0xf0, 0x0d}; 125 | 126 | /** 127 | * struct bit_creek - a struct for managing a buffer as a FIFO stream of bits 128 | */ 129 | struct bit_creek { 130 | u8 *start; 131 | size_t size; 132 | size_t pos; 133 | u8 overflow:1; 134 | }; 135 | 136 | #define BIT_CREEK_INIT(buf, size) {buf, size, 0, 0,} 137 | 138 | struct creek_data { 139 | u8 magic[4]; 140 | u8 ver:4; 141 | u8 str_count; 142 | u8 str_size; 143 | const char **string_index; 144 | u8 spi_pin_num[MCP2210_NUM_PINS]; 145 | u8 spi_count; 146 | u8 name_index[MCP2210_NUM_PINS]; 147 | u8 modalias_index[MCP2210_NUM_PINS]; 148 | u8 have_gpio_irqs; 149 | }; 150 | 151 | uint creek_get_bits(struct bit_creek *bs, uint num_bits); 152 | int creek_put_bits(struct bit_creek *bs, uint value, uint num_bits); 153 | int creek_encode(const struct mcp2210_board_config *src, 154 | const struct mcp2210_chip_settings *chip, u8* dest, 155 | size_t size, u8 ver); 156 | struct mcp2210_board_config *creek_decode( 157 | const struct mcp2210_chip_settings *chip_settings, 158 | const u8* src, size_t size, gfp_t gfp_flags); 159 | 160 | #ifdef __KERNEL__ 161 | struct mcp2210_board_config *mcp2210_creek_probe(struct mcp2210_device *dev, 162 | gfp_t gfp_flags); 163 | #endif /* __KERNEL__ */ 164 | 165 | #ifdef __cplusplus 166 | } 167 | #endif /* __cplusplus */ 168 | #endif // _MCP2210_CREEK_H 169 | -------------------------------------------------------------------------------- /mcp2210-ctl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP 2210 driver for linux 3 | * 4 | * Copyright (c) 2013-2017 Daniel Santos 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "mcp2210.h" 26 | #include "mcp2210-debug.h" 27 | 28 | static int ctl_submit_prepare(struct mcp2210_cmd *cmd); 29 | static int ctl_complete_urb(struct mcp2210_cmd *cmd); 30 | 31 | const struct mcp2210_cmd_type mcp2210_cmd_type_ctl = { 32 | .id = MCP2210_CMD_TYPE_CTL, 33 | .submit_prepare = ctl_submit_prepare, 34 | .complete_urb = ctl_complete_urb, 35 | .dump = dump_cmd_ctl, 36 | .desc = "ctl", 37 | }; 38 | 39 | /****************************************************************************** 40 | * Control Command Functions 41 | */ 42 | 43 | static inline void flip_chip_settings(struct mcp2210_msg *msg) { 44 | struct mcp2210_chip_settings *body = &msg->body.chip; 45 | body->gpio_value = cpu_to_le16(body->gpio_value); 46 | body->gpio_direction = cpu_to_le16(body->gpio_direction); 47 | } 48 | 49 | static inline void flip_gpio(struct mcp2210_msg *msg) { 50 | msg->body.gpio = cpu_to_le16(msg->body.gpio); 51 | } 52 | 53 | static inline void flip_spi_settings(struct mcp2210_msg *msg) { 54 | struct mcp2210_spi_xfer_settings *body = &msg->body.spi; 55 | body->bitrate = cpu_to_le32(body->bitrate); 56 | body->idle_cs = cpu_to_le16(body->idle_cs); 57 | body->active_cs = cpu_to_le16(body->active_cs); 58 | body->cs_to_data_delay = cpu_to_le16(body->cs_to_data_delay); 59 | body->last_byte_to_cs_delay = cpu_to_le16(body->last_byte_to_cs_delay); 60 | body->delay_between_bytes = cpu_to_le16(body->delay_between_bytes); 61 | body->bytes_per_trans = cpu_to_le16(body->bytes_per_trans); 62 | } 63 | 64 | static inline void flip_get_usb_params(struct mcp2210_msg *msg) { 65 | msg->body.get_usb_params.vid = cpu_to_le16(msg->body.get_usb_params.vid); 66 | msg->body.get_usb_params.pid = cpu_to_le16(msg->body.get_usb_params.pid); 67 | } 68 | 69 | static inline void flip_set_usb_params(struct mcp2210_msg *msg) { 70 | msg->body.set_usb_params.vid = cpu_to_le16(msg->body.set_usb_params.vid); 71 | msg->body.set_usb_params.pid = cpu_to_le16(msg->body.set_usb_params.pid); 72 | } 73 | 74 | static inline void flip_interrupt_event_counter(struct mcp2210_msg *msg) { 75 | msg->body.interrupt_event_counter = cpu_to_le16(msg->body.interrupt_event_counter); 76 | } 77 | 78 | /* Keep this a no-inline, monolithic function */ 79 | static __attribute__((flatten)) noinline void flip_msg(struct mcp2210_msg *msg) 80 | { 81 | /* put our most common cases first */ 82 | switch (msg->cmd) { 83 | case MCP2210_CMD_SPI_TRANSFER: 84 | return; 85 | 86 | case MCP2210_CMD_GET_PIN_VALUE: 87 | case MCP2210_CMD_SET_PIN_VALUE: 88 | flip_gpio(msg); 89 | return; 90 | 91 | case MCP2210_CMD_GET_INTERRUPTS: 92 | flip_interrupt_event_counter(msg); 93 | return; 94 | }; 95 | 96 | switch (msg->cmd) { 97 | case MCP2210_CMD_SET_NVRAM: 98 | case MCP2210_CMD_GET_NVRAM: 99 | switch (msg->head.req.xet.sub_cmd) { 100 | case MCP2210_NVRAM_SPI: 101 | goto spi_config; 102 | 103 | case MCP2210_NVRAM_CHIP: 104 | goto chip_config; 105 | 106 | case MCP2210_NVRAM_KEY_PARAMS: 107 | if (msg->cmd == MCP2210_CMD_SET_NVRAM) 108 | flip_set_usb_params(msg); 109 | else 110 | flip_get_usb_params(msg); 111 | break; 112 | 113 | case MCP2210_NVRAM_PROD: 114 | case MCP2210_NVRAM_MFG: 115 | default: 116 | break; 117 | }; 118 | break; 119 | 120 | case MCP2210_CMD_GET_SPI_CONFIG: 121 | case MCP2210_CMD_SET_SPI_CONFIG: 122 | spi_config: 123 | flip_spi_settings(msg); 124 | break; 125 | 126 | case MCP2210_CMD_GET_CHIP_CONFIG: 127 | case MCP2210_CMD_SET_CHIP_CONFIG: 128 | chip_config: 129 | flip_chip_settings(msg); 130 | break; 131 | 132 | case MCP2210_CMD_GET_PIN_DIR: 133 | case MCP2210_CMD_SET_PIN_DIR: 134 | flip_gpio(msg); 135 | break; 136 | 137 | case MCP2210_CMD_READ_EEPROM: 138 | case MCP2210_CMD_WRITE_EEPROM: 139 | case MCP2210_CMD_SPI_CANCEL: 140 | case MCP2210_CMD_SPI_RELEASE: 141 | case MCP2210_CMD_SEND_PASSWD: 142 | default: 143 | break; 144 | }; 145 | } 146 | 147 | static inline void flip_ep_buffer(struct mcp2210_endpoint *ep, unsigned to_mcp) 148 | { 149 | to_mcp = !!to_mcp; 150 | 151 | if (ep->is_mcp_endianness != to_mcp) { 152 | flip_msg(ep->buffer); 153 | ep->is_mcp_endianness = to_mcp; 154 | } 155 | } 156 | 157 | static inline void flip_ctl_cmd_req(struct mcp2210_cmd_ctl *cmd, unsigned to_mcp) 158 | { 159 | to_mcp = !!to_mcp; 160 | 161 | if (cmd->is_mcp_endianness != to_mcp) { 162 | flip_msg(&cmd->req); 163 | cmd->is_mcp_endianness = to_mcp; 164 | } 165 | } 166 | 167 | static void store_chip_settings(struct mcp2210_device *dev, 168 | const struct mcp2210_chip_settings *cfg, 169 | int is_power_up) 170 | { 171 | struct mcp2210_chip_settings *dest = is_power_up 172 | ? &dev->s.power_up_chip_settings 173 | : &dev->s.chip_settings; 174 | 175 | memcpy(dest, cfg, sizeof(*cfg)); 176 | 177 | if (is_power_up) 178 | dev->s.have_power_up_chip_settings = 1; 179 | else 180 | dev->s.have_chip_settings = 1; 181 | } 182 | 183 | static void store_spi_settings(struct mcp2210_device *dev, 184 | const struct mcp2210_spi_xfer_settings *cfg, 185 | unsigned pin, int is_power_up) 186 | { 187 | struct mcp2210_spi_xfer_settings *dest = is_power_up 188 | ? &dev->s.power_up_spi_settings 189 | : &dev->s.spi_settings; 190 | 191 | memcpy(dest, cfg, sizeof(*cfg)); 192 | 193 | if (is_power_up) 194 | dev->s.have_power_up_spi_settings = 1; 195 | else { 196 | unsigned long data_xfer_time; 197 | //struct mcp2210_pin_config_spi *devcfg = &dev->config.pins[pin].body.spi; 198 | dev->s.have_spi_settings = 1; 199 | dev->s.cur_spi_config = pin < MCP2210_NUM_PINS ? pin : -1; 200 | dev->s.idle_cs = cfg->idle_cs; 201 | dev->s.active_cs = cfg->active_cs; 202 | 203 | 204 | /* on 32-bit machines, use a less accurate calculation to 205 | * prevent overflow */ 206 | BUILD_BUG_ON(sizeof(unsigned long) < 4); 207 | if (sizeof(unsigned long) == 4) 208 | data_xfer_time = 10000ul * 1024ul * 8ul 209 | / cfg->bitrate * 100ul; 210 | else 211 | data_xfer_time = 1000000ul * 1024ul * 8ul 212 | / cfg->bitrate; 213 | dev->s.spi_delay_per_kb = data_xfer_time + 100ul * 1024ul 214 | * cfg->delay_between_bytes; 215 | } 216 | } 217 | 218 | static void store_usb_key_params(struct mcp2210_device *dev, 219 | const struct mcp2210_usb_key_params *params, 220 | const typeof((*(struct mcp2210_msg*)0).body.get_usb_params) *params_get) 221 | { 222 | struct mcp2210_usb_key_params tmp; 223 | 224 | /* playing protocol-as-structs screws us here, but we can fix it */ 225 | if (params_get) { 226 | tmp.vid = params_get->vid; 227 | tmp.pid = params_get->pid; 228 | tmp.chip_power_option = params_get->chip_power_option; 229 | tmp.requested_power = params_get->requested_power; 230 | params = &tmp; 231 | } 232 | 233 | memcpy(&dev->s.usb_key_params, params, sizeof(*params)); 234 | dev->s.have_usb_key_params = 1; 235 | } 236 | 237 | /* if the command needs to be repeated, then just set the state to NEW before 238 | * calling process_commands */ 239 | static int ctl_complete_urb(struct mcp2210_cmd *cmd_head) 240 | { 241 | struct mcp2210_device *dev = cmd_head->dev; 242 | struct mcp2210_cmd_ctl *cmd = (struct mcp2210_cmd_ctl *)cmd_head; 243 | 244 | struct mcp2210_msg *req = &cmd->req;//dev->eps[EP_OUT].buffer; 245 | struct mcp2210_msg *rep = dev->eps[EP_IN].buffer; 246 | u8 sub_cmd = req->head.req.xet.sub_cmd; 247 | 248 | /* First we need to convert the correct buffer to the endianness */ 249 | switch (rep->cmd) { 250 | case MCP2210_CMD_GET_NVRAM: 251 | case MCP2210_CMD_GET_SPI_CONFIG: 252 | case MCP2210_CMD_GET_CHIP_CONFIG: 253 | case MCP2210_CMD_GET_PIN_DIR: 254 | case MCP2210_CMD_GET_PIN_VALUE: 255 | case MCP2210_CMD_GET_INTERRUPTS: 256 | //mcp2210_info("flip ep"); 257 | flip_ep_buffer(&dev->eps[EP_IN], false); 258 | break; 259 | case MCP2210_CMD_SET_NVRAM: 260 | case MCP2210_CMD_SET_SPI_CONFIG: 261 | case MCP2210_CMD_SET_CHIP_CONFIG: 262 | case MCP2210_CMD_SET_PIN_DIR: 263 | case MCP2210_CMD_SET_PIN_VALUE: 264 | //mcp2210_info("flip req"); 265 | flip_ctl_cmd_req(cmd, false); 266 | break; 267 | default: 268 | break; 269 | }; 270 | 271 | switch (rep->cmd) { 272 | /* When retrieving settings, we store a copy of the response body */ 273 | case MCP2210_CMD_GET_NVRAM: 274 | switch (sub_cmd) { 275 | case MCP2210_NVRAM_SPI: 276 | mcp2210_info("MCP2210_CMD_GET_NVRAM: SPI xfer settings"); 277 | store_spi_settings(dev, &rep->body.spi, cmd->pin, true); 278 | break; 279 | case MCP2210_NVRAM_CHIP: 280 | mcp2210_info("MCP2210_CMD_GET_NVRAM: chip settings"); 281 | store_chip_settings(dev, &rep->body.chip, true); 282 | break; 283 | case MCP2210_NVRAM_KEY_PARAMS: 284 | mcp2210_info("MCP2210_CMD_GET_NVRAM: usb key params"); 285 | store_usb_key_params(dev, 0, &rep->body.get_usb_params); 286 | break; 287 | case MCP2210_NVRAM_PROD: 288 | case MCP2210_NVRAM_MFG: 289 | /* we don't care about these */ 290 | default: 291 | mcp2210_err("MCP2210_CMD_%cET_NVRAM: unprocessed sub " 292 | "command! 0x%02hhx", 'G', sub_cmd); 293 | break; 294 | }; 295 | break; 296 | 297 | /* When writing settings, we store a copy of the request body */ 298 | case MCP2210_CMD_SET_NVRAM: 299 | switch (sub_cmd) { 300 | case MCP2210_NVRAM_SPI: 301 | mcp2210_info("MCP2210_CMD_SET_NVRAM: SPI xfer settings"); 302 | store_spi_settings(dev, &req->body.spi, cmd->pin, true); 303 | break; 304 | case MCP2210_NVRAM_CHIP: 305 | mcp2210_info("MCP2210_CMD_SET_NVRAM: chip settings"); 306 | store_chip_settings(dev, &req->body.chip, true); 307 | //dump_chip_settings(&rep->body.chip); 308 | break; 309 | case MCP2210_NVRAM_KEY_PARAMS: 310 | mcp2210_info("MCP2210_CMD_SET_NVRAM: usb key params"); 311 | store_usb_key_params(dev, &req->body.set_usb_params, 0); 312 | break; 313 | case MCP2210_NVRAM_PROD: 314 | case MCP2210_NVRAM_MFG: 315 | /* we don't care about these */ 316 | default: 317 | mcp2210_err("MCP2210_CMD_%cET_NVRAM: unprocessed sub " 318 | "command! 0x%02hhx", 'S', sub_cmd); 319 | break; 320 | }; 321 | break; 322 | 323 | case MCP2210_CMD_GET_SPI_CONFIG: 324 | mcp2210_info("MCP2210_CMD_GET_SPI_CONFIG"); 325 | store_spi_settings(dev, &rep->body.spi, cmd->pin, false); 326 | break; 327 | case MCP2210_CMD_SET_SPI_CONFIG: 328 | mcp2210_info("MCP2210_CMD_SET_SPI_CONFIG"); 329 | store_spi_settings(dev, &req->body.spi, cmd->pin, false); 330 | break; 331 | case MCP2210_CMD_GET_CHIP_CONFIG: 332 | mcp2210_info("MCP2210_CMD_GET_CHIP_CONFIG"); 333 | store_chip_settings(dev, &rep->body.chip, false); 334 | break; 335 | case MCP2210_CMD_SET_CHIP_CONFIG: 336 | mcp2210_info("MCP2210_CMD_SET_CHIP_CONFIG"); 337 | store_chip_settings(dev, &req->body.chip, false); 338 | break; 339 | case MCP2210_CMD_SPI_CANCEL: 340 | mcp2210_info("MCP2210_CMD_SPI_CANCEL"); 341 | dev->spi_in_flight = 0; 342 | break; 343 | case MCP2210_CMD_GET_PIN_DIR: 344 | dev->s.chip_settings.gpio_direction = rep->body.gpio; 345 | mcp2210_info("MCP2210_CMD_GET_PIN_DIR"); 346 | break; 347 | case MCP2210_CMD_SET_PIN_DIR: 348 | dev->s.chip_settings.gpio_direction = req->body.gpio; 349 | mcp2210_info("MCP2210_CMD_SET_PIN_DIR"); 350 | break; 351 | case MCP2210_CMD_GET_PIN_VALUE: 352 | /* notify interrupt controller */ 353 | if (dev->is_irq_probed) 354 | mcp2210_irq_do_gpio(dev, dev->s.chip_settings.gpio_value, 355 | rep->body.gpio); 356 | dev->s.chip_settings.gpio_value = rep->body.gpio; 357 | mcp2210_info("MCP2210_CMD_GET_PIN_VALUE"); 358 | #ifdef CONFIG_MCP2210_IRQ 359 | dev->last_poll_gpio = dev->eps[EP_IN].submit_time; 360 | #endif 361 | break; 362 | case MCP2210_CMD_SET_PIN_VALUE: 363 | dev->s.chip_settings.gpio_value = req->body.gpio; 364 | mcp2210_info("MCP2210_CMD_SET_PIN_VALUE"); 365 | break; 366 | case MCP2210_CMD_GET_INTERRUPTS: 367 | #ifdef CONFIG_MCP2210_IRQ 368 | /* notify interrupt controller */ 369 | if (dev->is_irq_probed) 370 | mcp2210_irq_do_intr_counter(dev, rep->body.interrupt_event_counter); 371 | dev->interrupt_event_counter = req->body.interrupt_event_counter; 372 | mcp2210_info("MCP2210_CMD_GET_INTERRUPTS"); 373 | dev->last_poll_intr = jiffies; 374 | #endif /* CONFIG_MCP2210_IRQ */ 375 | break; 376 | 377 | default: 378 | mcp2210_err("unprocessed command code: 0x%02hhx", rep->cmd); 379 | 380 | }; 381 | 382 | return 0; 383 | } 384 | 385 | static int ctl_submit_prepare(struct mcp2210_cmd *cmd_head) 386 | { 387 | const struct mcp2210_cmd_type *type = cmd_head->type; 388 | struct mcp2210_cmd_ctl *cmd = (struct mcp2210_cmd_ctl *)cmd_head; 389 | struct mcp2210_device *dev; 390 | 391 | if (!cmd_head || !cmd_head->dev || !type || type != &mcp2210_cmd_type_ctl) { 392 | printk(KERN_ERR "%s: bad data! %p, %p, %p, %d", __func__, 393 | cmd_head, cmd_head->dev, type, type != &mcp2210_cmd_type_ctl); 394 | return -EINVAL; 395 | } 396 | 397 | dev = cmd_head->dev; 398 | 399 | mcp2210_info(); 400 | 401 | if (dev->dead) 402 | return -ESHUTDOWN; 403 | 404 | flip_ctl_cmd_req(cmd, true); 405 | memcpy(dev->eps[EP_OUT].buffer, &cmd->req, sizeof(cmd->req)); 406 | memset(dev->eps[EP_IN].buffer, 0, 64); /* FIXME: eventually remove, not really needed */ 407 | 408 | mcp2210_debug("cmd->is_mcp_endianness = %hhu", cmd->is_mcp_endianness); 409 | 410 | return 0; 411 | } 412 | 413 | /** 414 | * ctl_cmd_init - initialize a struct mcp2210_cmd_ctl object 415 | * @pin: either pin 0-8 or 0xff to indicate that no pin is intended to 416 | * be specified. 417 | */ 418 | void ctl_cmd_init(struct mcp2210_device *dev, struct mcp2210_cmd_ctl *cmd, 419 | u8 cmd_code, u8 subcmd_code, void *body, size_t body_size, 420 | u8 is_mcp_endianness) 421 | { 422 | cmd->head.dev = dev; 423 | cmd->head.type = &mcp2210_cmd_type_ctl; 424 | mcp2210_init_msg(&cmd->req, cmd_code, subcmd_code, 0, body, 425 | body_size, true); 426 | cmd->head.can_retry = 1; 427 | cmd->pin = 0x7f; 428 | cmd->is_mcp_endianness = is_mcp_endianness; 429 | 430 | mcp2210_debug("pin = %hhu, is_mcp_endianness = %hhu", cmd->pin, cmd->is_mcp_endianness); 431 | 432 | } 433 | 434 | 435 | /** mcp2210_add_ctl_cmd - add a new mcp2210 control command to the queue 436 | * 437 | * Adds the newly allocated and initialized control command to the queue and 438 | * returns its address. 439 | */ 440 | struct mcp2210_cmd_ctl *mcp2210_alloc_ctl_cmd(struct mcp2210_device *dev, 441 | u8 cmd_code, u8 subcmd_code, void *body, size_t body_size, 442 | u8 is_mcp_endianness, gfp_t gfp_flags) 443 | { 444 | struct mcp2210_cmd_ctl *cmd = mcp2210_alloc_cmd_type(dev, 445 | struct mcp2210_cmd_ctl, &mcp2210_cmd_type_ctl, 446 | gfp_flags); 447 | 448 | if (cmd) 449 | ctl_cmd_init(dev, cmd, cmd_code, subcmd_code, body, body_size, is_mcp_endianness); 450 | 451 | return cmd; 452 | } 453 | 454 | #ifdef CONFIG_MCP2210_SPI 455 | static void calculate_active_cs(const struct mcp2210_device *dev, 456 | const struct spi_device *spi, u8 pin, 457 | u16 *active_cs, u16 *idle_cs) 458 | { 459 | const struct mcp2210_pin_config *pin_cfg; 460 | u8 i; 461 | u8 use_cs_gpio = dev->config->pins[pin].spi.use_cs_gpio; 462 | 463 | *active_cs = 0; 464 | *idle_cs = 0; 465 | 466 | for (i = 0; i < MCP2210_NUM_PINS; ++i) { 467 | pin_cfg = &dev->config->pins[i]; 468 | if (pin_cfg->mode == MCP2210_PIN_SPI) { 469 | u16 cs = !(pin_cfg->spi.mode & SPI_CS_HIGH); 470 | 471 | *idle_cs |= cs << i; 472 | if (!use_cs_gpio && pin == i) { 473 | if (spi) 474 | cs = !!(spi->mode & SPI_CS_HIGH); 475 | else 476 | cs = !cs; 477 | } 478 | 479 | *active_cs |= cs << i; 480 | } 481 | } 482 | } 483 | 484 | void calculate_spi_settings(struct mcp2210_spi_xfer_settings *dest, 485 | const struct mcp2210_device *dev, 486 | const struct spi_device *spi, 487 | const struct spi_message *msg, 488 | const struct spi_transfer *xfer, u8 pin) 489 | { 490 | const struct mcp2210_spi_xfer_settings *cur; 491 | const struct spi_transfer *pos = xfer; 492 | uint len = 0; 493 | 494 | if (IS_ENABLED(CONFIG_MCP2210_DEBUG)) { 495 | BUG_ON(!dev); 496 | BUG_ON(!dev->config); 497 | BUG_ON(!spi); 498 | BUG_ON(!xfer); 499 | BUG_ON(dev->spi_master != spi->master); 500 | BUG_ON(pin > 8); 501 | } 502 | 503 | /* count bytes in this transfer chain */ 504 | list_for_each_entry_from(pos, &msg->transfers, transfer_list) { 505 | len += pos->len; 506 | 507 | if (pos->cs_change) 508 | break; 509 | } 510 | 511 | cur = (pin == dev->s.cur_spi_config) ? &dev->s.spi_settings : NULL; 512 | 513 | /* per-xfer takes precedence */ 514 | if (xfer->speed_hz) 515 | dest->bitrate = xfer->speed_hz; 516 | else 517 | dest->bitrate = spi->max_speed_hz; 518 | 519 | calculate_active_cs(dev, spi, pin, &dest->active_cs, &dest->idle_cs); 520 | dest->bytes_per_trans = len; 521 | /* "mode" for the mcp2210 means "spi mode" only */ 522 | dest->mode = spi->mode & SPI_MODE_3; 523 | 524 | if (cur) { 525 | dest->cs_to_data_delay = cur->cs_to_data_delay; 526 | dest->last_byte_to_cs_delay = cur->last_byte_to_cs_delay; 527 | dest->delay_between_bytes = cur->delay_between_bytes; 528 | } else { 529 | const struct mcp2210_pin_config_spi *cfg = &dev->config 530 | ->pins[pin].spi; 531 | dest->cs_to_data_delay = cfg->cs_to_data_delay; 532 | dest->last_byte_to_cs_delay = cfg->last_byte_to_cs_delay; 533 | dest->delay_between_bytes = cfg->delay_between_bytes; 534 | } 535 | } 536 | #endif /* CONFIG_MCP2210_SPI */ 537 | 538 | -------------------------------------------------------------------------------- /mcp2210-debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Verbose Debug functions for MCP2210 driver & libs 3 | * 4 | * Copyright (c) 2013-2017 Daniel Santos 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 3 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | 22 | #ifndef _MCP2210_DEBUG_H 23 | #define _MCP2210_DEBUG_H 24 | 25 | #include "mcp2210.h" 26 | 27 | #ifdef __KERNEL__ 28 | # include 29 | # include 30 | #endif 31 | 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif /* __cplusplus */ 36 | 37 | /* Do not define CONFIG_MCP2210_LOGGING_FAST_PATH unless you're debugging a 38 | * timing-sensitive a problem and you need log spew to be as optimized as 39 | * possible. */ 40 | #ifdef CONFIG_MCP2210_LOGGING_FAST_PATH 41 | # define MCP2210_LOG_UNLIKELY(expr) (expr) 42 | #else 43 | # define MCP2210_LOG_UNLIKELY(expr) unlikely(expr) 44 | #endif 45 | 46 | #ifdef __KERNEL__ 47 | 48 | #define _mcp2210_log(level, fmt, ...) \ 49 | do { \ 50 | if (MCP2210_LOG_UNLIKELY((level[1] - '0') <= debug_level))\ 51 | dev_printk(level, &dev->udev->dev, \ 52 | "%s: " fmt, __func__, ##__VA_ARGS__);\ 53 | } while(0) 54 | 55 | /* TODO: we need to submit a cleaned up version of this to mainline for 56 | * compiler*.h */ 57 | #define warn_on_non_const(value) \ 58 | do { \ 59 | extern void not_const_warn(void) __compiletime_warning( \ 60 | #value " is not a compile-time constant, this " \ 61 | "will cause some bloating of generated code"); \ 62 | \ 63 | if (!__builtin_constant_p(value)) \ 64 | not_const_warn(); \ 65 | } while(0) 66 | 67 | /** 68 | * mcp2210_log - logs messages 69 | * 70 | * Logs messages but assures that debug messages are completely compiled out 71 | * when CONFIG_MCP2210_DEBUG is not enabled 72 | */ 73 | #define mcp2210_log(level, fmt, ...) \ 74 | do { \ 75 | const char _level = level[1] - '0'; \ 76 | const char _dbg = KERN_DEBUG[1] - '0'; \ 77 | \ 78 | warn_on_non_const(_level); \ 79 | \ 80 | /* compile-out debug messages unless \ 81 | * CONFIG_MCP2210_DEBUG is enabled */ \ 82 | if (!IS_ENABLED(CONFIG_MCP2210_DEBUG) && _level == _dbg)\ 83 | break; \ 84 | \ 85 | _mcp2210_log(level, fmt, ##__VA_ARGS__); \ 86 | } while(0) 87 | 88 | #define mcp2210_emerg(fmt, ...) mcp2210_log(KERN_EMERG, fmt, ##__VA_ARGS__) 89 | #define mcp2210_alert(fmt, ...) mcp2210_log(KERN_ALERT, fmt, ##__VA_ARGS__) 90 | #define mcp2210_crit(fmt, ...) mcp2210_log(KERN_CRIT, fmt, ##__VA_ARGS__) 91 | #define mcp2210_err(fmt, ...) mcp2210_log(KERN_ERR, fmt, ##__VA_ARGS__) 92 | #define mcp2210_warn(fmt, ...) mcp2210_log(KERN_WARNING,fmt, ##__VA_ARGS__) 93 | #define mcp2210_notice(fmt, ...)mcp2210_log(KERN_NOTICE,fmt, ##__VA_ARGS__) 94 | #define mcp2210_info(fmt, ...) mcp2210_log(KERN_INFO, fmt, ##__VA_ARGS__) 95 | #define mcp2210_debug(fmt, ...) mcp2210_log(KERN_DEBUG, fmt, ##__VA_ARGS__) 96 | 97 | #ifdef CONFIG_MCP2210_DEBUG 98 | # define MCP_ASSERT(cond) BUG_ON(!(cond)) 99 | #else 100 | # define MCP_ASSERT(cond) do{}while(0) 101 | #endif 102 | 103 | #ifdef CONFIG_MCP2210_DEBUG_VERBOSE 104 | void dump_dev( 105 | const char *level, unsigned indent, const char *start, 106 | const struct mcp2210_device *dev); 107 | void dump_ep( 108 | const char *level, unsigned indent, const char *start, 109 | const struct mcp2210_endpoint *ep); 110 | void dump_cmd_head( 111 | const char *level, unsigned indent, const char *start, 112 | const struct mcp2210_cmd *cmd); 113 | void dump_cmd_ctl( 114 | const char *level, unsigned indent, const char *start, 115 | const struct mcp2210_cmd *cmd_head); 116 | void dump_cmd_spi( 117 | const char *level, unsigned indent, const char *start, 118 | const struct mcp2210_cmd *cmd_head); 119 | void dump_cmd_eeprom( 120 | const char *level, unsigned indent, const char *start, 121 | const struct mcp2210_cmd *cmd_head); 122 | void dump_spi_message( 123 | const char *level, unsigned indent, const char *start, 124 | const struct spi_message *msg); 125 | void dump_spi_transfer( 126 | const char *level, unsigned indent, const char *start, 127 | const struct spi_transfer *xfer); 128 | void dump_spi_device( 129 | const char *level, unsigned indent, const char *start, 130 | const struct spi_device *spi_dev); 131 | #else 132 | # define dump_dev _dump_nothing_void 133 | # define dump_ep _dump_nothing_void 134 | # define dump_cmd_head _dump_nothing_cmd 135 | # define dump_cmd_ctl _dump_nothing_cmd 136 | # define dump_cmd_spi _dump_nothing_cmd 137 | # define dump_cmd_eeprom _dump_nothing_cmd 138 | # define dump_spi_message _dump_nothing_void 139 | # define dump_spi_transfer _dump_nothing_void 140 | # define dump_spi_device _dump_nothing_void 141 | 142 | static inline void _dump_nothing_cmd( 143 | const char *level, unsigned indent, const char *start, 144 | const struct mcp2210_cmd *cmd_head){} 145 | static inline void _dump_nothing_void( 146 | const char *level, unsigned indent, const char *start, 147 | const void *cmd_head){} 148 | #endif 149 | 150 | static inline void print_mcp_msg(const char *level, const char *start, 151 | const struct mcp2210_msg *data) 152 | { 153 | print_hex_dump(level, start, DUMP_PREFIX_OFFSET, 16, 1, data, 64, true); 154 | } 155 | 156 | void _mcp2210_dump_urbs(struct mcp2210_device *dev, const char *level, 157 | int urb_mask); 158 | static inline void mcp2210_dump_urbs(struct mcp2210_device *dev, 159 | const char *level, int urb_mask) 160 | { 161 | if (IS_ENABLED(CONFIG_MCP2210_DEBUG) && (dump_urbs 162 | || level[1] <= KERN_WARNING[1])) 163 | _mcp2210_dump_urbs(dev, level, urb_mask); 164 | } 165 | 166 | void _dump_cmd(const char *level, unsigned indent, const char *start, 167 | const struct mcp2210_cmd *cmd_head); 168 | static inline void dump_cmd(const char *level, unsigned indent, 169 | const char *start, 170 | const struct mcp2210_cmd *cmd_head) 171 | { 172 | if (IS_ENABLED(CONFIG_MCP2210_DEBUG_VERBOSE) && dump_cmds) 173 | _dump_cmd(level, indent, start, cmd_head); 174 | } 175 | 176 | 177 | #endif /* __KERNEL__ */ 178 | 179 | /* both kernel & userspace functions */ 180 | #ifdef CONFIG_MCP2210_DEBUG_VERBOSE 181 | const char *get_cmd_str(u8 cmd); 182 | const char *get_sub_cmd_str(u8 sub_cmd); 183 | const char *get_status_str(u8 status); 184 | const char *get_pin_mode_str(u8 mode); 185 | const char *get_eeprom_status_str(u8 mode); 186 | const char *get_cmd_type_str(u8 mode); 187 | const char *get_state_str(u8 mode); 188 | void dump_pin_config( 189 | const char *level, unsigned indent, const char *start, 190 | const struct mcp2210_pin_config *cfg); 191 | void dump_chip_settings( 192 | const char *level, unsigned indent, const char *start, 193 | const struct mcp2210_chip_settings *s); 194 | void dump_board_config( 195 | const char *level, unsigned indent, const char *start, 196 | const struct mcp2210_board_config *bc); 197 | void dump_spi_xfer_settings( 198 | const char *level, unsigned indent, const char *start, 199 | const struct mcp2210_spi_xfer_settings *s); 200 | void dump_usb_key_params( 201 | const char *level, unsigned indent, const char *start, 202 | const struct mcp2210_usb_key_params *params); 203 | void dump_state( 204 | const char *level, unsigned indent, const char *start, 205 | const struct mcp2210_state *s); 206 | void dump_mcp_msg( 207 | const char *level, unsigned indent, const char *start, 208 | struct mcp2210_msg *msg, int is_req); 209 | #else 210 | # define get_cmd_str(value) while(0){} 211 | # define get_sub_cmd_str(value) while(0){} 212 | # define get_status_str(value) while(0){} 213 | # define get_pin_mode_str(value) while(0){} 214 | # define get_eeprom_status_str(value) while(0){} 215 | # define get_cmd_type_str(value) while(0){} 216 | # define get_state_str(value) while(0){} 217 | # define dump_pin_config(level, indent, start, x) while(0){} 218 | # define dump_chip_settings(level, indent, start, x) while(0){} 219 | # define dump_board_config(level, indent, start, x) while(0){} 220 | # define dump_spi_xfer_settings(level, indent, start, x)while(0){} 221 | # define dump_usb_key_params(level, indent, start, x) while(0){} 222 | # define dump_state(level, indent, start, x) while(0){} 223 | # define dump_mcp_msg(level, indent, start, x) while(0){} 224 | #endif /* CONFIG_MCP2210_DEBUG_VERBOSE */ 225 | 226 | 227 | /* compile-time validation of struct mcp2210_msg */ 228 | static inline void msg_validate_size(void) 229 | { 230 | struct mcp2210_msg validation_msg; 231 | 232 | BUILD_BUG_ON(IS_ENABLED(CONFIG_MCP2210_CREEK) 233 | && (!IS_ENABLED(CONFIG_MCP2210_EEPROM))); 234 | 235 | /* sanity checks on struct mcp2210_msg */ 236 | BUILD_BUG_ON(sizeof(struct mcp2210_msg) != MCP2210_BUFFER_SIZE); 237 | BUILD_BUG_ON(sizeof(validation_msg.head) != 3); 238 | BUILD_BUG_ON(sizeof(validation_msg.body) != 60); 239 | 240 | /* validate head offsets */ 241 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, head) != 1); 242 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, head.req) != 1); 243 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, head.rep) != 1); 244 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, head.rep.status) != 1); 245 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, head.rep.xet.status) != 1); 246 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, head.rep.xet.sub_cmd) != 2); 247 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, head.rep.xet.reserved) != 3); 248 | 249 | /* validate body offsets */ 250 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body) != 4); 251 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.chip) != 4); 252 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.spi) != 4); 253 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.get_usb_params) != 4); 254 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.set_usb_params) != 4); 255 | 256 | /* validate body sizes */ 257 | BUILD_BUG_ON(sizeof(validation_msg.body.chip) != 23); 258 | BUILD_BUG_ON(sizeof(validation_msg.body.spi) != 17); 259 | BUILD_BUG_ON(sizeof(validation_msg.body.get_usb_params) != 27); 260 | BUILD_BUG_ON(sizeof(validation_msg.body.set_usb_params) != 6); 261 | BUILD_BUG_ON(sizeof(validation_msg.body.usb_string) != 60); 262 | BUILD_BUG_ON(sizeof(validation_msg.body.raw) != 60); 263 | 264 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.chip.password) != 19); 265 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.spi.mode) != 20); 266 | 267 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.gpio) != 4); 268 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.password) != 4); 269 | BUILD_BUG_ON(offsetof(struct mcp2210_msg, body.raw) != 4); 270 | } 271 | 272 | #ifdef __cplusplus 273 | } 274 | #endif /* __cplusplus */ 275 | #endif /* _MCP2210_DEBUG_H */ 276 | -------------------------------------------------------------------------------- /mcp2210-eeprom.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 eeprom management 3 | * 4 | * Copyright (c) 2013-2017 Daniel Santos 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "mcp2210.h" 26 | #include "mcp2210-debug.h" 27 | 28 | #ifdef CONFIG_MCP2210_EEPROM 29 | 30 | static int eeprom_submit_prepare(struct mcp2210_cmd *cmd_head); 31 | static int eeprom_complete_urb(struct mcp2210_cmd *cmd_head); 32 | 33 | const static struct mcp2210_cmd_type mcp2210_cmd_type_eeprom = { 34 | .id = MCP2210_CMD_TYPE_EEPROM, 35 | .submit_prepare = eeprom_submit_prepare, 36 | .complete_urb = eeprom_complete_urb, 37 | .dump = dump_cmd_eeprom, 38 | .desc = "eeprom", 39 | }; 40 | 41 | static inline int is_eeprom_range_valid(u8 addr, u16 size) 42 | { 43 | int ret = size && (size + (u16)addr) <= 0x100; 44 | if (unlikely(!ret)) 45 | printk(KERN_ERR "%s: invalid address, size or both. " 46 | "addr=0x%02hhx, size=0x%02x, sum=0x%02x", 47 | __func__, addr, size, (size + (unsigned)addr)); 48 | return ret; 49 | } 50 | 51 | static inline int get_eeprom_byte_status(struct mcp2210_device *dev, u8 addr) 52 | { 53 | const unsigned shift = (addr % 4) * 2; 54 | 55 | BUG_ON(!is_eeprom_range_valid(addr, 1)); 56 | return (dev->eeprom_state[addr / 4] >> shift) & 3; 57 | } 58 | 59 | static inline void set_eeprom_byte_status(struct mcp2210_device *dev, u8 addr, 60 | int status) 61 | { 62 | u8 * const p = &dev->eeprom_state[addr / 4]; 63 | const unsigned shift = (addr % 4) * 2; 64 | 65 | BUG_ON(!is_eeprom_range_valid(addr, 1)); 66 | BUILD_BUG_ON(status != (status & 3)); 67 | 68 | *p &= ~(3 << shift); 69 | *p |= status << shift; 70 | } 71 | 72 | static int queue_eeprom_cmd(struct mcp2210_device *dev, u8 op, u8 addr, 73 | u16 size, mcp2210_complete_t complete, 74 | void *context, gfp_t gfp_flags) 75 | { 76 | struct mcp2210_cmd_eeprom *cmd = mcp2210_alloc_cmd_type(dev, 77 | struct mcp2210_cmd_eeprom, &mcp2210_cmd_type_eeprom, 78 | gfp_flags); 79 | //unsigned long irqflags; 80 | int ret; 81 | 82 | mcp2210_debug("dev: %p, op: 0x%02hhx, addr: 0x%02hhx, size: 0x%02hhx", 83 | dev, op, addr, size); 84 | if (!cmd) 85 | return -ENOMEM; 86 | 87 | if (unlikely(!(op == MCP2210_CMD_READ_EEPROM 88 | || op == MCP2210_CMD_WRITE_EEPROM))) { 89 | mcp2210_err("operation not one of MCP2210_CMD_READ_EEPROM or " 90 | "MCP2210_CMD_WRITE_EEPROM"); 91 | return -EINVAL; 92 | } 93 | 94 | cmd->head.can_retry = 1; 95 | cmd->op = op; 96 | cmd->addr = addr; 97 | cmd->size = size; 98 | cmd->zero_tail = 1; 99 | cmd->head.complete = complete; 100 | cmd->head.context = context; 101 | 102 | // dump_cmd_eeprom(KERN_WARNING, 0, "eeprom_cmd = ", &cmd->head); 103 | 104 | ret = mcp2210_add_cmd(&cmd->head, true); 105 | if (!dev->cur_cmd) 106 | process_commands(dev, false, gfp_flags == GFP_KERNEL); 107 | 108 | return ret; 109 | } 110 | 111 | /* 112 | * Populates addr with the next address for this command to process. 113 | * 114 | * Returns zero if there are no (more) addresses that need to be processed, 115 | * non-zero otherwise. 116 | */ 117 | static int next_addr(struct mcp2210_cmd_eeprom *cmd, u8 *addr) 118 | { 119 | const u16 end = cmd->addr + cmd->size; 120 | u16 i; 121 | 122 | for (i = cmd->addr; i < end; ++i) { 123 | switch (get_eeprom_byte_status(cmd->head.dev, i)) { 124 | case MCP2210_EEPROM_READ: 125 | continue; 126 | 127 | case MCP2210_EEPROM_DIRTY: 128 | /* We wont write dirty values on a read command, but 129 | * there should be an upcomming write command in the 130 | * queue. 131 | */ 132 | if (cmd->op == MCP2210_CMD_READ_EEPROM) 133 | continue; 134 | 135 | /* fall-through */ 136 | case MCP2210_EEPROM_UNREAD: 137 | case MCP2210_EEPROM_READ_PENDING: 138 | if (unlikely(i > 0xff)) { 139 | BUG(); 140 | return -EINVAL; 141 | } 142 | 143 | if (addr) 144 | *addr = i; 145 | return 1; 146 | 147 | default: 148 | BUG(); 149 | }; 150 | } 151 | 152 | return 0; 153 | } 154 | 155 | static int eeprom_submit_prepare(struct mcp2210_cmd *cmd_head) 156 | { 157 | struct mcp2210_cmd_eeprom *cmd = (void *)cmd_head; 158 | struct mcp2210_device *dev = cmd->head.dev; 159 | u8 addr; 160 | u8 value; 161 | 162 | mcp2210_debug(); 163 | 164 | if (dev->dead) 165 | return -ESHUTDOWN; 166 | 167 | if (!next_addr(cmd, &addr)) { 168 | /* it's possible to have nothing to do, like reading values 169 | * that have already been read and are cached. 170 | */ 171 | cmd->head.state = MCP2210_STATE_COMPLETE; 172 | return -EALREADY; 173 | } 174 | 175 | if (cmd->op == MCP2210_CMD_WRITE_EEPROM) 176 | value = dev->eeprom_cache[addr]; 177 | else 178 | value = 0; 179 | 180 | /* We only need to zero the tail once. This should help on operations 181 | * over a large range of addresses. */ 182 | mcp2210_init_msg(dev->eps[EP_OUT].buffer, cmd->op, addr, value, NULL, 183 | 0, cmd->zero_tail); 184 | cmd->zero_tail = 0; 185 | 186 | return 0; 187 | } 188 | 189 | static int eeprom_complete_urb(struct mcp2210_cmd *cmd_head) 190 | { 191 | struct mcp2210_cmd_eeprom *cmd = (void *)cmd_head; 192 | struct mcp2210_device *dev = cmd->head.dev; 193 | struct mcp2210_msg *req = dev->eps[EP_OUT].buffer; 194 | struct mcp2210_msg *rep = dev->eps[EP_IN].buffer; 195 | u8 addr = req->head.req.eeprom.addr; 196 | 197 | mcp2210_debug(); 198 | switch (req->cmd) { 199 | case MCP2210_CMD_READ_EEPROM: 200 | //mcp2210_debug("read 0x%02hhx = ", addr, dev->eeprom_cache[addr]); 201 | dev->eeprom_cache[addr] = rep->head.rep.eeprom.value; 202 | 203 | /* fall-through */ 204 | case MCP2210_CMD_WRITE_EEPROM: 205 | mcp2210_debug("set_eeprom_byte_status: 0x%02hhx, " 206 | "MCP2210_EEPROM_READ", addr); 207 | set_eeprom_byte_status(dev, addr, MCP2210_EEPROM_READ); 208 | mcp2210_debug("get_eeprom_byte_status(dev, addr) = %d", 209 | get_eeprom_byte_status(dev, addr)); 210 | 211 | break; 212 | default: 213 | BUG(); 214 | }; 215 | 216 | /* find out if we have more addresses to do in this command */ 217 | if (next_addr(cmd, &addr)) 218 | return -EAGAIN; 219 | 220 | return 0; 221 | } 222 | 223 | int mcp2210_eeprom_read(struct mcp2210_device *dev, u8 *dest, u8 addr, 224 | u16 size, mcp2210_complete_t complete, void *context, 225 | gfp_t gfp_flags) 226 | { 227 | const u16 end = size + addr; 228 | unsigned long irqflags; 229 | u16 i; 230 | int ready = 1; 231 | int ret; 232 | 233 | mcp2210_debug(); 234 | 235 | if (!dev || !complete) 236 | return -EINVAL; 237 | 238 | if (!is_eeprom_range_valid(addr, size)) { 239 | mcp2210_err("invalid range, addr=%hhu, size=%hu\n", addr, size); 240 | return -EINVAL; 241 | } 242 | 243 | spin_lock_irqsave(&dev->eeprom_spinlock, irqflags); 244 | for (i = addr; i != end; ++i) { 245 | int status = get_eeprom_byte_status(dev, i); 246 | switch (status) { 247 | case MCP2210_EEPROM_UNREAD: 248 | set_eeprom_byte_status(dev, i, MCP2210_EEPROM_READ_PENDING); 249 | 250 | /* intentional fall-through */ 251 | case MCP2210_EEPROM_READ_PENDING: 252 | ready = 0; 253 | break; 254 | 255 | default: 256 | /* no action on anything else */ 257 | break; 258 | } 259 | } 260 | 261 | /* if all values are already read we copy it immediately to the 262 | * caller's buffer (if one was supplied) */ 263 | if (ready && dest) 264 | memcpy(dest, &dev->eeprom_cache[addr], size); 265 | 266 | spin_unlock_irqrestore(&dev->eeprom_spinlock, irqflags); 267 | 268 | if (ready && dest) 269 | return 0; 270 | 271 | /* otherwise, we queue the command */ 272 | ret = queue_eeprom_cmd(dev, MCP2210_CMD_READ_EEPROM, addr, size, 273 | complete, context, gfp_flags); 274 | 275 | if (ret) { 276 | mcp2210_err("failed to add command to queue, %d\n", ret); 277 | return ret; 278 | } 279 | 280 | return -EINPROGRESS; 281 | } 282 | 283 | /** 284 | * mcp2210_eeprom_write 285 | * 286 | * Returns: 287 | * Zero if there is nothing to do, -EINPROGRESS if message is queued or an 288 | * error. 289 | */ 290 | int mcp2210_eeprom_write(struct mcp2210_device *dev, const u8 *src, u8 addr, 291 | u16 size, mcp2210_complete_t complete, void *context, 292 | gfp_t gfp_flags) 293 | { 294 | const u16 end = size + addr; 295 | unsigned long irqflags; 296 | const u8 *in = src; 297 | u16 i; 298 | int writes_in_progress = 0; 299 | int ret; 300 | 301 | mcp2210_debug(); 302 | 303 | if (!dev || !src || !complete) 304 | return -EINVAL; 305 | 306 | if (!is_eeprom_range_valid(addr, size)) 307 | return -EINVAL; 308 | 309 | spin_lock_irqsave(&dev->eeprom_spinlock, irqflags); 310 | for (i = addr; i != end; ++i, ++in) { 311 | switch (get_eeprom_byte_status(dev, i)) { 312 | /* cached value is current */ 313 | case MCP2210_EEPROM_READ: 314 | /* if value being written is the same, just ignore it */ 315 | if (dev->eeprom_cache[i] == *in) 316 | continue; 317 | break; 318 | 319 | case MCP2210_EEPROM_DIRTY: 320 | /* write already pending: just change the target value */ 321 | dev->eeprom_cache[i] = *in; 322 | ++writes_in_progress; 323 | continue; 324 | 325 | default: 326 | /* read pending or unread, we'll treat the same here */ 327 | break; 328 | }; 329 | 330 | /* all other cases, we queue up a write eeprom command */ 331 | // TODO: queue write request 332 | dev->eeprom_cache[i] = *in; 333 | set_eeprom_byte_status(dev, i, MCP2210_EEPROM_DIRTY); 334 | ++writes_in_progress; 335 | } 336 | spin_unlock_irqrestore(&dev->eeprom_spinlock, irqflags); 337 | 338 | if (!writes_in_progress) 339 | return 0; 340 | 341 | ret = queue_eeprom_cmd(dev, MCP2210_CMD_WRITE_EEPROM, addr, size, 342 | complete, context, gfp_flags); 343 | if (ret) 344 | return ret; 345 | 346 | return -EINPROGRESS; 347 | } 348 | 349 | #endif /* CONFIG_MCP2210_EEPROM */ 350 | -------------------------------------------------------------------------------- /mcp2210-gpio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 gpio support 3 | * 4 | * Copyright (c) 2013-2017 Daniel Santos 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifdef CONFIG_MCP2210_GPIO 22 | 23 | #include 24 | #include 25 | 26 | #include "mcp2210.h" 27 | #include "mcp2210-debug.h" 28 | 29 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0) 30 | # define HAVE_GET_DIRECTION 1 31 | #endif 32 | 33 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) \ 34 | && LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) 35 | # define HAVE_SET_DEBOUNCE 1 36 | #endif 37 | 38 | //static int request (struct gpio_chip *chip, unsigned offset); 39 | //static void free (struct gpio_chip *chip, unsigned offset); 40 | #ifdef HAVE_GET_DIRECTION 41 | static int mcp2210_get_direction (struct gpio_chip *chip, unsigned offset); 42 | #endif 43 | static int mcp2210_direction_input (struct gpio_chip *chip, unsigned offset); 44 | static int mcp2210_get (struct gpio_chip *chip, unsigned offset); 45 | static int mcp2210_direction_output(struct gpio_chip *chip, unsigned offset, int value); 46 | //static int set_debounce (struct gpio_chip *chip, unsigned offset, unsigned debounce); 47 | static void mcp2210_set (struct gpio_chip *chip, unsigned offset, int value); 48 | //static void dbg_show (struct seq_file *s, struct gpio_chip *chip); 49 | 50 | static inline struct mcp2210_device *chip2dev(struct gpio_chip *chip) { 51 | return container_of(chip, struct mcp2210_device, gpio); 52 | } 53 | 54 | 55 | #ifdef CONFIG_MCP2210_IRQ 56 | static int mcp2210_to_irq(struct gpio_chip *chip, unsigned offset) 57 | { 58 | struct mcp2210_device *dev = container_of(chip, struct mcp2210_device, gpio); 59 | 60 | if (offset >= MCP2210_NUM_PINS) 61 | return -EINVAL; 62 | 63 | if (!mcp2210_is_pin_irq_producer(dev, offset)) { 64 | mcp2210_warn("%s: pin %u not configured as irq producer\n", 65 | __func__, offset); 66 | return -ENOSYS; 67 | } else { 68 | const struct mcp2210_pin_config *pin = &dev->config->pins[offset]; 69 | return dev->irq_base + pin->irq; 70 | } 71 | } 72 | #else 73 | static int (*const mcp2210_to_irq)(struct gpio_chip *chip, unsigned offset) = NULL; 74 | #endif 75 | 76 | 77 | /****************************************************************************** 78 | * probe & remove 79 | */ 80 | 81 | int mcp2210_gpio_probe(struct mcp2210_device *dev) 82 | { 83 | struct gpio_chip *gpio = &dev->gpio; 84 | int ret; 85 | int is_gpio_probed = dev->is_gpio_probed; /* unalias */ 86 | 87 | mcp2210_info(); 88 | 89 | BUG_ON(!dev); 90 | BUG_ON(!dev->config); 91 | BUG_ON(is_gpio_probed); 92 | 93 | if (!dev || !dev->config || is_gpio_probed) 94 | return -EINVAL; 95 | 96 | gpio->label = "mcp2210"; 97 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,5,0) 98 | gpio->parent = &dev->udev->dev; 99 | #else 100 | gpio->dev = &dev->udev->dev; 101 | #endif 102 | gpio->owner = THIS_MODULE; 103 | // INIT_LIST_HEAD(&gpio->list); 104 | 105 | gpio->request = NULL; 106 | gpio->free = NULL; 107 | #ifdef HAVE_GET_DIRECTION 108 | gpio->get_direction = mcp2210_get_direction; 109 | #endif 110 | gpio->direction_input = mcp2210_direction_input; 111 | gpio->direction_output = mcp2210_direction_output; 112 | gpio->get = mcp2210_get; 113 | gpio->set = mcp2210_set; 114 | #ifdef HAVE_SET_DEBOUNCE 115 | gpio->set_debounce = NULL; 116 | #endif 117 | gpio->to_irq = mcp2210_to_irq; 118 | gpio->dbg_show = NULL; 119 | 120 | gpio->base = -1; /* request dynamic ID allocation */ 121 | gpio->ngpio = MCP2210_NUM_PINS; /* FIXME */ 122 | /* private: gpio->desc */ 123 | gpio->names = (void*)dev->names; /* older kernels use char** */ 124 | gpio->can_sleep = 1; /* we have to make them sleep because we 125 | need to do an URB */ 126 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0) 127 | gpio->exported = 0; 128 | #endif 129 | 130 | ret = gpiochip_add(gpio); 131 | if (ret) { 132 | mcp2210_err("Failed to register GPIOs: %d", ret); 133 | return ret; 134 | } 135 | 136 | mcp2210_info("registered GPIOs from %d to %d", gpio->base, 137 | gpio->base + gpio->ngpio - 1); 138 | 139 | dev->is_gpio_probed = 1; 140 | 141 | return 0; 142 | } 143 | 144 | void mcp2210_gpio_remove(struct mcp2210_device *dev) 145 | { 146 | int ret = 0; 147 | 148 | if (!dev->is_gpio_probed) 149 | return; 150 | 151 | mcp2210_info(); 152 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0) 153 | gpiochip_remove(&dev->gpio); 154 | #else 155 | ret = gpiochip_remove(&dev->gpio); 156 | #endif 157 | if (ret) { 158 | mcp2210_err("gpiochip_remove() failed with %d", ret); 159 | return; 160 | } 161 | dev->is_gpio_probed = 0; 162 | } 163 | 164 | 165 | /****************************************************************************** 166 | * struct gpio_chip functions 167 | */ 168 | 169 | struct gpio_completion { 170 | struct completion completion; 171 | u16 gpio_val_dir; 172 | int status; 173 | u8 mcp_status; 174 | }; 175 | 176 | static int complete_cmd_chip(struct mcp2210_cmd *cmd_head, void *context) 177 | { 178 | struct gpio_completion *c = context; 179 | struct mcp2210_device *dev = cmd_head->dev; 180 | struct mcp2210_cmd_ctl *cmd = (void *)cmd_head; 181 | 182 | switch (cmd->req.cmd) { 183 | case MCP2210_CMD_GET_PIN_DIR: 184 | case MCP2210_CMD_GET_PIN_VALUE: 185 | c->gpio_val_dir = dev->eps[EP_IN].buffer->body.gpio; 186 | break; 187 | case MCP2210_CMD_SET_CHIP_CONFIG: 188 | case MCP2210_CMD_SET_PIN_DIR: 189 | case MCP2210_CMD_SET_PIN_VALUE: 190 | break; 191 | default: 192 | MCP_ASSERT(0); 193 | } 194 | 195 | c->status = cmd_head->status; 196 | c->mcp_status = cmd_head->mcp_status; 197 | complete_all(&c->completion); 198 | return 0; 199 | } 200 | 201 | static int do_gpio_cmd(struct mcp2210_device *dev, u8 cmd_code, void *body, 202 | size_t size) 203 | { 204 | struct gpio_completion c; 205 | struct mcp2210_cmd_ctl *cmd; 206 | int ret; 207 | 208 | cmd = mcp2210_alloc_ctl_cmd(dev, cmd_code, 0, body, size, false, GFP_KERNEL); 209 | if (!cmd) 210 | return -ENOMEM; 211 | 212 | memset(&c, 0, sizeof(c)); 213 | init_completion(&c.completion); 214 | 215 | switch (cmd_code) { 216 | case MCP2210_CMD_SET_CHIP_CONFIG: 217 | case MCP2210_CMD_GET_PIN_DIR: 218 | case MCP2210_CMD_SET_PIN_DIR: 219 | case MCP2210_CMD_GET_PIN_VALUE: 220 | case MCP2210_CMD_SET_PIN_VALUE: 221 | break; 222 | default: 223 | MCP_ASSERT(0); 224 | } 225 | 226 | cmd->head.complete = complete_cmd_chip; 227 | cmd->head.context = &c; 228 | 229 | ret = mcp2210_add_cmd(&cmd->head, true); 230 | if (ret) 231 | return ret; 232 | 233 | process_commands(dev, false, true); 234 | 235 | wait_for_completion(&c.completion); 236 | 237 | if (c.status) { 238 | MCP_ASSERT(c.status < 0); 239 | return c.status; 240 | } 241 | 242 | return c.gpio_val_dir; 243 | } 244 | 245 | #ifdef HAVE_GET_DIRECTION 246 | static int mcp2210_get_direction(struct gpio_chip *chip, unsigned offset) 247 | { 248 | struct mcp2210_device *dev = chip2dev(chip); 249 | unsigned long irqflags; 250 | int ret; 251 | 252 | BUG_ON(offset >= MCP2210_NUM_PINS); 253 | 254 | mcp2210_debug(); 255 | spin_lock_irqsave(&dev->dev_spinlock, irqflags); 256 | ret = 1 & (dev->s.chip_settings.gpio_direction >> offset); 257 | spin_unlock_irqrestore(&dev->dev_spinlock, irqflags); 258 | 259 | return ret; 260 | } 261 | #endif 262 | 263 | static int set_dir_and_value(struct gpio_chip *chip, unsigned pin, int dir, 264 | int value) 265 | { 266 | struct mcp2210_device *dev = chip2dev(chip); 267 | unsigned long irqflags; 268 | int ret = 0; 269 | u8 pin_mode; 270 | u16 pin_vals; 271 | u16 pin_dirs; 272 | u16 new_vals; 273 | u16 new_dirs; 274 | u8 set_vals = 0; 275 | u8 set_dirs = 0; 276 | 277 | BUG_ON(pin >= MCP2210_NUM_PINS); 278 | 279 | mcp2210_debug("chip:%p, pin: %u, dir: %d, value: %d", chip, pin, dir, 280 | value); 281 | spin_lock_irqsave(&dev->dev_spinlock, irqflags); 282 | pin_vals = dev->s.chip_settings.gpio_value; 283 | pin_dirs = dev->s.chip_settings.gpio_direction; 284 | pin_mode = dev->config->pins[pin].mode; 285 | spin_unlock_irqrestore(&dev->dev_spinlock, irqflags); 286 | 287 | /* treat the dedicated interrupt counter as an ordinary gpio that 288 | * clears when read? || (pin_mode == MCP2210_PIN_DEDICATED && pin == 6)*/ 289 | if (pin_mode != MCP2210_PIN_GPIO) { 290 | mcp2210_err("pin %u not gpio", pin); 291 | return -EPERM; 292 | } 293 | 294 | switch (dir) { 295 | /* If not changing direction and current dir is input, then error */ 296 | case MCP2210_GPIO_NO_CHANGE: 297 | if (pin_dirs & 1 << pin) 298 | return -EROFS; 299 | /* intentional fall-through */ 300 | 301 | case MCP2210_GPIO_OUTPUT: 302 | new_vals = (pin_vals & ~(1 << pin)) | value << pin; 303 | if (new_vals != pin_vals) 304 | set_vals = 1; 305 | 306 | if (dir == MCP2210_GPIO_NO_CHANGE) 307 | break; 308 | /* intentional fall-through */ 309 | 310 | case MCP2210_GPIO_INPUT: 311 | new_dirs = (pin_dirs & ~(1 << pin)) | dir << pin; 312 | if (new_dirs != pin_dirs) 313 | set_dirs = 1; 314 | break; 315 | 316 | default: 317 | BUG(); 318 | } 319 | 320 | mcp2210_debug("pin_vals = 0x%04hx, pin_dirs = 0x%04hx\n" 321 | "new_vals = 0x%04hx, new_dirs = 0x%04hx\n" 322 | "set_vals = %hhu , new_dirs = %hhu", 323 | pin_vals, pin_dirs, new_vals, new_dirs, 324 | set_dirs, set_vals); 325 | if (set_vals & set_dirs) { 326 | /* I'm not aware of any other mechanism to change the direction 327 | * to output and the value at the same time, and I don't think 328 | * this device supports changing the direction to output, but 329 | * leaving it's actual state in high-z until you give it a 330 | * value, so we'll just set the full chip settings. 331 | */ 332 | struct mcp2210_chip_settings cs = dev->s.chip_settings; 333 | 334 | cs.gpio_value = new_vals; 335 | cs.gpio_direction = new_dirs; 336 | return do_gpio_cmd(dev, MCP2210_CMD_SET_CHIP_CONFIG, &cs, 337 | sizeof(cs)); 338 | } 339 | 340 | if (set_vals) { 341 | ret = do_gpio_cmd(dev, MCP2210_CMD_SET_PIN_VALUE, &new_vals, 342 | sizeof(new_vals)); 343 | if (ret < 0) 344 | return ret; 345 | } 346 | 347 | if (set_dirs) { 348 | ret = do_gpio_cmd(dev, MCP2210_CMD_SET_PIN_DIR, &new_dirs, 349 | sizeof(new_dirs)); 350 | } 351 | 352 | return ret; 353 | } 354 | 355 | static int mcp2210_direction_input(struct gpio_chip *chip, unsigned offset) 356 | { 357 | return set_dir_and_value(chip, offset, MCP2210_GPIO_INPUT, 0); 358 | } 359 | 360 | static int mcp2210_direction_output(struct gpio_chip *chip, unsigned offset, int value) 361 | { 362 | return set_dir_and_value(chip, offset, MCP2210_GPIO_OUTPUT, value); 363 | } 364 | 365 | static int mcp2210_get(struct gpio_chip *chip, unsigned offset) 366 | { 367 | struct mcp2210_device *dev = chip2dev(chip); 368 | unsigned long irqflags; 369 | enum mcp2210_pin_mode mode; 370 | int val; 371 | int dir; 372 | int ret; 373 | unsigned long now = jiffies; 374 | unsigned long last_poll; 375 | u32 uninitialized_var(stale_usecs); 376 | 377 | BUG_ON(offset >= MCP2210_NUM_PINS); 378 | 379 | mcp2210_debug(); 380 | 381 | spin_lock_irqsave(&dev->dev_spinlock, irqflags); 382 | mode = dev->s.chip_settings.pin_mode[offset]; 383 | val = 1 & (dev->s.chip_settings.gpio_value >> offset); 384 | dir = 1 & (dev->s.chip_settings.gpio_direction >> offset); 385 | last_poll = dev->last_poll_gpio; 386 | if (IS_ENABLED(CONFIG_MCP2210_IRQ)) 387 | stale_usecs = dev->config->stale_gpio_usecs; 388 | spin_unlock_irqrestore(&dev->dev_spinlock, irqflags); 389 | 390 | switch (mode) { 391 | case MCP2210_PIN_GPIO: 392 | break; 393 | 394 | case MCP2210_PIN_DEDICATED: 395 | if (offset == 6) { 396 | ret = !!dev->interrupt_event_counter; 397 | dev->interrupt_event_counter = 0; 398 | return ret; 399 | } 400 | /* fall-throguh */ 401 | 402 | default: 403 | return -EINVAL; 404 | }; 405 | 406 | /* If the value was read within stale_usecs microseconds, then we just 407 | * return that value */ 408 | if (IS_ENABLED(CONFIG_MCP2210_IRQ) && stale_usecs && time_before(now, 409 | last_poll + usecs_to_jiffies(stale_usecs))) { 410 | return val; 411 | } 412 | 413 | if (dir == MCP2210_GPIO_OUTPUT) 414 | return val; 415 | 416 | ret = do_gpio_cmd(dev, MCP2210_CMD_GET_PIN_VALUE, NULL, 0); 417 | 418 | if (ret < 0) 419 | return ret; 420 | else 421 | return 1 & ret >> offset; 422 | } 423 | 424 | static void mcp2210_set(struct gpio_chip *chip, unsigned offset, int value) 425 | { 426 | set_dir_and_value(chip, offset, MCP2210_GPIO_NO_CHANGE, value); 427 | } 428 | 429 | #endif /* CONFIG_MCP2210_GPIO */ 430 | -------------------------------------------------------------------------------- /mcp2210-ioctl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ioctl interface for MCP2210 driver 3 | * 4 | * Copyright (c) 2013-2017 Daniel Santos 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifdef CONFIG_MCP2210_IOCTL 22 | 23 | #include 24 | #include 25 | 26 | #include "mcp2210.h" 27 | #include "mcp2210-debug.h" 28 | 29 | struct ioctl_result { 30 | enum mcp2210_ioctl_cmd ioctl_cmd; 31 | struct completion completion; 32 | __user struct mcp2210_ioctl_data *user_data; 33 | union mcp2210_cmd_any *cmd; 34 | int status; 35 | u8 mcp_status; 36 | unsigned num_cmds; 37 | unsigned num_cmds_finished; 38 | struct mcp2210_board_config *new_config; 39 | struct mcp2210_ioctl_data payload[0]; 40 | }; 41 | 42 | static long mcp2210_ioctl_cmd(struct mcp2210_device *dev, struct ioctl_result *result); 43 | static long mcp2210_ioctl_eeprom(struct mcp2210_device *dev, struct ioctl_result *result); 44 | static long mcp2210_ioctl_config_get(struct mcp2210_device *dev, struct ioctl_result *result); 45 | static long mcp2210_ioctl_config_set(struct mcp2210_device *dev, struct ioctl_result *result); 46 | 47 | static struct ioctl_cmds { 48 | long (*func)(struct mcp2210_device *dev, struct ioctl_result *result); 49 | u32 min_size; 50 | } ioctl_cmds [MCP2210_IOCTL_MAX] = { 51 | {mcp2210_ioctl_cmd, IOCTL_DATA_CMD_SIZE, }, 52 | {mcp2210_ioctl_eeprom, IOCTL_DATA_EEPROM_SIZE + 1, }, 53 | {mcp2210_ioctl_config_get, IOCTL_DATA_CONFIG_SIZE, }, 54 | {mcp2210_ioctl_config_set, IOCTL_DATA_CONFIG_SIZE, }, 55 | }; 56 | 57 | long mcp2210_ioctl(struct file *file, unsigned int request, unsigned long arg) 58 | { 59 | struct mcp2210_device *dev = file->private_data; 60 | enum mcp2210_ioctl_cmd ioctl_cmd = _IOC_NR(request); 61 | int is_rw = _IOC_DIR(request) & _IOC_WRITE; 62 | __user struct mcp2210_ioctl_data *user_data = (void __user *)arg; 63 | u32 struct_size; 64 | size_t result_size; 65 | struct ioctl_result *result; 66 | int ret; 67 | 68 | mcp2210_debug("request: 0x%08x, io_dir: %d, is_rw: %d, ioctl_cmd: %u, magic: 0x%02x\n", 69 | request, _IOC_DIR(request), is_rw, ioctl_cmd, _IOC_TYPE(request)); 70 | 71 | BUG_ON(!dev); 72 | 73 | if (_IOC_TYPE(request) != MCP2210_IOCTL_MAGIC) 74 | return -ENOTTY; 75 | 76 | /* we don't have any requests that do not read data */ 77 | if (!(_IOC_DIR(request) & (_IOC_READ | _IOC_WRITE))) 78 | return -ENOTTY; 79 | 80 | if (ioctl_cmd < 0 || ioctl_cmd >= MCP2210_IOCTL_MAX) { 81 | mcp2210_warn("Invalid ioctl command number %d", ioctl_cmd); 82 | return -ENOTTY; 83 | } 84 | 85 | if ((ret = get_user(struct_size, &user_data->struct_size))) 86 | return -EFAULT; 87 | 88 | //mcp2210_debug("struct_size = %u, ioctl_cmds[ioctl_cmd].min_size = %u", struct_size, ioctl_cmds[ioctl_cmd].min_size); 89 | 90 | /* minimum valid size */ 91 | if (unlikely(struct_size < ioctl_cmds[ioctl_cmd].min_size)) { 92 | 93 | return -EOVERFLOW; 94 | } 95 | 96 | /* max size */ 97 | if (unlikely(struct_size > 0x4000)) { 98 | mcp2210_warn("request too large: %u", struct_size); 99 | return -EINVAL; 100 | } 101 | 102 | result_size = sizeof(struct ioctl_result) + struct_size; 103 | if (!(result = kzalloc(result_size, GFP_KERNEL))) { 104 | mcp2210_err("Failled to allocate %u bytes\n", (uint)result_size); 105 | return -ENOMEM; 106 | } 107 | 108 | init_completion(&result->completion); 109 | result->ioctl_cmd = ioctl_cmd; 110 | result->user_data = user_data; 111 | if ((ret = copy_from_user(result->payload, user_data, struct_size))) { 112 | ret = -EFAULT; 113 | goto exit_free; 114 | } 115 | 116 | /* just a sanity check */ 117 | BUG_ON(result->payload->struct_size != struct_size); 118 | 119 | if ((ret = ioctl_cmds[ioctl_cmd].func(dev, result))) 120 | goto exit_free; 121 | 122 | //print_hex_dump(KERN_DEBUG, "ret: ", DUMP_PREFIX_OFFSET, 16, 1, 123 | // result->payload, struct_size, true); 124 | if (is_rw && copy_to_user(user_data, result->payload, struct_size)) 125 | ret = -EFAULT; 126 | 127 | exit_free: 128 | kfree(result); 129 | return ret; 130 | } 131 | 132 | /** 133 | * mcp2210_ioctl_complete - callback for driver to complete ioctls 134 | * 135 | * Returns -EINPROGRESS if somebody else will free the command later 136 | */ 137 | static int mcp2210_ioctl_complete(struct mcp2210_cmd *cmd_head, void *context) 138 | { 139 | struct ioctl_result *r = context; 140 | struct mcp2210_device *dev = cmd_head->dev; 141 | int cmd_status; 142 | int result_has_status = r->status && r->status != -EINPROGRESS; 143 | int ret = 0; 144 | 145 | mcp2210_info(); 146 | 147 | /* run configure general-purpose command */ 148 | if (r->ioctl_cmd == MCP2210_IOCTL_CONFIG_SET && !cmd_head->type) { 149 | if (result_has_status) { 150 | mcp2210_err("not configuring due to previous errors"); 151 | } else { 152 | BUG_ON(dev->config); 153 | BUG_ON(!r->new_config); 154 | 155 | cmd_head->status = mcp2210_configure(dev, r->new_config); 156 | if (cmd_head->status) { 157 | mcp2210_err("failed to configure %d", ret); 158 | kfree(r->new_config); 159 | } 160 | } 161 | r->new_config = NULL; 162 | } 163 | 164 | cmd_status = cmd_head->status; 165 | if (!result_has_status && cmd_status) { 166 | r->status = cmd_status; 167 | r->mcp_status = cmd_head->mcp_status; 168 | } 169 | 170 | switch (r->ioctl_cmd) { 171 | case MCP2210_IOCTL_CMD: { 172 | //struct mcp2210_cmd_ctl *cmd = (void *)cmd_head; 173 | struct mcp2210_ioctl_data_cmd *idc = &r->payload[0].body.cmd; 174 | 175 | BUG_ON(!cmd_head->type); 176 | BUG_ON(cmd_head->type->id != MCP2210_CMD_TYPE_CTL); 177 | 178 | /* if the command completed, copy the response msg */ 179 | if (!cmd_status) 180 | memcpy(&idc->rep, dev->eps[EP_IN].buffer, 64); 181 | } 182 | 183 | #ifdef CONFIG_MCP2210_EEPROM 184 | case MCP2210_IOCTL_EEPROM: { 185 | struct mcp2210_cmd_eeprom *cmd = (void *)cmd_head; 186 | 187 | BUG_ON(!cmd_head->type); 188 | BUG_ON(cmd_head->type->id != MCP2210_CMD_TYPE_EEPROM); 189 | 190 | if (cmd->op == MCP2210_CMD_READ_EEPROM) { 191 | struct mcp2210_ioctl_data_eeprom *ide; 192 | 193 | ide = &r->payload[0].body.eeprom; 194 | BUG_ON(cmd->addr + cmd->size > MCP2210_EEPROM_SIZE); 195 | BUG_ON(cmd->size != ide->size); 196 | // mcp2210_debug("in my mind, ide: %p, ide->data: %p, cmd->addr: %hhu, cmd->size: %hu", ide, ide->data, cmd->addr, cmd->size); 197 | // mcp2210_debug("dev->eeprom_cache: %02x %02x...", dev->eeprom_cache[0], dev->eeprom_cache[1]); 198 | memcpy(ide->data, &dev->eeprom_cache[cmd->addr], cmd->size); 199 | } 200 | break; 201 | } 202 | #endif /* CONFIG_MCP2210_EEPROM */ 203 | 204 | case MCP2210_IOCTL_CONFIG_GET: 205 | /* this ioctl doesn't require a callback */ 206 | BUG(); 207 | break; 208 | 209 | case MCP2210_IOCTL_CONFIG_SET: 210 | ++r->num_cmds_finished; 211 | 212 | /* don't complete until all commands are done */ 213 | if (r->num_cmds_finished < r->num_cmds) 214 | return 0; 215 | 216 | break; 217 | 218 | default: 219 | BUG(); 220 | break; 221 | }; 222 | 223 | //exit: 224 | r->cmd = (ret == -EINPROGRESS) ? (void*)cmd_head : NULL; 225 | complete_all(&r->completion); 226 | return ret; 227 | } 228 | 229 | static long mcp2210_ioctl_cmd(struct mcp2210_device *dev, struct ioctl_result *result) 230 | { 231 | struct mcp2210_ioctl_data_cmd *idc = &result->payload->body.cmd; 232 | struct mcp2210_cmd_ctl *cmd; 233 | int ret; 234 | 235 | mcp2210_debug(); 236 | 237 | /* commands we don't allow because they will screw with things */ 238 | switch (idc->req.cmd) { 239 | case MCP2210_CMD_SPI_TRANSFER: 240 | case MCP2210_CMD_READ_EEPROM: 241 | case MCP2210_CMD_WRITE_EEPROM: 242 | case MCP2210_CMD_SET_SPI_CONFIG: 243 | case MCP2210_CMD_SET_CHIP_CONFIG: 244 | mcp2210_err("Command 0x%hhx not permitted or supported via this interface", idc->req.cmd); 245 | return -EPERM; 246 | 247 | default: 248 | break; 249 | } 250 | 251 | cmd = mcp2210_alloc_ctl_cmd(dev, 252 | idc->req.cmd, 253 | idc->req.head.req.xet.sub_cmd, 254 | idc->req.body.raw, 255 | sizeof(idc->req.body.raw), 256 | false, GFP_KERNEL); 257 | if (!cmd) 258 | return -ENOMEM; 259 | 260 | cmd->head.complete = mcp2210_ioctl_complete; 261 | cmd->head.context = result; 262 | 263 | if (!(ret = mcp2210_add_cmd((void *)cmd, true))) 264 | return ret; 265 | 266 | mcp2210_info("waiting..."); 267 | wait_for_completion(&result->completion); 268 | return result->status; 269 | } 270 | 271 | #ifndef CONFIG_MCP2210_EEPROM 272 | static long mcp2210_ioctl_eeprom(struct mcp2210_device *dev, struct ioctl_result *result) 273 | { 274 | mcp2210_warn("EEPROM support unavailable."); 275 | return -EPERM; 276 | } 277 | #else 278 | static long mcp2210_ioctl_eeprom(struct mcp2210_device *dev, struct ioctl_result *result) 279 | { 280 | struct mcp2210_ioctl_data_eeprom *ide = &result->payload->body.eeprom; 281 | int ret; 282 | 283 | mcp2210_info(); 284 | 285 | /* make sure the buffer is really large enough */ 286 | if (result->payload->struct_size < IOCTL_DATA_EEPROM_SIZE + ide->size) 287 | return -EOVERFLOW; 288 | 289 | /* I hope the compiler only generates the setup once */ 290 | if (ide->is_read) 291 | ret = mcp2210_eeprom_read(dev, ide->data, ide->addr, ide->size, 292 | mcp2210_ioctl_complete, result, 293 | GFP_KERNEL); 294 | else 295 | ret = mcp2210_eeprom_write(dev, ide->data, ide->addr, ide->size, 296 | mcp2210_ioctl_complete, result, 297 | GFP_KERNEL); 298 | 299 | /* zero return value means that we're already done */ 300 | if (!ret) 301 | goto complete_ioctl; 302 | else if (ret != -EINPROGRESS) 303 | return ret; 304 | 305 | mcp2210_info("waiting..."); 306 | wait_for_completion(&result->completion); 307 | 308 | complete_ioctl: 309 | return result->status; 310 | } 311 | #endif /* CONFIG_MCP2210_EEPROM */ 312 | 313 | static void reset_string_addr_single(const char **str, const char *min, const char *max, long diff) 314 | { 315 | const void *orig = *str; 316 | 317 | if (*str < min || *str > max) 318 | *str = NULL; 319 | else 320 | *str -= diff; 321 | 322 | printk(KERN_DEBUG "str: %p --> %p\n", orig, *str); 323 | } 324 | 325 | static void reset_string_addr(struct mcp2210_board_config *bc, const __user struct mcp2210_board_config *user_ptr) 326 | { 327 | long diff = (long)bc - (long)user_ptr; 328 | const char *min = bc->strings; 329 | const char *max = &bc->strings[bc->strings_size - 1]; 330 | u8 i; 331 | 332 | //printk(KERN_DEBUG "bc = %p (min = %p, max = %p), user_ptr = %p, diff = 0x%08x\n", bc, min, max, user_ptr, diff); 333 | /* reset addresses of strings and NULL out any that don't point to a string within the struct */ 334 | for (i = 0; i < MCP2210_NUM_PINS; ++i) { 335 | struct mcp2210_pin_config *pin = &bc->pins[i]; 336 | 337 | reset_string_addr_single(&pin->name, min, max, diff); 338 | reset_string_addr_single(&pin->modalias, min, max, diff); 339 | } 340 | } 341 | 342 | static long mcp2210_ioctl_config_get(struct mcp2210_device *dev, struct ioctl_result *result) 343 | { 344 | struct mcp2210_ioctl_data *id = result->payload; 345 | struct mcp2210_ioctl_data_config *idc = &id->body.config; 346 | struct mcp2210_state *idcs = &idc->state; 347 | struct mcp2210_state *devs = &dev->s; 348 | unsigned long irqflags; 349 | int ret; 350 | 351 | mcp2210_info(); 352 | 353 | spin_lock_irqsave(&dev->dev_spinlock, irqflags); 354 | idcs->have_chip_settings = devs->have_chip_settings; 355 | idcs->have_power_up_chip_settings = devs->have_power_up_chip_settings; 356 | idcs->have_spi_settings = devs->have_spi_settings; 357 | idcs->have_power_up_spi_settings = devs->have_power_up_spi_settings; 358 | idcs->have_usb_key_params = devs->have_usb_key_params; 359 | dev->have_config = !!dev->config; 360 | dev->is_spi_probed = !!dev->spi_master; 361 | dev->is_gpio_probed = dev->is_gpio_probed; 362 | 363 | if (devs->have_chip_settings) 364 | memcpy(&idcs->chip_settings, 365 | &devs->chip_settings, 366 | sizeof(idcs->chip_settings)); 367 | 368 | if (devs->have_power_up_chip_settings) 369 | memcpy(&idcs->power_up_chip_settings, 370 | &devs->power_up_chip_settings, 371 | sizeof(idcs->power_up_chip_settings)); 372 | 373 | if (devs->have_spi_settings) 374 | memcpy(&idcs->spi_settings, 375 | &devs->spi_settings, 376 | sizeof(idcs->spi_settings)); 377 | 378 | if (devs->have_power_up_spi_settings) 379 | memcpy(&idcs->power_up_spi_settings, 380 | &devs->power_up_spi_settings, 381 | sizeof(idcs->power_up_spi_settings)); 382 | 383 | if (devs->have_usb_key_params) 384 | memcpy(&idcs->usb_key_params, 385 | &devs->usb_key_params, 386 | sizeof(idcs->usb_key_params)); 387 | 388 | if (dev->config) { 389 | void *vret; 390 | idc->config.strings_size = id->struct_size - IOCTL_DATA_CONFIG_SIZE; 391 | vret = copy_board_config(&idc->config, dev->config, 0); 392 | if (IS_ERR(vret)) { 393 | ret = PTR_ERR(vret); 394 | goto exit_unlock; 395 | } 396 | reset_string_addr(&idc->config, &result->user_data->body.config.config); 397 | } 398 | 399 | spin_unlock_irqrestore(&dev->dev_spinlock, irqflags); 400 | 401 | if (0) { 402 | printk(KERN_DEBUG "is_spi_probed: %hhu\n", dev->is_spi_probed); 403 | printk(KERN_DEBUG "is_gpio_probed: %hhu\n", dev->is_gpio_probed); 404 | printk(KERN_DEBUG "have_config: %hhu\n", dev->have_config); 405 | dump_chip_settings(KERN_DEBUG, 0, ".chip_settings = ", &idcs->chip_settings); 406 | dump_chip_settings(KERN_DEBUG, 0, ".power_up_chip_settings = ", &idcs->power_up_chip_settings); 407 | if (dev->have_config) 408 | dump_board_config(KERN_DEBUG, 0, ".config = ", &idc->config); 409 | } 410 | 411 | return 0; 412 | 413 | exit_unlock: 414 | spin_unlock_irqrestore(&dev->dev_spinlock, irqflags); 415 | return ret; 416 | } 417 | 418 | static long mcp2210_ioctl_config_set(struct mcp2210_device *dev, struct ioctl_result *result) 419 | { 420 | struct mcp2210_ioctl_data *id = result->payload; 421 | struct mcp2210_ioctl_data_config *idc = &id->body.config; 422 | unsigned long irqflags; 423 | int ret = 0; 424 | LIST_HEAD(cmds); 425 | 426 | mcp2210_info(); 427 | 428 | if (dev->config && idc->have_config) { 429 | mcp2210_err("already configured (can only set board_config once)"); 430 | return -EPERM; 431 | } 432 | 433 | if (idc->have_config && !(idc->state.have_chip_settings 434 | || dev->s.have_chip_settings)) { 435 | mcp2210_err("need chip settings"); 436 | return -EPERM; 437 | } 438 | 439 | if (idc->state.have_chip_settings) { 440 | struct mcp2210_cmd_ctl *cmd = mcp2210_alloc_ctl_cmd( 441 | dev, MCP2210_CMD_SET_CHIP_CONFIG, 0, 442 | &idc->state.chip_settings, 443 | sizeof(idc->state.chip_settings), 444 | false, GFP_KERNEL); 445 | if (!cmd) 446 | goto exit_nomem; 447 | list_add_tail(&cmd->head.node, &cmds); 448 | } 449 | 450 | if (idc->state.have_power_up_chip_settings) { 451 | struct mcp2210_cmd_ctl *cmd = mcp2210_alloc_ctl_cmd( 452 | dev, MCP2210_CMD_SET_NVRAM, MCP2210_NVRAM_CHIP, 453 | &idc->state.power_up_chip_settings, 454 | sizeof(idc->state.power_up_chip_settings), 455 | false, GFP_KERNEL); 456 | if (!cmd) 457 | goto exit_nomem; 458 | list_add_tail(&cmd->head.node, &cmds); 459 | } 460 | 461 | if (idc->state.have_spi_settings) { 462 | struct mcp2210_cmd_ctl *cmd = mcp2210_alloc_ctl_cmd( 463 | dev, MCP2210_CMD_SET_SPI_CONFIG, 0, 464 | &idc->state.spi_settings, 465 | sizeof(idc->state.spi_settings), 466 | false, GFP_KERNEL); 467 | if (!cmd) 468 | goto exit_nomem; 469 | list_add_tail(&cmd->head.node, &cmds); 470 | } 471 | 472 | if (idc->state.have_power_up_spi_settings) { 473 | struct mcp2210_cmd_ctl *cmd = mcp2210_alloc_ctl_cmd( 474 | dev, MCP2210_CMD_SET_NVRAM, MCP2210_NVRAM_SPI, 475 | &idc->state.power_up_spi_settings, 476 | sizeof(idc->state.power_up_spi_settings), 477 | false, GFP_KERNEL); 478 | if (!cmd) 479 | goto exit_nomem; 480 | list_add_tail(&cmd->head.node, &cmds); 481 | } 482 | 483 | if (idc->state.have_usb_key_params) { 484 | struct mcp2210_cmd_ctl *cmd = mcp2210_alloc_ctl_cmd( 485 | dev, MCP2210_CMD_SET_NVRAM, MCP2210_NVRAM_KEY_PARAMS, 486 | &idc->state.usb_key_params, 487 | sizeof(idc->state.usb_key_params), 488 | false, GFP_KERNEL); 489 | if (!cmd) 490 | goto exit_nomem; 491 | list_add_tail(&cmd->head.node, &cmds); 492 | } 493 | 494 | if (idc->have_config) { 495 | struct mcp2210_cmd *cmd; 496 | 497 | result->new_config = copy_board_config(NULL, &idc->config, GFP_KERNEL); 498 | if (!result->new_config) 499 | goto exit_nomem; 500 | 501 | cmd = mcp2210_alloc_cmd(dev, NULL, sizeof(struct mcp2210_cmd), GFP_KERNEL); 502 | if (!cmd) { 503 | kfree(result->new_config); 504 | result->new_config = NULL; 505 | goto exit_nomem; 506 | } 507 | 508 | cmd->nonatomic = 1; 509 | list_add_tail(&cmd->node, &cmds); 510 | } 511 | 512 | /* if they didn't specify anything... */ 513 | if (list_empty(&cmds)) { 514 | mcp2210_err("no configuration parameters selected"); 515 | return -EINVAL; 516 | } 517 | 518 | /* once creation of all commands succeeds, we add to queue */ 519 | 520 | spin_lock_irqsave(&dev->dev_spinlock, irqflags); 521 | spin_lock(&dev->queue_spinlock); 522 | 523 | if (dev->dead) 524 | ret = -ERESTARTSYS; 525 | else { 526 | while (!list_empty(&cmds)) { 527 | struct mcp2210_cmd *cmd; 528 | 529 | cmd = list_first_entry(&cmds, struct mcp2210_cmd, node); 530 | list_del(&cmd->node); 531 | cmd->complete = mcp2210_ioctl_complete; 532 | cmd->context = result; 533 | ++result->num_cmds; 534 | list_add_tail(&cmd->node, &dev->cmd_queue); 535 | dump_cmd(KERN_DEBUG, 0, "adding cmd: ", cmd); 536 | } 537 | } 538 | 539 | spin_unlock(&dev->queue_spinlock); 540 | if (!ret) 541 | process_commands(dev, true, false); 542 | spin_unlock_irqrestore(&dev->dev_spinlock, irqflags); 543 | 544 | /* help command execute more quickly? */ 545 | schedule(); 546 | 547 | if (!ret) { 548 | mcp2210_info("waiting for completion..."); 549 | wait_for_completion(&result->completion); 550 | } else 551 | result->status = ret; 552 | 553 | return result->status; 554 | 555 | exit_nomem: 556 | while (!list_empty(&cmds)) { 557 | struct mcp2210_cmd *cmd = list_first_entry(&cmds, 558 | struct mcp2210_cmd, 559 | node); 560 | list_del(cmds.next); 561 | kfree(cmd); 562 | } 563 | 564 | return -ENOMEM; 565 | } 566 | #endif /* CONFIG_MCP2210_IOCTL */ 567 | 568 | -------------------------------------------------------------------------------- /mcp2210-irq.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 software IRQ controller 3 | * 4 | * Copyright (c) 2013-2017 Daniel Santos 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include "mcp2210.h" 22 | #include "mcp2210-debug.h" 23 | 24 | #ifdef CONFIG_MCP2210_IRQ 25 | 26 | #include 27 | 28 | static int complete_poll(struct mcp2210_cmd *cmd, void *context); 29 | 30 | static void mcp2210_irq_mask(struct irq_data *data) { 31 | struct mcp2210_device *dev = irq_data_get_irq_chip_data(data); 32 | dev->irq_mask |= data->mask; 33 | } 34 | 35 | static void mcp2210_irq_unmask(struct irq_data *data) { 36 | struct mcp2210_device *dev = irq_data_get_irq_chip_data(data); 37 | dev->irq_mask &= ~data->mask; 38 | } 39 | 40 | static int mcp2210_irq_set_type(struct irq_data *data, unsigned int flow_type) { 41 | struct mcp2210_device *dev = irq_data_get_irq_chip_data(data); 42 | BUG_ON(data->hwirq > 7); 43 | dev->irq_type[data->hwirq] = flow_type; 44 | mcp2210_info("irq type set to 0x%02x", flow_type); 45 | return 0; 46 | } 47 | 48 | static struct irq_chip mcp2210_irq_chip = { 49 | .name = "mcp2210", 50 | .irq_mask = mcp2210_irq_mask, 51 | .irq_unmask = mcp2210_irq_unmask, 52 | .irq_set_type = mcp2210_irq_set_type, 53 | }; 54 | 55 | /****************************************************************************** 56 | * probe & remove 57 | */ 58 | int mcp2210_irq_probe(struct mcp2210_device *dev) 59 | { 60 | uint i; 61 | int ret; 62 | 63 | mcp2210_info(); 64 | mutex_init(&dev->irq_lock); 65 | 66 | dev->nr_irqs = 0; 67 | dev->poll_intr = 0; 68 | dev->poll_gpio = 0; 69 | 70 | for (i = 0; i < MCP2210_NUM_PINS; ++i) { 71 | const struct mcp2210_pin_config *pin = &dev->config->pins[i]; 72 | 73 | if (pin->mode == MCP2210_PIN_SPI || !pin->has_irq) 74 | continue; 75 | 76 | ++dev->nr_irqs; 77 | BUG_ON(dev->irq_revmap[i]); 78 | dev->irq_revmap[i] = pin->irq; 79 | 80 | if (pin->mode == MCP2210_PIN_DEDICATED) 81 | dev->poll_intr = 1; 82 | else if (pin->mode == MCP2210_PIN_GPIO) { 83 | dev->poll_gpio = 1; 84 | dev->irq_type[i] = pin->irq_type; 85 | } 86 | } 87 | 88 | if (!dev->nr_irqs) 89 | return 0; 90 | 91 | ret = irq_alloc_descs(-1, 0, dev->nr_irqs, 0); 92 | if (ret < 0) { 93 | /* CONFIG_SPARSE_IRQ needed? */ 94 | mcp2210_err("Failed to allocate %u irq descriptors: %d", dev->nr_irqs, ret); 95 | return ret; 96 | } 97 | dev->irq_base = ret; 98 | 99 | for (i = 0; i < dev->nr_irqs; ++i) { 100 | int virq = dev->irq_base + i; 101 | 102 | dev->irq_descs[i] = irq_to_desc(virq); 103 | BUG_ON(!dev->irq_descs[i]); 104 | irq_set_chip_data(virq, dev); 105 | irq_set_chip(virq, &mcp2210_irq_chip); 106 | 107 | #if defined(CONFIG_ARM) && LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) 108 | set_irq_flags(virq, 0); 109 | #else 110 | irq_set_noprobe(virq); 111 | #endif 112 | } 113 | 114 | #ifdef CONFIG_MCP2210_GPIO 115 | if (dev->poll_gpio) { 116 | ctl_cmd_init(dev, &dev->cmd_poll_gpio, 117 | MCP2210_CMD_GET_PIN_VALUE, 0, NULL, 0, false); 118 | dev->cmd_poll_gpio.head.complete = complete_poll; 119 | mcp2210_add_cmd(&dev->cmd_poll_gpio.head, false); 120 | } 121 | #endif /* CONFIG_MCP2210_GPIO */ 122 | 123 | if (dev->poll_intr) { 124 | /* read and then reset */ 125 | ctl_cmd_init(dev, &dev->cmd_poll_intr, 126 | MCP2210_CMD_GET_INTERRUPTS, 0, NULL, 0, false); 127 | dev->cmd_poll_intr.head.complete = complete_poll; 128 | mcp2210_add_cmd(&dev->cmd_poll_intr.head, false); 129 | } 130 | 131 | dev->is_irq_probed = 1; 132 | dev->suppress_poll_warn = 0; 133 | 134 | return 0; 135 | } 136 | 137 | void mcp2210_irq_disable(struct mcp2210_device *dev) 138 | { 139 | mcp2210_info(); 140 | if (dev->is_irq_probed) { 141 | const int virq_end = dev->irq_base + dev->nr_irqs; 142 | int virq; 143 | 144 | for (virq = dev->irq_base; virq < virq_end; ++virq) { 145 | irq_set_status_flags(virq, IRQ_NOREQUEST); 146 | irq_set_chip_and_handler(virq, NULL, NULL); 147 | synchronize_irq(virq); 148 | } 149 | } 150 | } 151 | 152 | void mcp2210_irq_remove(struct mcp2210_device *dev) 153 | { 154 | mcp2210_info(); 155 | if (dev->is_irq_probed) { 156 | const int virq_end = dev->irq_base + dev->nr_irqs; 157 | int virq; 158 | 159 | for (virq = dev->irq_base; virq < virq_end; ++virq) { 160 | irq_free_desc(virq); 161 | dev->irq_descs[virq] = NULL; 162 | } 163 | dev->is_irq_probed = 0; 164 | } 165 | } 166 | 167 | /****************************************************************************** 168 | * polling and virtual IRQ trigger functions 169 | */ 170 | 171 | void mcp2210_irq_do_gpio(struct mcp2210_device *dev, u16 old_val, u16 new_val) 172 | { 173 | uint i; 174 | for (i = 0; i < MCP2210_NUM_PINS; ++i) { 175 | struct mcp2210_pin_config *pin = &dev->config->pins[i]; 176 | int old_pin_val; 177 | int new_pin_val; 178 | int edge_mask, level_mask; 179 | struct irq_desc *desc; 180 | 181 | if (!pin->has_irq || pin->mode != MCP2210_PIN_GPIO) 182 | continue; 183 | 184 | old_pin_val = old_val & (1 << i); 185 | new_pin_val = new_val & (1 << i); 186 | level_mask = new_pin_val ? IRQ_TYPE_LEVEL_HIGH 187 | : IRQ_TYPE_LEVEL_LOW; 188 | desc = dev->irq_descs[pin->irq]; 189 | 190 | if (new_pin_val > old_val) 191 | edge_mask = IRQ_TYPE_EDGE_RISING; 192 | else if (new_pin_val < old_val) 193 | edge_mask = IRQ_TYPE_EDGE_FALLING; 194 | else 195 | edge_mask = 0; 196 | 197 | if (pin->irq_type & edge_mask) { 198 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0) 199 | handle_simple_irq(desc); 200 | #else 201 | handle_simple_irq(dev->irq_base + pin->irq, desc); 202 | #endif 203 | } 204 | 205 | if (pin->irq_type & level_mask) { 206 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0) 207 | handle_level_irq(desc); 208 | #else 209 | handle_level_irq(dev->irq_base + pin->irq, desc); 210 | #endif 211 | } 212 | } 213 | } 214 | 215 | void _mcp2210_irq_do_intr_counter(struct mcp2210_device *dev, u16 count) 216 | { 217 | struct mcp2210_pin_config *pin = &dev->config->pins[6]; 218 | struct irq_desc *desc = dev->irq_descs[pin->irq]; 219 | 220 | if (dev->s.chip_settings.pin_mode[6] != MCP2210_PIN_DEDICATED) 221 | return; 222 | 223 | /* We're discarding count and just letting handlers know that at least 224 | * one interrupt occured. Should this have a mechanism to report the 225 | * interrupt once for each count? */ 226 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0) 227 | handle_simple_irq(desc); 228 | #else 229 | handle_simple_irq(dev->irq_base + pin->irq, desc); 230 | #endif 231 | } 232 | 233 | static void warn_poll_past_due(struct mcp2210_device *dev, unsigned long now, 234 | long how_late) 235 | { 236 | mcp2210_warn("Next poll is past due by %ums.\n", 237 | jiffies_to_msecs(how_late)); 238 | 239 | if (!poll_delay_warn_secs) 240 | return; 241 | 242 | /* If result ends up zero then it'll just repeat once */ 243 | dev->suppress_poll_warn = now + msecs_to_jiffies(1000 244 | * poll_delay_warn_secs); 245 | mcp2210_warn("...suppressing for %u seconds.\n", poll_delay_warn_secs); 246 | } 247 | 248 | static int complete_poll(struct mcp2210_cmd *cmd_head, void *context) 249 | { 250 | struct mcp2210_device *dev = cmd_head->dev; 251 | struct mcp2210_cmd_ctl *cmd = (void*)cmd_head; 252 | unsigned long now = jiffies; 253 | int enabled; 254 | unsigned long interval; 255 | unsigned long interval_j; 256 | unsigned long next; 257 | long next_diff; 258 | 259 | mcp2210_debug(); 260 | 261 | if (dev->dead) 262 | return -EINPROGRESS; 263 | 264 | if (cmd->req.cmd == MCP2210_CMD_GET_PIN_VALUE) { 265 | enabled = dev->poll_gpio; 266 | interval = dev->config->poll_gpio_usecs; 267 | dev->last_poll_gpio = now; 268 | } else { 269 | enabled = dev->poll_intr; 270 | interval = dev->config->poll_intr_usecs; 271 | dev->last_poll_intr = now; 272 | } 273 | 274 | if (dev->suppress_poll_warn && jiffdiff(dev->suppress_poll_warn, now) <= 0) 275 | dev->suppress_poll_warn = 0; 276 | 277 | if (!enabled) 278 | return -EINPROGRESS; 279 | 280 | interval_j = usecs_to_jiffies(interval); 281 | next = dev->eps[EP_OUT].submit_time + interval_j; 282 | next_diff = jiffdiff(next, now); 283 | cmd->head.delayed = 1; 284 | 285 | if (next_diff < 0) { 286 | next = now + interval_j; 287 | if (!dev->suppress_poll_warn) 288 | warn_poll_past_due(dev, now, -next_diff); 289 | } 290 | cmd->head.delay_until = next; 291 | 292 | #if 0 293 | mcp2210_debug("interval_j: %lu, submit_time: %lu, next: %lu, jiffies: %lu", 294 | interval_j, dev->eps[EP_OUT].submit_time, next, jiffies); 295 | #endif 296 | 297 | cmd->head.state = MCP2210_STATE_NEW; 298 | 299 | mcp2210_add_cmd(cmd_head, false); 300 | 301 | return -EINPROGRESS; /* tell process_commands not to free us */ 302 | } 303 | 304 | #endif /* CONFIG_MCP2210_IRQ */ 305 | -------------------------------------------------------------------------------- /mcp2210-spi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 driver spi layer 3 | * 4 | * Copyright (c) 2013 Mathew King for Trilithic, Inc 5 | * 2013-2017 Daniel Santos 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "mcp2210.h" 27 | #include "mcp2210-debug.h" 28 | 29 | #ifdef CONFIG_MCP2210_SPI 30 | 31 | /* The non-queued mechanism will supposedly be phased out in the future. 32 | * However, we don't get any benefit from the new API since we just queue 33 | * a command (in our own queue) when we get a new message anyway. Thus, when/if 34 | * the old SPI transfer() mechanism is phased out, this version check should be 35 | * modified to force use of the new queued mechanism. 36 | */ 37 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(99,99,0) 38 | # define USE_SPI_QUEUE 1 39 | #endif 40 | 41 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0) 42 | # define HAVE_SPI_CS_GPIO 1 43 | #endif 44 | 45 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,12,0) 46 | # define HAVE_SPI_MASTER_SPEED 1 47 | #endif 48 | 49 | static int spi_submit_prepare(struct mcp2210_cmd *cmd_head); 50 | static int spi_complete_urb(struct mcp2210_cmd *cmd_head); 51 | static int spi_mcp_error(struct mcp2210_cmd *cmd_head); 52 | static int spi_complete_cmd(struct mcp2210_cmd *cmd_head, void *context); 53 | 54 | const struct mcp2210_cmd_type mcp2210_cmd_type_spi = { 55 | .id = MCP2210_CMD_TYPE_SPI, 56 | .submit_prepare = spi_submit_prepare, 57 | .complete_urb = spi_complete_urb, 58 | .mcp_error = spi_mcp_error, 59 | .dump = dump_cmd_spi, 60 | .desc = "spi" 61 | }; 62 | 63 | static inline struct mcp2210_device *mcp2210_spi2dev(struct spi_device *spi) 64 | { 65 | return *((struct mcp2210_device **)spi_master_get_devdata(spi->master)); 66 | } 67 | 68 | static inline struct mcp2210_device *mcp2210_spi_master2dev(struct spi_master *master) 69 | { 70 | return *((struct mcp2210_device **)spi_master_get_devdata(master)); 71 | } 72 | 73 | /****************************************************************************** 74 | * SPI Master funtions 75 | */ 76 | 77 | static void mcp2210_spi_cleanup(struct spi_device *spi); 78 | static int mcp2210_spi_setup(struct spi_device *spi); 79 | #ifndef USE_SPI_QUEUE 80 | static int transfer(struct spi_device *spi, struct spi_message *msg); 81 | #else 82 | static int prepare_transfer_hardware(struct spi_master *master); 83 | static int transfer_one_message(struct spi_master *master, struct spi_message *msg); 84 | static int unprepare_transfer_hardware(struct spi_master *master); 85 | #endif /* USE_SPI_QUEUE */ 86 | 87 | static void mcp2210_spi_probe_async(struct work_struct *work); 88 | 89 | struct async_spi_probe { 90 | struct work_struct work; 91 | struct mcp2210_device *dev; 92 | }; 93 | 94 | /** 95 | * mcp2210_spi_probe - 96 | * @dev: 97 | */ 98 | // may sleep 99 | int mcp2210_spi_probe(struct mcp2210_device *dev) { 100 | struct spi_master *master; /* just a local for code brievity */ 101 | int ret; 102 | struct async_spi_probe *async_probe; 103 | 104 | mcp2210_info(); 105 | 106 | BUG_ON(!dev); 107 | BUG_ON(!dev->config); 108 | BUG_ON(dev->spi_master); 109 | 110 | if (!dev || !dev->config || dev->spi_master) 111 | return -EINVAL; 112 | 113 | async_probe = kzalloc(sizeof(*async_probe), GFP_KERNEL); 114 | if (!async_probe) 115 | return -ENOMEM; 116 | 117 | INIT_WORK(&async_probe->work, mcp2210_spi_probe_async); 118 | async_probe->dev = dev; 119 | 120 | master = spi_alloc_master(&dev->udev->dev, sizeof(void*)); 121 | if (!master) 122 | return -ENOMEM; 123 | 124 | /* we only need a pointer to the struct mcp2210_device */ 125 | *((struct mcp2210_device **)spi_master_get_devdata(master)) = dev; 126 | 127 | #ifdef HAVE_SPI_MASTER_SPEED 128 | master->max_speed_hz = MCP2210_MAX_SPEED; 129 | master->min_speed_hz = MCP2210_MIN_SPEED; 130 | #endif 131 | master->bus_num = -1; 132 | master->num_chipselect = MCP2210_NUM_PINS; 133 | /* unused: master->dma_alignment */ 134 | master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; // | SPI_3WIRE | SPI_NO_CS; 135 | /* TODO: what's bits_per_word_mask for? */ 136 | master->flags = 0; 137 | /* unused: master->bus_lock_spinlock 138 | * unused: master->bus_lock_mutex 139 | * unused: master->bus_lock_flag 140 | */ 141 | master->setup = mcp2210_spi_setup; 142 | master->cleanup = mcp2210_spi_cleanup; 143 | #ifndef USE_SPI_QUEUE 144 | master->transfer = transfer; 145 | #else 146 | master->transfer = NULL; 147 | master->prepare_transfer_hardware = prepare_transfer_hardware; 148 | master->transfer_one_message = transfer_one_message; 149 | master->unprepare_transfer_hardware = unprepare_transfer_hardware; 150 | #endif 151 | 152 | memset(dev->chips, 0, sizeof(dev->chips)); 153 | 154 | ret = spi_register_master(master); 155 | 156 | if (ret) { 157 | spi_master_put(master); 158 | dev->spi_master = NULL; 159 | 160 | return ret; 161 | } 162 | 163 | dev->spi_master = master; 164 | dev->is_spi_probed = 1; 165 | schedule_work(&async_probe->work); 166 | 167 | return 0; 168 | } 169 | 170 | /* Manage probing SPI protocol drivers, which must be done in a context that 171 | * can sleep. */ 172 | static void mcp2210_spi_probe_async(struct work_struct *work) { 173 | struct async_spi_probe *async_probe = (void*)work; 174 | struct mcp2210_device *dev = async_probe->dev; 175 | struct spi_master *master = dev->spi_master; 176 | int ret; 177 | unsigned i; 178 | 179 | mcp2210_info(); 180 | 181 | for (i = 0; i < MCP2210_NUM_PINS; ++i) { 182 | struct mcp2210_pin_config *cfg = &dev->config->pins[i]; 183 | struct spi_device *chip; 184 | const char *modalias; 185 | 186 | if (cfg->mode != MCP2210_PIN_SPI) 187 | continue; 188 | 189 | chip = spi_alloc_device(master); 190 | if (!chip) { 191 | ret = -ENOMEM; 192 | goto error; 193 | } 194 | 195 | chip->max_speed_hz = cfg->spi.max_speed_hz; 196 | chip->chip_select = i; 197 | chip->mode = cfg->spi.mode; 198 | chip->bits_per_word = cfg->spi.bits_per_word; 199 | 200 | #ifdef HAVE_SPI_CS_GPIO 201 | # ifdef CONFIG_MCP2210_GPIO 202 | if (cfg->spi.use_cs_gpio) 203 | chip->cs_gpio = dev->gpio.base + cfg->spi.cs_gpio; 204 | else 205 | # endif 206 | chip->cs_gpio = -EINVAL; 207 | #endif /* HAVE_SPI_CS_GPIO */ 208 | 209 | #ifdef CONFIG_MCP2210_IRQ 210 | if (cfg->has_irq) 211 | chip->irq = dev->irq_base + cfg->irq; 212 | else 213 | #endif 214 | chip->irq = -1; 215 | 216 | /* unused: chip->controller_state 217 | * unused: chip->controller_data 218 | */ 219 | modalias = cfg->modalias ? cfg->modalias : "spidev"; 220 | 221 | WARN_ON(strlen(modalias) >= sizeof(chip->modalias)); 222 | strncpy(chip->modalias, modalias, sizeof(chip->modalias)); 223 | 224 | dev->chips[i] = chip; 225 | ret = spi_add_device(chip); 226 | if (ret < 0) { 227 | /* FIXME: almost certainly a race condition */ 228 | dev->chips[i] = NULL; 229 | spi_dev_put(chip); 230 | goto error; 231 | } 232 | } 233 | 234 | mcp2210_info("spi device probe completed"); 235 | kfree(async_probe); 236 | return;// 0; 237 | 238 | error: 239 | mcp2210_err("SPI device failed to probe: %d\n", ret); 240 | dev->spi_master = NULL; 241 | memset(dev->chips, 0, sizeof(dev->chips)); 242 | spi_unregister_master(master); 243 | kfree(async_probe); 244 | 245 | return;// ret; 246 | } 247 | 248 | void mcp2210_spi_remove(struct mcp2210_device *dev) 249 | { 250 | if (!dev || !dev->spi_master) 251 | return; 252 | 253 | mcp2210_info(); 254 | spi_unregister_master(dev->spi_master); 255 | 256 | dev->spi_master = NULL; 257 | } 258 | 259 | 260 | /* cleanup for this SPI device (not the SPI master) */ 261 | static void mcp2210_spi_cleanup(struct spi_device *spi) 262 | { 263 | struct mcp2210_device *dev = mcp2210_spi2dev(spi); 264 | 265 | mcp2210_info("pin %d", spi->chip_select); 266 | 267 | /* Do we have any cleanup to do? */ 268 | } 269 | 270 | #ifdef HAVE_SPI_MASTER_SPEED 271 | static inline int validate_speed(struct mcp2210_device *dev, u32 bitrate) 272 | { 273 | return 0; 274 | } 275 | #else 276 | static int validate_speed(struct mcp2210_device *dev, u32 bitrate) 277 | { 278 | if (bitrate < MCP2210_MIN_SPEED || bitrate > MCP2210_MAX_SPEED) { 279 | mcp2210_err("The requested speed of %uHz is not supported " 280 | "(must be in the range of 1500Hz and 12MHz)", 281 | bitrate); 282 | return -EINVAL; 283 | } 284 | 285 | return 0; 286 | } 287 | #endif 288 | 289 | /* must hold dev_spinlock */ 290 | /** 291 | * is_spi_in_flight - determine if a non-failed SPI message is in progress on 292 | * the specified pin 293 | * 294 | * This will not return true if an SPI transfer is "in progress" or may be in 295 | * progress on the chip due to some communication failure, but only if a real, 296 | * live non-failed transfer is in progress. 297 | */ 298 | static int is_spi_in_flight(const struct mcp2210_device *dev, u8 pin) 299 | { 300 | const struct mcp2210_cmd *cmd_head = dev->cur_cmd; 301 | 302 | if (cmd_head && cmd_head->type == &mcp2210_cmd_type_spi) { 303 | const struct mcp2210_cmd_spi_msg *cmd = (void *)cmd_head; 304 | 305 | return cmd->spi_in_flight && cmd->spi->chip_select == pin; 306 | } 307 | return 0; 308 | } 309 | 310 | /* may sleep */ 311 | static int mcp2210_spi_setup(struct spi_device *spi) 312 | { 313 | struct mcp2210_device *dev = mcp2210_spi2dev(spi); 314 | unsigned long irqflags; 315 | u8 pin = spi->chip_select; 316 | int ret = 0; 317 | 318 | mcp2210_info("spi_in_flight: %d, cur_spi_config: %d", 319 | dev->spi_in_flight, dev->s.cur_spi_config); 320 | 321 | if (dump_cmds) 322 | dump_spi_device(KERN_INFO, 0, "mcp2210_spi_setup: " 323 | "spi_device = ", spi); 324 | 325 | if (validate_speed(dev, spi->max_speed_hz)) 326 | return -EINVAL; 327 | 328 | if (spi->bits_per_word != 8) { 329 | mcp2210_err("MCP2210 only supports 8 bits per word, got " 330 | "request for %hhu", spi->bits_per_word); 331 | return -EINVAL; 332 | } 333 | 334 | if (pin > 8) { 335 | mcp2210_err("spi->chip_select must be in the range of 0 - 8, " 336 | "got %hu", pin); 337 | return -EINVAL; 338 | } 339 | 340 | spin_lock_irqsave(&dev->dev_spinlock, irqflags); 341 | 342 | /* We must error if a transfer is in progress on the same SPI device */ 343 | if (is_spi_in_flight(dev, pin)) { 344 | mcp2210_err("SPI message in progress"); 345 | ret = -EBUSY; 346 | } 347 | 348 | spin_unlock_irqrestore(&dev->dev_spinlock, irqflags); 349 | return ret; 350 | } 351 | 352 | static int queue_msg(struct mcp2210_device *dev, struct spi_message *msg, 353 | bool can_sleep) 354 | { 355 | u8 pin = msg->spi->chip_select; 356 | struct mcp2210_cmd_spi_msg *cmd; 357 | struct list_head *pos; 358 | struct mcp2210_pin_config *pin_config = &dev->config->pins[pin]; 359 | struct spi_transfer *first_xfer = list_entry(msg->transfers.next, 360 | struct spi_transfer, 361 | transfer_list); 362 | struct spi_transfer *last_xfer = list_entry(msg->transfers.prev, 363 | struct spi_transfer, 364 | transfer_list); 365 | gfp_t gfp_flags = can_sleep ? GFP_KERNEL : GFP_ATOMIC; 366 | uint xfer_chain_size = 0; 367 | int ret; 368 | 369 | mcp2210_debug("Start new transfer (pin %d)\n", pin); 370 | 371 | if (dev->dead) 372 | return -ESHUTDOWN; 373 | 374 | /* debug-only sanity checks */ 375 | if (IS_ENABLED(CONFIG_MCP2210_DEBUG)) { 376 | if (pin_config->mode != MCP2210_PIN_SPI) { 377 | mcp2210_err("Attempt to SPI on non-spi pin!"); 378 | return -EINVAL; 379 | } 380 | 381 | if (list_empty(&msg->transfers)) { 382 | mcp2210_err("empty transfer list"); 383 | return -EINVAL; 384 | } 385 | } 386 | 387 | if (last_xfer->cs_change) { 388 | mcp2210_err("Unsupported: cs_change set on last transfer in " 389 | "message."); 390 | return -EINVAL; 391 | } 392 | 393 | list_for_each(pos, &msg->transfers) { 394 | struct spi_transfer *xfer = list_entry( 395 | pos, 396 | struct spi_transfer, 397 | transfer_list); 398 | 399 | /* Validate that an individual transfer or chain isn't too 400 | * large for this poor thing to handle (if you really need this 401 | * and are using gpio for cs then you can hack this driver to 402 | * make it happen). */ 403 | xfer_chain_size += xfer->len; 404 | 405 | if (xfer->len > 0xffff) { 406 | mcp2210_err("SPI transfer (or chain) too large for " 407 | "MCP2210 (%u bytes)", xfer_chain_size); 408 | return -EINVAL; 409 | } 410 | 411 | if (xfer->cs_change && xfer != last_xfer) 412 | xfer_chain_size = 0; 413 | 414 | BUG_ON(!xfer->tx_buf); 415 | 416 | if (xfer->bits_per_word && xfer->bits_per_word != 8) { 417 | mcp2210_warn("unsupported: spi_transfer.bits_per_word " 418 | "= %hhu", xfer->bits_per_word); 419 | return -EINVAL; 420 | } 421 | 422 | if (xfer->speed_hz && validate_speed(dev, xfer->speed_hz)) 423 | return -EINVAL; 424 | } 425 | 426 | 427 | cmd = mcp2210_alloc_cmd_type(dev, struct mcp2210_cmd_spi_msg, 428 | &mcp2210_cmd_type_spi, gfp_flags); 429 | 430 | if (!cmd) 431 | return -ENOMEM; 432 | 433 | cmd->head.can_retry = 1; 434 | cmd->head.complete = spi_complete_cmd; 435 | cmd->head.context = NULL; 436 | cmd->spi = msg->spi; 437 | cmd->msg = msg; 438 | cmd->xfer = first_xfer; 439 | cmd->head.non_preemptable = 1; 440 | /* not needed (kzalloc) 441 | cmd->pos = 0; 442 | cmd->pending_unacked = 0; 443 | cmd->pending_unacked = 0; 444 | cmd->pending_bytes = 0; 445 | cmd->busy_count = 0 446 | cmd->spi_in_flight = 0 447 | cmd->ctl_cmd = NULL; 448 | */ 449 | 450 | ret = mcp2210_add_cmd(&cmd->head, true); 451 | if (ret) 452 | return ret; 453 | 454 | return process_commands(dev, false, can_sleep); 455 | } 456 | 457 | #ifndef USE_SPI_QUEUE 458 | /* will not sleep */ 459 | static int transfer(struct spi_device *spi, struct spi_message *msg) 460 | { 461 | return queue_msg(mcp2210_spi2dev(spi), msg, false); 462 | } 463 | 464 | #else 465 | 466 | static int prepare_transfer_hardware(struct spi_master *master) 467 | { 468 | struct mcp2210_device *dev = mcp2210_spi_master2dev(master); 469 | mcp2210_info(); 470 | 471 | /* if we knew the size of the message, we could actually prepare */ 472 | 473 | return 0; 474 | } 475 | 476 | /* may sleep */ 477 | static int transfer_one_message(struct spi_master *master, 478 | struct spi_message *msg) 479 | { 480 | int ret; 481 | 482 | ret = queue_msg(mcp2210_spi_master2dev(master), msg, true); 483 | if (ret) { 484 | msg->status = ret; 485 | spi_finalize_current_message(master); 486 | } 487 | 488 | return ret; 489 | } 490 | 491 | static int unprepare_transfer_hardware(struct spi_master *master) 492 | { 493 | struct mcp2210_device *dev = mcp2210_spi_master2dev(master); 494 | mcp2210_info(); 495 | 496 | return 0; 497 | } 498 | #endif /* USE_SPI_QUEUE */ 499 | 500 | /****************************************************************************** 501 | * SPI Message command functions 502 | */ 503 | 504 | static int spi_ctl_cmd_submit_prepare(struct mcp2210_cmd_spi_msg *cmd) 505 | { 506 | struct mcp2210_device *dev = cmd->head.dev; 507 | struct mcp2210_cmd *ctl_cmd_head = &dev->ctl_cmd.head; 508 | 509 | cmd->ctl_cmd = &dev->ctl_cmd; 510 | cmd->head.can_retry = 1; 511 | 512 | mcp2210_info("----SUBMITING CONTROL COMMAND----"); 513 | return ctl_cmd_head->type->submit_prepare(ctl_cmd_head); 514 | } 515 | 516 | static int spi_prepare_device(struct mcp2210_cmd_spi_msg *cmd) 517 | { 518 | struct mcp2210_device *dev = cmd->head.dev; 519 | struct mcp2210_spi_xfer_settings needed; 520 | u8 pin = cmd->spi->chip_select; 521 | 522 | /* If we have an active control command it probably means that this is 523 | * a retry, (failed URB or the device was busy) so we need to retry. */ 524 | if (cmd->ctl_cmd) 525 | return spi_ctl_cmd_submit_prepare(cmd); 526 | 527 | /* Check if the chip is in the middle of a failed SPI transfer and if 528 | * so, cancel it. */ 529 | if (unlikely(dev->spi_in_flight)) { 530 | mcp2210_warn("*** old SPI message still in-flight, killing it " 531 | "***"); 532 | ctl_cmd_init(dev, &dev->ctl_cmd, MCP2210_CMD_SPI_CANCEL, 0, 533 | NULL, 0, false); 534 | return spi_ctl_cmd_submit_prepare(cmd); 535 | } 536 | 537 | if (cmd->spi->mode | SPI_3WIRE) { 538 | /* TODO: setup gpio controlling mosi */ 539 | } 540 | 541 | if (MCP2210_LOG_UNLIKELY(debug_level >= KERN_DEBUG[1] - '0')) { 542 | mcp2210_debug("dev->s.cur_spi_config = %d", 543 | dev->s.cur_spi_config); 544 | dump_spi_xfer_settings(KERN_DEBUG, 0, "dev->s.spi_settings = ", 545 | &dev->s.spi_settings); 546 | } 547 | 548 | calculate_spi_settings(&needed, dev, cmd->spi, cmd->msg, cmd->xfer, pin); 549 | 550 | if (needed.bytes_per_trans == 0) { 551 | mcp2210_err("Invalid: cannot send a zero-sized message"); 552 | return -EINVAL; 553 | } 554 | 555 | /* If this is the pin we are currently configured to SPI on, then let's 556 | * see if there's any difference in the SPI settings */ 557 | if (pin == dev->s.cur_spi_config) { 558 | /* if nothing changed, then then do the SPI xfer */ 559 | if (!compare_spi_settings(&needed, &dev->s.spi_settings)) 560 | return 0; 561 | 562 | if (MCP2210_LOG_UNLIKELY(debug_level >= KERN_DEBUG[1] - '0')) { 563 | mcp2210_debug("SPI transfer settings didn't match"); 564 | dump_spi_xfer_settings(KERN_DEBUG, 0, 565 | "needed = ", &needed); 566 | dump_spi_xfer_settings(KERN_DEBUG, 0, 567 | "dev->spi_settings = ", 568 | &dev->s.spi_settings); 569 | } 570 | } 571 | 572 | /* TODO: still missing cmd->spi->mode & (~(SPI_MODE_3 | SPI_CS_HIGH)) */ 573 | mcp2210_info("Settings SPI Transfer Settings"); 574 | ctl_cmd_init(dev, &dev->ctl_cmd, MCP2210_CMD_SET_SPI_CONFIG, 0, 575 | &needed, sizeof(needed), false); 576 | dev->ctl_cmd.pin = pin; 577 | 578 | return spi_ctl_cmd_submit_prepare(cmd); 579 | } 580 | 581 | static int spi_submit_prepare(struct mcp2210_cmd *cmd_head) 582 | { 583 | struct mcp2210_cmd_spi_msg *cmd = (void *)cmd_head; 584 | struct mcp2210_device *dev; 585 | struct mcp2210_msg *req; 586 | struct spi_transfer *xfer; 587 | unsigned len; 588 | const void *start; 589 | u8 pin; 590 | 591 | BUG_ON(!cmd_head->dev); 592 | dev = cmd_head->dev; 593 | pin = cmd->spi->chip_select; 594 | req = dev->eps[EP_OUT].buffer; 595 | 596 | mcp2210_debug("pin %hhu\n", pin); 597 | 598 | BUG_ON(pin > 8); 599 | BUG_ON(dev->config->pins[pin].mode != MCP2210_PIN_SPI); 600 | 601 | /* If we're in the middle of an SPI transfer, we can't do control 602 | * commands. Thus, we should never have a control command going on at 603 | * the saime time we're in the middle of an SPI transfer. Also, SPI 604 | * transfers cannot be retried if there's a failed or stalled URB at 605 | * the USB level. This mostly applies to broken usb drivers, but can 606 | * also with hardware failures, etc. 607 | */ 608 | if (cmd->spi_in_flight) 609 | BUG_ON(cmd->ctl_cmd); 610 | else { 611 | int ret = spi_prepare_device(cmd); 612 | 613 | /* cmd->ctl_cmd will be non-null if we're submitting a control 614 | * command. */ 615 | if (ret || cmd->ctl_cmd) 616 | return ret; 617 | } 618 | 619 | xfer = cmd->xfer; 620 | /* Dump SPI TX on very first xfer of message. */ 621 | if (IS_ENABLED(CONFIG_MCP2210_DEBUG) 622 | && cmd->pos + cmd->pending_bytes == 0 && dump_spi) { 623 | char msg_prefix[32]; 624 | BUG_ON(!xfer->tx_buf); 625 | snprintf(msg_prefix, sizeof(msg_prefix), "%s -->: ", 626 | dev_name(&cmd->spi->dev)); 627 | print_hex_dump(KERN_INFO, msg_prefix, DUMP_PREFIX_OFFSET, 16, 1, 628 | xfer->tx_buf, xfer->len, true); 629 | } 630 | 631 | /* Write spi transfer command */ 632 | if (cmd->pos + cmd->pending_bytes < xfer->len) { 633 | len = xfer->len - cmd->pos - cmd->pending_bytes; 634 | 635 | /* don't try to send more than the buffer will hold */ 636 | if (len > MCP2210_BUFFER_SIZE - cmd->pending_bytes) { 637 | len = MCP2210_BUFFER_SIZE - cmd->pending_bytes; 638 | if (!len) 639 | goto buffer_full; 640 | } 641 | 642 | if (len > MCP2210_BUFFER_SIZE - 4) 643 | len = MCP2210_BUFFER_SIZE - 4; 644 | 645 | /* for NULL tx buffer means we just send zeros. Unfortunately, 646 | * we can't do 3-wire and have MOSI in high-z with this device, 647 | * so you'll have to use a gpio and external component to 648 | * enable that */ 649 | start = xfer->tx_buf 650 | ? xfer->tx_buf + cmd->pos + cmd->pending_bytes 651 | : NULL; 652 | 653 | cmd->pending_unacked = len; 654 | cmd->pending_bytes += len; 655 | } else { 656 | buffer_full: 657 | len = 0; 658 | start = NULL; 659 | } 660 | 661 | mcp2210_init_msg(req, MCP2210_CMD_SPI_TRANSFER, len, 0, start, len, 662 | true); 663 | 664 | if (len) 665 | mcp2210_debug("sending %u bytes", len); 666 | else 667 | mcp2210_debug("requesting final data"); 668 | 669 | mcp2210_debug("len: %u, cmd->pending_bytes: %hu\n", len, 670 | cmd->pending_bytes); 671 | dev->spi_in_flight = 1; 672 | cmd->spi_in_flight = 1; 673 | cmd->head.can_retry = 0; 674 | 675 | /* FIXME: dump message params */ 676 | return 0; 677 | } 678 | 679 | static void spi_complete_ctl_cmd(struct mcp2210_cmd_spi_msg *cmd) 680 | { 681 | struct mcp2210_device *dev = cmd->head.dev; 682 | struct mcp2210_cmd *cc = &cmd->ctl_cmd->head; 683 | 684 | /* always returns zero, so ignoring return value */ 685 | cc->type->complete_urb(cc); 686 | 687 | if (IS_ENABLED(CONFIG_MCP2210_DEBUG) && dump_cmds) { 688 | mcp2210_debug("----CONTROL COMMAND RESPONSED----"); 689 | 690 | cc->state = MCP2210_STATE_COMPLETE; 691 | /* get the dump function to print the response */ 692 | mcp2210_debug("dev->s.cur_spi_config = %d", 693 | dev->s.cur_spi_config); 694 | dump_spi_xfer_settings(KERN_INFO, 0, "spi settings now: ", 695 | &dev->s.spi_settings); 696 | } 697 | 698 | cmd->ctl_cmd = NULL; 699 | } 700 | 701 | static int spi_complete_urb(struct mcp2210_cmd *cmd_head) 702 | { 703 | struct mcp2210_cmd_spi_msg *cmd = (void *)cmd_head; 704 | struct mcp2210_device *dev = cmd_head->dev; 705 | struct mcp2210_msg *rep; 706 | const struct mcp2210_pin_config_spi *cfg; 707 | unsigned long now = jiffies; 708 | struct spi_transfer *xfer; 709 | // long urb_duration = jiffdiff(now, dev->eps[EP_OUT].submit_time); 710 | // int bytes_pending; 711 | long expected_time_usec = 0; 712 | u8 pin = cmd->spi->chip_select; 713 | u8 len; 714 | 715 | if (IS_ENABLED(CONFIG_MCP2210_DEBUG)) { 716 | BUG_ON(!cmd_head->dev); 717 | BUG_ON(pin > 8); 718 | BUG_ON(dev->config->pins[pin].mode != MCP2210_PIN_SPI); 719 | } 720 | 721 | mcp2210_debug(); 722 | 723 | if(cmd->ctl_cmd) { 724 | spi_complete_ctl_cmd(cmd); 725 | return -EAGAIN; 726 | } 727 | 728 | rep = dev->eps[EP_IN].buffer; 729 | len = rep->head.rep.spi.size; 730 | cfg = &dev->config->pins[pin].spi; 731 | 732 | 733 | /* len is the number of bytes successfully txed and rxed to/from the 734 | * SPI peripheral */ 735 | if (cmd->pending_bytes < len) { 736 | mcp2210_err("Device returned (and transmitted) more bytes " 737 | "than expected."); 738 | return -EOVERFLOW; 739 | } 740 | 741 | cmd->pending_bytes -= len; 742 | cmd->pending_unacked = 0; 743 | 744 | mcp2210_debug("pin %hhu\n", pin); 745 | //mcp2210_debug("len: %hhu, cmd->pending_bytes: %hu, URB duration %uus (%lu jiffies)\n", len, cmd->pending_bytes, jiffies_to_usecs(urb_duration), urb_duration); 746 | //dump_msg(cmd->head.dev, KERN_DEBUG, "SPI response: ", rep); 747 | 748 | xfer = cmd->xfer; 749 | /* See if there is data to receive */ 750 | if (len) { 751 | #if 0 752 | /* bounds check first */ 753 | if (unlikely(cmd->pos + len > xfer->len)) { 754 | mcp2210_err("received more data from device than " 755 | "expected."); 756 | return -EOVERFLOW; 757 | } 758 | #endif 759 | 760 | if(xfer->rx_buf) 761 | memcpy(xfer->rx_buf + cmd->pos, rep->body.raw, 762 | len); 763 | 764 | cmd->pos += len; 765 | cmd->msg->actual_length += len; 766 | } 767 | 768 | /* check if xfer is finished */ 769 | if(cmd->pos == xfer->len) { 770 | struct list_head *next = xfer->transfer_list.next; 771 | 772 | BUG_ON(cmd->pending_bytes != 0); /* sanity check */ 773 | 774 | /* Transfer is done move next */ 775 | mcp2210_info("%u byte xfer complete (all rx bytes read)", 776 | xfer->len); 777 | 778 | if (IS_ENABLED(CONFIG_MCP2210_DEBUG) && dump_spi) { 779 | char msg_prefix[32]; 780 | snprintf(msg_prefix, sizeof(msg_prefix), "%s <--: ", 781 | dev_name(&cmd->spi->dev)); 782 | if (xfer->rx_buf) 783 | print_hex_dump(KERN_INFO, msg_prefix, 784 | DUMP_PREFIX_OFFSET, 16, 1, 785 | xfer->rx_buf, xfer->len, true); 786 | else 787 | printk(KERN_INFO "%s(rx_buf is NULL)", 788 | msg_prefix); 789 | } 790 | 791 | /* if cs_change, then clearing cmd->spi_in_flight will trigger 792 | * a re-check of spi transfer settings in the next call to 793 | * spi_submit_prepare() */ 794 | if (xfer->cs_change) 795 | dev->spi_in_flight = cmd->spi_in_flight = 0; 796 | 797 | if (list_is_last(&xfer->transfer_list, 798 | &cmd->msg->transfers)) { 799 | mcp2210_info("Command complete"); 800 | dev->spi_in_flight = cmd->spi_in_flight = 0; 801 | cmd->msg->status = cmd->head.status = 0; 802 | return 0; 803 | } 804 | 805 | mcp2210_info("Starting next spi_transfer..."); 806 | xfer = list_entry(next, struct spi_transfer, 807 | transfer_list); 808 | cmd->pos = 0; 809 | cmd->pending_bytes = 0; 810 | cmd->busy_count = 0; 811 | cmd->ctl_cmd = NULL; 812 | 813 | /* honor delay between xfers here */ 814 | expected_time_usec = 100ul * cfg->delay_between_xfers; 815 | 816 | } else if (cmd->pending_bytes >= pending_bytes_wait_threshold) { 817 | /* if the MCP2210's tiny buffer has at least 818 | * pending_bytes_wait_threshold bytes in it then we'll give it 819 | * some more time before sneding more */ 820 | 821 | mcp2210_debug("cmd->pending_bytes: %u @ %uHz\n", cmd->pending_bytes, dev->s.spi_settings.bitrate); 822 | expected_time_usec = cmd->pending_bytes 823 | * dev->s.spi_delay_per_kb / 1024ul; 824 | 825 | /* Account for last byte to CS delay if applicable */ 826 | if (xfer->cs_change && list_is_last( 827 | &xfer->transfer_list, 828 | &cmd->msg->transfers)) { 829 | expected_time_usec += 100ul * cfg->last_byte_to_cs_delay; 830 | } 831 | 832 | /* We only get this at the start of a transfer, so this should 833 | * be correct */ 834 | if (rep->head.rep.spi.spi_status == 0x20) 835 | expected_time_usec += 100ul * cfg->cs_to_data_delay; 836 | } 837 | 838 | mcp2210_debug("expected_time_usec: %lu (%lu jiffies)\n", 839 | expected_time_usec, usecs_to_jiffies(expected_time_usec)); 840 | 841 | if (expected_time_usec) { 842 | cmd->head.delay_until = now + usecs_to_jiffies(expected_time_usec); 843 | cmd->head.delayed = 1; 844 | } else 845 | cmd->head.delayed = 0; 846 | 847 | return -EAGAIN; 848 | } 849 | 850 | static int spi_mcp_error(struct mcp2210_cmd *cmd_head) 851 | { 852 | struct mcp2210_cmd_spi_msg *cmd = (void *)cmd_head; 853 | struct mcp2210_device *dev = cmd->head.dev; 854 | 855 | /* remove the rejected bytes from the in-process count */ 856 | BUG_ON(cmd->pending_unacked > cmd->pending_bytes); 857 | cmd->pending_bytes -= cmd->pending_unacked; 858 | cmd->pending_unacked = 0; 859 | 860 | if (cmd->head.mcp_status == MCP2210_STATUS_BUSY) { 861 | mcp2210_warn("cmd->busy_count %u\n", cmd->busy_count); 862 | 863 | ++cmd->busy_count; 864 | if (cmd->busy_count < 64) { 865 | /* hmm, hopefully shoudn't happen */ 866 | /* FIXME: tweak this somehow */ 867 | cmd->head.delay_until = jiffies + usecs_to_jiffies(750); 868 | cmd->head.delayed = 1; 869 | return -EAGAIN; 870 | } else 871 | return -EBUSY; 872 | } 873 | mcp2210_err("Unexpected return value from MCP2210: 0x%02hhx", 874 | cmd->head.mcp_status); 875 | return -EIO; 876 | } 877 | 878 | static int spi_complete_cmd(struct mcp2210_cmd *cmd_head, void *context) 879 | { 880 | struct mcp2210_cmd_spi_msg *cmd = (void*)cmd_head; 881 | struct spi_message *msg = cmd->msg; 882 | 883 | msg->status = cmd->head.status; 884 | 885 | #ifndef USE_SPI_QUEUE 886 | if (msg->complete) 887 | msg->complete(msg->context); 888 | #else 889 | spi_finalize_current_message(cmd->head.dev->spi_master); 890 | #endif 891 | return 0; 892 | } 893 | 894 | #endif /* CONFIG_MCP2210_SPI */ 895 | -------------------------------------------------------------------------------- /out-of-tree-autoconf.h.template: -------------------------------------------------------------------------------- 1 | /* 2 | * out-of-tree-autoconf.h 3 | * 4 | * Use this for your configuration when building the MCP2210 out of tree, which 5 | * happens to be always right now. 6 | */ 7 | 8 | #ifndef _OUT_OF_TREE_AUTOCONF_H_ 9 | #define _OUT_OF_TREE_AUTOCONF_H_ 10 | 11 | /* hack for KDevelop's parser until they add the ability fucking specify 12 | * gcc-style -include files */ 13 | #ifdef __KERNEL__ 14 | # undef __KERNEL__ 15 | #elif !defined(__USER__) 16 | # include 17 | #endif 18 | #ifndef __USER__ 19 | # define __KERNEL__ 20 | #endif 21 | 22 | /* like include/generated/autoconf.h from the kernel tree, comment out what you 23 | * don't want (setting it to zero wont work) */ 24 | 25 | #define CONFIG_MCP2210_MODULE 1 26 | #define CONFIG_MCP2210_IOCTL 1 27 | #define CONFIG_MCP2210_SPI 1 28 | #define CONFIG_MCP2210_GPIO 1 29 | #define CONFIG_MCP2210_EEPROM 1 30 | #define CONFIG_MCP2210_CREEK 1 31 | #define CONFIG_MCP2210_IRQ 1 32 | #define CONFIG_MCP2210_DEBUG 1 33 | #define CONFIG_MCP2210_DEBUG_VERBOSE 1 34 | #define CONFIG_MCP2210_DEBUG_INITIAL 5 35 | 36 | /* basically, the RPi */ 37 | //#define CONFIG_MCP2210_USB_QUIRKS 1 38 | 39 | #endif /* _OUT_OF_TREE_AUTOCONF_H_ */ 40 | -------------------------------------------------------------------------------- /udev/99_mcp2210.rules: -------------------------------------------------------------------------------- 1 | # Unbind usbhid and rebind to mcp2210 driver 2 | 3 | ACTION=="add", DRIVER=="usbhid", SUBSYSTEMS=="usb", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="00de", RUN+="/usr/local/sbin/rebind_sysfs_driver.sh %S%p %k %S/bus/usb/drivers/mcp2210" 4 | -------------------------------------------------------------------------------- /udev/README: -------------------------------------------------------------------------------- 1 | To automatically unbind usbhid from the mcp2210, and bind to the 2 | mcp2210 driver, use the scripts included in this directory. 3 | 4 | Copy rebind_sysfs_driver.sh to /usr/local/sbin, set permissions to 0755. 5 | Copy 99_unbind_mcp2210_usbhid.rules to /etc/udev/rules.d. 6 | 7 | When you connect the mcp2210, the kernel will attach usbhid, but the 8 | small script will rebind to the proper kernel module, using the 9 | 10 | .../usbdev/driver/unbind 11 | and 12 | bus/usb/drivers/mcp2210/bind 13 | 14 | sysfs attributes. 15 | -------------------------------------------------------------------------------- /udev/rebind_sysfs_driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$#" != 3 ] ; then 4 | echo "Usage: $0 sysfs_path kernel_name new_driver_path" >&2 5 | echo "" >&2 6 | echo "To be used in udev rules:" >&2 7 | echo " RUN+=\"$0 %k %S%p %S/bus/usb/drivers/mcp2210\"" >&2 8 | echo "which rebinds a particular device to a new driver." >&2 9 | exit 1 10 | fi 11 | 12 | set -e 13 | logger -t "$0" "Rebind device $1($2) to driver $3." 14 | 15 | echo "$2" >> "$1/driver/unbind" 16 | sleep 1 17 | echo "$2" >> "$3/bind" 18 | -------------------------------------------------------------------------------- /user/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS ?= -O2 -pipe -g3 2 | CXXFLAGS ?= $(CFLAGS) 3 | WARN_FLAGS = -Wall -Wextra -Werror -Wcast-align -Wno-unused-parameter \ 4 | -Wno-missing-field-initializers -Wstrict-prototypes \ 5 | -Werror-implicit-function-declaration -Wundef 6 | CFLAGS += -fPIC $(WARN_FLAGS) 7 | CXXFLAGS += -fPIC $(WARN_FLAGS) 8 | CPPFLAGS += -I.. -D__USER__ -include out-of-tree-autoconf.h 9 | CC = $(CROSS_COMPILE)gcc 10 | CXX = $(CROSS_COMPILE)g++ 11 | 12 | LIB = libmcp2210.so 13 | LIB_OBJ_FILES = mcp2210-lib.o mcp2210-user.o 14 | UTIL_OBJ_FILES = mcp2210.o tests.o 15 | SELECT_GPIO_FILES = select-gpio.o 16 | ALL_OBJ_FILES = $(LIB_OBJ_FILES) $(UTIL_OBJ_FILES) 17 | HEADER_FILES = mcp2210-user.h settings.h ../mcp2210.h ../mcp2210-creek.h \ 18 | ../mcp2210-debug.h ../out-of-tree-autoconf.h 19 | SYMLINK_FILES = mcp2210-lib.c 20 | 21 | all: $(LIB) mcp2210-util select-gpio 22 | 23 | links: $(SYMLINK_FILES) 24 | 25 | mcp2210-lib.c: 26 | ln -fs ../mcp2210-lib.c . 27 | 28 | $(ALL_OBJ_FILES): $(HEADER_FILES) 29 | 30 | $(LIB): $(LIB_OBJ_FILES) 31 | $(CC) $(CFLAGS) $(LIB_OBJ_FILES) -shared -o $(LIB) 32 | 33 | # Create settings.h from example file if it doesn't exist 34 | settings.h: 35 | cp -n settings-example.h settings.h 36 | 37 | # FIXME: mcp2210 target doesn't need -fPIC in cc on obj files & link 38 | mcp2210-util: links $(UTIL_OBJ_FILES) $(HEADER_FILES) $(LIB) 39 | $(CC) $(CFLAGS) $(UTIL_OBJ_FILES) -lmcp2210 -L. -o mcp2210-util 40 | 41 | select-gpio: $(SELECT_GPIO_FILES) 42 | $(CC) $(CFLAGS) $(SELECT_GPIO_FILES) -o select-gpio 43 | 44 | .PHONY : clean 45 | clean: 46 | rm -f $(SYMLINK_FILES) *.o mcp2210-util select-gpio libmcp2210.so 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /user/mcp2210-user.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 userspace library 3 | * Copyright (c) 2013-2017 Daniel Santos 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "mcp2210-user.h" 20 | 21 | int mcp2210_do_ioctl(const char *devname, enum mcp2210_ioctl_cmd cmd, struct mcp2210_ioctl_data *data) { 22 | //int mcp2210_do_ioctl(const char *devname, enum mcp2210_ioctl_cmd cmd, void *arg) { 23 | struct mcp2210_device dev;// = INIT_MCP2210_DEVICE(devname); 24 | int ret = 0; 25 | 26 | if (mcp2210_open(&dev, devname) == -1) { 27 | perror("open failed"); 28 | return -1; 29 | } 30 | 31 | ret = mcp2210_ioctl(&dev, cmd, data); 32 | if (ret < 0) { 33 | perror("ioctl failed"); 34 | return -1; 35 | } 36 | 37 | if (mcp2210_close(&dev) == -1) { 38 | perror("ioctl failed"); 39 | return -1; 40 | } 41 | 42 | return 0; 43 | } 44 | #if 0 45 | int mcp2210_get_config(const char *devname, struct mcp2210_ioctl_data *dest) { 46 | struct mcp2210_ioctl_data *data; 47 | // struct mcp2210_device dev; 48 | const size_t struct_size = IOCTL_DATA_CONFIG_SIZE + 0x200; 49 | int ret = 0; 50 | 51 | data = malloc(struct_size); 52 | if (!data) { 53 | fatal_error("no mem"); 54 | return -ENOMEM; 55 | } 56 | 57 | memset(data, 0, struct_size); 58 | data->struct_size = struct_size; 59 | 60 | fprintf(stderr, "offset = %lu\n", (unsigned long)offsetof(struct mcp2210_ioctl_data, body.config)); 61 | 62 | ret = mcp2210_do_ioctl(devname, MCP2210_IOCTL_CONFIG_GET, data); 63 | if (ret < 0) 64 | perror("mcp2210_do_ioctl"); 65 | 66 | /* 67 | if ((ret = mcp2210_open(&dev, devname))) 68 | goto exit_free; 69 | 70 | ret = mcp2210_ioctl(&dev, MCP2210_IOCTL_CONFIG_GET, (unsigned long)((void*)data)); 71 | mcp2210_close(&dev); 72 | */ 73 | 74 | free(data); 75 | return ret; 76 | } 77 | 78 | int mcp2210_set_config(const char *devname, const struct mcp2210_ioctl_data *src) { 79 | struct mcp2210_ioctl_data *data; 80 | // struct mcp2210_device dev; 81 | int ret = 0; 82 | 83 | data = malloc(struct_size); 84 | if (!data) { 85 | fatal_error("no mem"); 86 | return -ENOMEM; 87 | } 88 | 89 | memset(data, 0, struct_size); 90 | data->struct_size = struct_size; 91 | 92 | fprintf(stderr, "offset = %lu\n", (unsigned long)offsetof(struct mcp2210_ioctl_data, body.config)); 93 | 94 | ret = mcp2210_do_ioctl(devname, MCP2210_IOCTL_CONFIG_GET, data); 95 | if (ret < 0) 96 | perror("mcp2210_do_ioctl"); 97 | 98 | free(data); 99 | return ret; 100 | } 101 | #endif 102 | -------------------------------------------------------------------------------- /user/mcp2210-user.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 userspace library 3 | * Copyright (c) 2013-2017 Daniel Santos 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP2210_USER_H 20 | #define _MCP2210_USER_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "mcp2210.h" 30 | #include "mcp2210-creek.h" 31 | #include "mcp2210-debug.h" 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif /* __cplusplus */ 36 | 37 | struct mcp2210_device { 38 | int fd; 39 | const char *name; 40 | }; 41 | 42 | #define INIT_MCP2210_DEVICE(_name) {.fd = -1, .name = _name} 43 | 44 | static inline int mcp2210_open(struct mcp2210_device *dev, const char *name) { 45 | int fd; 46 | 47 | memset(dev, 0, sizeof(*dev)); 48 | if ((fd = open(name, O_RDWR)) == -1) 49 | return -1; 50 | 51 | dev->fd = fd; 52 | dev->name = name; 53 | 54 | return 0; 55 | } 56 | 57 | static inline int mcp2210_ioctl(struct mcp2210_device *dev, uint cmd, 58 | struct mcp2210_ioctl_data *data) { 59 | int ret; 60 | 61 | if (cmd >= MCP2210_IOCTL_MAX) { 62 | errno = EINVAL; 63 | return -1; 64 | } 65 | 66 | ret = ioctl(dev->fd, mcp2210_ioctl_map[cmd], (unsigned long)data); 67 | if (ret) { 68 | errno = -ret; 69 | return -1; 70 | } 71 | 72 | return 0; 73 | } 74 | 75 | static inline int mcp2210_close(struct mcp2210_device *dev) { 76 | return close(dev->fd); 77 | } 78 | 79 | /** 80 | */ 81 | int mcp2210_do_ioctl(const char *devname, enum mcp2210_ioctl_cmd cmd, struct mcp2210_ioctl_data *data); 82 | 83 | 84 | int test_encoding(int argc, char *argv[]); 85 | int test_bit_stream(int argc, char *argv[]); 86 | #define _do_error(fatal, err, file, line, fmt, ...) error_at_line(fatal, err, file, line, "%s - " fmt, __PRETTY_FUNCTION__, ## __VA_ARGS__) 87 | #define fatal_error(fmt, ...) _do_error(1, errno, __FILE__, __LINE__, fmt, ## __VA_ARGS__) 88 | #define nonfatal_error(fmt, ...) _do_error(0, errno, __FILE__, __LINE__, fmt, ## __VA_ARGS__) 89 | 90 | #ifdef __cplusplus 91 | extern "C" { 92 | #endif /* __cplusplus */ 93 | #endif /* _MCP2210_USER_H */ 94 | -------------------------------------------------------------------------------- /user/mcp2210.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 userspace utility 3 | * Copyright (c) 2013-2017 Daniel Santos 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | //#include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "mcp2210-user.h" 36 | #include "settings.h" 37 | 38 | static const char *argv0; 39 | 40 | static void show_usage(void); 41 | 42 | static int eeprom_read(int argc, char *argv[]); 43 | static int eeprom_write(int argc, char *argv[]); 44 | static int eeprom_read_write(int is_read, int argc, char *argv[]); 45 | 46 | static const char *hex_digits="0123456789abcdef"; 47 | 48 | static inline char hex_digit(int v) { 49 | return hex_digits[v & 0xf]; 50 | } 51 | 52 | void dump_hex(FILE *dest, const u8* src, size_t size) { 53 | const unsigned byte_per_row = 32; 54 | const unsigned space_every = 4; 55 | const size_t ascii_start = byte_per_row * 2 + (byte_per_row + space_every - 1) / space_every + 1; 56 | char buf[128]; 57 | unsigned i; 58 | 59 | assert(sizeof(buf) >= ascii_start + byte_per_row + 2); 60 | // printf("%lu >= %lu\n", sizeof(buf), ascii_start + byte_per_row + 2); 61 | 62 | memset(buf, 0, sizeof(buf)); 63 | 64 | buf[ascii_start - 1] = '|'; 65 | buf[ascii_start + byte_per_row ] = '|'; 66 | buf[ascii_start + byte_per_row + 1] = 0; 67 | for (i = 0; i < size;) { 68 | u8 b = src[i]; 69 | unsigned col = i % byte_per_row; 70 | size_t off = col * 2 + col / 4; 71 | 72 | buf[off++] = hex_digit(b >> 4); 73 | buf[off++] = hex_digit(b); 74 | buf[off++] = ' '; 75 | buf[ascii_start + col] = (isprint(b)) ? b : '.'; 76 | 77 | ++i; 78 | if ((!(i % byte_per_row))) { 79 | fprintf(dest, "%s\n", buf); 80 | } else if (i == size) { 81 | ssize_t fill1_size = ascii_start - off - 1; 82 | ssize_t fill2_size = byte_per_row - col - 1; 83 | 84 | if (fill1_size > 0) 85 | memset(&buf[off], ' ', fill1_size); 86 | if (fill2_size > 0) 87 | memset(&buf[ascii_start + col + 1], ' ', fill2_size); 88 | fprintf(dest, "%s\n", buf); 89 | break; 90 | } 91 | } 92 | } 93 | 94 | 95 | static struct config { 96 | bool verbose; 97 | const char *name; 98 | const char *cmd; 99 | const char *target; 100 | const char *filein; 101 | const char *fileout; 102 | struct { 103 | u8 addr; 104 | u16 size; 105 | } eeprom; 106 | struct { 107 | const char *name; 108 | u8 mode; 109 | u32 speed_hz; 110 | u16 delay_usecs; 111 | u8 bits_per_word; 112 | } spi; 113 | } config = { 114 | .verbose = 1, 115 | .name = "/dev/usb2spi_bridge0", 116 | .cmd = NULL, 117 | .target = NULL, 118 | .filein = NULL, 119 | .fileout = NULL, 120 | .eeprom = { 121 | .addr = 0, 122 | .size = 0x100, 123 | }, 124 | .spi = { 125 | .name = "/dev/spidev0.0", 126 | .mode = 3, 127 | .speed_hz = 100 * 1000, 128 | .delay_usecs = 0, 129 | .bits_per_word = 8, 130 | }, 131 | }; 132 | /** 133 | * Determine base by prefix and offset to number. Uses standard rules 134 | * (expressed in regex below): 135 | * 0[xX][0-9a-fA-F]+ denotes a hexidecimal number 136 | * 0[0-7]+ denotes an octal number 137 | * (0|[1-9][0-9]*) denotes a decimal number 138 | */ 139 | int get_param_base_and_start(const char **str) 140 | { 141 | assert(*str); 142 | 143 | /* Parse an "0x", "0X" or "0" -prefixed string, but not the string 144 | * "0" (which will be treated as base ten). */ 145 | if (**str == '0' && *(*str + 1)) { 146 | ++*str; 147 | if (**str == 'x' || **str == 'X') { 148 | ++*str; 149 | return 16; 150 | } else { 151 | return 8; 152 | } 153 | } else { 154 | return 10; 155 | } 156 | } 157 | 158 | static void _get_ull_param(const char *str, unsigned long long *dest, 159 | size_t size, 160 | unsigned long long min, 161 | unsigned long long max) 162 | { 163 | const char *start = str; 164 | char *endptr; 165 | int base = get_param_base_and_start(&start); 166 | 167 | errno = 0; 168 | if (size == 8) 169 | *dest = strtoull(start, &endptr, base); 170 | else 171 | *dest = strtoul(start, &endptr, base); 172 | 173 | if (errno) 174 | fatal_error("bad number: %s", str); 175 | 176 | if (*dest < min || *dest > max) { 177 | errno = ERANGE; 178 | fatal_error("%s should be between %llu and %llu", str, min, max); 179 | } 180 | } 181 | 182 | /** 183 | * @returns zero on success, non-zero if the string didn't represent a clean 184 | * number 185 | */ 186 | static __always_inline int _get_unsigned_param(const char *str, void *dest, 187 | size_t size, 188 | unsigned long long min, 189 | unsigned long long max) 190 | { 191 | unsigned long long valull; 192 | 193 | /* debug sanity-check sizes */ 194 | assert(min <= max); 195 | 196 | if (size == sizeof(unsigned char)) 197 | assert(max <= UCHAR_MAX); 198 | else if (size == sizeof(unsigned short)) 199 | assert(max <= USHRT_MAX); 200 | else if (size == sizeof(unsigned int)) 201 | assert(max <= UINT_MAX); 202 | else if (size == sizeof(unsigned long)) 203 | assert(max <= ULONG_MAX); 204 | else if (size == sizeof(unsigned long long)) 205 | assert(max <= ULLONG_MAX); 206 | 207 | _get_ull_param(str, &valull, size, min, max); 208 | 209 | if (size == sizeof(unsigned char)) 210 | *(unsigned char *)dest = valull; 211 | else if (size == sizeof(unsigned short)) 212 | *(unsigned short *)dest = valull; 213 | else if (size == sizeof(unsigned int)) 214 | *(unsigned int *)dest = valull; 215 | else if (size == sizeof(unsigned long)) 216 | *(unsigned long *)dest = valull; 217 | else if (size == sizeof(unsigned long long)) 218 | *(unsigned long long *)dest = valull; 219 | 220 | return 0; 221 | } 222 | 223 | #define get_unsigned_param(str, dest, min, max) \ 224 | _get_unsigned_param((str), (dest), sizeof(*(dest)), (min), (max)) 225 | 226 | 227 | 228 | static inline u8 get_bool_param(const char *str) 229 | { 230 | u8 dest; 231 | get_unsigned_param(str, &dest, 0, 1); 232 | return dest; 233 | } 234 | 235 | 236 | 237 | /****************************************************************************** 238 | * 239 | */ 240 | 241 | int get_input_data(void *dest, size_t size, int short_read_ok) { 242 | const char *file_name = config.filein; 243 | int use_stdin = !file_name || !strcmp(file_name, "-"); 244 | int fd; 245 | ssize_t ret; 246 | 247 | fprintf(stderr, "get_input_data dest %p, size %lu\n", dest, (unsigned long) size); 248 | 249 | if (use_stdin) 250 | fd = fileno(stdin); 251 | else if ((fd = open(file_name, O_RDONLY)) < 0) { 252 | perror("open"); 253 | return errno; 254 | } 255 | 256 | ret = read(fd, dest, size); 257 | if (ret < 0) { 258 | perror("read(): reading input"); 259 | ret = errno; 260 | } else if ((size_t)ret != size && !short_read_ok) { 261 | fprintf(stderr, "Short input: expected %lu bytes, got %ld.\n", 262 | (unsigned long)size, (long)ret); 263 | ret = -EOVERFLOW; 264 | } 265 | 266 | if (!use_stdin) 267 | close(fd); 268 | 269 | return ret; 270 | } 271 | 272 | /* what a crappy fn name... */ 273 | int get_input_data2(void **dest, size_t size) { 274 | int ret; 275 | 276 | if (!size) 277 | size = 0x8000; 278 | 279 | *dest = malloc(size); 280 | if (!*dest) 281 | return -ENOMEM; 282 | 283 | ret = get_input_data(*dest, size, 1); 284 | 285 | if (ret < 0) { 286 | free(*dest); 287 | *dest = NULL; 288 | } else { 289 | size = (size_t)ret; 290 | *dest = realloc(*dest, size); 291 | } 292 | 293 | return ret; 294 | } 295 | 296 | 297 | int put_output_data(const void *src, size_t size, int append) { 298 | const char *file_name = config.fileout; 299 | int use_stdout = !file_name || !strcmp(file_name, "-"); 300 | int fd; 301 | ssize_t ret; 302 | int flags = O_RDWR | O_CREAT | (append ? O_APPEND : O_TRUNC ); 303 | 304 | fprintf(stderr, "put_output_data src: %p, size: %lu\n", src, (unsigned long) size); 305 | 306 | if (use_stdout) { 307 | fd = fileno(stdout); 308 | if (isatty(fd)) { 309 | fprintf(stderr, "Cowardly refusing to write binary data to terminal\n"); 310 | return -EPERM; 311 | } 312 | } else if ((fd = open(file_name, flags, S_IRUSR | S_IWUSR)) < 0) { 313 | perror("open"); 314 | return errno; 315 | } 316 | 317 | ret = write(fd, src, size); 318 | if (ret < 0) { 319 | perror("write(): writing output"); 320 | ret = errno; 321 | } else if ((size_t)ret != size) { 322 | fprintf(stderr, "put_output_data: Failed to write all data: have %lu bytes, only wrote %ld.\n", 323 | (unsigned long)size, (long)ret); 324 | ret = -EOVERFLOW; 325 | } else 326 | ret = 0; 327 | 328 | if (!use_stdout) 329 | close(fd); 330 | 331 | return ret; 332 | } 333 | 334 | 335 | 336 | int get_config(int argc, char *argv[]) { 337 | struct mcp2210_ioctl_data *data; 338 | struct mcp2210_ioctl_data_config *cfg; 339 | const size_t struct_size = IOCTL_DATA_CONFIG_SIZE + 0x100; 340 | int ret = 0; 341 | u8 buf[0x100]; 342 | 343 | memset(buf, 0, sizeof(buf)); 344 | 345 | data = malloc(struct_size); 346 | if (!data) { 347 | fatal_error("no mem"); 348 | return -ENOMEM; 349 | } 350 | 351 | memset(data, 0, struct_size); 352 | data->struct_size = struct_size; 353 | cfg = &data->body.config; 354 | 355 | fprintf(stderr, "offset = %u\n", (uint)offsetof(struct mcp2210_ioctl_data, body.config)); 356 | 357 | ret = mcp2210_do_ioctl(config.name, MCP2210_IOCTL_CONFIG_GET, data); 358 | if (ret) 359 | goto exit_free; 360 | 361 | dump_state("", 0, "state = ", &cfg->state); 362 | if (cfg->have_config) 363 | dump_board_config("", 0, ".config = ", &cfg->config); 364 | 365 | #if 0 366 | ret = creek_encode(&cfg->config, &cfg->state.power_up_chip_settings, buf, sizeof(buf)); 367 | if (ret < 0) { 368 | errno = -ret; 369 | fatal_error("creek_encode"); 370 | } 371 | 372 | ret = (ret + 7) / 8; 373 | 374 | ret = put_output_data(buf, ret, 0); 375 | 376 | if (ret > 0) { 377 | fprintf(stderr, "wrote %d bytes\n", ret); 378 | ret = 0; 379 | } 380 | #endif 381 | exit_free: 382 | free(data); 383 | return ret; 384 | } 385 | 386 | #ifndef CONFIG_MCP2210_CREEK 387 | static int encode(int argc, char *argv[]) { 388 | fprintf(stderr, "Feature disabled (set CONFIG_MCP2210_CREEK to enable)\n"); 389 | return -EPERM; 390 | } 391 | 392 | static int decode(int argc, char *argv[]) { 393 | return encode(argc, argv); 394 | } 395 | 396 | #else /* CONFIG_MCP2210_CREEK */ 397 | 398 | static int encode(int argc, char *argv[]) { 399 | struct mcp2210_board_config *board_config; 400 | u8 buf[0x100]; 401 | uint size; 402 | int ret = 0; 403 | 404 | memset(buf, 0, sizeof(buf)); 405 | board_config = copy_board_config(NULL, &my_board_config, 0); 406 | if (!board_config) { 407 | errno = ENOMEM; 408 | perror("copy_board_config"); 409 | goto exit_free; 410 | } 411 | 412 | ret = creek_encode(board_config, &my_chip_settings, buf, sizeof(buf), 413 | 2); 414 | if (ret < 0) { 415 | errno = -ret; 416 | perror("creek_encode"); 417 | goto exit_free; 418 | } 419 | 420 | size = (ret + 7) / 8; 421 | fprintf(stderr, "encoded into %d bits (%u bytes)\n", ret, size); 422 | 423 | ret = put_output_data(buf, size, 0); 424 | 425 | exit_free: 426 | free(board_config); 427 | return ret; 428 | } 429 | 430 | static int decode(int argc, char *argv[]) { 431 | struct mcp2210_board_config *board_config; 432 | u8 buf[0x100]; 433 | int ret; 434 | 435 | ret = get_input_data(buf, sizeof(buf), 1); 436 | if (ret < 0) 437 | return ret; 438 | 439 | board_config = creek_decode(&my_chip_settings, buf, (uint)ret, 0); 440 | if (IS_ERR_VALUE(board_config)) { 441 | ret = PTR_ERR(board_config); 442 | fprintf(stderr, "decodeing creek config failed: "); 443 | errno = -ret; 444 | perror("creek_decode"); 445 | return 0; 446 | } 447 | 448 | dump_board_config(KERN_INFO, 0, "board_config = ", board_config); 449 | 450 | free(board_config); 451 | return 0; 452 | } 453 | #endif /* CONFIG_MCP2210_CREEK */ 454 | 455 | enum settings_mask { 456 | SETTINGS_CHIP_SETTINGS = 1 << 0, 457 | SETTINGS_POWER_UP_CHIP_SETTINGS = 1 << 1, 458 | SETTINGS_SPI_SETTINGS = 1 << 2, 459 | SETTINGS_POWER_UP_SPI_SETTINGS = 1 << 3, 460 | SETTINGS_USB_KEY_PARAMS = 1 << 4, 461 | SETTINGS_BOARD_CONFIG = 1 << 5, 462 | }; 463 | 464 | int set_config(int argc, char *argv[]) { 465 | struct mcp2210_ioctl_data *data; 466 | struct mcp2210_ioctl_data_config *cfg; 467 | size_t struct_size = IOCTL_DATA_CONFIG_SIZE; 468 | size_t strings_size = 0; 469 | int ret = 0; 470 | 471 | u8 mask; 472 | 473 | get_unsigned_param(argv[0], &mask, 0, 0x3f); 474 | 475 | if (mask & SETTINGS_BOARD_CONFIG) { 476 | unsigned i; 477 | 478 | for (i = 0; i < MCP2210_NUM_PINS; ++i) { 479 | const struct mcp2210_pin_config *pin = &my_board_config.pins[i]; 480 | if (pin->name && *pin->name) 481 | strings_size += strlen(pin->name) + 1; 482 | if (pin->modalias && *pin->modalias) 483 | strings_size += strlen(pin->modalias) + 1; 484 | } 485 | struct_size += strings_size; 486 | } 487 | 488 | data = malloc(struct_size); 489 | if (!data) { 490 | fatal_error("no mem"); 491 | return -ENOMEM; 492 | } 493 | 494 | memset(data, 0, struct_size); 495 | data->struct_size = struct_size; 496 | cfg = &data->body.config; 497 | 498 | if (mask & SETTINGS_CHIP_SETTINGS) { 499 | cfg->state.have_chip_settings = 1; 500 | memcpy(&cfg->state.chip_settings, 501 | &my_chip_settings, 502 | sizeof(my_chip_settings)); 503 | } 504 | 505 | if (mask & SETTINGS_POWER_UP_CHIP_SETTINGS) { 506 | cfg->state.have_power_up_chip_settings = 1; 507 | memcpy(&cfg->state.power_up_chip_settings, 508 | &my_power_up_chip_settings, 509 | sizeof(my_power_up_chip_settings)); 510 | } 511 | 512 | if (mask & SETTINGS_SPI_SETTINGS) { 513 | cfg->state.have_spi_settings = 1; 514 | memcpy(&cfg->state.spi_settings, 515 | &my_spi_settings, 516 | sizeof(my_spi_settings)); 517 | } 518 | 519 | if (mask & SETTINGS_POWER_UP_SPI_SETTINGS) { 520 | cfg->state.have_power_up_spi_settings = 1; 521 | memcpy(&cfg->state.power_up_spi_settings, 522 | &my_power_up_spi_settings, 523 | sizeof(my_power_up_spi_settings)); 524 | } 525 | 526 | if (mask & SETTINGS_USB_KEY_PARAMS) { 527 | cfg->state.have_usb_key_params = 1; 528 | memcpy(&cfg->state.usb_key_params, 529 | &my_usb_key_params, 530 | sizeof(my_usb_key_params)); 531 | } 532 | 533 | if (mask & SETTINGS_BOARD_CONFIG) { 534 | cfg->have_config = 1; 535 | cfg->config.strings_size = strings_size; 536 | copy_board_config(&cfg->config, &my_board_config, 0); 537 | } 538 | 539 | dump_state("", 0, "state = ", &cfg->state); 540 | if (cfg->have_config) 541 | dump_board_config("", 0, ".config = ", &cfg->config); 542 | 543 | ret = mcp2210_do_ioctl(config.name, MCP2210_IOCTL_CONFIG_SET, data); 544 | 545 | free(data); 546 | return ret; 547 | } 548 | 549 | static int eeprom_read(int argc, char *argv[]) { 550 | return eeprom_read_write(1, argc, argv); 551 | } 552 | 553 | static int eeprom_write(int argc, char *argv[]) { 554 | return eeprom_read_write(0, argc, argv); 555 | } 556 | 557 | static int eeprom_read_write(int is_read, int argc, char *argv[]) { 558 | struct mcp2210_ioctl_data *data; 559 | struct mcp2210_ioctl_data_eeprom *eeprom; 560 | const size_t struct_size = IOCTL_DATA_EEPROM_SIZE + 0x100; 561 | uint addr = 0; 562 | uint size = 0; 563 | int i; 564 | int ret = 0; 565 | 566 | for (i = 0; i < argc; ++i) { 567 | const char *arg = argv[i]; 568 | if (!strncmp(arg, "addr=", 5)) { 569 | get_unsigned_param(&arg[5], &addr, 0, 0xff); 570 | } else if (!strncmp(arg, "size=", 5)) { 571 | get_unsigned_param(&arg[5], &size, 1, 0x100); 572 | } else { 573 | fprintf(stderr, "what you talkin' 'bout Wilis? %s\n", arg); 574 | show_usage(); 575 | exit (-EINVAL); 576 | } 577 | } 578 | 579 | fprintf(stderr, "eeprom_read_write: addr %u, size %u\n", addr, size); 580 | 581 | data = malloc(struct_size); 582 | if (!data) { 583 | fatal_error("no mem"); 584 | return -ENOMEM; 585 | } 586 | 587 | memset(data, 0, struct_size); 588 | data->struct_size = struct_size; 589 | eeprom = &data->body.eeprom; 590 | eeprom->addr = addr; 591 | eeprom->size = size; 592 | eeprom->is_read = is_read; 593 | 594 | if (!is_read) { 595 | ret = get_input_data(eeprom->data, eeprom->size, 0); 596 | if (ret != eeprom->size) 597 | goto exit_free; 598 | } 599 | 600 | // config.fileout = "a.out"; 601 | // ret = put_output_data(data, struct_size); 602 | 603 | ret = mcp2210_do_ioctl(config.name, MCP2210_IOCTL_EEPROM, data); 604 | 605 | if (is_read) { 606 | // config.fileout = "b.out"; 607 | ret = put_output_data(eeprom->data, eeprom->size, 0); 608 | // ret = put_output_data(data, struct_size); 609 | } 610 | 611 | exit_free: 612 | free(data); 613 | return ret; 614 | } 615 | 616 | struct spi_msg { 617 | size_t struct_size; 618 | u8 *buf; 619 | size_t buf_size; 620 | unsigned num_xfers; 621 | struct spi_ioc_transfer xfers[0]; 622 | }; 623 | 624 | struct spi_msg *parse_msgs(int argc, char *argv[]) { 625 | size_t size; 626 | const char *p; 627 | struct spi_msg *msg = NULL; 628 | unsigned next_buf_off; 629 | struct spi_ioc_transfer *xfer; 630 | struct spi_msg proto; 631 | 632 | 633 | if (argc != 1) { 634 | fprintf(stderr, "too many arguments\n"); 635 | return ERR_PTR(-EINVAL); 636 | } 637 | 638 | if (!(**argv)) { 639 | fprintf(stderr, "msg is an string empty\n"); 640 | return ERR_PTR(-EINVAL); 641 | } 642 | 643 | /* Parse the message string and determine the number of messages as 644 | * well as the sum of message sizes */ 645 | memset(&proto, 0, sizeof(proto)); 646 | p = *argv; 647 | size = 0; 648 | while (1) { 649 | u8 val; 650 | 651 | if (*p == ',' || !*p) { 652 | ++proto.num_xfers; 653 | if (!size) 654 | fprintf(stderr, "WARNING: zero-sized transfer\n"); 655 | 656 | proto.buf_size += size; 657 | 658 | if (!*p || !p[1]) 659 | break; 660 | 661 | size = 0; 662 | ++p; 663 | continue; 664 | } 665 | 666 | if (isspace(*p)) { 667 | ++p; 668 | continue; 669 | } 670 | 671 | if (!strncasecmp(p, "cs", 2)) { 672 | p += 2; 673 | continue; 674 | } 675 | 676 | if (sscanf(p, "%02hhx", &val) != 1) 677 | goto exit_bad_fmt; 678 | 679 | p += 2; 680 | ++size; 681 | } 682 | 683 | proto.buf_size *= 2; /* separate tx & rx buffers */ 684 | proto.struct_size = sizeof(struct spi_msg) 685 | + sizeof(struct spi_ioc_transfer) * proto.num_xfers 686 | + proto.buf_size; 687 | 688 | /* allocate & init msg object */ 689 | if (!(msg = malloc(proto.struct_size))) 690 | return ERR_PTR(-ENOMEM); 691 | memset(msg, 0, proto.struct_size); 692 | 693 | msg->struct_size = proto.struct_size; 694 | msg->buf = &((u8 *)msg)[proto.struct_size - proto.buf_size]; 695 | msg->buf_size = proto.buf_size; 696 | msg->num_xfers = proto.num_xfers; 697 | 698 | 699 | /* Parse again and init each xfer (minus tx_buf contents) */ 700 | p = *argv; 701 | size = 0; 702 | next_buf_off = 0; 703 | xfer = msg->xfers; 704 | xfer->cs_change = 0; 705 | while (1) { 706 | u8 val; 707 | 708 | if (*p == ',' || !*p) { 709 | xfer->tx_buf = (ulong)&msg->buf[next_buf_off]; 710 | next_buf_off += size; 711 | xfer->rx_buf = (ulong)&msg->buf[next_buf_off]; 712 | next_buf_off += size; 713 | xfer->len = size; 714 | xfer->speed_hz = config.spi.speed_hz; 715 | xfer->delay_usecs = config.spi.delay_usecs; 716 | xfer->bits_per_word = config.spi.bits_per_word; 717 | ++xfer; 718 | xfer->cs_change = 0; 719 | 720 | if (!*p || !p[1]) 721 | break; 722 | size = 0; 723 | ++p; 724 | continue; 725 | } 726 | 727 | if (isspace(*p)) { 728 | ++p; 729 | continue; 730 | } 731 | 732 | if (!strncasecmp(p, "cs", 2)) { 733 | xfer->cs_change = 1; 734 | p += 2; 735 | continue; 736 | } 737 | 738 | if (sscanf(p, "%02hhx", &val) != 1) 739 | goto exit_bad_fmt; 740 | 741 | p += 2; 742 | ++size; 743 | }; 744 | assert(xfer == &msg->xfers[msg->num_xfers]); 745 | assert(next_buf_off == msg->buf_size); 746 | 747 | /* Parse a third time and populate contents of each xfer tx_buf */ 748 | p = *argv; 749 | size = 0; 750 | next_buf_off = 0; 751 | xfer = msg->xfers; 752 | while (1) { 753 | u8 val; 754 | 755 | if (*p == ',' || !*p) { 756 | assert(xfer->len == size); 757 | 758 | if (!*p || !p[1]) 759 | break; 760 | 761 | ++xfer; 762 | size = 0; 763 | ++p; 764 | continue; 765 | } 766 | 767 | if (isspace(*p)) { 768 | ++p; 769 | continue; 770 | } 771 | 772 | if (!strncasecmp(p, "cs", 2)) { 773 | p += 2; 774 | continue; 775 | } 776 | 777 | if (sscanf(p, "%02hhx", &val) != 1) 778 | goto exit_bad_fmt; 779 | 780 | p += 2; 781 | ((u8*)(ulong)(xfer->tx_buf))[size++] = val; 782 | }; 783 | 784 | return msg; 785 | 786 | exit_bad_fmt: 787 | fprintf(stderr, "bad msg format"); 788 | if (msg) 789 | free (msg); 790 | return ERR_PTR(-EINVAL); 791 | } 792 | 793 | static __always_inline void _spidev_set(int fd, unsigned long request, void *value, u8 size, const char *desc) { 794 | const unsigned long magic = _IOC_TYPE(request); 795 | const unsigned long ordinal = _IOC_NR(request); 796 | const unsigned long sz = _IOC_SIZE(request); 797 | const unsigned long read_req = _IOC(_IOC_READ, magic, ordinal, sz); 798 | const unsigned long write_req = _IOC(_IOC_WRITE, magic, ordinal, sz); 799 | union { 800 | u8 u8; 801 | u16 u16; 802 | u32 u32; 803 | u64 u64; 804 | } val; 805 | u64 cur_val64; 806 | u64 target_val64; 807 | 808 | assert(sz == size); 809 | 810 | if (ioctl(fd, read_req, &val) < 0) 811 | fatal_error("failed to read %s", desc); 812 | 813 | switch (size) { 814 | case 1: cur_val64 = val.u8; target_val64 = *(u8 *)value; break; 815 | case 2: cur_val64 = val.u16; target_val64 = *(u16*)value; break; 816 | case 4: cur_val64 = val.u32; target_val64 = *(u32*)value; break; 817 | case 8: cur_val64 = val.u64; target_val64 = *(u64*)value; break; 818 | default: assert(0); 819 | }; 820 | 821 | /* only call the spi_setup if needed */ 822 | if (cur_val64 != target_val64) { 823 | if (ioctl(fd, write_req, value) < 0) 824 | fatal_error("failed to set %s", desc); 825 | } 826 | } 827 | 828 | struct spi_msg *get_msg_from_file(void) { 829 | struct spi_msg *msg; 830 | void *data; 831 | u8 *tx_buf; 832 | u8 *rx_buf; 833 | size_t data_size; 834 | size_t struct_size; 835 | int ret; 836 | 837 | ret = get_input_data2(&data, 0x8000); 838 | 839 | if (ret < 0) 840 | return NULL; 841 | 842 | data_size = (size_t)ret; 843 | 844 | /* the spi_msg, a struct spi_ioc_transfer payload and then two buffers 845 | * for the data */ 846 | struct_size = sizeof(*msg) 847 | + sizeof(struct spi_ioc_transfer) 848 | + data_size * 2; 849 | 850 | msg = malloc(struct_size); 851 | if (!msg) 852 | goto error_free; 853 | 854 | memset(msg, 0, struct_size); 855 | 856 | tx_buf = &((u8*)msg)[struct_size - data_size * 2]; 857 | rx_buf = &((u8*)msg)[struct_size - data_size]; 858 | 859 | msg->struct_size = struct_size; 860 | msg->buf = data; 861 | msg->buf_size = (size_t)ret; 862 | msg->num_xfers = 1; 863 | msg->xfers->tx_buf = (ulong)memcpy(tx_buf, data, data_size); 864 | msg->xfers->rx_buf = (ulong)memset(rx_buf, 0, data_size); 865 | msg->xfers->len = data_size; 866 | msg->xfers->speed_hz = config.spi.speed_hz; 867 | msg->xfers->delay_usecs = config.spi.delay_usecs; 868 | msg->xfers->bits_per_word = config.spi.bits_per_word; 869 | msg->xfers->cs_change = 0; 870 | 871 | return msg; 872 | 873 | error_free: 874 | free(data); 875 | free(msg); 876 | return NULL; 877 | } 878 | 879 | #define spidev_set(fd, request, ignore_zero_value, value, desc) \ 880 | do { \ 881 | if (ignore_zero_value && !(*(value))) \ 882 | break; \ 883 | _spidev_set(fd, request, value, sizeof(*value), desc); \ 884 | } while (0) 885 | 886 | static int spidev_send(int argc, char *argv[]) { 887 | int ret = 0; 888 | int fd; 889 | unsigned i; 890 | struct spi_msg *msg; 891 | 892 | if (argc) { 893 | msg = parse_msgs(argc, argv); 894 | if (IS_ERR(msg)) { 895 | if (PTR_ERR(msg) == -EINVAL) { 896 | show_usage(); 897 | return -1; 898 | } 899 | errno = -PTR_ERR(msg); 900 | fatal_error("failed to parse messages"); 901 | } 902 | } else 903 | msg = get_msg_from_file(); 904 | 905 | if (!msg) 906 | return -ENOMEM; 907 | 908 | for (i = 0; i < msg->num_xfers; ++i) { 909 | fprintf(stderr, "request %d:\n", i); 910 | dump_hex(stderr, (u8*)(ulong)msg->xfers[i].tx_buf, msg->xfers[i].len); 911 | } 912 | 913 | if ((fd = open(config.spi.name, O_RDWR)) < 0) 914 | fatal_error("open failed for %s", config.spi.name); 915 | 916 | spidev_set(fd, SPI_IOC_RD_MODE, 0, &config.spi.mode, "spi mode"); 917 | spidev_set(fd, SPI_IOC_RD_MAX_SPEED_HZ, 1, &config.spi.speed_hz, "spi max speed"); 918 | spidev_set(fd, SPI_IOC_RD_BITS_PER_WORD, 1, &config.spi.bits_per_word, "spi bits per word"); 919 | 920 | if (ioctl(fd, SPI_IOC_MESSAGE(msg->num_xfers), msg->xfers) < 0) 921 | fatal_error("spi message failed"); 922 | 923 | for (i = 0; i < msg->num_xfers; ++i) { 924 | const u8 *data = (u8*)(ulong)msg->xfers[i].rx_buf; 925 | size_t len = msg->xfers[i].len; 926 | fprintf(stderr, "response %d:\n", i); 927 | dump_hex(stderr, data, len); 928 | put_output_data(data, len, i); 929 | } 930 | close(fd); 931 | free (msg); 932 | return ret; 933 | } 934 | 935 | struct command { 936 | const char *name; 937 | int min_args; 938 | int max_args; 939 | const struct command *sub_cmd; 940 | int (*func)(int argc, char *argv[]); 941 | }; 942 | 943 | static int dispatch(int argc, char *argv[], const struct command cmd_list[]) { 944 | const struct command *cmd; 945 | 946 | if (argc < 1) { 947 | fprintf(stderr, "Not enough arguments supplied\n"); 948 | show_usage(); 949 | return -EINVAL; 950 | } 951 | 952 | for (cmd = cmd_list; cmd->name; ++cmd) { 953 | if (!strcmp(cmd->name, *argv)) { 954 | if (argc < cmd->min_args) { 955 | fprintf(stderr, "too few arguments supplied for command %s, expected %u, got %u\n", cmd->name, cmd->min_args, argc); 956 | show_usage(); 957 | return -EINVAL; 958 | } 959 | --argc; 960 | ++argv; 961 | return cmd->sub_cmd 962 | ? dispatch(argc, argv, cmd->sub_cmd) 963 | : cmd->func(argc, argv); 964 | } 965 | } 966 | 967 | fprintf(stderr, "Invalid argument `%s'\n", argv[0]); 968 | show_usage(); 969 | return -EINVAL; 970 | } 971 | 972 | static int not_yet_implemented(int argc, char *argv[]) { 973 | fprintf(stderr, "not yet implemented\n"); 974 | return 0; 975 | } 976 | 977 | 978 | static const struct command sub_cmd_list_get[] = { 979 | { 980 | .name = "config", 981 | .func = get_config, 982 | }, {} 983 | }; 984 | 985 | static const struct command sub_cmd_list_set[] = { 986 | { 987 | .name = "config", 988 | .min_args = 1, 989 | .max_args = 1, 990 | .func = set_config, 991 | }, {} 992 | }; 993 | 994 | static const struct command sub_cmd_list_eeprom[] = { 995 | { 996 | .name = "read", 997 | .func = eeprom_read, 998 | }, { 999 | .name = "write", 1000 | .func = eeprom_write, 1001 | }, {} 1002 | }; 1003 | 1004 | static const struct command sub_cmd_list_test[] = { 1005 | { 1006 | .name = "bitstream", 1007 | .func = test_bit_stream, 1008 | }, { 1009 | .name = "config", 1010 | .func = test_encoding, 1011 | }, {} 1012 | }; 1013 | 1014 | static const struct command command_list[] = { 1015 | { 1016 | .name = "get", 1017 | .min_args = 1, 1018 | .sub_cmd = sub_cmd_list_get, 1019 | }, { 1020 | .name = "set", 1021 | .min_args = 1, 1022 | .sub_cmd = sub_cmd_list_set, 1023 | }, { 1024 | .name = "encode", 1025 | .min_args = 1, 1026 | .func = encode, 1027 | }, { 1028 | .name = "decode", 1029 | .min_args = 1, 1030 | .func = decode, 1031 | }, { 1032 | .name = "eeprom", 1033 | .min_args = 1, 1034 | .sub_cmd = sub_cmd_list_eeprom, 1035 | }, { 1036 | .name = "test", 1037 | .min_args = 1, 1038 | .sub_cmd = sub_cmd_list_test, 1039 | }, { 1040 | .name = "msg", 1041 | .min_args = 0, 1042 | .func = not_yet_implemented, 1043 | }, { 1044 | .name = "spi", 1045 | .min_args = 1, 1046 | .func = spidev_send, 1047 | }, { }, 1048 | 1049 | }; 1050 | 1051 | static void show_usage() { 1052 | fprintf(stderr, 1053 | "Usage: %s [options] command [arguments]\n" 1054 | "Options:\n" 1055 | " -h --help Show this help\n" 1056 | " -v --verbose Verbose output\n" 1057 | " -d --device Device name (default /dev/usb2spi_bridge)\n" 1058 | " -i --in An input file (default: standard in)\n" 1059 | " -o --out An output file (default: standard out)\n" 1060 | "\n" 1061 | "SPI Options:\n" 1062 | /* " -s --spi name[,mode[,speed[,bits_per_word]]]\n" */ 1063 | " -D --spidev SPI device (default /dev/spidev0.0)\n" 1064 | " -s --speed max speed in Hz (default 20kHz)\n" 1065 | " -e --delay delay in uS between messages (default 0)\n" 1066 | " -b --bpw bits per word (default 8, !=8 unsupported by mcp2210)\n" 1067 | " -l --loop enable loopback (default disabled, unsupported by mcp2210)\n" 1068 | " -H --cpha <1|0> clock phase (default 1)\n" 1069 | " -O --cpol <1|0> clock polarity (default 1)\n" 1070 | " -L --lsb least significant bit first (default MSB first)\n" 1071 | " -C --cs-high chip select active high (default low)\n" 1072 | " -3 --3wire SDI/SDO signals shared (default disabled)\n" 1073 | "\n" 1074 | "Commands: (this section may be incomplete, incorrect, etc.)\n" 1075 | " get config Retrieve configuration from device.\n" 1076 | " set config \n" 1077 | " Upload configuration to device. The value of mask specifies\n" 1078 | " the information set(s) from settings.h to upload to the\n" 1079 | " device ORed together:\n" 1080 | " 1 current chip settings\n" 1081 | " 2 power-up chip settings\n" 1082 | " 4 current spi transfer settings\n" 1083 | " 8 power-up spi transfer settings\n" 1084 | " 16 key USB parameters\n" 1085 | " 32 board config (causes spi_master to be probed)\n" 1086 | "\n" 1087 | " encode Encodes settings (in settings.h) into creek format.\n" 1088 | " decode Decodes creek blob into settings and outputs to stderr. Note\n" 1089 | " pin modes in settings.h must match.\n" 1090 | "\n" 1091 | " eeprom Reads from or writes to user-EEPROM area of device.\n" 1092 | " read addr=n size=n\n" 1093 | " write addr=n size=n\n" 1094 | " test Runs a unit test\n" 1095 | " bitstream\n" 1096 | " config\n" 1097 | " msg Send a raw message to the MCP2210 device.\n" 1098 | " spi \"\"\n" 1099 | " Send an SPI message to the spidev device (use -D to specify\n" 1100 | " device). A valid msg_string will consist of whitespace,\n" 1101 | " hexadecimal digit pairs and comma. A comma will delimit\n" 1102 | " individual transfers within the message. See Examples below.\n" 1103 | " (Note, currently lacking support for an input file)\n" 1104 | "\n" 1105 | "Examples:\n" 1106 | " %s -d /dev/usb2spi_bridge0 get config > creek.dat\n" 1107 | " Retrieve the settings from the device and encode the board_config\n" 1108 | " into creek.dat (note: subject to change soon)\n" 1109 | "\n" 1110 | " %s set config 1\n" 1111 | " Set the current chip settings to the values from settings.h to the\n" 1112 | " default device /dev/usb2spi_bridge0\n" 1113 | "\n" 1114 | " %s set config 63\n" 1115 | " Upload all of the settings in settings.h to device and probe spi\n" 1116 | "\n" 1117 | " %s get eeprom addr=0 size=0x100 > eeprom.dat\n" 1118 | " or\n" 1119 | " %s -o eeprom.dat get eeprom addr=0 size=0x100\n" 1120 | " Read the user-EEPROM area and write it to eeprom.dat\n" 1121 | "\n" 1122 | " %s test config\n" 1123 | " Run the configuration unit test\n" 1124 | "\n" 1125 | " %s spi 0100\n" 1126 | " Send an spi message with one transfer that's two bytes long\n" 1127 | "\n" 1128 | " %s spi 38,00,00,00\n" 1129 | " Send an spi message with four transfers, each one byte in size (reads\n" 1130 | " the CONFIG register on an L6470)\n" 1131 | "\n", argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0); 1132 | } 1133 | 1134 | static int process_args(int argc, char *argv[]) 1135 | { 1136 | int c; 1137 | while (1) { 1138 | static struct option long_options[] = { 1139 | { "help", no_argument, 0, 'h' }, 1140 | { "verbose", no_argument, 0, 'v' }, 1141 | { "device", required_argument, 0, 'd' }, 1142 | { "in", required_argument, 0, 'i' }, 1143 | { "out", required_argument, 0, 'o' }, 1144 | 1145 | /*{ "spi", required_argument, 0, 's' },*/ 1146 | { "spidev", required_argument, 0, 'D' }, 1147 | { "speed", required_argument, 0, 's' }, 1148 | { "delay", required_argument, 0, 'e' }, 1149 | { "bpw", required_argument, 0, 'b' }, 1150 | { "loop", no_argument, 0, 'l' }, 1151 | { "cpha", required_argument, 0, 'H' }, 1152 | { "cpol", required_argument, 0, 'O' }, 1153 | { "lsb", no_argument, 0, 'L' }, 1154 | { "cs-high", no_argument, 0, 'C' }, 1155 | { "3wire", no_argument, 0, '3' }, 1156 | { NULL, 0, 0, 0 }, 1157 | }; 1158 | 1159 | /* getopt_long stores the option index here. */ 1160 | int option_index = 0; 1161 | c = getopt_long(argc, argv, "hvd:i:o:D:s:e:b:lH:O:LC3", long_options, &option_index); 1162 | 1163 | /* Detect the end of the options. */ 1164 | if (c == -1) 1165 | break; 1166 | 1167 | switch (c) { 1168 | case '?': fprintf(stderr, "Invalid argument: %s\n", argv[optind]); 1169 | /* fall-through */ 1170 | case 'h': show_usage(); return 0; 1171 | case 'v': config.verbose = 1; break; 1172 | case 'd': config.name = optarg; break; 1173 | case 'i': config.filein = optarg; break; 1174 | case 'o': config.fileout = optarg; break; 1175 | 1176 | /* SPI options */ 1177 | case 'D': config.spi.name = optarg; break; 1178 | case 's': 1179 | get_unsigned_param(optarg, &config.spi.speed_hz, MCP2210_MIN_SPEED, MCP2210_MAX_SPEED); 1180 | break; 1181 | case 'e': 1182 | get_unsigned_param(optarg, &config.spi.delay_usecs, 0, UINT_MAX); 1183 | break; 1184 | case 'b': 1185 | get_unsigned_param(optarg, &config.spi.bits_per_word, 8, 8); 1186 | break; 1187 | case 'l': config.spi.mode |= SPI_LOOP; break; 1188 | case 'H': 1189 | if (!get_bool_param(optarg)) 1190 | config.spi.mode &= ~SPI_CPHA; 1191 | break; 1192 | case 'O': 1193 | if (!get_bool_param(optarg)) 1194 | config.spi.mode &= ~SPI_CPOL; 1195 | break; 1196 | case 'L': config.spi.mode |= SPI_LSB_FIRST; break; 1197 | case 'C': config.spi.mode |= SPI_CS_HIGH; break; 1198 | case '3': config.spi.mode |= SPI_3WIRE; break; 1199 | default: 1200 | return -EINVAL; 1201 | break; 1202 | } 1203 | } 1204 | 1205 | return dispatch(argc - optind, &argv[optind], command_list); 1206 | } 1207 | 1208 | 1209 | int main(int argc, char *argv[]) 1210 | { 1211 | int ret; 1212 | 1213 | // u8 buf[5] = {1,2,64,65,31}; 1214 | // dump_hex(stderr, buf, sizeof(buf)); 1215 | // ret = 0; 1216 | //if (0) { 1217 | argv0 = argv[0]; 1218 | if ((ret = process_args(argc, argv))) { 1219 | if (ret < 0) 1220 | ret = -ret; 1221 | errno = ret; 1222 | perror("main"); 1223 | } 1224 | //} 1225 | return ret; 1226 | } 1227 | 1228 | -------------------------------------------------------------------------------- /user/mcp2210_bind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is a work-around for the lack of a mechanism to tell hid-generic 4 | # and usbhid to not bind to the mcp2210 device. 5 | 6 | vid="04d8" 7 | pid="00de" 8 | 9 | die() { 10 | echo "${BAD}ERROR${NORMAL}$(test $# -eq 0 || echo ": $*")" >&2 11 | exit 1 12 | } 13 | 14 | # Find sysfs mount point (may not be /sys) 15 | sysfs_mount=$( 16 | grep '^sysfs ' < /proc/mounts | 17 | perl -pe 's:sysfs (/\w+) .*:$1:g' | 18 | tail -1 19 | ) 20 | 21 | test -n "${sysfs_mount}" || die "OMG! no sysfs mount point! O_o" 22 | pushd "${sysfs_mount}/bus/usb/devices" || die 23 | 24 | # Find all devices 25 | mcp2210_devices=$( 26 | for f in *; do 27 | if grep -i ${vid} $f/idVendor > /dev/null 2>&1 && 28 | grep -i ${pid} $f/idProduct > /dev/null 2>&1; then 29 | echo $f; 30 | fi; 31 | done 32 | ) 33 | 34 | if [[ -z "${mcp2210_devices}" ]]; then 35 | die "No devices found. If you have altered the vid/pid, please" \ 36 | "update this script with the new values." 37 | fi 38 | 39 | for dev in ${mcp2210_devices}; do 40 | echo "${dev}:1.0" > "${sysfs_mount}/bus/usb/drivers/usbhid/unbind" 2>/dev/null 41 | echo "${dev}:1.0" > "${sysfs_mount}/bus/usb/drivers/mcp2210/unbind" 2>/dev/null 42 | done 43 | 44 | # This only needs to be done if the mcp2210 driver has already been probed. 45 | # Otherwise, it will properly bind to the devices on its own. 46 | if lsmod | grep '^mcp2210 ' > /dev/null; then 47 | for dev in ${mcp2210_devices}; do 48 | echo "${dev}:1.0" > "${sysfs_mount}/bus/usb/drivers/mcp2210/bind" 49 | done 50 | fi 51 | -------------------------------------------------------------------------------- /user/select-gpio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A simple program for testing IRQs via userspace gpio sysfs 3 | * 4 | * Copyright (c) 2016-2017 Daniel Santos 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | static void usage(const char *argv0) { 28 | fprintf(stderr, "USAGE: %s \n\n", argv0); 29 | fprintf(stderr, " Where file_name is the name of a sysfs gpio value file.\n"); 30 | fprintf(stderr, " Example: %s /sys/class/gpio/GP510/value\n\n", argv0); 31 | fprintf(stderr, "See Documentation/gpio/sysfs.txt for more info\n"); 32 | exit(1); 33 | } 34 | 35 | static void poll_pin(const char *fn) { 36 | struct pollfd fdlist[1]; 37 | int fd; 38 | 39 | fd = open(fn, O_RDONLY); 40 | if (fd == -1) { 41 | perror("open"); 42 | fprintf(stderr, "Failed to open file %s\n", fn); 43 | return; 44 | } 45 | fdlist[0].fd = fd; 46 | fdlist[0].events = POLLPRI; 47 | 48 | while (1) { 49 | int ret; 50 | char buf[3]; 51 | 52 | if (poll(fdlist, 1, -1) == -1) { 53 | perror("poll"); 54 | return; 55 | } 56 | 57 | ret = read(fdlist[0].fd, buf, 2); 58 | if (ret == -1) { 59 | perror("read"); 60 | return; 61 | } 62 | buf[ret] = 0; 63 | printf("event on pin %s, value = %c\n", fn, buf[0]); 64 | 65 | if (lseek(fd, 0, SEEK_SET) == -1) { 66 | perror("lseek"); 67 | return; 68 | } 69 | } 70 | } 71 | 72 | int main(int argc, char *argv[]) { 73 | if (argc != 2) 74 | usage(argv[0]); 75 | 76 | poll_pin(argv[1]); 77 | 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /user/settings-example.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 hacky edit-this-file header for settings 3 | * Copyright (c) 2013-2017 Daniel Santos 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * @file due to the fact that I have no better means to do configuration, edit 21 | * this file to your needs and recompile the userspace utility 22 | */ 23 | 24 | #ifndef _SETTINGS_H 25 | #define _SETTINGS_H 26 | 27 | #include 28 | 29 | #ifndef _LINUX_IRQ_H 30 | enum { 31 | IRQ_TYPE_NONE = 0x00000000, 32 | IRQ_TYPE_EDGE_RISING = 0x00000001, 33 | IRQ_TYPE_EDGE_FALLING = 0x00000002, 34 | IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING), 35 | IRQ_TYPE_LEVEL_HIGH = 0x00000004, 36 | IRQ_TYPE_LEVEL_LOW = 0x00000008, 37 | IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH), 38 | IRQ_TYPE_SENSE_MASK = 0x0000000f, 39 | IRQ_TYPE_DEFAULT = IRQ_TYPE_SENSE_MASK, 40 | }; 41 | #endif 42 | 43 | /* 44 | * The power up settings are just that, the settings the chip will have at 45 | * power up. These settings are configured into the mcp2210 flash by the 46 | * appropriate command. These setting (not the flash entry) may be changed by 47 | * the driver during initialization using the my_board_config settings. 48 | * The power up and the my_board_config settings may be different. 49 | */ 50 | 51 | /* 52 | * Used to configure MMCP2210 CHIP SETTINGS POWER-UP DEFAULT using 53 | * mcp2210-util set config 2 54 | * 55 | * MCP2210 reference: 56 | * 3.1.1 CHIP SETTINGS POWER-UP DEFAULT 57 | */ 58 | 59 | static const struct mcp2210_chip_settings my_power_up_chip_settings = { 60 | .pin_mode = { 61 | MCP2210_PIN_SPI, 62 | MCP2210_PIN_GPIO, 63 | MCP2210_PIN_DEDICATED, /* Dedicated = USB Suspend */ 64 | MCP2210_PIN_DEDICATED, /* Dedicated = SPI Transfer LED */ 65 | MCP2210_PIN_DEDICATED, /* Dedicated = USB Low Power */ 66 | MCP2210_PIN_DEDICATED, /* Dedicated = USB Configured */ 67 | MCP2210_PIN_DEDICATED, /* Dedicated = External Interrupt */ 68 | MCP2210_PIN_SPI, /* Dedicated = SPI Bus Release ACK */ 69 | MCP2210_PIN_GPIO, /* Dedicated = SPI Bus Release REQ */ 70 | }, 71 | .gpio_value = 0x0002, 72 | .gpio_direction = 0x0140, 73 | .other_settings = MCP2210_SPI_BUS_RELEASE_DISABLED 74 | | MCP2210_INTERRUPT_LOW_PULSE, 75 | .nvram_access_control = 0, 76 | .password = {0, 0, 0, 0, 0, 0, 0, 0}, 77 | }; 78 | 79 | 80 | /* 81 | * Used to configure SET SPI POWER-UP TRANSFER SETTINGS using 82 | * mcp2210-util set config 8 83 | * 84 | * MCP2210 reference 85 | * 3.1.2 SET SPI POWER-UP TRANSFER SETTINGS 86 | */ 87 | static const struct mcp2210_spi_xfer_settings my_power_up_spi_settings = { 88 | .bitrate = MCP2210_MAX_SPEED, 89 | .idle_cs = 0x01ff, 90 | .active_cs = 0x0000, 91 | .cs_to_data_delay = 1, 92 | .last_byte_to_cs_delay = 1, 93 | .delay_between_bytes = 1, 94 | .bytes_per_trans = 4, 95 | .mode = SPI_MODE_3 96 | }; 97 | 98 | 99 | /* 100 | * Used to set SET (VM) CURRENT CHIP SETTINGS using 101 | * mcp2210-util set config 1 102 | * Also used for creek encode command using 103 | * mcp2210-util encode 104 | * 105 | * Usually the same as my_power_up_chip_settings 106 | * 107 | * MCP2210 reference 108 | * 3.2.4 SET (VM) CURRENT CHIP SETTINGS 109 | */ 110 | #define my_chip_settings my_power_up_chip_settings 111 | 112 | /* 113 | * Used to set SET (VM) SPI TRANSFER SETTINGS using 114 | * mcp2210-util set config 4 115 | * Also used for creek encode command using 116 | * mcp2210-util encode 117 | * 118 | * Usually the same as my_power_up_spi_settings 119 | * 120 | * MCP2210 reference 121 | * 3.2.2 SET (VM) SPI TRANSFER SETTINGS 122 | */ 123 | #define my_spi_settings my_power_up_spi_settings 124 | 125 | 126 | /* 127 | * Used to set SET USB POWER-UP KEY PARAMETERS using 128 | * mcp2210-util set config 16 129 | * 130 | * MCP2210 reference: 131 | * 3.1.3 SET USB POWER-UP KEY PARAMETERS 132 | */ 133 | static const struct mcp2210_usb_key_params my_usb_key_params = { 134 | .vid = USB_VENDOR_ID_MICROCHIP, 135 | .pid = USB_DEVICE_ID_MCP2210, 136 | .chip_power_option = 0x80, 137 | .requested_power = 0x32, /* 100mA */ 138 | }; 139 | 140 | 141 | /* 142 | * Used to configure the board using using 143 | * mcp2210-util set config 32 144 | * Aslo used for creek encode using 145 | * mcp2210-util encode 146 | * 147 | * see mcp2210-creek for further details. 148 | */ 149 | static const struct mcp2210_board_config my_board_config = { 150 | .pins = { 151 | { 152 | .mode = MCP2210_PIN_SPI, 153 | .spi.max_speed_hz = 20000, 154 | .spi.min_speed_hz = 2000, 155 | .spi.mode = SPI_MODE_3, 156 | .spi.bits_per_word = 8, 157 | .spi.cs_to_data_delay = 1, 158 | .spi.last_byte_to_cs_delay = 1, 159 | .spi.delay_between_bytes = 1, 160 | .spi.delay_between_xfers = 1, 161 | .modalias = "spidev", 162 | .name = "L6470", 163 | }, { 164 | .mode = MCP2210_PIN_GPIO, 165 | .has_irq = 0, 166 | .irq = 0, 167 | .name = "unused%d", 168 | }, { 169 | .mode = MCP2210_PIN_DEDICATED, 170 | .name = "SSPND" 171 | }, { 172 | .mode = MCP2210_PIN_DEDICATED, 173 | .name = "USBLED", 174 | }, { 175 | .mode = MCP2210_PIN_DEDICATED, 176 | .name = "LOWPWR", 177 | }, { 178 | .mode = MCP2210_PIN_DEDICATED, 179 | .name = "USBCFG", 180 | }, { 181 | .mode = MCP2210_PIN_DEDICATED, 182 | .has_irq = 1, 183 | .irq = 0, 184 | /* .irq_type would be ignored here because you use 185 | * struct my_power_up_chip_settings::other_settings 186 | * for dedicated GP6. */ 187 | .name = "MOTION", 188 | }, { 189 | .mode = MCP2210_PIN_SPI, 190 | .has_irq = 1, 191 | .irq = 0, 192 | .spi.max_speed_hz = 20000, 193 | .spi.min_speed_hz = 5000, 194 | .spi.mode = SPI_MODE_3, 195 | .spi.bits_per_word = 8, 196 | .spi.use_cs_gpio = 0, 197 | .spi.cs_gpio = 0, 198 | .spi.cs_to_data_delay = 2, 199 | .spi.last_byte_to_cs_delay = 2, 200 | .spi.delay_between_bytes = 4, 201 | .spi.delay_between_xfers = 10, 202 | .modalias = "adns9x", 203 | .name = "ADNS-9800", 204 | }, { 205 | .mode = MCP2210_PIN_GPIO, 206 | .has_irq = 1, 207 | .irq = 1, 208 | .irq_type = IRQ_TYPE_EDGE_RISING, 209 | .name = "gpio_int%d", 210 | } 211 | }, 212 | .poll_gpio_usecs = 25000, /* 40 Hz poll rate */ 213 | .stale_gpio_usecs = 2500, 214 | .poll_intr_usecs = 25000, /* 40 Hz poll rate */ 215 | .stale_intr_usecs = 0, 216 | ._3wire_capable = 0, 217 | ._3wire_tx_enable_active_high = 0, 218 | ._3wire_tx_enable_pin = 0, 219 | .strings_size = 0, 220 | }; 221 | 222 | #endif /* _SETTINGS_H */ 223 | -------------------------------------------------------------------------------- /user/tests.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2210 userspace test functions 3 | * Copyright (c) 2013-2017 Daniel Santos 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "mcp2210-user.h" 20 | #include "settings.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef CONFIG_MCP2210_CREEK 27 | static int feature_disabled(void) { 28 | fprintf(stderr, "Feature disabled (set CONFIG_MCP2210_CREEK to enable)\n"); 29 | return -EPERM; 30 | } 31 | 32 | int test_encoding(int argc, char *argv[]) { 33 | return feature_disabled(); 34 | } 35 | 36 | int test_bit_stream(int argc, char *argv[]) { 37 | return feature_disabled(); 38 | } 39 | 40 | #else /* CONFIG_MCP2210_CREEK */ 41 | 42 | static int compare_board_config(const struct mcp2210_board_config *a, 43 | const struct mcp2210_board_config *b) { 44 | uint i; 45 | 46 | if (a->poll_gpio_usecs != b->poll_gpio_usecs) return 1; 47 | if (a->poll_intr_usecs != b->poll_intr_usecs) return 2; 48 | if (a->stale_gpio_usecs != b->stale_gpio_usecs) return 3; 49 | if (a->stale_intr_usecs != b->stale_intr_usecs) return 4; 50 | 51 | for (i = 0; i < MCP2210_NUM_PINS; ++i) { 52 | const struct mcp2210_pin_config *pa = &a->pins[i]; 53 | const struct mcp2210_pin_config *pb = &b->pins[i]; 54 | uint desc = (i + 1) << 4; 55 | 56 | if (pa->mode != pb->mode) 57 | return desc; 58 | 59 | if (!!pa->name != !!pb->name) 60 | return desc | 1; 61 | 62 | if (pa->name && strcmp(pa->name, pb->name)) 63 | return desc | 2; 64 | 65 | if (!!pa->modalias != !!pb->modalias) 66 | return desc | 3; 67 | 68 | if (pa->modalias && strcmp(pa->modalias, pb->modalias)) 69 | return desc | 4; 70 | 71 | switch (pa->mode) { 72 | case MCP2210_PIN_DEDICATED: 73 | case MCP2210_PIN_GPIO: 74 | if (pa->has_irq != pb->has_irq ) return desc | 5; 75 | if (pa->irq != pb->irq ) return desc | 6; 76 | if (pa->irq_type != pb->irq_type) return desc | 7; 77 | break; 78 | 79 | case MCP2210_PIN_SPI: 80 | if (memcmp(&pa->spi, &pb->spi, sizeof(pa->spi))) 81 | return desc | 8; 82 | break; 83 | 84 | default: 85 | break; 86 | } 87 | } 88 | 89 | 90 | return 0; 91 | } 92 | 93 | static void print_failed_item(int val) { 94 | const char *item; 95 | uint section = val >> 4; 96 | 97 | if (val < 16) { 98 | switch (val) { 99 | case 1: item = "poll_gpio_usecs"; break; 100 | case 2: item = "poll_intr_usecs"; break; 101 | case 3: item = "stale_gpio_usecs"; break; 102 | case 4: item = "stale_intr_usecs"; break; 103 | default: item = "bad code"; break; 104 | } 105 | fprintf(stderr, "%s", item); 106 | } else { 107 | uint pin = section - 1; 108 | 109 | switch (val & 0xf) { 110 | case 0: item = "mode"; break; 111 | case 1: item = "name presence"; break; 112 | case 2: item = "name"; break; 113 | case 3: item = "modalias presence"; break; 114 | case 4: item = "modalias"; break; 115 | case 5: item = "has_irq"; break; 116 | case 6: item = "irq"; break; 117 | case 7: item = "irq_type"; break; 118 | case 8: item = "spi memcmp"; break; 119 | default: item = "bad code"; break; 120 | } 121 | fprintf(stderr, "pin %u, %s", pin, item); 122 | } 123 | } 124 | 125 | int test_encoding(int argc, char *argv[]) { 126 | struct mcp2210_board_config *board_config; 127 | struct mcp2210_board_config *new_cfg; 128 | u8 buf[0x100]; 129 | int ret; 130 | 131 | memset(buf, 0, sizeof(buf)); 132 | board_config = copy_board_config(NULL, &my_board_config, 0); 133 | if (!board_config) { 134 | errno = ENOMEM; 135 | perror(""); 136 | return -ENOMEM; 137 | } 138 | 139 | ret = compare_board_config(board_config, &my_board_config); 140 | if (ret) { 141 | fprintf(stderr, "copy_board_config() produced a non-matching " 142 | "copy: ret = %d\n", ret); 143 | goto exit_free; 144 | } 145 | 146 | ret = creek_encode(board_config, &my_chip_settings, buf, sizeof(buf), 147 | 2); 148 | 149 | if (ret < 0) { 150 | errno = -ret; 151 | perror("creek_encode"); 152 | goto exit_free; 153 | } 154 | fprintf(stderr, "encoded into %d bits (%d bytes)\n", ret, (ret + 7) / 8); 155 | 156 | new_cfg = creek_decode(&my_chip_settings, buf, sizeof(buf), GFP_KERNEL); 157 | 158 | if (IS_ERR_VALUE(new_cfg)) { 159 | ret = PTR_ERR(new_cfg); 160 | errno = -ret; 161 | perror("creek_decode"); 162 | goto exit_free; 163 | } 164 | 165 | dump_board_config(KERN_INFO, 0, "original = ", board_config); 166 | dump_board_config(KERN_INFO, 0, "extra_crispy = ", new_cfg); 167 | 168 | ret = compare_board_config(board_config, new_cfg); 169 | if (ret) { 170 | fprintf(stderr, "They don't match: "); 171 | print_failed_item(ret); 172 | fputs("\n", stderr); 173 | }else 174 | fprintf(stderr, "Success\n"); 175 | 176 | free(new_cfg); 177 | exit_free: 178 | errno = 0; 179 | free(board_config); 180 | return ret; 181 | } 182 | 183 | int test_bit_stream(int argc, char *argv[]) 184 | { 185 | int ret; 186 | int fd; 187 | unsigned i; 188 | u8 sizes[0x4000]; 189 | u8 src[0x10000]; 190 | u8 dest[0x10000]; 191 | 192 | struct bit_creek bs1 = { 193 | .start = src, 194 | .size = sizeof(src), 195 | }; 196 | 197 | struct bit_creek bs2 = { 198 | .start = dest, 199 | .size = sizeof(dest), 200 | }; 201 | 202 | /* Get some random data */ 203 | if ((fd = open("/dev/urandom", O_RDONLY)) < 0) { 204 | perror("can't open device"); 205 | fatal_error("failed to open %s", "/dev/urandom"); 206 | 207 | } 208 | 209 | /* random size of read/writes */ 210 | if ((ret = read(fd, sizes, sizeof(sizes))) != sizeof(sizes)) { 211 | ret = -errno; 212 | perror("failed to read random size data"); 213 | return ret; 214 | } 215 | 216 | /* get random data to read/write */ 217 | 218 | if ((ret = read(fd, src, sizeof(src))) != sizeof(src)) { 219 | ret = -errno; 220 | perror("failed to read random data"); 221 | return ret; 222 | } 223 | 224 | close(fd); 225 | 226 | for (i = 0; i < sizeof(sizes); ++i) { 227 | u8 bits = (sizes[i] & 31) + 1; 228 | unsigned u; 229 | 230 | u = creek_get_bits(&bs1, bits); 231 | if (bs1.overflow) { 232 | fprintf(stderr, "overflow in 1st bitstream, doesn't indicate a failure\n"); 233 | bs1.pos -= bits; 234 | break; 235 | } 236 | 237 | //fprintf(stderr, "%lu (%lu), %hhu, 0x%08x\n", bs1.pos, bs1.pos / 8, bits, u); 238 | creek_put_bits(&bs2, u, bits); 239 | if (bs2.overflow) { 240 | fprintf(stderr, "overflow"); 241 | return 1; 242 | } 243 | } 244 | 245 | if (bs1.pos != bs2.pos) { 246 | fprintf(stderr, "Fail, bs1.pos != bs2.pos (%u != %u)\n", (unsigned)bs1.pos, (unsigned)bs2.pos); 247 | return 1; 248 | } 249 | 250 | if (memcmp(src, dest, bs1.pos / 8 - 1)) { 251 | fprintf(stderr, "Fail, didn't match\n"); 252 | return 1; 253 | } 254 | 255 | fprintf(stderr, "Success\n"); 256 | return 0; 257 | } 258 | #endif /* CONFIG_MCP2210_CREEK */ 259 | --------------------------------------------------------------------------------