Commit | Line | Data |
---|---|---|
de55d871 MH |
1 | /* |
2 | * drivers/extcon/extcon_class.c | |
3 | * | |
4 | * External connector (extcon) class driver | |
5 | * | |
6 | * Copyright (C) 2012 Samsung Electronics | |
7 | * Author: Donggeun Kim <dg77.kim@samsung.com> | |
8 | * Author: MyungJoo Ham <myungjoo.ham@samsung.com> | |
9 | * | |
10 | * based on android/drivers/switch/switch_class.c | |
11 | * Copyright (C) 2008 Google, Inc. | |
12 | * Author: Mike Lockwood <lockwood@android.com> | |
13 | * | |
14 | * This software is licensed under the terms of the GNU General Public | |
15 | * License version 2, as published by the Free Software Foundation, and | |
16 | * may be copied, distributed, and modified under those terms. | |
17 | * | |
18 | * This program is distributed in the hope that it will be useful, | |
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | * GNU General Public License for more details. | |
22 | * | |
23 | */ | |
24 | ||
25 | #include <linux/module.h> | |
26 | #include <linux/types.h> | |
27 | #include <linux/init.h> | |
28 | #include <linux/device.h> | |
29 | #include <linux/fs.h> | |
30 | #include <linux/err.h> | |
31 | #include <linux/extcon.h> | |
32 | #include <linux/slab.h> | |
33 | ||
34 | struct class *extcon_class; | |
35 | #if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) | |
36 | static struct class_compat *switch_class; | |
37 | #endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ | |
38 | ||
39 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, | |
40 | char *buf) | |
41 | { | |
42 | struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); | |
43 | ||
44 | if (edev->print_state) { | |
45 | int ret = edev->print_state(edev, buf); | |
46 | ||
47 | if (ret >= 0) | |
48 | return ret; | |
49 | /* Use default if failed */ | |
50 | } | |
51 | return sprintf(buf, "%u\n", edev->state); | |
52 | } | |
53 | ||
54 | static ssize_t name_show(struct device *dev, struct device_attribute *attr, | |
55 | char *buf) | |
56 | { | |
57 | struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); | |
58 | ||
59 | /* Optional callback given by the user */ | |
60 | if (edev->print_name) { | |
61 | int ret = edev->print_name(edev, buf); | |
62 | if (ret >= 0) | |
63 | return ret; | |
64 | } | |
65 | ||
66 | return sprintf(buf, "%s\n", dev_name(edev->dev)); | |
67 | } | |
68 | ||
69 | /** | |
70 | * extcon_set_state() - Set the cable attach states of the extcon device. | |
71 | * @edev: the extcon device | |
72 | * @state: new cable attach status for @edev | |
73 | * | |
74 | * Changing the state sends uevent with environment variable containing | |
75 | * the name of extcon device (envp[0]) and the state output (envp[1]). | |
76 | * Tizen uses this format for extcon device to get events from ports. | |
77 | * Android uses this format as well. | |
78 | */ | |
79 | void extcon_set_state(struct extcon_dev *edev, u32 state) | |
80 | { | |
81 | char name_buf[120]; | |
82 | char state_buf[120]; | |
83 | char *prop_buf; | |
84 | char *envp[3]; | |
85 | int env_offset = 0; | |
86 | int length; | |
87 | ||
88 | if (edev->state != state) { | |
89 | edev->state = state; | |
90 | ||
91 | prop_buf = (char *)get_zeroed_page(GFP_KERNEL); | |
92 | if (prop_buf) { | |
93 | length = name_show(edev->dev, NULL, prop_buf); | |
94 | if (length > 0) { | |
95 | if (prop_buf[length - 1] == '\n') | |
96 | prop_buf[length - 1] = 0; | |
97 | snprintf(name_buf, sizeof(name_buf), | |
98 | "NAME=%s", prop_buf); | |
99 | envp[env_offset++] = name_buf; | |
100 | } | |
101 | length = state_show(edev->dev, NULL, prop_buf); | |
102 | if (length > 0) { | |
103 | if (prop_buf[length - 1] == '\n') | |
104 | prop_buf[length - 1] = 0; | |
105 | snprintf(state_buf, sizeof(state_buf), | |
106 | "STATE=%s", prop_buf); | |
107 | envp[env_offset++] = state_buf; | |
108 | } | |
109 | envp[env_offset] = NULL; | |
110 | kobject_uevent_env(&edev->dev->kobj, KOBJ_CHANGE, envp); | |
111 | free_page((unsigned long)prop_buf); | |
112 | } else { | |
113 | dev_err(edev->dev, "out of memory in extcon_set_state\n"); | |
114 | kobject_uevent(&edev->dev->kobj, KOBJ_CHANGE); | |
115 | } | |
116 | } | |
117 | } | |
118 | EXPORT_SYMBOL_GPL(extcon_set_state); | |
119 | ||
120 | static struct device_attribute extcon_attrs[] = { | |
121 | __ATTR_RO(state), | |
122 | __ATTR_RO(name), | |
123 | }; | |
124 | ||
125 | static int create_extcon_class(void) | |
126 | { | |
127 | if (!extcon_class) { | |
128 | extcon_class = class_create(THIS_MODULE, "extcon"); | |
129 | if (IS_ERR(extcon_class)) | |
130 | return PTR_ERR(extcon_class); | |
131 | extcon_class->dev_attrs = extcon_attrs; | |
132 | ||
133 | #if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) | |
134 | switch_class = class_compat_register("switch"); | |
135 | if (WARN(!switch_class, "cannot allocate")) | |
136 | return -ENOMEM; | |
137 | #endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ | |
138 | } | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
143 | static void extcon_cleanup(struct extcon_dev *edev, bool skip) | |
144 | { | |
145 | if (!skip && get_device(edev->dev)) { | |
146 | device_unregister(edev->dev); | |
147 | put_device(edev->dev); | |
148 | } | |
149 | ||
150 | kfree(edev->dev); | |
151 | } | |
152 | ||
153 | static void extcon_dev_release(struct device *dev) | |
154 | { | |
155 | struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); | |
156 | ||
157 | extcon_cleanup(edev, true); | |
158 | } | |
159 | ||
160 | /** | |
161 | * extcon_dev_register() - Register a new extcon device | |
162 | * @edev : the new extcon device (should be allocated before calling) | |
163 | * @dev : the parent device for this extcon device. | |
164 | * | |
165 | * Among the members of edev struct, please set the "user initializing data" | |
166 | * in any case and set the "optional callbacks" if required. However, please | |
167 | * do not set the values of "internal data", which are initialized by | |
168 | * this function. | |
169 | */ | |
170 | int extcon_dev_register(struct extcon_dev *edev, struct device *dev) | |
171 | { | |
172 | int ret; | |
173 | ||
174 | if (!extcon_class) { | |
175 | ret = create_extcon_class(); | |
176 | if (ret < 0) | |
177 | return ret; | |
178 | } | |
179 | ||
180 | edev->dev = kzalloc(sizeof(struct device), GFP_KERNEL); | |
181 | edev->dev->parent = dev; | |
182 | edev->dev->class = extcon_class; | |
183 | edev->dev->release = extcon_dev_release; | |
184 | ||
185 | dev_set_name(edev->dev, edev->name ? edev->name : dev_name(dev)); | |
186 | ret = device_register(edev->dev); | |
187 | if (ret) { | |
188 | put_device(edev->dev); | |
189 | goto err_dev; | |
190 | } | |
191 | #if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) | |
192 | if (switch_class) | |
193 | ret = class_compat_create_link(switch_class, edev->dev, | |
194 | dev); | |
195 | #endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ | |
196 | ||
197 | dev_set_drvdata(edev->dev, edev); | |
198 | edev->state = 0; | |
199 | return 0; | |
200 | ||
201 | err_dev: | |
202 | kfree(edev->dev); | |
203 | return ret; | |
204 | } | |
205 | EXPORT_SYMBOL_GPL(extcon_dev_register); | |
206 | ||
207 | /** | |
208 | * extcon_dev_unregister() - Unregister the extcon device. | |
209 | * @edev: the extcon device instance to be unregitered. | |
210 | * | |
211 | * Note that this does not call kfree(edev) because edev was not allocated | |
212 | * by this class. | |
213 | */ | |
214 | void extcon_dev_unregister(struct extcon_dev *edev) | |
215 | { | |
216 | extcon_cleanup(edev, false); | |
217 | } | |
218 | EXPORT_SYMBOL_GPL(extcon_dev_unregister); | |
219 | ||
220 | static int __init extcon_class_init(void) | |
221 | { | |
222 | return create_extcon_class(); | |
223 | } | |
224 | module_init(extcon_class_init); | |
225 | ||
226 | static void __exit extcon_class_exit(void) | |
227 | { | |
228 | class_destroy(extcon_class); | |
229 | } | |
230 | module_exit(extcon_class_exit); | |
231 | ||
232 | MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); | |
233 | MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); | |
234 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | |
235 | MODULE_DESCRIPTION("External connector (extcon) class driver"); | |
236 | MODULE_LICENSE("GPL"); |