Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907// SPDX-License-Identifier: GPL-2.0-only/* * Intel La Jolla Cove Adapter USB driver * * Copyright (c) 2023, Intel Corporation. */
#include <linux/acpi.h>#include <linux/auxiliary_bus.h>#include <linux/dev_printk.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/types.h>#include <linux/usb.h>#include <linux/usb/ljca.h>
#include <linux/unaligned.h>
/* command flags */#define LJCA_ACK_FLAG BIT(0)#define LJCA_RESP_FLAG BIT(1)#define LJCA_CMPL_FLAG BIT(2)
#define LJCA_MAX_PACKET_SIZE 64u#define LJCA_MAX_PAYLOAD_SIZE \ (LJCA_MAX_PACKET_SIZE - sizeof(struct ljca_msg))
#define LJCA_WRITE_TIMEOUT_MS 200#define LJCA_WRITE_ACK_TIMEOUT_MS 500#define LJCA_ENUM_CLIENT_TIMEOUT_MS 20
/* ljca client type */enum ljca_client_type { LJCA_CLIENT_MNG = 1, LJCA_CLIENT_GPIO = 3, LJCA_CLIENT_I2C = 4, LJCA_CLIENT_SPI = 5,};
/* MNG client commands */enum ljca_mng_cmd { LJCA_MNG_RESET = 2, LJCA_MNG_ENUM_GPIO = 4, LJCA_MNG_ENUM_I2C = 5, LJCA_MNG_ENUM_SPI = 8,};
/* ljca client acpi _ADR */enum ljca_client_acpi_adr { LJCA_GPIO_ACPI_ADR, LJCA_I2C1_ACPI_ADR, LJCA_I2C2_ACPI_ADR, LJCA_SPI1_ACPI_ADR, LJCA_SPI2_ACPI_ADR, LJCA_CLIENT_ACPI_ADR_MAX,};
/* ljca cmd message structure */struct ljca_msg { u8 type; u8 cmd; u8 flags; u8 len; u8 data[] __counted_by(len);} __packed;
struct ljca_i2c_ctr_info { u8 id; u8 capacity; u8 intr_pin;} __packed;
struct ljca_i2c_descriptor { u8 num; struct ljca_i2c_ctr_info info[] __counted_by(num);} __packed;
struct ljca_spi_ctr_info { u8 id; u8 capacity; u8 intr_pin;} __packed;
struct ljca_spi_descriptor { u8 num; struct ljca_spi_ctr_info info[] __counted_by(num);} __packed;
struct ljca_bank_descriptor { u8 bank_id; u8 pin_num;
/* 1 bit for each gpio, 1 means valid */ __le32 valid_pins;} __packed;
struct ljca_gpio_descriptor { u8 pins_per_bank; u8 bank_num; struct ljca_bank_descriptor bank_desc[] __counted_by(bank_num);} __packed;
/** * struct ljca_adapter - represent a ljca adapter * * @intf: the usb interface for this ljca adapter * @usb_dev: the usb device for this ljca adapter * @dev: the specific device info of the usb interface * @rx_pipe: bulk in pipe for receive data from firmware * @tx_pipe: bulk out pipe for send data to firmware * @rx_urb: urb used for the bulk in pipe * @rx_buf: buffer used to receive command response and event * @rx_len: length of rx buffer * @ex_buf: external buffer to save command response * @ex_buf_len: length of external buffer * @actual_length: actual length of data copied to external buffer * @tx_buf: buffer used to download command to firmware * @tx_buf_len: length of tx buffer * @lock: spinlock to protect tx_buf and ex_buf * @cmd_completion: completion object as the command receives ack * @mutex: mutex to avoid command download concurrently * @client_list: client device list * @disconnect: usb disconnect ongoing or not * @reset_id: used to reset firmware */struct ljca_adapter { struct usb_interface *intf; struct usb_device *usb_dev; struct device *dev;
unsigned int rx_pipe; unsigned int tx_pipe;
struct urb *rx_urb; void *rx_buf; unsigned int rx_len;
u8 *ex_buf; u8 ex_buf_len; u8 actual_length;
void *tx_buf; u8 tx_buf_len;
spinlock_t lock;
struct completion cmd_completion; struct mutex mutex;
struct list_head client_list;
bool disconnect;
u32 reset_id;};
struct ljca_match_ids_walk_data { const struct acpi_device_id *ids; const char *uid; struct acpi_device *adev;};
/* * ACPI hardware IDs for LJCA client devices. * * [1] Some BIOS implementations use these IDs for denoting LJCA client devices * even though the IDs have been allocated for USBIO. This isn't a problem * as the usb-ljca driver is probed based on the USB device's vendor and * product IDs and its client drivers are probed based on auxiliary device * names, not these ACPI _HIDs. List of such systems: * * Dell Precision 5490 */static const struct acpi_device_id ljca_gpio_hids[] = { { "INTC100B" }, /* RPL LJCA GPIO */ { "INTC1074" }, /* CVF LJCA GPIO */ { "INTC1096" }, /* ADL LJCA GPIO */ { "INTC10B5" }, /* LNL LJCA GPIO */ { "INTC10D1" }, /* MTL (CVF VSC) USBIO GPIO [1] */ {},};
static const struct acpi_device_id ljca_i2c_hids[] = { { "INTC100C" }, /* RPL LJCA I2C */ { "INTC1075" }, /* CVF LJCA I2C */ { "INTC1097" }, /* ADL LJCA I2C */ { "INTC10D2" }, /* MTL (CVF VSC) USBIO I2C [1] */ {},};
static const struct acpi_device_id ljca_spi_hids[] = { { "INTC100D" }, /* RPL LJCA SPI */ { "INTC1091" }, /* TGL/ADL LJCA SPI */ { "INTC1098" }, /* ADL LJCA SPI */ { "INTC10D3" }, /* MTL (CVF VSC) USBIO SPI [1] */ {},};
static void ljca_handle_event(struct ljca_adapter *adap, struct ljca_msg *header){ struct ljca_client *client;
list_for_each_entry(client, &adap->client_list, link) { /* * Currently only GPIO register event callback, but * firmware message structure should include id when * multiple same type clients register event callback. */ if (client->type == header->type) { unsigned long flags;
spin_lock_irqsave(&client->event_cb_lock, flags); client->event_cb(client->context, header->cmd, header->data, header->len); spin_unlock_irqrestore(&client->event_cb_lock, flags);
break; } }}
/* process command ack and received data if available */static void ljca_handle_cmd_ack(struct ljca_adapter *adap, struct ljca_msg *header){ struct ljca_msg *tx_header = adap->tx_buf; u8 ibuf_len, actual_len = 0; unsigned long flags; u8 *ibuf;
spin_lock_irqsave(&adap->lock, flags);
if (tx_header->type != header->type || tx_header->cmd != header->cmd) { spin_unlock_irqrestore(&adap->lock, flags); dev_err(adap->dev, "cmd ack mismatch error\n"); return; }
ibuf_len = adap->ex_buf_len; ibuf = adap->ex_buf;
if (ibuf && ibuf_len) { actual_len = min(header->len, ibuf_len);
/* copy received data to external buffer */ memcpy(ibuf, header->data, actual_len); } /* update copied data length */ adap->actual_length = actual_len;
spin_unlock_irqrestore(&adap->lock, flags);
complete(&adap->cmd_completion);}
static void ljca_recv(struct urb *urb){ struct ljca_msg *header = urb->transfer_buffer; struct ljca_adapter *adap = urb->context; int ret;
switch (urb->status) { case 0: /* success */ break; case -ENOENT: /* * directly complete the possible ongoing transfer * during disconnect */ if (adap->disconnect) complete(&adap->cmd_completion); return; case -ECONNRESET: case -ESHUTDOWN: case -EPIPE: /* rx urb is terminated */ dev_dbg(adap->dev, "rx urb terminated with status: %d\n", urb->status); return; default: dev_dbg(adap->dev, "rx urb error: %d\n", urb->status); goto resubmit; }
if (header->len + sizeof(*header) != urb->actual_length) goto resubmit;
if (header->flags & LJCA_ACK_FLAG) ljca_handle_cmd_ack(adap, header); else ljca_handle_event(adap, header);
resubmit: ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret && ret != -EPERM) dev_err(adap->dev, "resubmit rx urb error %d\n", ret);}
static int ljca_send(struct ljca_adapter *adap, u8 type, u8 cmd, const u8 *obuf, u8 obuf_len, u8 *ibuf, u8 ibuf_len, bool ack, unsigned long timeout){ unsigned int msg_len = sizeof(struct ljca_msg) + obuf_len; struct ljca_msg *header = adap->tx_buf; unsigned int transferred; unsigned long flags; int ret;
if (adap->disconnect) return -ENODEV;
if (msg_len > adap->tx_buf_len) return -EINVAL;
mutex_lock(&adap->mutex);
spin_lock_irqsave(&adap->lock, flags);
header->type = type; header->cmd = cmd; header->len = obuf_len; if (obuf) memcpy(header->data, obuf, obuf_len);
header->flags = LJCA_CMPL_FLAG | (ack ? LJCA_ACK_FLAG : 0);
adap->ex_buf = ibuf; adap->ex_buf_len = ibuf_len; adap->actual_length = 0;
spin_unlock_irqrestore(&adap->lock, flags);
reinit_completion(&adap->cmd_completion);
ret = usb_autopm_get_interface(adap->intf); if (ret < 0) goto out;
ret = usb_bulk_msg(adap->usb_dev, adap->tx_pipe, header, msg_len, &transferred, LJCA_WRITE_TIMEOUT_MS); if (ret < 0) goto out_put; if (transferred != msg_len) { ret = -EIO; goto out_put; }
if (ack) { ret = wait_for_completion_timeout(&adap->cmd_completion, timeout); if (!ret) { ret = -ETIMEDOUT; goto out_put; } } ret = adap->actual_length;
out_put: usb_autopm_put_interface(adap->intf);
out: spin_lock_irqsave(&adap->lock, flags); adap->ex_buf = NULL; adap->ex_buf_len = 0;
memset(header, 0, sizeof(*header)); spin_unlock_irqrestore(&adap->lock, flags);
mutex_unlock(&adap->mutex);
return ret;}
int ljca_transfer(struct ljca_client *client, u8 cmd, const u8 *obuf, u8 obuf_len, u8 *ibuf, u8 ibuf_len){ return ljca_send(client->adapter, client->type, cmd, obuf, obuf_len, ibuf, ibuf_len, true, LJCA_WRITE_ACK_TIMEOUT_MS);}EXPORT_SYMBOL_NS_GPL(ljca_transfer, "LJCA");
int ljca_transfer_noack(struct ljca_client *client, u8 cmd, const u8 *obuf, u8 obuf_len){ return ljca_send(client->adapter, client->type, cmd, obuf, obuf_len, NULL, 0, false, LJCA_WRITE_ACK_TIMEOUT_MS);}EXPORT_SYMBOL_NS_GPL(ljca_transfer_noack, "LJCA");
int ljca_register_event_cb(struct ljca_client *client, ljca_event_cb_t event_cb, void *context){ unsigned long flags;
if (!event_cb) return -EINVAL;
spin_lock_irqsave(&client->event_cb_lock, flags);
if (client->event_cb) { spin_unlock_irqrestore(&client->event_cb_lock, flags); return -EALREADY; }
client->event_cb = event_cb; client->context = context;
spin_unlock_irqrestore(&client->event_cb_lock, flags);
return 0;}EXPORT_SYMBOL_NS_GPL(ljca_register_event_cb, "LJCA");
void ljca_unregister_event_cb(struct ljca_client *client){ unsigned long flags;
spin_lock_irqsave(&client->event_cb_lock, flags);
client->event_cb = NULL; client->context = NULL;
spin_unlock_irqrestore(&client->event_cb_lock, flags);}EXPORT_SYMBOL_NS_GPL(ljca_unregister_event_cb, "LJCA");
static int ljca_match_device_ids(struct acpi_device *adev, void *data){ struct ljca_match_ids_walk_data *wd = data; const char *uid = acpi_device_uid(adev);
if (acpi_match_device_ids(adev, wd->ids)) return 0;
if (!wd->uid) goto match;
if (!uid) /* * Some DSDTs have only one ACPI companion for the two I2C * controllers and they don't set a UID at all (e.g. Dell * Latitude 9420). On these platforms only the first I2C * controller is used, so if a HID match has no UID we use * "0" as the UID and assign ACPI companion to the first * I2C controller. */ uid = "0"; else uid = strchr(uid, wd->uid[0]);
if (!uid || strcmp(uid, wd->uid)) return 0;
match: wd->adev = adev;
return 1;}
/* bind auxiliary device to acpi device */static void ljca_auxdev_acpi_bind(struct ljca_adapter *adap, struct auxiliary_device *auxdev, u64 adr, u8 id){ struct ljca_match_ids_walk_data wd = { 0 }; struct device *dev = adap->dev; struct acpi_device *parent; char uid[4];
parent = ACPI_COMPANION(dev); if (!parent) return;
/* * Currently LJCA hw doesn't use _ADR instead the shipped * platforms use _HID to distinguish children devices. */ switch (adr) { case LJCA_GPIO_ACPI_ADR: wd.ids = ljca_gpio_hids; break; case LJCA_I2C1_ACPI_ADR: case LJCA_I2C2_ACPI_ADR: snprintf(uid, sizeof(uid), "%d", id); wd.uid = uid; wd.ids = ljca_i2c_hids; break; case LJCA_SPI1_ACPI_ADR: case LJCA_SPI2_ACPI_ADR: wd.ids = ljca_spi_hids; break; default: dev_warn(dev, "unsupported _ADR\n"); return; }
acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd); if (wd.adev) { ACPI_COMPANION_SET(&auxdev->dev, wd.adev); return; }
parent = ACPI_COMPANION(dev->parent->parent); if (!parent) return;
acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd); if (wd.adev) ACPI_COMPANION_SET(&auxdev->dev, wd.adev);}
static void ljca_auxdev_release(struct device *dev){ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
kfree(auxdev->dev.platform_data);}
static int ljca_new_client_device(struct ljca_adapter *adap, u8 type, u8 id, char *name, void *data, u64 adr){ struct auxiliary_device *auxdev; struct ljca_client *client; int ret;
client = kzalloc_obj(*client); if (!client) { kfree(data); return -ENOMEM; }
client->type = type; client->id = id; client->adapter = adap; spin_lock_init(&client->event_cb_lock);
auxdev = &client->auxdev; auxdev->name = name; auxdev->id = id;
auxdev->dev.parent = adap->dev; auxdev->dev.platform_data = data; auxdev->dev.release = ljca_auxdev_release;
ret = auxiliary_device_init(auxdev); if (ret) { kfree(data); goto err_free; }
ljca_auxdev_acpi_bind(adap, auxdev, adr, id);
ret = auxiliary_device_add(auxdev); if (ret) goto err_uninit;
list_add_tail(&client->link, &adap->client_list);
return 0;
err_uninit: auxiliary_device_uninit(auxdev);
err_free: kfree(client);
return ret;}
static int ljca_enumerate_gpio(struct ljca_adapter *adap){ u32 valid_pin[LJCA_MAX_GPIO_NUM / BITS_PER_TYPE(u32)]; struct ljca_gpio_descriptor *desc; struct ljca_gpio_info *gpio_info; u8 buf[LJCA_MAX_PAYLOAD_SIZE]; int ret, gpio_num; unsigned int i;
ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_GPIO, NULL, 0, buf, sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); if (ret < 0) return ret;
/* check firmware response */ desc = (struct ljca_gpio_descriptor *)buf; if (ret != struct_size(desc, bank_desc, desc->bank_num)) return -EINVAL;
gpio_num = desc->pins_per_bank * desc->bank_num; if (gpio_num > LJCA_MAX_GPIO_NUM) return -EINVAL;
if (desc->bank_num > ARRAY_SIZE(valid_pin)) return -EINVAL;
/* construct platform data */ gpio_info = kzalloc_obj(*gpio_info); if (!gpio_info) return -ENOMEM; gpio_info->num = gpio_num;
for (i = 0; i < desc->bank_num; i++) valid_pin[i] = get_unaligned_le32(&desc->bank_desc[i].valid_pins); bitmap_from_arr32(gpio_info->valid_pin_map, valid_pin, gpio_num);
return ljca_new_client_device(adap, LJCA_CLIENT_GPIO, 0, "ljca-gpio", gpio_info, LJCA_GPIO_ACPI_ADR);}
static int ljca_enumerate_i2c(struct ljca_adapter *adap){ struct ljca_i2c_descriptor *desc; struct ljca_i2c_info *i2c_info; u8 buf[LJCA_MAX_PAYLOAD_SIZE]; unsigned int i; int ret;
ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_I2C, NULL, 0, buf, sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); if (ret < 0) return ret;
/* check firmware response */ desc = (struct ljca_i2c_descriptor *)buf; if (ret != struct_size(desc, info, desc->num)) return -EINVAL;
for (i = 0; i < desc->num; i++) { /* construct platform data */ i2c_info = kzalloc_obj(*i2c_info); if (!i2c_info) return -ENOMEM;
i2c_info->id = desc->info[i].id; i2c_info->capacity = desc->info[i].capacity; i2c_info->intr_pin = desc->info[i].intr_pin;
ret = ljca_new_client_device(adap, LJCA_CLIENT_I2C, i, "ljca-i2c", i2c_info, LJCA_I2C1_ACPI_ADR + i); if (ret) return ret; }
return 0;}
static int ljca_enumerate_spi(struct ljca_adapter *adap){ struct ljca_spi_descriptor *desc; struct ljca_spi_info *spi_info; u8 buf[LJCA_MAX_PAYLOAD_SIZE]; unsigned int i; int ret;
/* Not all LJCA chips implement SPI, a timeout reading the descriptors is normal */ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_SPI, NULL, 0, buf, sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); if (ret < 0) return (ret == -ETIMEDOUT) ? 0 : ret;
/* check firmware response */ desc = (struct ljca_spi_descriptor *)buf; if (ret != struct_size(desc, info, desc->num)) return -EINVAL;
for (i = 0; i < desc->num; i++) { /* construct platform data */ spi_info = kzalloc_obj(*spi_info); if (!spi_info) return -ENOMEM;
spi_info->id = desc->info[i].id; spi_info->capacity = desc->info[i].capacity;
ret = ljca_new_client_device(adap, LJCA_CLIENT_SPI, i, "ljca-spi", spi_info, LJCA_SPI1_ACPI_ADR + i); if (ret) return ret; }
return 0;}
static int ljca_reset_handshake(struct ljca_adapter *adap){ __le32 reset_id = cpu_to_le32(adap->reset_id); __le32 reset_id_ret = 0; int ret;
adap->reset_id++;
ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_RESET, (u8 *)&reset_id, sizeof(__le32), (u8 *)&reset_id_ret, sizeof(__le32), true, LJCA_WRITE_ACK_TIMEOUT_MS); if (ret < 0) return ret;
if (reset_id_ret != reset_id) return -EINVAL;
return 0;}
static int ljca_enumerate_clients(struct ljca_adapter *adap){ struct ljca_client *client, *next; int ret;
ret = ljca_reset_handshake(adap); if (ret) goto err_kill;
ret = ljca_enumerate_gpio(adap); if (ret) { dev_err(adap->dev, "enumerate GPIO error\n"); goto err_kill; }
ret = ljca_enumerate_i2c(adap); if (ret) { dev_err(adap->dev, "enumerate I2C error\n"); goto err_kill; }
ret = ljca_enumerate_spi(adap); if (ret) { dev_err(adap->dev, "enumerate SPI error\n"); goto err_kill; }
return 0;
err_kill: adap->disconnect = true;
usb_kill_urb(adap->rx_urb);
list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) { auxiliary_device_delete(&client->auxdev); auxiliary_device_uninit(&client->auxdev);
list_del_init(&client->link); kfree(client); }
return ret;}
static int ljca_probe(struct usb_interface *interface, const struct usb_device_id *id){ struct usb_device *usb_dev = interface_to_usbdev(interface); struct usb_host_interface *alt = interface->cur_altsetting; struct usb_endpoint_descriptor *ep_in, *ep_out; struct device *dev = &interface->dev; struct ljca_adapter *adap; int ret;
adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); if (!adap) return -ENOMEM;
/* separate tx buffer allocation for alignment */ adap->tx_buf = devm_kzalloc(dev, LJCA_MAX_PACKET_SIZE, GFP_KERNEL); if (!adap->tx_buf) return -ENOMEM; adap->tx_buf_len = LJCA_MAX_PACKET_SIZE;
mutex_init(&adap->mutex); spin_lock_init(&adap->lock); init_completion(&adap->cmd_completion); INIT_LIST_HEAD(&adap->client_list);
adap->intf = interface; adap->usb_dev = usb_dev; adap->dev = dev;
/* * find the first bulk in and out endpoints. * ignore any others. */ ret = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL); if (ret) { dev_err(dev, "bulk endpoints not found\n"); goto err_destroy_mutex; } adap->rx_pipe = usb_rcvbulkpipe(usb_dev, usb_endpoint_num(ep_in)); adap->tx_pipe = usb_sndbulkpipe(usb_dev, usb_endpoint_num(ep_out));
/* setup rx buffer */ adap->rx_len = usb_endpoint_maxp(ep_in); adap->rx_buf = devm_kzalloc(dev, adap->rx_len, GFP_KERNEL); if (!adap->rx_buf) { ret = -ENOMEM; goto err_destroy_mutex; }
/* alloc rx urb */ adap->rx_urb = usb_alloc_urb(0, GFP_KERNEL); if (!adap->rx_urb) { ret = -ENOMEM; goto err_destroy_mutex; } usb_fill_bulk_urb(adap->rx_urb, usb_dev, adap->rx_pipe, adap->rx_buf, adap->rx_len, ljca_recv, adap);
usb_set_intfdata(interface, adap);
/* submit rx urb before enumerate clients */ ret = usb_submit_urb(adap->rx_urb, GFP_KERNEL); if (ret) { dev_err(dev, "submit rx urb failed: %d\n", ret); goto err_free; }
ret = ljca_enumerate_clients(adap); if (ret) goto err_free;
/* * This works around problems with ov2740 initialization on some * Lenovo platforms. The autosuspend delay, has to be smaller than * the delay after setting the reset_gpio line in ov2740_resume(). * Otherwise the sensor randomly fails to initialize. */ pm_runtime_set_autosuspend_delay(&usb_dev->dev, 10);
usb_enable_autosuspend(usb_dev);
return 0;
err_free: usb_free_urb(adap->rx_urb);err_destroy_mutex: mutex_destroy(&adap->mutex);
return ret;}
static void ljca_disconnect(struct usb_interface *interface){ struct ljca_adapter *adap = usb_get_intfdata(interface); struct ljca_client *client, *next;
adap->disconnect = true;
usb_kill_urb(adap->rx_urb);
list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) { auxiliary_device_delete(&client->auxdev); auxiliary_device_uninit(&client->auxdev);
list_del_init(&client->link); kfree(client); }
usb_free_urb(adap->rx_urb);
mutex_destroy(&adap->mutex);}
static int ljca_suspend(struct usb_interface *interface, pm_message_t message){ struct ljca_adapter *adap = usb_get_intfdata(interface);
usb_kill_urb(adap->rx_urb);
return 0;}
static int ljca_resume(struct usb_interface *interface){ struct ljca_adapter *adap = usb_get_intfdata(interface);
return usb_submit_urb(adap->rx_urb, GFP_KERNEL);}
static const struct usb_device_id ljca_table[] = { { USB_DEVICE(0x8086, 0x0b63) }, { /* sentinel */ }};MODULE_DEVICE_TABLE(usb, ljca_table);
static struct usb_driver ljca_driver = { .name = "ljca", .id_table = ljca_table, .probe = ljca_probe, .disconnect = ljca_disconnect, .suspend = ljca_suspend, .resume = ljca_resume, .supports_autosuspend = 1,};module_usb_driver(ljca_driver);
MODULE_AUTHOR("Wentong Wu");MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");MODULE_AUTHOR("Lixu Zhang <lixu.zhang@intel.com>");MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB driver");MODULE_LICENSE("GPL");