162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci* cypress_cy7c63.c 462306a36Sopenharmony_ci* 562306a36Sopenharmony_ci* Copyright (c) 2006-2007 Oliver Bock (bock@tfh-berlin.de) 662306a36Sopenharmony_ci* 762306a36Sopenharmony_ci* This driver is based on the Cypress USB Driver by Marcus Maul 862306a36Sopenharmony_ci* (cyport) and the 2.0 version of Greg Kroah-Hartman's 962306a36Sopenharmony_ci* USB Skeleton driver. 1062306a36Sopenharmony_ci* 1162306a36Sopenharmony_ci* This is a generic driver for the Cypress CY7C63xxx family. 1262306a36Sopenharmony_ci* For the time being it enables you to read from and write to 1362306a36Sopenharmony_ci* the single I/O ports of the device. 1462306a36Sopenharmony_ci* 1562306a36Sopenharmony_ci* Supported vendors: AK Modul-Bus Computer GmbH 1662306a36Sopenharmony_ci* (Firmware "Port-Chip") 1762306a36Sopenharmony_ci* 1862306a36Sopenharmony_ci* Supported devices: CY7C63001A-PC 1962306a36Sopenharmony_ci* CY7C63001C-PXC 2062306a36Sopenharmony_ci* CY7C63001C-SXC 2162306a36Sopenharmony_ci* 2262306a36Sopenharmony_ci* Supported functions: Read/Write Ports 2362306a36Sopenharmony_ci* 2462306a36Sopenharmony_ci* 2562306a36Sopenharmony_ci* For up-to-date information please visit: 2662306a36Sopenharmony_ci* http://www.obock.de/kernel/cypress 2762306a36Sopenharmony_ci*/ 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include <linux/module.h> 3062306a36Sopenharmony_ci#include <linux/kernel.h> 3162306a36Sopenharmony_ci#include <linux/slab.h> 3262306a36Sopenharmony_ci#include <linux/usb.h> 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define DRIVER_AUTHOR "Oliver Bock (bock@tfh-berlin.de)" 3562306a36Sopenharmony_ci#define DRIVER_DESC "Cypress CY7C63xxx USB driver" 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define CYPRESS_VENDOR_ID 0xa2c 3862306a36Sopenharmony_ci#define CYPRESS_PRODUCT_ID 0x8 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define CYPRESS_READ_PORT 0x4 4162306a36Sopenharmony_ci#define CYPRESS_WRITE_PORT 0x5 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#define CYPRESS_READ_RAM 0x2 4462306a36Sopenharmony_ci#define CYPRESS_WRITE_RAM 0x3 4562306a36Sopenharmony_ci#define CYPRESS_READ_ROM 0x1 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci#define CYPRESS_READ_PORT_ID0 0 4862306a36Sopenharmony_ci#define CYPRESS_WRITE_PORT_ID0 0 4962306a36Sopenharmony_ci#define CYPRESS_READ_PORT_ID1 0x2 5062306a36Sopenharmony_ci#define CYPRESS_WRITE_PORT_ID1 1 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#define CYPRESS_MAX_REQSIZE 8 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci/* table of devices that work with this driver */ 5662306a36Sopenharmony_cistatic const struct usb_device_id cypress_table[] = { 5762306a36Sopenharmony_ci { USB_DEVICE(CYPRESS_VENDOR_ID, CYPRESS_PRODUCT_ID) }, 5862306a36Sopenharmony_ci { } 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, cypress_table); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci/* structure to hold all of our device specific stuff */ 6362306a36Sopenharmony_cistruct cypress { 6462306a36Sopenharmony_ci struct usb_device * udev; 6562306a36Sopenharmony_ci unsigned char port[2]; 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* used to send usb control messages to device */ 6962306a36Sopenharmony_cistatic int vendor_command(struct cypress *dev, unsigned char request, 7062306a36Sopenharmony_ci unsigned char address, unsigned char data) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci int retval = 0; 7362306a36Sopenharmony_ci unsigned int pipe; 7462306a36Sopenharmony_ci unsigned char *iobuf; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* allocate some memory for the i/o buffer*/ 7762306a36Sopenharmony_ci iobuf = kzalloc(CYPRESS_MAX_REQSIZE, GFP_KERNEL); 7862306a36Sopenharmony_ci if (!iobuf) { 7962306a36Sopenharmony_ci retval = -ENOMEM; 8062306a36Sopenharmony_ci goto error; 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "Sending usb_control_msg (data: %d)\n", data); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* prepare usb control message and send it upstream */ 8662306a36Sopenharmony_ci pipe = usb_rcvctrlpipe(dev->udev, 0); 8762306a36Sopenharmony_ci retval = usb_control_msg(dev->udev, pipe, request, 8862306a36Sopenharmony_ci USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, 8962306a36Sopenharmony_ci address, data, iobuf, CYPRESS_MAX_REQSIZE, 9062306a36Sopenharmony_ci USB_CTRL_GET_TIMEOUT); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci /* store returned data (more READs to be added) */ 9362306a36Sopenharmony_ci switch (request) { 9462306a36Sopenharmony_ci case CYPRESS_READ_PORT: 9562306a36Sopenharmony_ci if (address == CYPRESS_READ_PORT_ID0) { 9662306a36Sopenharmony_ci dev->port[0] = iobuf[1]; 9762306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, 9862306a36Sopenharmony_ci "READ_PORT0 returned: %d\n", 9962306a36Sopenharmony_ci dev->port[0]); 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci else if (address == CYPRESS_READ_PORT_ID1) { 10262306a36Sopenharmony_ci dev->port[1] = iobuf[1]; 10362306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, 10462306a36Sopenharmony_ci "READ_PORT1 returned: %d\n", 10562306a36Sopenharmony_ci dev->port[1]); 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci break; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci kfree(iobuf); 11162306a36Sopenharmony_cierror: 11262306a36Sopenharmony_ci return retval; 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci/* write port value */ 11662306a36Sopenharmony_cistatic ssize_t write_port(struct device *dev, struct device_attribute *attr, 11762306a36Sopenharmony_ci const char *buf, size_t count, 11862306a36Sopenharmony_ci int port_num, int write_id) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci int value = -1; 12162306a36Sopenharmony_ci int result = 0; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 12462306a36Sopenharmony_ci struct cypress *cyp = usb_get_intfdata(intf); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci dev_dbg(&cyp->udev->dev, "WRITE_PORT%d called\n", port_num); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* validate input data */ 12962306a36Sopenharmony_ci if (sscanf(buf, "%d", &value) < 1) { 13062306a36Sopenharmony_ci result = -EINVAL; 13162306a36Sopenharmony_ci goto error; 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci if (value < 0 || value > 255) { 13462306a36Sopenharmony_ci result = -EINVAL; 13562306a36Sopenharmony_ci goto error; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci result = vendor_command(cyp, CYPRESS_WRITE_PORT, write_id, 13962306a36Sopenharmony_ci (unsigned char)value); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); 14262306a36Sopenharmony_cierror: 14362306a36Sopenharmony_ci return result < 0 ? result : count; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci/* attribute callback handler (write) */ 14762306a36Sopenharmony_cistatic ssize_t port0_store(struct device *dev, 14862306a36Sopenharmony_ci struct device_attribute *attr, 14962306a36Sopenharmony_ci const char *buf, size_t count) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci return write_port(dev, attr, buf, count, 0, CYPRESS_WRITE_PORT_ID0); 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci/* attribute callback handler (write) */ 15562306a36Sopenharmony_cistatic ssize_t port1_store(struct device *dev, 15662306a36Sopenharmony_ci struct device_attribute *attr, 15762306a36Sopenharmony_ci const char *buf, size_t count) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci return write_port(dev, attr, buf, count, 1, CYPRESS_WRITE_PORT_ID1); 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci/* read port value */ 16362306a36Sopenharmony_cistatic ssize_t read_port(struct device *dev, struct device_attribute *attr, 16462306a36Sopenharmony_ci char *buf, int port_num, int read_id) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci int result = 0; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 16962306a36Sopenharmony_ci struct cypress *cyp = usb_get_intfdata(intf); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci return sprintf(buf, "%d", cyp->port[port_num]); 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci/* attribute callback handler (read) */ 18162306a36Sopenharmony_cistatic ssize_t port0_show(struct device *dev, 18262306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci return read_port(dev, attr, buf, 0, CYPRESS_READ_PORT_ID0); 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(port0); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci/* attribute callback handler (read) */ 18962306a36Sopenharmony_cistatic ssize_t port1_show(struct device *dev, 19062306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci return read_port(dev, attr, buf, 1, CYPRESS_READ_PORT_ID1); 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_cistatic DEVICE_ATTR_RW(port1); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic struct attribute *cypress_attrs[] = { 19762306a36Sopenharmony_ci &dev_attr_port0.attr, 19862306a36Sopenharmony_ci &dev_attr_port1.attr, 19962306a36Sopenharmony_ci NULL, 20062306a36Sopenharmony_ci}; 20162306a36Sopenharmony_ciATTRIBUTE_GROUPS(cypress); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic int cypress_probe(struct usb_interface *interface, 20462306a36Sopenharmony_ci const struct usb_device_id *id) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci struct cypress *dev; 20762306a36Sopenharmony_ci int retval = -ENOMEM; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci /* allocate memory for our device state and initialize it */ 21062306a36Sopenharmony_ci dev = kzalloc(sizeof(*dev), GFP_KERNEL); 21162306a36Sopenharmony_ci if (!dev) 21262306a36Sopenharmony_ci goto error_mem; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci dev->udev = usb_get_dev(interface_to_usbdev(interface)); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci /* save our data pointer in this interface device */ 21762306a36Sopenharmony_ci usb_set_intfdata(interface, dev); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* let the user know that the device is now attached */ 22062306a36Sopenharmony_ci dev_info(&interface->dev, 22162306a36Sopenharmony_ci "Cypress CY7C63xxx device now attached\n"); 22262306a36Sopenharmony_ci return 0; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cierror_mem: 22562306a36Sopenharmony_ci return retval; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic void cypress_disconnect(struct usb_interface *interface) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct cypress *dev; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci dev = usb_get_intfdata(interface); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci /* the intfdata can be set to NULL only after the 23562306a36Sopenharmony_ci * device files have been removed */ 23662306a36Sopenharmony_ci usb_set_intfdata(interface, NULL); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci usb_put_dev(dev->udev); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci dev_info(&interface->dev, 24162306a36Sopenharmony_ci "Cypress CY7C63xxx device now disconnected\n"); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci kfree(dev); 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic struct usb_driver cypress_driver = { 24762306a36Sopenharmony_ci .name = "cypress_cy7c63", 24862306a36Sopenharmony_ci .probe = cypress_probe, 24962306a36Sopenharmony_ci .disconnect = cypress_disconnect, 25062306a36Sopenharmony_ci .id_table = cypress_table, 25162306a36Sopenharmony_ci .dev_groups = cypress_groups, 25262306a36Sopenharmony_ci}; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cimodule_usb_driver(cypress_driver); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 25762306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 260