oplpanel.c revision 7656:2621e50fdf4a
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * All Rights Reserved, Copyright (c) FUJITSU LIMITED 2006
24 */
25
26/*
27 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
28 * Use is subject to license terms.
29 */
30
31
32#include <sys/types.h>
33#include <sys/time.h>
34#include <sys/errno.h>
35#include <sys/cmn_err.h>
36#include <sys/param.h>
37#include <sys/modctl.h>
38#include <sys/conf.h>
39#include <sys/open.h>
40#include <sys/stat.h>
41#include <sys/ddi.h>
42#include <sys/sunddi.h>
43#include <sys/file.h>
44#include <sys/intr.h>
45#include <sys/machsystm.h>
46
47#define	PNLIE_MASK	0x010	/* interrupt enable/disable */
48#define	PNLINT_MASK	0x001	/* interrupted flag */
49
50#ifdef DEBUG
51int panel_debug = 0;
52static void panel_ddi_put8(ddi_acc_handle_t, uint8_t *, uint8_t);
53#define	DCMN_ERR(x)	if (panel_debug) cmn_err x
54
55#else
56
57#define	DCMN_ERR(x)
58#define	panel_ddi_put8(x, y, z)	ddi_put8(x, y, z)
59
60#endif
61
62static int	panel_getinfo(dev_info_t *, ddi_info_cmd_t, void *,  void **);
63static int	panel_attach(dev_info_t *, ddi_attach_cmd_t);
64static int	panel_detach(dev_info_t *, ddi_detach_cmd_t);
65static uint_t	panel_intr(caddr_t);
66static int	panel_open(dev_t *, int, int, cred_t *);
67static int	panel_close(dev_t, int, int, cred_t *);
68
69static char	*panel_name = "oplpanel";
70int		panel_enable = 1;	/* enable or disable */
71
72extern uint64_t	cpc_level15_inum;	/* in cpc_subr.c */
73
74struct panel_state {
75	dev_info_t		*dip;
76	ddi_iblock_cookie_t	iblock_cookie;
77	ddi_acc_handle_t	panel_regs_handle;
78	uint8_t			*panelregs;		/* mapping address */
79	uint8_t			panelregs_state;	/* keeping regs. */
80};
81
82struct cb_ops panel_cb_ops = {
83	nodev,		/* open */
84	nodev,		/* close */
85	nodev,		/* strategy */
86	nodev,		/* print */
87	nodev,		/* dump */
88	nodev,		/* read */
89	nodev,		/* write */
90	nodev,		/* ioctl */
91	nodev,		/* devmap */
92	nodev,		/* mmap */
93	nodev,		/* segmap */
94	nochpoll,	/* poll */
95	nodev,		/* prop_op */
96	NULL,		/* streamtab */
97	D_NEW | D_MP | D_HOTPLUG,	/* flag */
98	CB_REV,		/* cb_rev */
99	nodev,		/* async I/O read entry point */
100	nodev		/* async I/O write entry point */
101};
102
103static struct dev_ops panel_dev_ops = {
104	DEVO_REV,		/* driver build version */
105	0,			/* device reference count */
106	panel_getinfo,		/* getinfo */
107	nulldev,		/* identify */
108	nulldev,		/* probe */
109	panel_attach,		/* attach */
110	panel_detach,		/* detach */
111	nulldev,		/* reset */
112	&panel_cb_ops,		/* cb_ops */
113	NULL,			/* bus_ops */
114	nulldev,		/* power */
115	ddi_quiesce_not_supported,	/* devo_quiesce */
116};
117
118/* module configuration stuff */
119static void		*panelstates;
120extern struct mod_ops	mod_driverops;
121
122static struct modldrv modldrv = {
123	&mod_driverops,
124	"OPL panel driver",
125	&panel_dev_ops
126};
127
128static struct modlinkage modlinkage = {
129	MODREV_1,
130	&modldrv,
131	0
132};
133
134
135int
136_init(void)
137{
138	int	status;
139
140	DCMN_ERR((CE_CONT, "%s: _init\n", panel_name));
141
142	status = ddi_soft_state_init(&panelstates,
143	    sizeof (struct panel_state), 0);
144	if (status != 0) {
145		cmn_err(CE_WARN, "%s: ddi_soft_state_init failed.",
146		    panel_name);
147		return (status);
148	}
149
150	status = mod_install(&modlinkage);
151	if (status != 0) {
152		ddi_soft_state_fini(&panelstates);
153	}
154
155	return (status);
156}
157
158int
159_fini(void)
160{
161	/*
162	 * Can't unload to make sure the panel switch always works.
163	 */
164	return (EBUSY);
165}
166
167int
168_info(struct modinfo *modinfop)
169{
170	return (mod_info(&modlinkage, modinfop));
171}
172
173static int
174panel_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
175{
176
177	int instance;
178	struct panel_state *statep = NULL;
179
180	ddi_device_acc_attr_t access_attr = {
181		DDI_DEVICE_ATTR_V0,
182		DDI_STRUCTURE_BE_ACC,
183		DDI_STRICTORDER_ACC
184	};
185
186	instance = ddi_get_instance(dip);
187
188	DCMN_ERR((CE_CONT, "%s%d: attach\n", panel_name, instance));
189
190	switch (cmd) {
191	case DDI_ATTACH:
192		DCMN_ERR((CE_CONT, "%s%d: DDI_ATTACH\n",
193		    panel_name, instance));
194		break;
195
196	case DDI_RESUME:
197		DCMN_ERR((CE_CONT, "%s%d: DDI_RESUME\n",
198		    panel_name, instance));
199
200		if ((statep = (struct panel_state *)
201		    ddi_get_soft_state(panelstates, instance)) == NULL) {
202			cmn_err(CE_WARN, "%s%d: ddi_get_soft_state failed.",
203			    panel_name, instance);
204			return (DDI_FAILURE);
205		}
206
207		/* enable the interrupt just in case */
208		panel_ddi_put8(statep->panel_regs_handle, statep->panelregs,
209		    statep->panelregs_state);
210		return (DDI_SUCCESS);
211
212	default:
213		return (DDI_FAILURE);
214	}
215
216	/*
217	 * Attach routine
218	 */
219
220	/* alloc and get soft state */
221	if (ddi_soft_state_zalloc(panelstates, instance) != DDI_SUCCESS) {
222		cmn_err(CE_WARN, "%s%d: ddi_soft_state_zalloc failed.",
223		    panel_name, instance);
224		goto attach_failed2;
225	}
226	if ((statep = (struct panel_state *)
227	    ddi_get_soft_state(panelstates, instance)) == NULL) {
228		cmn_err(CE_WARN, "%s%d: ddi_get_soft_state failed.",
229		    panel_name, instance);
230		goto attach_failed1;
231	}
232
233	/* set the dip in the soft state */
234	statep->dip = dip;
235
236	/* mapping register */
237	if (ddi_regs_map_setup(dip, 0, (caddr_t *)&statep->panelregs,
238	    0, 0, /* the entire space is mapped */
239	    &access_attr, &statep->panel_regs_handle) != DDI_SUCCESS) {
240		cmn_err(CE_WARN, "%s%d: ddi_regs_map_setup failed.",
241		    panel_name, instance);
242		goto attach_failed1;
243	}
244
245	/* setup the interrupt handler */
246	ddi_get_iblock_cookie(dip, 0, &statep->iblock_cookie);
247	if (ddi_add_intr(dip, 0, &statep->iblock_cookie, 0, &panel_intr,
248	    (caddr_t)statep) != DDI_SUCCESS) {
249		cmn_err(CE_WARN, "%s%d: cannot add interrupt handler.",
250		    panel_name, instance);
251		goto attach_failed0;
252	}
253
254	/* ATTACH SUCCESS */
255
256	/* announce the device */
257	ddi_report_dev(dip);
258
259	/* turn on interrupt */
260	statep->panelregs_state = 0 | PNLIE_MASK;
261	panel_ddi_put8(statep->panel_regs_handle, statep->panelregs,
262	    statep->panelregs_state);
263
264	return (DDI_SUCCESS);
265
266attach_failed0:
267	ddi_regs_map_free(&statep->panel_regs_handle);
268attach_failed1:
269	ddi_soft_state_free(panelstates, instance);
270attach_failed2:
271	DCMN_ERR((CE_NOTE, "%s%d: attach failed", panel_name, instance));
272	return (DDI_FAILURE);
273}
274
275static int
276panel_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
277{
278	int instance;
279	struct panel_state *statep;
280
281	instance = ddi_get_instance(dip);
282
283	DCMN_ERR((CE_CONT, "%s%d: detach\n", panel_name, instance));
284
285	if ((statep = (struct panel_state *)
286	    ddi_get_soft_state(panelstates, instance)) == NULL) {
287		cmn_err(CE_WARN, "%s%d: ddi_get_soft_state failed.",
288		    panel_name, instance);
289		return (DDI_FAILURE);
290	}
291
292	switch (cmd) {
293	case DDI_DETACH:
294		DCMN_ERR((CE_CONT, "%s%d: DDI_DETACH\n",
295		    panel_name, instance));
296
297		/* turn off interrupt */
298		statep->panelregs_state &= ~PNLIE_MASK;
299		panel_ddi_put8(statep->panel_regs_handle, statep->panelregs,
300		    statep->panelregs_state);
301
302		/* free all resources for the dip */
303		ddi_remove_intr(dip, 0, statep->iblock_cookie);
304
305		/* need not free iblock_cookie */
306		ddi_regs_map_free(&statep->panel_regs_handle);
307		ddi_soft_state_free(panelstates, instance);
308
309		return (DDI_SUCCESS);
310
311	case DDI_SUSPEND:
312		DCMN_ERR((CE_CONT, "%s%d: DDI_SUSPEND\n",
313		    panel_name, instance));
314		return (DDI_SUCCESS);
315
316	default:
317		return (DDI_FAILURE);
318
319	}
320	/* Not reached */
321}
322
323/*ARGSUSED*/
324static int
325panel_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,  void **resultp)
326{
327	struct panel_state *statep;
328	int	instance;
329	dev_t	dev = (dev_t)arg;
330
331	instance = getminor(dev);
332
333	DCMN_ERR((CE_CONT, "%s%d: getinfo\n", panel_name, instance));
334
335	switch (cmd) {
336	case DDI_INFO_DEVT2DEVINFO:
337		if ((statep = (struct panel_state *)
338		    ddi_get_soft_state(panelstates, instance)) == NULL) {
339			cmn_err(CE_WARN, "%s%d: ddi_get_soft_state failed.",
340			    panel_name, instance);
341			*resultp = NULL;
342			return (DDI_FAILURE);
343		}
344		*resultp = statep->dip;
345		break;
346	case DDI_INFO_DEVT2INSTANCE:
347		*resultp = (void *)(uintptr_t)instance;
348		break;
349	default:
350		return (DDI_FAILURE);
351	}
352
353	return (DDI_SUCCESS);
354}
355
356static  uint_t
357panel_intr(caddr_t arg)
358{
359	struct panel_state *statep = (struct panel_state *)arg;
360
361	/* to confirm the validity of the interrupt */
362	if (!(ddi_get8(statep->panel_regs_handle, statep->panelregs) &
363	    PNLINT_MASK)) {
364		return (DDI_INTR_UNCLAIMED);
365	}
366
367	/*
368	 * Clear the PNLINT bit
369	 * HW reported that there might be a delay in the PNLINT bit
370	 * clearing. We force synchronization by attempting to read
371	 * back the reg after clearing the bit.
372	 */
373	panel_ddi_put8(statep->panel_regs_handle, statep->panelregs,
374	    statep->panelregs_state | PNLINT_MASK);
375	ddi_get8(statep->panel_regs_handle, statep->panelregs);
376
377	if (panel_enable) {
378		uint_t pstate_save;
379
380		/* avoid double panic */
381		panel_enable 	= 0;
382
383		/*
384		 * Re-enqueue the cpc interrupt handler for PIL15 here since we
385		 * are not unwinding back to the interrupt handler subsystem.
386		 * This is to allow potential cpc overflow interrupts to
387		 * function while we go thru the panic flow. Note that this
388		 * logic could be implemented in panic_enter_hw(), we do
389		 * it here for now as it is less risky. This particular
390		 * condition is only specific to OPL hardware and we want
391		 * to minimize exposure of this new logic to other existing
392		 * platforms.
393		 */
394		pstate_save = disable_vec_intr();
395		intr_enqueue_req(PIL_15, cpc_level15_inum);
396		enable_vec_intr(pstate_save);
397
398		cmn_err(CE_PANIC,
399		    "System Panel Driver: Emergency panic request "
400		    "detected!");
401		/* Not reached */
402	}
403
404	return (DDI_INTR_CLAIMED);
405}
406
407#ifdef DEBUG
408static void
409panel_ddi_put8(ddi_acc_handle_t handle, uint8_t *dev_addr, uint8_t value)
410{
411	if (panel_debug) {
412		cmn_err(CE_CONT, "%s: old value = 0x%x\n",
413		    panel_name, ddi_get8(handle, dev_addr));
414		cmn_err(CE_CONT, "%s: writing value = 0x%x\n",
415		    panel_name, value);
416		ddi_put8(handle, dev_addr, value);
417		cmn_err(CE_CONT, "%s: new value = 0x%x\n",
418		    panel_name, ddi_get8(handle, dev_addr));
419	} else {
420		ddi_put8(handle, dev_addr, value);
421	}
422}
423#endif
424