162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for Rio Karma
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *   (c) 2006 Bob Copeland <me@bobcopeland.com>
662306a36Sopenharmony_ci *   (c) 2006 Keith Bennett <keith@mcs.st-and.ac.uk>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <scsi/scsi.h>
1362306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h>
1462306a36Sopenharmony_ci#include <scsi/scsi_device.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "usb.h"
1762306a36Sopenharmony_ci#include "transport.h"
1862306a36Sopenharmony_ci#include "debug.h"
1962306a36Sopenharmony_ci#include "scsiglue.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define DRV_NAME "ums-karma"
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for Rio Karma");
2462306a36Sopenharmony_ciMODULE_AUTHOR("Bob Copeland <me@bobcopeland.com>, Keith Bennett <keith@mcs.st-and.ac.uk>");
2562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2662306a36Sopenharmony_ciMODULE_IMPORT_NS(USB_STORAGE);
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define RIO_PREFIX "RIOP\x00"
2962306a36Sopenharmony_ci#define RIO_PREFIX_LEN 5
3062306a36Sopenharmony_ci#define RIO_SEND_LEN 40
3162306a36Sopenharmony_ci#define RIO_RECV_LEN 0x200
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define RIO_ENTER_STORAGE 0x1
3462306a36Sopenharmony_ci#define RIO_LEAVE_STORAGE 0x2
3562306a36Sopenharmony_ci#define RIO_RESET 0xC
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistruct karma_data {
3862306a36Sopenharmony_ci	int in_storage;
3962306a36Sopenharmony_ci	char *recv;
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int rio_karma_init(struct us_data *us);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/*
4662306a36Sopenharmony_ci * The table of devices
4762306a36Sopenharmony_ci */
4862306a36Sopenharmony_ci#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \
4962306a36Sopenharmony_ci		    vendorName, productName, useProtocol, useTransport, \
5062306a36Sopenharmony_ci		    initFunction, flags) \
5162306a36Sopenharmony_ci{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \
5262306a36Sopenharmony_ci  .driver_info = (flags) }
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic struct usb_device_id karma_usb_ids[] = {
5562306a36Sopenharmony_ci#	include "unusual_karma.h"
5662306a36Sopenharmony_ci	{ }		/* Terminating entry */
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, karma_usb_ids);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci#undef UNUSUAL_DEV
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/*
6362306a36Sopenharmony_ci * The flags table
6462306a36Sopenharmony_ci */
6562306a36Sopenharmony_ci#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \
6662306a36Sopenharmony_ci		    vendor_name, product_name, use_protocol, use_transport, \
6762306a36Sopenharmony_ci		    init_function, Flags) \
6862306a36Sopenharmony_ci{ \
6962306a36Sopenharmony_ci	.vendorName = vendor_name,	\
7062306a36Sopenharmony_ci	.productName = product_name,	\
7162306a36Sopenharmony_ci	.useProtocol = use_protocol,	\
7262306a36Sopenharmony_ci	.useTransport = use_transport,	\
7362306a36Sopenharmony_ci	.initFunction = init_function,	\
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic struct us_unusual_dev karma_unusual_dev_list[] = {
7762306a36Sopenharmony_ci#	include "unusual_karma.h"
7862306a36Sopenharmony_ci	{ }		/* Terminating entry */
7962306a36Sopenharmony_ci};
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci#undef UNUSUAL_DEV
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/*
8562306a36Sopenharmony_ci * Send commands to Rio Karma.
8662306a36Sopenharmony_ci *
8762306a36Sopenharmony_ci * For each command we send 40 bytes starting 'RIOP\0' followed by
8862306a36Sopenharmony_ci * the command number and a sequence number, which the device will ack
8962306a36Sopenharmony_ci * with a 512-byte packet with the high four bits set and everything
9062306a36Sopenharmony_ci * else null.  Then we send 'RIOP\x80' followed by a zero and the
9162306a36Sopenharmony_ci * sequence number, until byte 5 in the response repeats the sequence
9262306a36Sopenharmony_ci * number.
9362306a36Sopenharmony_ci */
9462306a36Sopenharmony_cistatic int rio_karma_send_command(char cmd, struct us_data *us)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	int result;
9762306a36Sopenharmony_ci	unsigned long timeout;
9862306a36Sopenharmony_ci	static unsigned char seq = 1;
9962306a36Sopenharmony_ci	struct karma_data *data = (struct karma_data *) us->extra;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	usb_stor_dbg(us, "sending command %04x\n", cmd);
10262306a36Sopenharmony_ci	memset(us->iobuf, 0, RIO_SEND_LEN);
10362306a36Sopenharmony_ci	memcpy(us->iobuf, RIO_PREFIX, RIO_PREFIX_LEN);
10462306a36Sopenharmony_ci	us->iobuf[5] = cmd;
10562306a36Sopenharmony_ci	us->iobuf[6] = seq;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(6000);
10862306a36Sopenharmony_ci	for (;;) {
10962306a36Sopenharmony_ci		result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe,
11062306a36Sopenharmony_ci			us->iobuf, RIO_SEND_LEN, NULL);
11162306a36Sopenharmony_ci		if (result != USB_STOR_XFER_GOOD)
11262306a36Sopenharmony_ci			goto err;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci		result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe,
11562306a36Sopenharmony_ci			data->recv, RIO_RECV_LEN, NULL);
11662306a36Sopenharmony_ci		if (result != USB_STOR_XFER_GOOD)
11762306a36Sopenharmony_ci			goto err;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		if (data->recv[5] == seq)
12062306a36Sopenharmony_ci			break;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		if (time_after(jiffies, timeout))
12362306a36Sopenharmony_ci			goto err;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci		us->iobuf[4] = 0x80;
12662306a36Sopenharmony_ci		us->iobuf[5] = 0;
12762306a36Sopenharmony_ci		msleep(50);
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	seq++;
13162306a36Sopenharmony_ci	if (seq == 0)
13262306a36Sopenharmony_ci		seq = 1;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	usb_stor_dbg(us, "sent command %04x\n", cmd);
13562306a36Sopenharmony_ci	return 0;
13662306a36Sopenharmony_cierr:
13762306a36Sopenharmony_ci	usb_stor_dbg(us, "command %04x failed\n", cmd);
13862306a36Sopenharmony_ci	return USB_STOR_TRANSPORT_FAILED;
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci/*
14262306a36Sopenharmony_ci * Trap START_STOP and READ_10 to leave/re-enter storage mode.
14362306a36Sopenharmony_ci * Everything else is propagated to the normal bulk layer.
14462306a36Sopenharmony_ci */
14562306a36Sopenharmony_cistatic int rio_karma_transport(struct scsi_cmnd *srb, struct us_data *us)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	int ret;
14862306a36Sopenharmony_ci	struct karma_data *data = (struct karma_data *) us->extra;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (srb->cmnd[0] == READ_10 && !data->in_storage) {
15162306a36Sopenharmony_ci		ret = rio_karma_send_command(RIO_ENTER_STORAGE, us);
15262306a36Sopenharmony_ci		if (ret)
15362306a36Sopenharmony_ci			return ret;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci		data->in_storage = 1;
15662306a36Sopenharmony_ci		return usb_stor_Bulk_transport(srb, us);
15762306a36Sopenharmony_ci	} else if (srb->cmnd[0] == START_STOP) {
15862306a36Sopenharmony_ci		ret = rio_karma_send_command(RIO_LEAVE_STORAGE, us);
15962306a36Sopenharmony_ci		if (ret)
16062306a36Sopenharmony_ci			return ret;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci		data->in_storage = 0;
16362306a36Sopenharmony_ci		return rio_karma_send_command(RIO_RESET, us);
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci	return usb_stor_Bulk_transport(srb, us);
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic void rio_karma_destructor(void *extra)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct karma_data *data = (struct karma_data *) extra;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	kfree(data->recv);
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic int rio_karma_init(struct us_data *us)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	struct karma_data *data = kzalloc(sizeof(struct karma_data), GFP_NOIO);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (!data)
18062306a36Sopenharmony_ci		return -ENOMEM;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	data->recv = kmalloc(RIO_RECV_LEN, GFP_NOIO);
18362306a36Sopenharmony_ci	if (!data->recv) {
18462306a36Sopenharmony_ci		kfree(data);
18562306a36Sopenharmony_ci		return -ENOMEM;
18662306a36Sopenharmony_ci	}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	us->extra = data;
18962306a36Sopenharmony_ci	us->extra_destructor = rio_karma_destructor;
19062306a36Sopenharmony_ci	if (rio_karma_send_command(RIO_ENTER_STORAGE, us))
19162306a36Sopenharmony_ci		return -EIO;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	data->in_storage = 1;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return 0;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic struct scsi_host_template karma_host_template;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic int karma_probe(struct usb_interface *intf,
20162306a36Sopenharmony_ci			 const struct usb_device_id *id)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	struct us_data *us;
20462306a36Sopenharmony_ci	int result;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	result = usb_stor_probe1(&us, intf, id,
20762306a36Sopenharmony_ci			(id - karma_usb_ids) + karma_unusual_dev_list,
20862306a36Sopenharmony_ci			&karma_host_template);
20962306a36Sopenharmony_ci	if (result)
21062306a36Sopenharmony_ci		return result;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	us->transport_name = "Rio Karma/Bulk";
21362306a36Sopenharmony_ci	us->transport = rio_karma_transport;
21462306a36Sopenharmony_ci	us->transport_reset = usb_stor_Bulk_reset;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	result = usb_stor_probe2(us);
21762306a36Sopenharmony_ci	return result;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic struct usb_driver karma_driver = {
22162306a36Sopenharmony_ci	.name =		DRV_NAME,
22262306a36Sopenharmony_ci	.probe =	karma_probe,
22362306a36Sopenharmony_ci	.disconnect =	usb_stor_disconnect,
22462306a36Sopenharmony_ci	.suspend =	usb_stor_suspend,
22562306a36Sopenharmony_ci	.resume =	usb_stor_resume,
22662306a36Sopenharmony_ci	.reset_resume =	usb_stor_reset_resume,
22762306a36Sopenharmony_ci	.pre_reset =	usb_stor_pre_reset,
22862306a36Sopenharmony_ci	.post_reset =	usb_stor_post_reset,
22962306a36Sopenharmony_ci	.id_table =	karma_usb_ids,
23062306a36Sopenharmony_ci	.soft_unbind =	1,
23162306a36Sopenharmony_ci	.no_dynamic_id = 1,
23262306a36Sopenharmony_ci};
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cimodule_usb_stor_driver(karma_driver, karma_host_template, DRV_NAME);
235