-
Notifications
You must be signed in to change notification settings - Fork 30
/
fl2000_interrupt.c
152 lines (129 loc) · 4.36 KB
/
fl2000_interrupt.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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
// SPDX-License-Identifier: GPL-2.0
/*
* (C) Copyright 2017, Fresco Logic, Incorporated.
* (C) Copyright 2018-2020, Artem Mygaiev
*/
#include "fl2000.h"
#define INTR_BUFSIZE 1
struct fl2000_intr {
struct usb_device *usb_dev;
struct drm_device *drm;
u8 poll_interval;
struct urb *urb;
u8 *buf;
dma_addr_t transfer_dma;
struct work_struct work;
struct workqueue_struct *work_queue;
};
static void fl2000_intr_work(struct work_struct *work)
{
int event;
struct fl2000_intr *intr = container_of(work, struct fl2000_intr, work);
event = fl2000_check_interrupt(intr->usb_dev);
if (event)
drm_kms_helper_hotplug_event(intr->drm);
}
static void fl2000_intr_release(struct device *dev, void *res)
{
struct fl2000_intr *intr = res;
struct usb_device *usb_dev = to_usb_device(dev);
usb_poison_urb(intr->urb);
cancel_work_sync(&intr->work);
destroy_workqueue(intr->work_queue);
usb_free_coherent(usb_dev, INTR_BUFSIZE, intr->buf, intr->transfer_dma);
usb_free_urb(intr->urb);
}
static void fl2000_intr_completion(struct urb *urb)
{
int ret;
struct usb_device *usb_dev = urb->dev;
struct fl2000_intr *intr = urb->context;
ret = fl2000_urb_status(usb_dev, urb->status, urb->pipe);
if (ret) {
dev_err(&usb_dev->dev, "Stopping interrupts");
return;
}
/* This possibly involves reading I2C registers, etc. so better to schedule a work queue */
queue_work(intr->work_queue, &intr->work);
/* For interrupt URBs, as part of successful URB submission urb->interval is modified to
* reflect the actual transfer period used, so we need to restore it
*/
urb->interval = intr->poll_interval;
urb->start_frame = -1;
/* Restart urb */
ret = fl2000_submit_urb(urb);
if (ret) {
dev_err(&usb_dev->dev, "URB submission failed (%d)", ret);
/* TODO: Signal fault to system and start shutdown of usb_dev */
}
}
/**
* fl2000_intr_create() - interrupt processing context creation
* @interface: USB interrupt transfers interface
*
* This function is called only on Interrupt interface probe
*
* Function initiates USB Interrupt transfers
*
* Return: Operation result
*/
struct fl2000_intr *fl2000_intr_create(struct usb_device *usb_dev, struct drm_device *drm)
{
int ret;
struct fl2000_intr *intr;
struct usb_endpoint_descriptor *desc;
struct usb_interface *interface = usb_ifnum_to_if(usb_dev, FL2000_USBIF_INTERRUPT);
/* There's only one altsetting (#0) and one endpoint (#3) in the interrupt interface (#2)
* but lets try and "find" it anyway
*/
ret = usb_find_int_in_endpoint(interface->cur_altsetting, &desc);
if (ret) {
dev_err(&usb_dev->dev, "Cannot find interrupt endpoint");
return ERR_PTR(ret);
}
intr = devres_alloc(&fl2000_intr_release, sizeof(*intr), GFP_KERNEL);
if (!intr) {
dev_err(&usb_dev->dev, "Cannot allocate interrupt private structure");
return ERR_PTR(-ENOMEM);
}
devres_add(&usb_dev->dev, intr);
intr->poll_interval = desc->bInterval;
intr->usb_dev = usb_dev;
intr->drm = drm;
INIT_WORK(&intr->work, &fl2000_intr_work);
intr->urb = usb_alloc_urb(0, GFP_ATOMIC);
if (!intr->urb) {
dev_err(&usb_dev->dev, "Allocate interrupt URB failed");
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
return ERR_PTR(-ENOMEM);
}
intr->buf = usb_alloc_coherent(usb_dev, INTR_BUFSIZE, GFP_KERNEL, &intr->transfer_dma);
if (!intr->buf) {
dev_err(&usb_dev->dev, "Cannot allocate interrupt data");
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
return ERR_PTR(-ENOMEM);
}
intr->work_queue = create_workqueue("fl2000_interrupt");
if (!intr->work_queue) {
dev_err(&usb_dev->dev, "Create interrupt workqueue failed");
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
return ERR_PTR(-ENOMEM);
}
/* Interrupt URB configuration is static, including allocated buffer */
usb_fill_int_urb(intr->urb, usb_dev, usb_rcvintpipe(usb_dev, 3), intr->buf, INTR_BUFSIZE,
fl2000_intr_completion, intr, intr->poll_interval);
intr->urb->transfer_dma = intr->transfer_dma;
intr->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* use urb->transfer_dma */
/* Start checking for interrupts */
ret = usb_submit_urb(intr->urb, GFP_KERNEL);
if (ret) {
dev_err(&usb_dev->dev, "URB submission failed");
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
return ERR_PTR(ret);
}
return intr;
}
void fl2000_intr_destroy(struct usb_device *usb_dev)
{
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
}