17fa60654SVaibhav Hiremath /*
27fa60654SVaibhav Hiremath  * Arche Platform driver to enable Unipro link.
37fa60654SVaibhav Hiremath  *
47fa60654SVaibhav Hiremath  * Copyright 2014-2015 Google Inc.
57fa60654SVaibhav Hiremath  * Copyright 2014-2015 Linaro Ltd.
67fa60654SVaibhav Hiremath  *
77fa60654SVaibhav Hiremath  * Released under the GPLv2 only.
87fa60654SVaibhav Hiremath  */
97fa60654SVaibhav Hiremath 
107fa60654SVaibhav Hiremath #include <linux/clk.h>
11a463fc15SVaibhav Hiremath #include <linux/delay.h>
123b858df0SViresh Kumar #include <linux/gpio.h>
133b858df0SViresh Kumar #include <linux/init.h>
143b858df0SViresh Kumar #include <linux/module.h>
157fa60654SVaibhav Hiremath #include <linux/of_gpio.h>
163b858df0SViresh Kumar #include <linux/of_platform.h>
177fa60654SVaibhav Hiremath #include <linux/pinctrl/consumer.h>
183b858df0SViresh Kumar #include <linux/platform_device.h>
193b858df0SViresh Kumar #include <linux/pm.h>
20f760bbfbSVaibhav Hiremath #include <linux/interrupt.h>
21f760bbfbSVaibhav Hiremath #include <linux/irq.h>
22f760bbfbSVaibhav Hiremath #include <linux/time.h>
231e5dd1f8SGreg Kroah-Hartman #include "arche_platform.h"
247fa60654SVaibhav Hiremath 
25ad4d3f95SVaibhav Hiremath #include <linux/usb/usb3613.h>
26ad4d3f95SVaibhav Hiremath 
27f760bbfbSVaibhav Hiremath #define WD_COLDBOOT_PULSE_WIDTH_MS	30
28f760bbfbSVaibhav Hiremath 
29685353c1SVaibhav Hiremath enum svc_wakedetect_state {
30685353c1SVaibhav Hiremath 	WD_STATE_IDLE,			/* Default state = pulled high/low */
31685353c1SVaibhav Hiremath 	WD_STATE_BOOT_INIT,		/* WD = falling edge (low) */
32685353c1SVaibhav Hiremath 	WD_STATE_COLDBOOT_TRIG,		/* WD = rising edge (high), > 30msec */
33685353c1SVaibhav Hiremath 	WD_STATE_STANDBYBOOT_TRIG,	/* As of now not used ?? */
34685353c1SVaibhav Hiremath 	WD_STATE_COLDBOOT_START,	/* Cold boot process started */
35685353c1SVaibhav Hiremath 	WD_STATE_STANDBYBOOT_START,	/* Not used */
36685353c1SVaibhav Hiremath };
37685353c1SVaibhav Hiremath 
387fa60654SVaibhav Hiremath struct arche_platform_drvdata {
397fa60654SVaibhav Hiremath 	/* Control GPIO signals to and from AP <=> SVC */
407fa60654SVaibhav Hiremath 	int svc_reset_gpio;
417fa60654SVaibhav Hiremath 	bool is_reset_act_hi;
427fa60654SVaibhav Hiremath 	int svc_sysboot_gpio;
43a463fc15SVaibhav Hiremath 	int wake_detect_gpio; /* bi-dir,maps to WAKE_MOD & WAKE_FRAME signals */
447fa60654SVaibhav Hiremath 
45e74d04a5SVaibhav Hiremath 	enum arche_platform_state state;
46e74d04a5SVaibhav Hiremath 
477fa60654SVaibhav Hiremath 	unsigned int svc_refclk_req;
487fa60654SVaibhav Hiremath 	struct clk *svc_ref_clk;
497fa60654SVaibhav Hiremath 
507fa60654SVaibhav Hiremath 	struct pinctrl *pinctrl;
517fa60654SVaibhav Hiremath 	struct pinctrl_state *pin_default;
527fa60654SVaibhav Hiremath 
537fa60654SVaibhav Hiremath 	int num_apbs;
54a463fc15SVaibhav Hiremath 
55a463fc15SVaibhav Hiremath 	struct delayed_work delayed_work;
56685353c1SVaibhav Hiremath 	enum svc_wakedetect_state wake_detect_state;
57f760bbfbSVaibhav Hiremath 	int wake_detect_irq;
58f760bbfbSVaibhav Hiremath 	spinlock_t lock;
59f760bbfbSVaibhav Hiremath 	unsigned long wake_detect_start;
60685353c1SVaibhav Hiremath 
61a463fc15SVaibhav Hiremath 	struct device *dev;
627fa60654SVaibhav Hiremath };
637fa60654SVaibhav Hiremath 
647fa60654SVaibhav Hiremath static inline void svc_reset_onoff(unsigned int gpio, bool onoff)
657fa60654SVaibhav Hiremath {
667fa60654SVaibhav Hiremath 	gpio_set_value(gpio, onoff);
677fa60654SVaibhav Hiremath }
687fa60654SVaibhav Hiremath 
69f760bbfbSVaibhav Hiremath static int apb_cold_boot(struct device *dev, void *data)
70f760bbfbSVaibhav Hiremath {
71f760bbfbSVaibhav Hiremath 	int ret;
72f760bbfbSVaibhav Hiremath 
73f760bbfbSVaibhav Hiremath 	ret = apb_ctrl_coldboot(dev);
74f760bbfbSVaibhav Hiremath 	if (ret)
75f760bbfbSVaibhav Hiremath 		dev_warn(dev, "failed to coldboot\n");
76f760bbfbSVaibhav Hiremath 
77f760bbfbSVaibhav Hiremath 	/*Child nodes are independent, so do not exit coldboot operation */
78f760bbfbSVaibhav Hiremath 	return 0;
79f760bbfbSVaibhav Hiremath }
80f760bbfbSVaibhav Hiremath 
81fd60ac58SVaibhav Hiremath static int apb_fw_flashing_state(struct device *dev, void *data)
82fd60ac58SVaibhav Hiremath {
83fd60ac58SVaibhav Hiremath 	int ret;
84fd60ac58SVaibhav Hiremath 
85fd60ac58SVaibhav Hiremath 	ret = apb_ctrl_fw_flashing(dev);
86fd60ac58SVaibhav Hiremath 	if (ret)
87fd60ac58SVaibhav Hiremath 		dev_warn(dev, "failed to switch to fw flashing state\n");
88fd60ac58SVaibhav Hiremath 
89fd60ac58SVaibhav Hiremath 	/*Child nodes are independent, so do not exit coldboot operation */
90fd60ac58SVaibhav Hiremath 	return 0;
91fd60ac58SVaibhav Hiremath }
92fd60ac58SVaibhav Hiremath 
93fd60ac58SVaibhav Hiremath static int apb_poweroff(struct device *dev, void *data)
94fd60ac58SVaibhav Hiremath {
95fd60ac58SVaibhav Hiremath 	apb_ctrl_poweroff(dev);
96fd60ac58SVaibhav Hiremath 
97fd60ac58SVaibhav Hiremath 	return 0;
98fd60ac58SVaibhav Hiremath }
99fd60ac58SVaibhav Hiremath 
100a463fc15SVaibhav Hiremath /**
101db5a3bcaSVaibhav Hiremath  * hub_conf_delayed_work - Configures USB3613 device to HUB mode
102db5a3bcaSVaibhav Hiremath  *
103db5a3bcaSVaibhav Hiremath  * The idea here is to split the APB coldboot operation with slow HUB configuration,
104db5a3bcaSVaibhav Hiremath  * so that driver response to wake/detect event can be met.
105db5a3bcaSVaibhav Hiremath  * So expectation is, once code reaches here, means initial unipro linkup
106db5a3bcaSVaibhav Hiremath  * between APB<->Switch was successful, so now just take it to AP.
107a463fc15SVaibhav Hiremath  */
108db5a3bcaSVaibhav Hiremath static void hub_conf_delayed_work(struct work_struct *work)
109a463fc15SVaibhav Hiremath {
110a463fc15SVaibhav Hiremath 	struct arche_platform_drvdata *arche_pdata =
111a463fc15SVaibhav Hiremath 		container_of(work, struct arche_platform_drvdata, delayed_work.work);
112ad4d3f95SVaibhav Hiremath 
113ad4d3f95SVaibhav Hiremath 	/* Enable HUB3613 into HUB mode. */
114ad4d3f95SVaibhav Hiremath 	if (usb3613_hub_mode_ctrl(true))
115ad4d3f95SVaibhav Hiremath 		dev_warn(arche_pdata->dev, "failed to control hub device\n");
116a463fc15SVaibhav Hiremath }
117a463fc15SVaibhav Hiremath 
11816fe18caSVaibhav Hiremath static void assert_wakedetect(struct arche_platform_drvdata *arche_pdata)
11916fe18caSVaibhav Hiremath {
12016fe18caSVaibhav Hiremath 	/* Assert wake/detect = Detect event from AP */
12116fe18caSVaibhav Hiremath 	gpio_direction_output(arche_pdata->wake_detect_gpio, 1);
12216fe18caSVaibhav Hiremath 
12316fe18caSVaibhav Hiremath 	/* Enable interrupt here, to read event back from SVC */
12416fe18caSVaibhav Hiremath 	gpio_direction_input(arche_pdata->wake_detect_gpio);
12516fe18caSVaibhav Hiremath 	enable_irq(arche_pdata->wake_detect_irq);
12616fe18caSVaibhav Hiremath }
12716fe18caSVaibhav Hiremath 
128f760bbfbSVaibhav Hiremath static irqreturn_t arche_platform_wd_irq_thread(int irq, void *devid)
129f760bbfbSVaibhav Hiremath {
130f760bbfbSVaibhav Hiremath 	struct arche_platform_drvdata *arche_pdata = devid;
131f760bbfbSVaibhav Hiremath 	unsigned long flags;
132f760bbfbSVaibhav Hiremath 
133f760bbfbSVaibhav Hiremath 	spin_lock_irqsave(&arche_pdata->lock, flags);
134f760bbfbSVaibhav Hiremath 	if (arche_pdata->wake_detect_state != WD_STATE_COLDBOOT_TRIG) {
135f760bbfbSVaibhav Hiremath 		/* Something is wrong */
136f760bbfbSVaibhav Hiremath 		spin_unlock_irqrestore(&arche_pdata->lock, flags);
137f760bbfbSVaibhav Hiremath 		return IRQ_HANDLED;
138f760bbfbSVaibhav Hiremath 	}
139f760bbfbSVaibhav Hiremath 
140f760bbfbSVaibhav Hiremath 	arche_pdata->wake_detect_state = WD_STATE_COLDBOOT_START;
141f760bbfbSVaibhav Hiremath 	spin_unlock_irqrestore(&arche_pdata->lock, flags);
142f760bbfbSVaibhav Hiremath 
143f760bbfbSVaibhav Hiremath 	/* Bring APB out of reset: cold boot sequence */
144f760bbfbSVaibhav Hiremath 	device_for_each_child(arche_pdata->dev, NULL, apb_cold_boot);
145f760bbfbSVaibhav Hiremath 
146f760bbfbSVaibhav Hiremath 	spin_lock_irqsave(&arche_pdata->lock, flags);
147f760bbfbSVaibhav Hiremath 	/* USB HUB configuration */
148f760bbfbSVaibhav Hiremath 	schedule_delayed_work(&arche_pdata->delayed_work, msecs_to_jiffies(2000));
149f760bbfbSVaibhav Hiremath 	arche_pdata->wake_detect_state = WD_STATE_IDLE;
150f760bbfbSVaibhav Hiremath 	spin_unlock_irqrestore(&arche_pdata->lock, flags);
151f760bbfbSVaibhav Hiremath 
152f760bbfbSVaibhav Hiremath 	return IRQ_HANDLED;
153f760bbfbSVaibhav Hiremath }
154f760bbfbSVaibhav Hiremath 
155f760bbfbSVaibhav Hiremath static irqreturn_t arche_platform_wd_irq(int irq, void *devid)
156f760bbfbSVaibhav Hiremath {
157f760bbfbSVaibhav Hiremath 	struct arche_platform_drvdata *arche_pdata = devid;
158f760bbfbSVaibhav Hiremath 	unsigned long flags;
159f760bbfbSVaibhav Hiremath 
160f760bbfbSVaibhav Hiremath 	spin_lock_irqsave(&arche_pdata->lock, flags);
161f760bbfbSVaibhav Hiremath 
162f760bbfbSVaibhav Hiremath 	if (gpio_get_value(arche_pdata->wake_detect_gpio)) {
163f760bbfbSVaibhav Hiremath 		/* wake/detect rising */
164f760bbfbSVaibhav Hiremath 
165f760bbfbSVaibhav Hiremath 		/*
166f760bbfbSVaibhav Hiremath 		 * If wake/detect line goes high after low, within less than
167f760bbfbSVaibhav Hiremath 		 * 30msec, then standby boot sequence is initiated, which is not
168f760bbfbSVaibhav Hiremath 		 * supported/implemented as of now. So ignore it.
169f760bbfbSVaibhav Hiremath 		 */
170f760bbfbSVaibhav Hiremath 		if (arche_pdata->wake_detect_state == WD_STATE_BOOT_INIT) {
171f760bbfbSVaibhav Hiremath 			if (time_before(jiffies,
172f760bbfbSVaibhav Hiremath 					arche_pdata->wake_detect_start +
173f760bbfbSVaibhav Hiremath 					msecs_to_jiffies(WD_COLDBOOT_PULSE_WIDTH_MS))) {
174f760bbfbSVaibhav Hiremath 				/* No harm with cancellation, even if not pending */
175f760bbfbSVaibhav Hiremath 				cancel_delayed_work(&arche_pdata->delayed_work);
176f760bbfbSVaibhav Hiremath 				arche_pdata->wake_detect_state = WD_STATE_IDLE;
177f760bbfbSVaibhav Hiremath 			} else {
178f760bbfbSVaibhav Hiremath 				/* Check we are not in middle of irq thread already */
179f760bbfbSVaibhav Hiremath 				if (arche_pdata->wake_detect_state !=
180f760bbfbSVaibhav Hiremath 						WD_STATE_COLDBOOT_START) {
181f760bbfbSVaibhav Hiremath 					arche_pdata->wake_detect_state =
182f760bbfbSVaibhav Hiremath 						WD_STATE_COLDBOOT_TRIG;
183f760bbfbSVaibhav Hiremath 					spin_unlock_irqrestore(&arche_pdata->lock, flags);
184f760bbfbSVaibhav Hiremath 					return IRQ_WAKE_THREAD;
185f760bbfbSVaibhav Hiremath 				}
186f760bbfbSVaibhav Hiremath 			}
187f760bbfbSVaibhav Hiremath 		}
188f760bbfbSVaibhav Hiremath 	} else {
189f760bbfbSVaibhav Hiremath 		/* wake/detect falling */
190f760bbfbSVaibhav Hiremath 		if (arche_pdata->wake_detect_state == WD_STATE_IDLE) {
191f760bbfbSVaibhav Hiremath 			arche_pdata->wake_detect_start = jiffies;
192f760bbfbSVaibhav Hiremath 			/* No harm with cancellation even if it is not pending*/
193f760bbfbSVaibhav Hiremath 			cancel_delayed_work(&arche_pdata->delayed_work);
194f760bbfbSVaibhav Hiremath 			/*
195f760bbfbSVaibhav Hiremath 			 * In the begining, when wake/detect goes low (first time), we assume
196f760bbfbSVaibhav Hiremath 			 * it is meant for coldboot and set the flag. If wake/detect line stays low
197f760bbfbSVaibhav Hiremath 			 * beyond 30msec, then it is coldboot else fallback to standby boot.
198f760bbfbSVaibhav Hiremath 			 */
199f760bbfbSVaibhav Hiremath 			arche_pdata->wake_detect_state = WD_STATE_BOOT_INIT;
200f760bbfbSVaibhav Hiremath 		}
201f760bbfbSVaibhav Hiremath 	}
202f760bbfbSVaibhav Hiremath 
203f760bbfbSVaibhav Hiremath 	spin_unlock_irqrestore(&arche_pdata->lock, flags);
204f760bbfbSVaibhav Hiremath 
205f760bbfbSVaibhav Hiremath 	return IRQ_HANDLED;
206f760bbfbSVaibhav Hiremath }
207f760bbfbSVaibhav Hiremath 
208758ca99dSVaibhav Hiremath static int arche_platform_coldboot_seq(struct arche_platform_drvdata *arche_pdata)
209758ca99dSVaibhav Hiremath {
210758ca99dSVaibhav Hiremath 	int ret;
211758ca99dSVaibhav Hiremath 
212599159b6SVaibhav Hiremath 	if (arche_pdata->state == ARCHE_PLATFORM_STATE_ACTIVE)
213599159b6SVaibhav Hiremath 		return 0;
214599159b6SVaibhav Hiremath 
215758ca99dSVaibhav Hiremath 	dev_info(arche_pdata->dev, "Booting from cold boot state\n");
216758ca99dSVaibhav Hiremath 
217758ca99dSVaibhav Hiremath 	svc_reset_onoff(arche_pdata->svc_reset_gpio,
218758ca99dSVaibhav Hiremath 			arche_pdata->is_reset_act_hi);
219758ca99dSVaibhav Hiremath 
220758ca99dSVaibhav Hiremath 	gpio_set_value(arche_pdata->svc_sysboot_gpio, 0);
221758ca99dSVaibhav Hiremath 	usleep_range(100, 200);
222758ca99dSVaibhav Hiremath 
223758ca99dSVaibhav Hiremath 	ret = clk_prepare_enable(arche_pdata->svc_ref_clk);
224758ca99dSVaibhav Hiremath 	if (ret) {
225758ca99dSVaibhav Hiremath 		dev_err(arche_pdata->dev, "failed to enable svc_ref_clk: %d\n",
226758ca99dSVaibhav Hiremath 				ret);
227758ca99dSVaibhav Hiremath 		return ret;
228758ca99dSVaibhav Hiremath 	}
229758ca99dSVaibhav Hiremath 
230758ca99dSVaibhav Hiremath 	/* bring SVC out of reset */
231758ca99dSVaibhav Hiremath 	svc_reset_onoff(arche_pdata->svc_reset_gpio,
232758ca99dSVaibhav Hiremath 			!arche_pdata->is_reset_act_hi);
233758ca99dSVaibhav Hiremath 
234e74d04a5SVaibhav Hiremath 	arche_pdata->state = ARCHE_PLATFORM_STATE_ACTIVE;
235e74d04a5SVaibhav Hiremath 
236758ca99dSVaibhav Hiremath 	return 0;
237758ca99dSVaibhav Hiremath }
238758ca99dSVaibhav Hiremath 
2397691fed2SVaibhav Hiremath static void arche_platform_fw_flashing_seq(struct arche_platform_drvdata *arche_pdata)
2407691fed2SVaibhav Hiremath {
241599159b6SVaibhav Hiremath 	if (arche_pdata->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
242599159b6SVaibhav Hiremath 		return;
243599159b6SVaibhav Hiremath 
2447691fed2SVaibhav Hiremath 	dev_info(arche_pdata->dev, "Switching to FW flashing state\n");
2457691fed2SVaibhav Hiremath 
2467691fed2SVaibhav Hiremath 	svc_reset_onoff(arche_pdata->svc_reset_gpio,
2477691fed2SVaibhav Hiremath 			arche_pdata->is_reset_act_hi);
2487691fed2SVaibhav Hiremath 
2497691fed2SVaibhav Hiremath 	gpio_set_value(arche_pdata->svc_sysboot_gpio, 1);
2507691fed2SVaibhav Hiremath 
2517691fed2SVaibhav Hiremath 	usleep_range(100, 200);
2527691fed2SVaibhav Hiremath 	svc_reset_onoff(arche_pdata->svc_reset_gpio,
2537691fed2SVaibhav Hiremath 			!arche_pdata->is_reset_act_hi);
2547691fed2SVaibhav Hiremath 
2557691fed2SVaibhav Hiremath 	arche_pdata->state = ARCHE_PLATFORM_STATE_FW_FLASHING;
2567691fed2SVaibhav Hiremath 
2577691fed2SVaibhav Hiremath }
2587691fed2SVaibhav Hiremath 
2595993e2bfSVaibhav Hiremath static void arche_platform_poweroff_seq(struct arche_platform_drvdata *arche_pdata)
2607fa60654SVaibhav Hiremath {
261f760bbfbSVaibhav Hiremath 	unsigned long flags;
262f760bbfbSVaibhav Hiremath 
263599159b6SVaibhav Hiremath 	if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
264599159b6SVaibhav Hiremath 		return;
265599159b6SVaibhav Hiremath 
26625847ee7SVaibhav Hiremath 	/* If in fw_flashing mode, then no need to repeate things again */
26725847ee7SVaibhav Hiremath 	if (arche_pdata->state != ARCHE_PLATFORM_STATE_FW_FLASHING) {
268d2320b2dSVaibhav Hiremath 		disable_irq(arche_pdata->wake_detect_irq);
269b4c95fcaSVaibhav Hiremath 		/* Send disconnect/detach event to SVC */
2700786212dSVaibhav Hiremath 		gpio_direction_output(arche_pdata->wake_detect_gpio, 0);
271b4c95fcaSVaibhav Hiremath 		usleep_range(100, 200);
272f760bbfbSVaibhav Hiremath 		spin_lock_irqsave(&arche_pdata->lock, flags);
273685353c1SVaibhav Hiremath 		arche_pdata->wake_detect_state = WD_STATE_IDLE;
274f760bbfbSVaibhav Hiremath 		spin_unlock_irqrestore(&arche_pdata->lock, flags);
275b4c95fcaSVaibhav Hiremath 
276d8b16338SVaibhav Hiremath 		clk_disable_unprepare(arche_pdata->svc_ref_clk);
27725847ee7SVaibhav Hiremath 	}
27825847ee7SVaibhav Hiremath 
2797fa60654SVaibhav Hiremath 	/* As part of exit, put APB back in reset state */
2807fa60654SVaibhav Hiremath 	svc_reset_onoff(arche_pdata->svc_reset_gpio,
2817fa60654SVaibhav Hiremath 			arche_pdata->is_reset_act_hi);
282e74d04a5SVaibhav Hiremath 
283e74d04a5SVaibhav Hiremath 	arche_pdata->state = ARCHE_PLATFORM_STATE_OFF;
2847fa60654SVaibhav Hiremath }
2857fa60654SVaibhav Hiremath 
2862923c58eSVaibhav Hiremath static ssize_t state_store(struct device *dev,
2872923c58eSVaibhav Hiremath 		struct device_attribute *attr, const char *buf, size_t count)
2882923c58eSVaibhav Hiremath {
2892923c58eSVaibhav Hiremath 	struct platform_device *pdev = to_platform_device(dev);
2902923c58eSVaibhav Hiremath 	struct arche_platform_drvdata *arche_pdata = platform_get_drvdata(pdev);
2912923c58eSVaibhav Hiremath 	int ret = 0;
2922923c58eSVaibhav Hiremath 
2932923c58eSVaibhav Hiremath 	if (sysfs_streq(buf, "off")) {
2942923c58eSVaibhav Hiremath 		if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
2952923c58eSVaibhav Hiremath 			return count;
2962923c58eSVaibhav Hiremath 
297fd60ac58SVaibhav Hiremath 		/*  If SVC goes down, bring down APB's as well */
298fd60ac58SVaibhav Hiremath 		device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
299fd60ac58SVaibhav Hiremath 
3002923c58eSVaibhav Hiremath 		arche_platform_poweroff_seq(arche_pdata);
301ad4d3f95SVaibhav Hiremath 
302ad4d3f95SVaibhav Hiremath 		ret = usb3613_hub_mode_ctrl(false);
303ad4d3f95SVaibhav Hiremath 		if (ret)
304ad4d3f95SVaibhav Hiremath 			dev_warn(arche_pdata->dev, "failed to control hub device\n");
305ad4d3f95SVaibhav Hiremath 			/* TODO: Should we do anything more here ?? */
3062923c58eSVaibhav Hiremath 	} else if (sysfs_streq(buf, "active")) {
3072923c58eSVaibhav Hiremath 		if (arche_pdata->state == ARCHE_PLATFORM_STATE_ACTIVE)
3082923c58eSVaibhav Hiremath 			return count;
3092923c58eSVaibhav Hiremath 
3102923c58eSVaibhav Hiremath 		ret = arche_platform_coldboot_seq(arche_pdata);
31116fe18caSVaibhav Hiremath 
31216fe18caSVaibhav Hiremath 		assert_wakedetect(arche_pdata);
3132923c58eSVaibhav Hiremath 	} else if (sysfs_streq(buf, "standby")) {
3142923c58eSVaibhav Hiremath 		if (arche_pdata->state == ARCHE_PLATFORM_STATE_STANDBY)
3152923c58eSVaibhav Hiremath 			return count;
3162923c58eSVaibhav Hiremath 
3172923c58eSVaibhav Hiremath 		dev_warn(arche_pdata->dev, "standby state not supported\n");
3187691fed2SVaibhav Hiremath 	} else if (sysfs_streq(buf, "fw_flashing")) {
3197691fed2SVaibhav Hiremath 		if (arche_pdata->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
3207691fed2SVaibhav Hiremath 			return count;
3217691fed2SVaibhav Hiremath 
3227691fed2SVaibhav Hiremath 		/* First we want to make sure we power off everything
3237691fed2SVaibhav Hiremath 		 * and then enter FW flashing state */
324fd60ac58SVaibhav Hiremath 		device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
325fd60ac58SVaibhav Hiremath 
3267691fed2SVaibhav Hiremath 		arche_platform_poweroff_seq(arche_pdata);
327fd60ac58SVaibhav Hiremath 
328ad4d3f95SVaibhav Hiremath 		ret = usb3613_hub_mode_ctrl(false);
329ad4d3f95SVaibhav Hiremath 		if (ret)
330ad4d3f95SVaibhav Hiremath 			dev_warn(arche_pdata->dev, "failed to control hub device\n");
331ad4d3f95SVaibhav Hiremath 			/* TODO: Should we do anything more here ?? */
332ad4d3f95SVaibhav Hiremath 
3337691fed2SVaibhav Hiremath 		arche_platform_fw_flashing_seq(arche_pdata);
334fd60ac58SVaibhav Hiremath 
335fd60ac58SVaibhav Hiremath 		device_for_each_child(arche_pdata->dev, NULL, apb_fw_flashing_state);
3362923c58eSVaibhav Hiremath 	} else {
3372923c58eSVaibhav Hiremath 		dev_err(arche_pdata->dev, "unknown state\n");
3382923c58eSVaibhav Hiremath 		ret = -EINVAL;
3392923c58eSVaibhav Hiremath 	}
3402923c58eSVaibhav Hiremath 
3412923c58eSVaibhav Hiremath 	return ret ? ret : count;
3422923c58eSVaibhav Hiremath }
3432923c58eSVaibhav Hiremath 
3442923c58eSVaibhav Hiremath static ssize_t state_show(struct device *dev,
3452923c58eSVaibhav Hiremath 		struct device_attribute *attr, char *buf)
3462923c58eSVaibhav Hiremath {
3472923c58eSVaibhav Hiremath 	struct arche_platform_drvdata *arche_pdata = dev_get_drvdata(dev);
3482923c58eSVaibhav Hiremath 
3492923c58eSVaibhav Hiremath 	switch (arche_pdata->state) {
3502923c58eSVaibhav Hiremath 	case ARCHE_PLATFORM_STATE_OFF:
3512923c58eSVaibhav Hiremath 		return sprintf(buf, "off\n");
3522923c58eSVaibhav Hiremath 	case ARCHE_PLATFORM_STATE_ACTIVE:
3532923c58eSVaibhav Hiremath 		return sprintf(buf, "active\n");
3542923c58eSVaibhav Hiremath 	case ARCHE_PLATFORM_STATE_STANDBY:
3552923c58eSVaibhav Hiremath 		return sprintf(buf, "standby\n");
3567691fed2SVaibhav Hiremath 	case ARCHE_PLATFORM_STATE_FW_FLASHING:
3577691fed2SVaibhav Hiremath 		return sprintf(buf, "fw_flashing\n");
3582923c58eSVaibhav Hiremath 	default:
3592923c58eSVaibhav Hiremath 		return sprintf(buf, "unknown state\n");
3602923c58eSVaibhav Hiremath 	}
3612923c58eSVaibhav Hiremath }
3622923c58eSVaibhav Hiremath 
3632923c58eSVaibhav Hiremath static DEVICE_ATTR_RW(state);
3642923c58eSVaibhav Hiremath 
3657fa60654SVaibhav Hiremath static int arche_platform_probe(struct platform_device *pdev)
3667fa60654SVaibhav Hiremath {
3677fa60654SVaibhav Hiremath 	struct arche_platform_drvdata *arche_pdata;
3687fa60654SVaibhav Hiremath 	struct device *dev = &pdev->dev;
3697fa60654SVaibhav Hiremath 	struct device_node *np = dev->of_node;
3707fa60654SVaibhav Hiremath 	int ret;
3717fa60654SVaibhav Hiremath 
3727fa60654SVaibhav Hiremath 	arche_pdata = devm_kzalloc(&pdev->dev, sizeof(*arche_pdata), GFP_KERNEL);
3737fa60654SVaibhav Hiremath 	if (!arche_pdata)
3747fa60654SVaibhav Hiremath 		return -ENOMEM;
3757fa60654SVaibhav Hiremath 
3767fa60654SVaibhav Hiremath 	/* setup svc reset gpio */
3777fa60654SVaibhav Hiremath 	arche_pdata->is_reset_act_hi = of_property_read_bool(np,
3787fa60654SVaibhav Hiremath 					"svc,reset-active-high");
3797fa60654SVaibhav Hiremath 	arche_pdata->svc_reset_gpio = of_get_named_gpio(np, "svc,reset-gpio", 0);
3807fa60654SVaibhav Hiremath 	if (arche_pdata->svc_reset_gpio < 0) {
3817fa60654SVaibhav Hiremath 		dev_err(dev, "failed to get reset-gpio\n");
382f1f251b5SViresh Kumar 		return arche_pdata->svc_reset_gpio;
3837fa60654SVaibhav Hiremath 	}
3847fa60654SVaibhav Hiremath 	ret = devm_gpio_request(dev, arche_pdata->svc_reset_gpio, "svc-reset");
3857fa60654SVaibhav Hiremath 	if (ret) {
3867fa60654SVaibhav Hiremath 		dev_err(dev, "failed to request svc-reset gpio:%d\n", ret);
3877fa60654SVaibhav Hiremath 		return ret;
3887fa60654SVaibhav Hiremath 	}
3897fa60654SVaibhav Hiremath 	ret = gpio_direction_output(arche_pdata->svc_reset_gpio,
3907fa60654SVaibhav Hiremath 					arche_pdata->is_reset_act_hi);
3917fa60654SVaibhav Hiremath 	if (ret) {
3927fa60654SVaibhav Hiremath 		dev_err(dev, "failed to set svc-reset gpio dir:%d\n", ret);
3937fa60654SVaibhav Hiremath 		return ret;
3947fa60654SVaibhav Hiremath 	}
395e74d04a5SVaibhav Hiremath 	arche_pdata->state = ARCHE_PLATFORM_STATE_OFF;
3967fa60654SVaibhav Hiremath 
3977fa60654SVaibhav Hiremath 	arche_pdata->svc_sysboot_gpio = of_get_named_gpio(np,
3987fa60654SVaibhav Hiremath 					"svc,sysboot-gpio", 0);
3997fa60654SVaibhav Hiremath 	if (arche_pdata->svc_sysboot_gpio < 0) {
4007fa60654SVaibhav Hiremath 		dev_err(dev, "failed to get sysboot gpio\n");
401f1f251b5SViresh Kumar 		return arche_pdata->svc_sysboot_gpio;
4027fa60654SVaibhav Hiremath 	}
4037fa60654SVaibhav Hiremath 	ret = devm_gpio_request(dev, arche_pdata->svc_sysboot_gpio, "sysboot0");
4047fa60654SVaibhav Hiremath 	if (ret) {
4057fa60654SVaibhav Hiremath 		dev_err(dev, "failed to request sysboot0 gpio:%d\n", ret);
4067fa60654SVaibhav Hiremath 		return ret;
4077fa60654SVaibhav Hiremath 	}
4087fa60654SVaibhav Hiremath 	ret = gpio_direction_output(arche_pdata->svc_sysboot_gpio, 0);
4097fa60654SVaibhav Hiremath 	if (ret) {
4107fa60654SVaibhav Hiremath 		dev_err(dev, "failed to set svc-reset gpio dir:%d\n", ret);
4117fa60654SVaibhav Hiremath 		return ret;
4127fa60654SVaibhav Hiremath 	}
4137fa60654SVaibhav Hiremath 
4147fa60654SVaibhav Hiremath 	/* setup the clock request gpio first */
4157fa60654SVaibhav Hiremath 	arche_pdata->svc_refclk_req = of_get_named_gpio(np,
4167fa60654SVaibhav Hiremath 					"svc,refclk-req-gpio", 0);
4177fa60654SVaibhav Hiremath 	if (arche_pdata->svc_refclk_req < 0) {
4187fa60654SVaibhav Hiremath 		dev_err(dev, "failed to get svc clock-req gpio\n");
419f1f251b5SViresh Kumar 		return arche_pdata->svc_refclk_req;
4207fa60654SVaibhav Hiremath 	}
4217fa60654SVaibhav Hiremath 	ret = devm_gpio_request(dev, arche_pdata->svc_refclk_req, "svc-clk-req");
4227fa60654SVaibhav Hiremath 	if (ret) {
4237fa60654SVaibhav Hiremath 		dev_err(dev, "failed to request svc-clk-req gpio: %d\n", ret);
4247fa60654SVaibhav Hiremath 		return ret;
4257fa60654SVaibhav Hiremath 	}
4267fa60654SVaibhav Hiremath 	ret = gpio_direction_input(arche_pdata->svc_refclk_req);
4277fa60654SVaibhav Hiremath 	if (ret) {
4287fa60654SVaibhav Hiremath 		dev_err(dev, "failed to set svc-clk-req gpio dir :%d\n", ret);
4297fa60654SVaibhav Hiremath 		return ret;
4307fa60654SVaibhav Hiremath 	}
4317fa60654SVaibhav Hiremath 
4327fa60654SVaibhav Hiremath 	/* setup refclk2 to follow the pin */
4337fa60654SVaibhav Hiremath 	arche_pdata->svc_ref_clk = devm_clk_get(dev, "svc_ref_clk");
4347fa60654SVaibhav Hiremath 	if (IS_ERR(arche_pdata->svc_ref_clk)) {
4357fa60654SVaibhav Hiremath 		ret = PTR_ERR(arche_pdata->svc_ref_clk);
4367fa60654SVaibhav Hiremath 		dev_err(dev, "failed to get svc_ref_clk: %d\n", ret);
4377fa60654SVaibhav Hiremath 		return ret;
4387fa60654SVaibhav Hiremath 	}
4397fa60654SVaibhav Hiremath 
4407fa60654SVaibhav Hiremath 	platform_set_drvdata(pdev, arche_pdata);
4417fa60654SVaibhav Hiremath 
4427fa60654SVaibhav Hiremath 	arche_pdata->num_apbs = of_get_child_count(np);
4437fa60654SVaibhav Hiremath 	dev_dbg(dev, "Number of APB's available - %d\n", arche_pdata->num_apbs);
4447fa60654SVaibhav Hiremath 
445a463fc15SVaibhav Hiremath 	arche_pdata->wake_detect_gpio = of_get_named_gpio(np, "svc,wake-detect-gpio", 0);
446a463fc15SVaibhav Hiremath 	if (arche_pdata->wake_detect_gpio < 0) {
447a463fc15SVaibhav Hiremath 		dev_err(dev, "failed to get wake detect gpio\n");
448a463fc15SVaibhav Hiremath 		ret = arche_pdata->wake_detect_gpio;
449758ca99dSVaibhav Hiremath 		return ret;
45072a8c24bSViresh Kumar 	}
4517fa60654SVaibhav Hiremath 
452a463fc15SVaibhav Hiremath 	ret = devm_gpio_request(dev, arche_pdata->wake_detect_gpio, "wake detect");
453a463fc15SVaibhav Hiremath 	if (ret) {
454a463fc15SVaibhav Hiremath 		dev_err(dev, "Failed requesting wake_detect gpio %d\n",
455a463fc15SVaibhav Hiremath 				arche_pdata->wake_detect_gpio);
456758ca99dSVaibhav Hiremath 		return ret;
457a463fc15SVaibhav Hiremath 	}
458057aad29SMichael Scott 	/* deassert wake detect */
459057aad29SMichael Scott 	gpio_direction_output(arche_pdata->wake_detect_gpio, 0);
460685353c1SVaibhav Hiremath 	arche_pdata->wake_detect_state = WD_STATE_IDLE;
461a463fc15SVaibhav Hiremath 
462a463fc15SVaibhav Hiremath 	arche_pdata->dev = &pdev->dev;
463a463fc15SVaibhav Hiremath 
464f760bbfbSVaibhav Hiremath 	spin_lock_init(&arche_pdata->lock);
465f760bbfbSVaibhav Hiremath 	arche_pdata->wake_detect_irq =
466f760bbfbSVaibhav Hiremath 		gpio_to_irq(arche_pdata->wake_detect_gpio);
467f760bbfbSVaibhav Hiremath 
468f760bbfbSVaibhav Hiremath 	ret = devm_request_threaded_irq(dev, arche_pdata->wake_detect_irq,
469f760bbfbSVaibhav Hiremath 			arche_platform_wd_irq,
470f760bbfbSVaibhav Hiremath 			arche_platform_wd_irq_thread,
471f760bbfbSVaibhav Hiremath 			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
472f760bbfbSVaibhav Hiremath 			dev_name(dev), arche_pdata);
473f760bbfbSVaibhav Hiremath 	if (ret) {
474f760bbfbSVaibhav Hiremath 		dev_err(dev, "failed to request wake detect IRQ %d\n", ret);
475f760bbfbSVaibhav Hiremath 		return ret;
476f760bbfbSVaibhav Hiremath 	}
477f760bbfbSVaibhav Hiremath 	/* Enable it only after  sending wake/detect event */
478f760bbfbSVaibhav Hiremath 	disable_irq(arche_pdata->wake_detect_irq);
479f760bbfbSVaibhav Hiremath 
4802923c58eSVaibhav Hiremath 	ret = device_create_file(dev, &dev_attr_state);
4812923c58eSVaibhav Hiremath 	if (ret) {
4822923c58eSVaibhav Hiremath 		dev_err(dev, "failed to create state file in sysfs\n");
4832923c58eSVaibhav Hiremath 		return ret;
4842923c58eSVaibhav Hiremath 	}
4852923c58eSVaibhav Hiremath 
486758ca99dSVaibhav Hiremath 	ret = arche_platform_coldboot_seq(arche_pdata);
487758ca99dSVaibhav Hiremath 	if (ret) {
488758ca99dSVaibhav Hiremath 		dev_err(dev, "Failed to cold boot svc %d\n", ret);
4896743a6fdSVaibhav Hiremath 		goto err_coldboot;
490758ca99dSVaibhav Hiremath 	}
491758ca99dSVaibhav Hiremath 
492fd60ac58SVaibhav Hiremath 	ret = of_platform_populate(np, NULL, NULL, dev);
493fd60ac58SVaibhav Hiremath 	if (ret) {
494fd60ac58SVaibhav Hiremath 		dev_err(dev, "failed to populate child nodes %d\n", ret);
4956743a6fdSVaibhav Hiremath 		goto err_populate;
496fd60ac58SVaibhav Hiremath 	}
497fd60ac58SVaibhav Hiremath 
49816fe18caSVaibhav Hiremath 	assert_wakedetect(arche_pdata);
499db5a3bcaSVaibhav Hiremath 	INIT_DELAYED_WORK(&arche_pdata->delayed_work, hub_conf_delayed_work);
500fd60ac58SVaibhav Hiremath 
5017fa60654SVaibhav Hiremath 	dev_info(dev, "Device registered successfully\n");
50272a8c24bSViresh Kumar 	return 0;
5036743a6fdSVaibhav Hiremath 
5046743a6fdSVaibhav Hiremath err_populate:
5056743a6fdSVaibhav Hiremath 	arche_platform_poweroff_seq(arche_pdata);
5066743a6fdSVaibhav Hiremath err_coldboot:
5076743a6fdSVaibhav Hiremath 	device_remove_file(&pdev->dev, &dev_attr_state);
5086743a6fdSVaibhav Hiremath 	return ret;
5097fa60654SVaibhav Hiremath }
5107fa60654SVaibhav Hiremath 
511bc142bbbSVaibhav Hiremath static int arche_remove_child(struct device *dev, void *unused)
512bc142bbbSVaibhav Hiremath {
513bc142bbbSVaibhav Hiremath 	struct platform_device *pdev = to_platform_device(dev);
514bc142bbbSVaibhav Hiremath 
515bc142bbbSVaibhav Hiremath 	platform_device_unregister(pdev);
516bc142bbbSVaibhav Hiremath 
517bc142bbbSVaibhav Hiremath 	return 0;
518bc142bbbSVaibhav Hiremath }
519bc142bbbSVaibhav Hiremath 
5207fa60654SVaibhav Hiremath static int arche_platform_remove(struct platform_device *pdev)
5217fa60654SVaibhav Hiremath {
5227fa60654SVaibhav Hiremath 	struct arche_platform_drvdata *arche_pdata = platform_get_drvdata(pdev);
5237fa60654SVaibhav Hiremath 
5242923c58eSVaibhav Hiremath 	device_remove_file(&pdev->dev, &dev_attr_state);
52549e6e04bSVaibhav Hiremath 	cancel_delayed_work_sync(&arche_pdata->delayed_work);
526bc142bbbSVaibhav Hiremath 	device_for_each_child(&pdev->dev, NULL, arche_remove_child);
5275993e2bfSVaibhav Hiremath 	arche_platform_poweroff_seq(arche_pdata);
5287fa60654SVaibhav Hiremath 	platform_set_drvdata(pdev, NULL);
5297fa60654SVaibhav Hiremath 
530ad4d3f95SVaibhav Hiremath 	if (usb3613_hub_mode_ctrl(false))
531ad4d3f95SVaibhav Hiremath 		dev_warn(arche_pdata->dev, "failed to control hub device\n");
532ad4d3f95SVaibhav Hiremath 		/* TODO: Should we do anything more here ?? */
5337fa60654SVaibhav Hiremath 	return 0;
5347fa60654SVaibhav Hiremath }
5357fa60654SVaibhav Hiremath 
5367fa60654SVaibhav Hiremath static int arche_platform_suspend(struct device *dev)
5377fa60654SVaibhav Hiremath {
5387fa60654SVaibhav Hiremath 	/*
5397fa60654SVaibhav Hiremath 	 * If timing profile premits, we may shutdown bridge
5407fa60654SVaibhav Hiremath 	 * completely
5417fa60654SVaibhav Hiremath 	 *
5427fa60654SVaibhav Hiremath 	 * TODO: sequence ??
5437fa60654SVaibhav Hiremath 	 *
5447fa60654SVaibhav Hiremath 	 * Also, need to make sure we meet precondition for unipro suspend
5457fa60654SVaibhav Hiremath 	 * Precondition: Definition ???
5467fa60654SVaibhav Hiremath 	 */
5477fa60654SVaibhav Hiremath 	return 0;
5487fa60654SVaibhav Hiremath }
5497fa60654SVaibhav Hiremath 
5507fa60654SVaibhav Hiremath static int arche_platform_resume(struct device *dev)
5517fa60654SVaibhav Hiremath {
5527fa60654SVaibhav Hiremath 	/*
5537fa60654SVaibhav Hiremath 	 * Atleast for ES2 we have to meet the delay requirement between
5547fa60654SVaibhav Hiremath 	 * unipro switch and AP bridge init, depending on whether bridge is in
5557fa60654SVaibhav Hiremath 	 * OFF state or standby state.
5567fa60654SVaibhav Hiremath 	 *
5577fa60654SVaibhav Hiremath 	 * Based on whether bridge is in standby or OFF state we may have to
5587fa60654SVaibhav Hiremath 	 * assert multiple signals. Please refer to WDM spec, for more info.
5597fa60654SVaibhav Hiremath 	 *
5607fa60654SVaibhav Hiremath 	 */
5617fa60654SVaibhav Hiremath 	return 0;
5627fa60654SVaibhav Hiremath }
5637fa60654SVaibhav Hiremath 
5647fa60654SVaibhav Hiremath static SIMPLE_DEV_PM_OPS(arche_platform_pm_ops,
5657fa60654SVaibhav Hiremath 			arche_platform_suspend,
5667fa60654SVaibhav Hiremath 			arche_platform_resume);
5677fa60654SVaibhav Hiremath 
5687fa60654SVaibhav Hiremath static struct of_device_id arche_platform_of_match[] = {
5697fa60654SVaibhav Hiremath 	{ .compatible = "google,arche-platform", }, /* Use PID/VID of SVC device */
5707fa60654SVaibhav Hiremath 	{ },
5717fa60654SVaibhav Hiremath };
5721e5dd1f8SGreg Kroah-Hartman 
5731e5dd1f8SGreg Kroah-Hartman static struct of_device_id arche_apb_ctrl_of_match[] = {
5741e5dd1f8SGreg Kroah-Hartman 	{ .compatible = "usbffff,2", },
5751e5dd1f8SGreg Kroah-Hartman 	{ },
5761e5dd1f8SGreg Kroah-Hartman };
5771e5dd1f8SGreg Kroah-Hartman 
5781e5dd1f8SGreg Kroah-Hartman static struct of_device_id arche_combined_id[] = {
5791e5dd1f8SGreg Kroah-Hartman 	{ .compatible = "google,arche-platform", }, /* Use PID/VID of SVC device */
5801e5dd1f8SGreg Kroah-Hartman 	{ .compatible = "usbffff,2", },
5811e5dd1f8SGreg Kroah-Hartman 	{ },
5821e5dd1f8SGreg Kroah-Hartman };
5831e5dd1f8SGreg Kroah-Hartman MODULE_DEVICE_TABLE(of, arche_combined_id);
5847fa60654SVaibhav Hiremath 
5857fa60654SVaibhav Hiremath static struct platform_driver arche_platform_device_driver = {
5867fa60654SVaibhav Hiremath 	.probe		= arche_platform_probe,
5877fa60654SVaibhav Hiremath 	.remove		= arche_platform_remove,
5887fa60654SVaibhav Hiremath 	.driver		= {
5897fa60654SVaibhav Hiremath 		.name	= "arche-platform-ctrl",
5907fa60654SVaibhav Hiremath 		.pm	= &arche_platform_pm_ops,
5911e5dd1f8SGreg Kroah-Hartman 		.of_match_table = arche_platform_of_match,
5927fa60654SVaibhav Hiremath 	}
5937fa60654SVaibhav Hiremath };
5947fa60654SVaibhav Hiremath 
5951e5dd1f8SGreg Kroah-Hartman static struct platform_driver arche_apb_ctrl_device_driver = {
5961e5dd1f8SGreg Kroah-Hartman 	.probe		= arche_apb_ctrl_probe,
5971e5dd1f8SGreg Kroah-Hartman 	.remove		= arche_apb_ctrl_remove,
5981e5dd1f8SGreg Kroah-Hartman 	.driver		= {
5991e5dd1f8SGreg Kroah-Hartman 		.name	= "arche-apb-ctrl",
6001e5dd1f8SGreg Kroah-Hartman 		.pm	= &arche_apb_ctrl_pm_ops,
6011e5dd1f8SGreg Kroah-Hartman 		.of_match_table = arche_apb_ctrl_of_match,
6021e5dd1f8SGreg Kroah-Hartman 	}
6031e5dd1f8SGreg Kroah-Hartman };
6041e5dd1f8SGreg Kroah-Hartman 
6051e5dd1f8SGreg Kroah-Hartman static int __init arche_init(void)
6061e5dd1f8SGreg Kroah-Hartman {
6071e5dd1f8SGreg Kroah-Hartman 	int retval;
6081e5dd1f8SGreg Kroah-Hartman 
6091e5dd1f8SGreg Kroah-Hartman 	retval = platform_driver_register(&arche_platform_device_driver);
6101e5dd1f8SGreg Kroah-Hartman 	if (retval)
6111e5dd1f8SGreg Kroah-Hartman 		return retval;
6121e5dd1f8SGreg Kroah-Hartman 
6131e5dd1f8SGreg Kroah-Hartman 	retval = platform_driver_register(&arche_apb_ctrl_device_driver);
6141e5dd1f8SGreg Kroah-Hartman 	if (retval)
6151e5dd1f8SGreg Kroah-Hartman 		platform_driver_unregister(&arche_platform_device_driver);
6161e5dd1f8SGreg Kroah-Hartman 
6171e5dd1f8SGreg Kroah-Hartman 	return retval;
6181e5dd1f8SGreg Kroah-Hartman }
6191e5dd1f8SGreg Kroah-Hartman module_init(arche_init);
6201e5dd1f8SGreg Kroah-Hartman 
6211e5dd1f8SGreg Kroah-Hartman static void __exit arche_exit(void)
6221e5dd1f8SGreg Kroah-Hartman {
6231e5dd1f8SGreg Kroah-Hartman 	platform_driver_unregister(&arche_apb_ctrl_device_driver);
6241e5dd1f8SGreg Kroah-Hartman 	platform_driver_unregister(&arche_platform_device_driver);
6251e5dd1f8SGreg Kroah-Hartman }
6261e5dd1f8SGreg Kroah-Hartman module_exit(arche_exit);
6277fa60654SVaibhav Hiremath 
6287fa60654SVaibhav Hiremath MODULE_LICENSE("GPL");
6297fa60654SVaibhav Hiremath MODULE_AUTHOR("Vaibhav Hiremath <vaibhav.hiremath@linaro.org>");
6307fa60654SVaibhav Hiremath MODULE_DESCRIPTION("Arche Platform Driver");
631