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
|
/*
* otg.c - ChipIdea USB IP core OTG driver
*
* Copyright (C) 2013 Freescale Semiconductor, Inc.
*
* Author: Peter Chen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
/*
* This file mainly handles otgsc register, it may include OTG operation
* in the future.
*/
#include <linux/usb/otg.h>
#include <linux/usb/gadget.h>
#include <linux/usb/chipidea.h>
#include "ci.h"
#include "bits.h"
#include "otg.h"
/**
* ci_otg_role - pick role based on ID pin state
* @ci: the controller
*/
enum ci_role ci_otg_role(struct ci_hdrc *ci)
{
u32 sts = hw_read(ci, OP_OTGSC, ~0);
enum ci_role role = sts & OTGSC_ID
? CI_ROLE_GADGET
: CI_ROLE_HOST;
return role;
}
void ci_handle_vbus_change(struct ci_hdrc *ci)
{
u32 otgsc;
if (!ci->is_otg)
return;
otgsc = hw_read(ci, OP_OTGSC, ~0);
if (otgsc & OTGSC_BSV)
usb_gadget_vbus_connect(&ci->gadget);
else
usb_gadget_vbus_disconnect(&ci->gadget);
}
#define CI_VBUS_STABLE_TIMEOUT_MS 5000
static void ci_handle_id_switch(struct ci_hdrc *ci)
{
enum ci_role role = ci_otg_role(ci);
if (role != ci->role) {
dev_dbg(ci->dev, "switching from %s to %s\n",
ci_role(ci)->name, ci->roles[role]->name);
ci_role_stop(ci);
/* wait vbus lower than OTGSC_BSV */
hw_wait_reg(ci, OP_OTGSC, OTGSC_BSV, 0,
CI_VBUS_STABLE_TIMEOUT_MS);
ci_role_start(ci, role);
}
}
/**
* ci_otg_work - perform otg (vbus/id) event handle
* @work: work struct
*/
static void ci_otg_work(struct work_struct *work)
{
struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
if (ci->id_event) {
ci->id_event = false;
ci_handle_id_switch(ci);
} else if (ci->b_sess_valid_event) {
ci->b_sess_valid_event = false;
ci_handle_vbus_change(ci);
} else
dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
enable_irq(ci->irq);
}
/**
* ci_hdrc_otg_init - initialize otg struct
* ci: the controller
*/
int ci_hdrc_otg_init(struct ci_hdrc *ci)
{
INIT_WORK(&ci->work, ci_otg_work);
ci->wq = create_singlethread_workqueue("ci_otg");
if (!ci->wq) {
dev_err(ci->dev, "can't create workqueue\n");
return -ENODEV;
}
return 0;
}
/**
* ci_hdrc_otg_destroy - destroy otg struct
* ci: the controller
*/
void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
{
if (ci->wq) {
flush_workqueue(ci->wq);
destroy_workqueue(ci->wq);
}
ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS);
ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS);
}
|