forked from mchck/programmer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
adiv5-swd.rb
144 lines (124 loc) · 3.4 KB
/
adiv5-swd.rb
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require 'log'
class Adiv5Swd
ABORT = 0
SELECT = 8
RESEND = 8
RDBUFF = 12
class ProtocolError < StandardError
end
class ParityError < StandardError
end
class Wait < StandardError
end
class Fault < StandardError
end
def initialize(drv)
@drv = drv
switch_to_swd
write(:dp, ABORT, 0x1e) # clear all errors
write(:dp, SELECT, 0) # select DP bank 0
@drv.flush!
end
def switch_to_swd
@drv.raw_out(255.chr * 7) # at least 50 high
@drv.raw_out([0xe79e].pack('v')) # magic number
reset
end
def reset
@drv.raw_out(255.chr * 7) # at least 50 high
@drv.raw_out(0.chr) # at least 1 low
@drv.flush!
begin
@drv.transact(0xa5) # read DPIDR
rescue
# If we fail, try again. We might have been in an unfortunate state.
@drv.raw_out(255.chr * 7) # at least 50 high
@drv.raw_out(0.chr) # at least 1 low
@drv.transact(0xa5) # read DPIDR
end
end
def read(port, addr, opt={})
readcount = opt[:count] || 1
ret = []
Log(:swd, 2){ 'read %s %x (%d words)...' % [port, addr, readcount] }
readcount.times do |i|
ret << transact(port, :in, addr)
end
# reads to the AP are posted, so we need to get the result in a
# separate transaction.
if port == :ap
# first discard the first bogus result
ret.shift
# add last posted result
ret << transact(:dp, :in, RDBUFF)
end
Log(:swd, 1){ ['read %s %x <' % [port, addr], *ret.map{|e| "%08x" % e}] }
ret = ret.first if not opt[:count]
ret
end
def write(port, addr, val)
val = [val] unless val.respond_to? :each
Log(:swd, 1){ ['write %s %x =' % [port, addr], *val.map{|e| "%08x" % e}] }
val.each do |v|
transact(port, :out, addr, v)
end
end
def transact(port, dir, addr, data=nil)
try ||= 0
try += 1
Log(:swd, 2){ "SWD transaction #{port} #{dir} #{addr}, try #{try}" }
cmd = 0x81
case port
when :ap
cmd |= 0x2
end
case dir
when :in
cmd |= 0x4
end
cmd |= ((addr & 0xc) << 1)
parity = cmd
parity ^= parity >> 4
parity ^= parity >> 2
parity ^= parity >> 1
if parity & 1 != 0
cmd |= 0x20
end
@drv.transact(cmd, data)
rescue Wait
Log(:swd, 2){ 'SWD WAIT, retrying' }
retry
# XXX we might have to repeat the previous write instead of this transaction
# the fault/protocolerror might actually refer to the preceeding transaction.
rescue ProtocolError
if try <= 3
Log(:swd, 2){ 'SWD protocol error, retrying' }
reset
retry
else
Log(:swd, 2){ 'SWD protocol error unrecoverable, aborting' }
raise
end
rescue ParityError
Log(:swd, 2){ 'SWD parity error, restarting' }
if port == :ap || addr == RDBUFF
# If this transfer read from the AP, we have to read from RESEND
# instead.
read(:dp, RESEND)
else
# We can repeat simple DP reads
retry
end
rescue Fault
Log(:swd, 2){ 'SWD fault, clearing sticky error' }
# clear sticky error
transact(:dp, :out, ABORT, 1 << 2)
raise
end
end
# We require this here so that all our consumers can directly use
# BackendDriver. However, we cannot require this before the
# declaration of the class, or dependency loops get in our way.
if $0 == __FILE__
s = Adiv5Swd.new(BackendDriver.from_string(ARGV[0]))
end