firedtv: Use DEFINE_SPINLOCK
[pandora-kernel.git] / drivers / media / dvb / firesat / cmp.c
1 /*
2  * FireDTV driver (formerly known as FireSAT)
3  *
4  * Copyright (C) 2004 Andreas Monitzer <andy@monitzer.com>
5  * Copyright (C) 2008 Henrik Kurelid <henrik@kurelid.se>
6  *
7  *      This program is free software; you can redistribute it and/or
8  *      modify it under the terms of the GNU General Public License as
9  *      published by the Free Software Foundation; either version 2 of
10  *      the License, or (at your option) any later version.
11  */
12
13 #include <linux/device.h>
14 #include <linux/kernel.h>
15 #include <linux/mutex.h>
16 #include <linux/types.h>
17
18 #include <asm/byteorder.h>
19
20 #include <ieee1394.h>
21 #include <nodemgr.h>
22
23 #include "avc_api.h"
24 #include "cmp.h"
25 #include "firesat.h"
26
27 #define CMP_OUTPUT_PLUG_CONTROL_REG_0   0xfffff0000904ULL
28
29 static int cmp_read(struct firesat *firesat, void *buf, u64 addr, size_t len)
30 {
31         int ret;
32
33         if (mutex_lock_interruptible(&firesat->avc_mutex))
34                 return -EINTR;
35
36         ret = hpsb_node_read(firesat->ud->ne, addr, buf, len);
37         if (ret < 0)
38                 dev_err(&firesat->ud->device, "CMP: read I/O error\n");
39
40         mutex_unlock(&firesat->avc_mutex);
41         return ret;
42 }
43
44 static int cmp_lock(struct firesat *firesat, void *data, u64 addr, __be32 arg,
45                     int ext_tcode)
46 {
47         int ret;
48
49         if (mutex_lock_interruptible(&firesat->avc_mutex))
50                 return -EINTR;
51
52         ret = hpsb_node_lock(firesat->ud->ne, addr, ext_tcode, data,
53                              (__force quadlet_t)arg);
54         if (ret < 0)
55                 dev_err(&firesat->ud->device, "CMP: lock I/O error\n");
56
57         mutex_unlock(&firesat->avc_mutex);
58         return ret;
59 }
60
61 static inline u32 get_opcr(__be32 opcr, u32 mask, u32 shift)
62 {
63         return (be32_to_cpu(opcr) >> shift) & mask;
64 }
65
66 static inline void set_opcr(__be32 *opcr, u32 value, u32 mask, u32 shift)
67 {
68         *opcr &= ~cpu_to_be32(mask << shift);
69         *opcr |= cpu_to_be32((value & mask) << shift);
70 }
71
72 #define get_opcr_online(v)              get_opcr((v), 0x1, 31)
73 #define get_opcr_p2p_connections(v)     get_opcr((v), 0x3f, 24)
74 #define get_opcr_channel(v)             get_opcr((v), 0x3f, 16)
75
76 #define set_opcr_p2p_connections(p, v)  set_opcr((p), (v), 0x3f, 24)
77 #define set_opcr_channel(p, v)          set_opcr((p), (v), 0x3f, 16)
78 #define set_opcr_data_rate(p, v)        set_opcr((p), (v), 0x3, 14)
79 #define set_opcr_overhead_id(p, v)      set_opcr((p), (v), 0xf, 10)
80
81 int cmp_establish_pp_connection(struct firesat *firesat, int plug, int channel)
82 {
83         __be32 old_opcr, opcr;
84         u64 opcr_address = CMP_OUTPUT_PLUG_CONTROL_REG_0 + (plug << 2);
85         int attempts = 0;
86         int ret;
87
88         ret = cmp_read(firesat, &opcr, opcr_address, 4);
89         if (ret < 0)
90                 return ret;
91
92 repeat:
93         if (!get_opcr_online(opcr)) {
94                 dev_err(&firesat->ud->device, "CMP: output offline\n");
95                 return -EBUSY;
96         }
97
98         old_opcr = opcr;
99
100         if (get_opcr_p2p_connections(opcr)) {
101                 if (get_opcr_channel(opcr) != channel) {
102                         dev_err(&firesat->ud->device,
103                                 "CMP: cannot change channel\n");
104                         return -EBUSY;
105                 }
106                 dev_info(&firesat->ud->device,
107                          "CMP: overlaying existing connection\n");
108
109                 /* We don't allocate isochronous resources. */
110         } else {
111                 set_opcr_channel(&opcr, channel);
112                 set_opcr_data_rate(&opcr, IEEE1394_SPEED_400);
113
114                 /* FIXME: this is for the worst case - optimize */
115                 set_opcr_overhead_id(&opcr, 0);
116
117                 /* FIXME: allocate isochronous channel and bandwidth at IRM */
118         }
119
120         set_opcr_p2p_connections(&opcr, get_opcr_p2p_connections(opcr) + 1);
121
122         ret = cmp_lock(firesat, &opcr, opcr_address, old_opcr, 2);
123         if (ret < 0)
124                 return ret;
125
126         if (old_opcr != opcr) {
127                 /*
128                  * FIXME: if old_opcr.P2P_Connections > 0,
129                  * deallocate isochronous channel and bandwidth at IRM
130                  */
131
132                 if (++attempts < 6) /* arbitrary limit */
133                         goto repeat;
134                 return -EBUSY;
135         }
136
137         return 0;
138 }
139
140 void cmp_break_pp_connection(struct firesat *firesat, int plug, int channel)
141 {
142         __be32 old_opcr, opcr;
143         u64 opcr_address = CMP_OUTPUT_PLUG_CONTROL_REG_0 + (plug << 2);
144         int attempts = 0;
145
146         if (cmp_read(firesat, &opcr, opcr_address, 4) < 0)
147                 return;
148
149 repeat:
150         if (!get_opcr_online(opcr) || !get_opcr_p2p_connections(opcr) ||
151             get_opcr_channel(opcr) != channel) {
152                 dev_err(&firesat->ud->device, "CMP: no connection to break\n");
153                 return;
154         }
155
156         old_opcr = opcr;
157         set_opcr_p2p_connections(&opcr, get_opcr_p2p_connections(opcr) - 1);
158
159         if (cmp_lock(firesat, &opcr, opcr_address, old_opcr, 2) < 0)
160                 return;
161
162         if (old_opcr != opcr) {
163                 /*
164                  * FIXME: if old_opcr.P2P_Connections == 1, i.e. we were last
165                  * owner, deallocate isochronous channel and bandwidth at IRM
166                  */
167
168                 if (++attempts < 6) /* arbitrary limit */
169                         goto repeat;
170         }
171 }