585 lines
21 KiB
Plaintext
585 lines
21 KiB
Plaintext
|
'******************************************************************************
|
||
|
' Serial Driver for the C Function Library in Spin
|
||
|
' Author: Dave Hein
|
||
|
' Copyright (c) 2010
|
||
|
' See end of file for terms of use.
|
||
|
'******************************************************************************
|
||
|
'******************************************************************************
|
||
|
' Revison History
|
||
|
' v1.0 - 4/2/2010 First official release
|
||
|
'******************************************************************************
|
||
|
{{
|
||
|
This is a modified version of Ghip Gracey's Full-Duplex Serial Driver. This
|
||
|
serial driver has the following new features:
|
||
|
|
||
|
- Multiple serial ports may be started from any object or cog
|
||
|
- Any serial port is accessable from any object or cog using a device handle
|
||
|
- Transmit and receiver buffers can be different sizes
|
||
|
- Buffer sizes are defined by calling parameters
|
||
|
- Mode bit 4 enables the use of a lock to make the transmit multi-cog safe
|
||
|
|
||
|
The original FullDuplexSerial routines are retained for compatibility. A
|
||
|
handle is maintained locally so that the caller does not have to provide the
|
||
|
buffers or handle for the first serial port.
|
||
|
|
||
|
The enhanced methods use a handle, which is a pointer to a memory buffer that
|
||
|
contains the serial port's state information and transmit and receive buffers.
|
||
|
The size of the memory buffer is equal to header_size + rxsize + txsize in
|
||
|
bytes. The buffer must be long aligned. The transmit and receive buffer
|
||
|
sizes must be a power of 2. They can range anywhere from a value of 2 up to
|
||
|
the size of the available memory.
|
||
|
}}
|
||
|
CON
|
||
|
' Data structure byte offsets
|
||
|
' long variables
|
||
|
tm_seconds = 0
|
||
|
tm_clkticks = 4
|
||
|
bit_ticks = 8
|
||
|
' word variables
|
||
|
rx_head = 12
|
||
|
rx_tail = 14
|
||
|
tx_head = 16
|
||
|
tx_tail = 18
|
||
|
rx_buffer = 20
|
||
|
tx_buffer = 22
|
||
|
rx_mask = 24
|
||
|
tx_mask = 26
|
||
|
' byte variables
|
||
|
cog = 28
|
||
|
lock = 29
|
||
|
rx_pin = 30
|
||
|
tx_pin = 31
|
||
|
rxtx_mode = 32
|
||
|
header_size = 36
|
||
|
|
||
|
' Data structure storage for the default serial port
|
||
|
DAT
|
||
|
handle1 long 0
|
||
|
data_struct long 0[(header_size+16+16)/4]
|
||
|
|
||
|
''
|
||
|
''*****************************************************************************
|
||
|
''Original FullDuplexSerial routines for compatibility
|
||
|
''*****************************************************************************
|
||
|
PUB rxflush
|
||
|
'' Flush receive buffer
|
||
|
rxflush1(handle1)
|
||
|
|
||
|
PUB rxcheck
|
||
|
'' Check if byte received (never waits)
|
||
|
'' returns -1 if no byte received, $00..$FF if byte
|
||
|
return rxcheck1(handle1)
|
||
|
|
||
|
PUB rxtime(ms)
|
||
|
'' Wait ms milliseconds for a byte to be received
|
||
|
'' returns -1 if no byte received, $00..$FF if byte
|
||
|
return rxtime1(handle1, ms)
|
||
|
|
||
|
PUB rx
|
||
|
'' Receive byte (may wait for byte)
|
||
|
'' returns $00..$FF
|
||
|
return rx1(handle1)
|
||
|
|
||
|
PUB tx(txbyte)
|
||
|
'' Send byte (may wait for room in buffer)
|
||
|
tx1(handle1, txbyte)
|
||
|
|
||
|
PUB str(stringptr)
|
||
|
'' Send string
|
||
|
str1(handle1, stringptr)
|
||
|
|
||
|
PUB dec(value)
|
||
|
'' Print a decimal number
|
||
|
dec1(handle1, value)
|
||
|
|
||
|
PUB hex(value, digits)
|
||
|
'' Print a hexadecimal number
|
||
|
hex1(handle1, value, digits)
|
||
|
|
||
|
PUB bin(value, digits)
|
||
|
'' Print a binary number
|
||
|
bin1(handle1, value, digits)
|
||
|
|
||
|
''
|
||
|
''*****************************************************************************
|
||
|
''Enhanced routines
|
||
|
''*****************************************************************************
|
||
|
PUB kbhit
|
||
|
return kbhit1(handle1)
|
||
|
|
||
|
PUB kbhit1(handle)
|
||
|
return word[handle+rx_tail] <> word[handle+rx_head]
|
||
|
|
||
|
PUB rxflush1(handle)
|
||
|
'' Flush receive buffer
|
||
|
word[handle+rx_tail] := word[handle+rx_head]
|
||
|
|
||
|
PUB rxcheck1(handle) : rxbyte | rx_tail1, rx_buffer1
|
||
|
'' Check if byte received (never waits)
|
||
|
'' returns -1 if no byte received, $00..$FF if byte
|
||
|
rxbyte--
|
||
|
rx_tail1 := word[handle+rx_tail]
|
||
|
if rx_tail1 <> word[handle+rx_head]
|
||
|
rx_buffer1 := word[handle+rx_buffer]
|
||
|
rxbyte := byte[rx_buffer1+rx_tail1]
|
||
|
word[handle+rx_tail] := (rx_tail1 + 1) & word[handle+rx_mask]
|
||
|
|
||
|
PUB rxtime1(handle, ms) : rxbyte | t
|
||
|
'' Wait ms milliseconds for a byte to be received
|
||
|
'' returns -1 if no byte received, $00..$FF if byte
|
||
|
t := cnt
|
||
|
repeat until (rxbyte := rxcheck1(handle)) => 0 or (cnt - t) / (clkfreq / 1000) > ms
|
||
|
|
||
|
PUB rx1(handle) : rxbyte
|
||
|
'' Receive byte (may wait for byte)
|
||
|
'' returns $00..$FF
|
||
|
repeat while (rxbyte := rxcheck1(handle)) < 0
|
||
|
|
||
|
PRI txchar(handle, txbyte) | tx_mask1, tx_buffer1, tx_head1, tx_head2
|
||
|
'' Send byte (may wait for room in buffer)
|
||
|
tx_mask1 := word[handle+tx_mask]
|
||
|
tx_buffer1 := word[handle+tx_buffer]
|
||
|
tx_head1 := word[handle+tx_head]
|
||
|
tx_head2 := (tx_head1 + 1) & tx_mask1
|
||
|
repeat until (word[handle+tx_tail] <> tx_head2)
|
||
|
{
|
||
|
if (byte[handle+rxtx_mode] & %100000) and (txbyte == 10)
|
||
|
txbyte := 13
|
||
|
}
|
||
|
byte[tx_buffer1+tx_head1] := txbyte
|
||
|
word[handle+tx_head] := tx_head2
|
||
|
|
||
|
if byte[handle+rxtx_mode] & %1000
|
||
|
rx1(handle)
|
||
|
|
||
|
PUB tx1(handle, txbyte) | uselock, locknum
|
||
|
'' Send byte - Use lock if mode bit 4 is set
|
||
|
uselock := byte[handle+rxtx_mode] & %10000
|
||
|
|
||
|
if (uselock)
|
||
|
locknum := byte[handle+lock] - 1
|
||
|
repeat until not lockset(locknum)
|
||
|
|
||
|
txchar(handle, txbyte)
|
||
|
|
||
|
if (uselock)
|
||
|
lockclr(locknum)
|
||
|
|
||
|
PUB str1(handle, stringptr) | uselock, locknum, value
|
||
|
'' Send string - Use lock if mode bit 4 is set
|
||
|
uselock := byte[handle+rxtx_mode] & %10000
|
||
|
|
||
|
if (uselock)
|
||
|
locknum := byte[handle+lock] - 1
|
||
|
repeat until not lockset(locknum)
|
||
|
|
||
|
repeat strsize(stringptr)
|
||
|
txchar(handle, byte[stringptr++])
|
||
|
|
||
|
if (uselock)
|
||
|
lockclr(locknum)
|
||
|
|
||
|
PUB dec1(handle, value) | i
|
||
|
'' Print a decimal number
|
||
|
if (value < 0)
|
||
|
tx1(handle, "-")
|
||
|
if (value == NEGX)
|
||
|
tx1(handle, "2")
|
||
|
value += 2_000_000_000
|
||
|
value := -value
|
||
|
|
||
|
i := 1_000_000_000
|
||
|
repeat while (i > value and i > 1)
|
||
|
i /= 10
|
||
|
repeat while (i > 0)
|
||
|
tx1(handle, value/i + "0")
|
||
|
value //= i
|
||
|
i /= 10
|
||
|
|
||
|
PUB hex1(handle, value, digits)
|
||
|
'' Print a hexadecimal number
|
||
|
value <<= (8 - digits) << 2
|
||
|
repeat digits
|
||
|
tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))
|
||
|
|
||
|
PUB bin1(handle, value, digits)
|
||
|
'' Print a binary number
|
||
|
value <<= 32 - digits
|
||
|
repeat digits
|
||
|
tx((value <-= 1) & 1 + "0")
|
||
|
|
||
|
PUB gethandle1
|
||
|
'' Get the local handle
|
||
|
return handle1
|
||
|
|
||
|
PUB dbprintf0(fmtstr)
|
||
|
dbprintf(fmtstr, @fmtstr)
|
||
|
|
||
|
PUB dbprintf1(fmtstr, arg1)
|
||
|
dbprintf(fmtstr, @arg1)
|
||
|
|
||
|
PUB dbprintf2(fmtstr, arg1, arg2)
|
||
|
dbprintf(fmtstr, @arg1)
|
||
|
|
||
|
PUB dbprintf3(fmtstr, arg1, arg2, arg3)
|
||
|
dbprintf(fmtstr, @arg1)
|
||
|
|
||
|
PUB dbprintf4(fmtstr, arg1, arg2, arg3, arg4)
|
||
|
dbprintf(fmtstr, @arg1)
|
||
|
|
||
|
PUB dbprintf5(fmtstr, arg1, arg2, arg3, arg4, arg5)
|
||
|
dbprintf(fmtstr, @arg1)
|
||
|
|
||
|
PUB dbprintf6(fmtstr, arg1, arg2, arg3, arg4, arg5, arg6)
|
||
|
dbprintf(fmtstr, @arg1)
|
||
|
|
||
|
PUB dbprintf(fmtstr, arglist) | arg, val, digits
|
||
|
arg := long[arglist]
|
||
|
arglist += 4
|
||
|
repeat while (val := byte[fmtstr++])
|
||
|
if (val == "%")
|
||
|
digits := 0
|
||
|
repeat
|
||
|
case (val := byte[fmtstr++])
|
||
|
"d" : dec(arg)
|
||
|
"x" :
|
||
|
ifnot digits
|
||
|
digits := 8
|
||
|
hex(arg, digits)
|
||
|
"b" :
|
||
|
ifnot digits
|
||
|
digits := 32
|
||
|
bin(arg, digits)
|
||
|
"s" : str(arg)
|
||
|
"0".."9":
|
||
|
digits := (digits * 10) + val - "0"
|
||
|
next
|
||
|
0 : return
|
||
|
other: tx(val)
|
||
|
quit
|
||
|
arg := long[arglist]
|
||
|
arglist += 4
|
||
|
elseif (val == "\")
|
||
|
case (val := byte[fmtstr++])
|
||
|
"n" : tx(13)
|
||
|
"r" : tx(10)
|
||
|
0 : return
|
||
|
other: tx(val)
|
||
|
else
|
||
|
tx(val)
|
||
|
|
||
|
PUB settime(seconds, clkticks) | handle
|
||
|
handle := handle1
|
||
|
long[handle + tm_seconds] := seconds
|
||
|
long[handle + tm_clkticks] := clkticks | $80000000
|
||
|
repeat while long[handle + tm_clkticks] & $c0000000
|
||
|
|
||
|
PUB gettime(pclkticks) | handle
|
||
|
handle := handle1
|
||
|
long[handle + tm_clkticks] := $40000000
|
||
|
repeat while long[handle + tm_clkticks] & $c0000000
|
||
|
long[pclkticks] := long[handle + tm_clkticks]
|
||
|
return long[handle + tm_seconds]
|
||
|
|
||
|
''
|
||
|
''*****************************************************************************
|
||
|
''Original FullDuplexSerial routines for compatibility
|
||
|
''*****************************************************************************
|
||
|
PUB start(rxpin, txpin, mode, baudrate)
|
||
|
'' Start serial driver - starts a cog
|
||
|
'' returns false if no cog available
|
||
|
''
|
||
|
'' mode bit 0 = invert rx
|
||
|
'' mode bit 1 = invert tx
|
||
|
'' mode bit 2 = open-drain/source tx
|
||
|
'' mode bit 3 = ignore tx echo on rx
|
||
|
'' mode bit 4 = use lock
|
||
|
'' mode bit 5 = convert LF to CR on tx
|
||
|
return start1(@data_struct, rxpin, txpin, mode, baudrate, 256, 16)
|
||
|
|
||
|
''
|
||
|
''*****************************************************************************
|
||
|
''Enhanced routines
|
||
|
''*****************************************************************************
|
||
|
PUB start1(handle, rxpin, txpin, mode, baudrate, rxsize, txsize) : okay
|
||
|
'' Start serial driver - starts a cog
|
||
|
'' returns false if no cog available
|
||
|
''
|
||
|
'' mode bit 0 = invert rx
|
||
|
'' mode bit 1 = invert tx
|
||
|
'' mode bit 2 = open-drain/source tx
|
||
|
'' mode bit 3 = ignore tx echo on rx
|
||
|
'' mode bit 4 = use lock
|
||
|
'' mode bit 5 = convert LF to CR on tx
|
||
|
if (handle1)
|
||
|
if (handle1 == handle)
|
||
|
stop1(handle)
|
||
|
else
|
||
|
handle1 := handle
|
||
|
wordfill(handle+rx_head, 0, 4)
|
||
|
long[handle+tm_clkticks] := clkfreq + cnt
|
||
|
long[handle+tm_seconds] := 0
|
||
|
byte[handle+rx_pin] := rxpin
|
||
|
byte[handle+tx_pin] := txpin
|
||
|
byte[handle+rxtx_mode] := mode
|
||
|
long[handle+bit_ticks] := clkfreq / baudrate
|
||
|
word[handle+rx_buffer] := handle + header_size
|
||
|
word[handle+tx_buffer] := handle + header_size + rxsize
|
||
|
word[handle+rx_mask] := rxsize - 1
|
||
|
word[handle+tx_mask] := txsize - 1
|
||
|
if (mode & %10000)
|
||
|
okay := byte[handle+lock] := locknew + 1
|
||
|
if (okay == 0)
|
||
|
return 0
|
||
|
okay := byte[handle+cog] := cognew(@entry, handle) + 1
|
||
|
|
||
|
PUB stop1(handle) | cog1
|
||
|
'' Stop serial driver - frees a cog
|
||
|
cog1 := byte[handle+cog]
|
||
|
if cog1
|
||
|
cogstop(cog1 - 1)
|
||
|
longfill(handle, 0, header_size >> 2)
|
||
|
|
||
|
DAT
|
||
|
|
||
|
'***********************************
|
||
|
'* Assembly language serial driver *
|
||
|
'***********************************
|
||
|
|
||
|
org
|
||
|
'
|
||
|
'
|
||
|
' Entry
|
||
|
'
|
||
|
entry mov t1, par
|
||
|
add t1, #rx_pin
|
||
|
rdbyte t2,t1 'get rx_pin
|
||
|
mov rxbitmask,#1
|
||
|
shl rxbitmask,t2
|
||
|
|
||
|
add t1,#1 'get tx_pin
|
||
|
rdbyte t2,t1
|
||
|
mov txbitmask,#1
|
||
|
shl txbitmask,t2
|
||
|
|
||
|
add t1,#1 'get rxtx_mode
|
||
|
rdbyte rxtxmode,t1
|
||
|
|
||
|
mov t1, par
|
||
|
add t1, #bit_ticks 'get bit_ticks
|
||
|
rdlong bitticks,t1
|
||
|
|
||
|
mov t1, par
|
||
|
add t1, #tm_seconds 'get tmseconds
|
||
|
rdlong tmseconds,t1
|
||
|
|
||
|
mov t1, par
|
||
|
add t1, #tm_clkticks 'get tmclkticks
|
||
|
rdlong tmclkticks, #0 'get clkfreq
|
||
|
|
||
|
rdlong tmclkticks,t1
|
||
|
|
||
|
mov t1, par
|
||
|
add t1, #rx_buffer 'get rx_buffer ptr
|
||
|
rdword rxbuff,t1
|
||
|
|
||
|
add t1,#2 'get tx_buffer ptr
|
||
|
rdword txbuff,t1
|
||
|
|
||
|
add t1,#2 'get rx_mask
|
||
|
rdword rxmask,t1
|
||
|
|
||
|
add t1,#2 'get tx_mask
|
||
|
rdword txmask,t1
|
||
|
|
||
|
test rxtxmode,#%100 wz 'init tx pin according to mode
|
||
|
test rxtxmode,#%010 wc
|
||
|
if_z_ne_c or outa,txbitmask
|
||
|
if_z or dira,txbitmask
|
||
|
|
||
|
mov txcode,#transmit 'initialize multitasking addresses
|
||
|
mov clcode,#clock
|
||
|
|
||
|
'
|
||
|
'
|
||
|
' Receive
|
||
|
'
|
||
|
receive jmpret rxcode,txcode 'run a chunk of transmit code, then return
|
||
|
|
||
|
test rxtxmode,#%001 wz 'wait for start bit on rx pin
|
||
|
test rxbitmask,ina wc
|
||
|
if_z_eq_c jmp #receive
|
||
|
|
||
|
mov rxbits,#9 'ready to receive byte
|
||
|
mov rxcnt,bitticks
|
||
|
shr rxcnt,#1
|
||
|
add rxcnt,cnt
|
||
|
|
||
|
:bit add rxcnt,bitticks 'ready next bit period
|
||
|
|
||
|
:wait jmpret rxcode,txcode 'run a chuck of transmit code, then return
|
||
|
|
||
|
mov t1,rxcnt 'check if bit receive period done
|
||
|
sub t1,cnt
|
||
|
cmps t1,#0 wc
|
||
|
if_nc jmp #:wait
|
||
|
|
||
|
test rxbitmask,ina wc 'receive bit on rx pin
|
||
|
rcr rxdata,#1
|
||
|
djnz rxbits,#:bit
|
||
|
|
||
|
shr rxdata,#32-9 'justify and trim received byte
|
||
|
and rxdata,#$FF
|
||
|
test rxtxmode,#%001 wz 'if rx inverted, invert byte
|
||
|
if_nz xor rxdata,#$FF
|
||
|
|
||
|
mov t1, par
|
||
|
add t1, #rx_head
|
||
|
rdword t2, t1 'save received byte and inc head
|
||
|
add t2,rxbuff
|
||
|
wrbyte rxdata,t2
|
||
|
sub t2,rxbuff
|
||
|
add t2,#1
|
||
|
and t2,rxmask
|
||
|
wrword t2,t1
|
||
|
|
||
|
jmp #receive 'byte done, receive next byte
|
||
|
'
|
||
|
'
|
||
|
' Transmit
|
||
|
'
|
||
|
transmit jmpret txcode,clcode 'run a chunk of clock code, then return
|
||
|
|
||
|
mov t1,par 'check for head <> tail
|
||
|
add t1,#tx_head
|
||
|
rdword t2,t1
|
||
|
add t1,#2
|
||
|
rdword t3,t1
|
||
|
cmp t2,t3 wz
|
||
|
if_z jmp #transmit
|
||
|
|
||
|
add t3,txbuff 'get byte and inc tail
|
||
|
rdbyte txdata,t3
|
||
|
sub t3,txbuff
|
||
|
add t3,#1
|
||
|
and t3,txmask
|
||
|
wrword t3,t1
|
||
|
|
||
|
or txdata,#$100 'ready byte to transmit
|
||
|
shl txdata,#2
|
||
|
or txdata,#1
|
||
|
mov txbits,#11
|
||
|
mov txcnt,cnt
|
||
|
|
||
|
:bit test rxtxmode,#%100 wz 'output bit on tx pin according to mode
|
||
|
test rxtxmode,#%010 wc
|
||
|
if_z_and_c xor txdata,#1
|
||
|
shr txdata,#1 wc
|
||
|
if_z muxc outa,txbitmask
|
||
|
if_nz muxnc dira,txbitmask
|
||
|
add txcnt,bitticks 'ready next cnt
|
||
|
|
||
|
:wait jmpret txcode,clcode 'run a chunk of clock code, then return
|
||
|
|
||
|
mov t1,txcnt 'check if bit transmit period done
|
||
|
sub t1,cnt
|
||
|
cmps t1,#0 wc
|
||
|
if_nc jmp #:wait
|
||
|
|
||
|
djnz txbits,#:bit 'another bit to transmit?
|
||
|
|
||
|
jmp #transmit 'byte done, transmit next byte
|
||
|
|
||
|
clock jmpret clcode,rxcode 'run the receive task
|
||
|
mov t2, par
|
||
|
add t2, #tm_clkticks
|
||
|
rdlong t1, t2
|
||
|
shl t1, #1 wc
|
||
|
if_c jmp #setclock
|
||
|
shl t1, #1 wc
|
||
|
if_c jmp #getclock
|
||
|
mov t1, cnt
|
||
|
sub t1, tmclkticks
|
||
|
shl t1, #1 wc
|
||
|
if_c jmp #clock
|
||
|
add tmseconds, #1
|
||
|
rdlong t1, #0
|
||
|
add tmclkticks, t1
|
||
|
jmp #clock
|
||
|
|
||
|
setclock shl t1, #3
|
||
|
shr t1, #4
|
||
|
rdlong tmclkticks, #0
|
||
|
add tmclkticks, cnt
|
||
|
sub tmclkticks, t1
|
||
|
mov t2, par
|
||
|
add t2, #tm_seconds
|
||
|
rdlong tmseconds, t2
|
||
|
mov t2, par
|
||
|
add t2, #tm_clkticks
|
||
|
wrlong t1, t2
|
||
|
jmp #clock
|
||
|
|
||
|
getclock rdlong t1, #0
|
||
|
mov t2, cnt
|
||
|
add t2, t1
|
||
|
sub t2, tmclkticks
|
||
|
mov t3, par
|
||
|
add t3, #tm_seconds
|
||
|
wrlong tmseconds, t3
|
||
|
mov t3, par
|
||
|
add t3, #tm_clkticks
|
||
|
wrlong t2, t3
|
||
|
jmp #clock
|
||
|
|
||
|
'
|
||
|
'
|
||
|
' Uninitialized data
|
||
|
'
|
||
|
t1 res 1
|
||
|
t2 res 1
|
||
|
t3 res 1
|
||
|
|
||
|
rxtxmode res 1
|
||
|
bitticks res 1
|
||
|
|
||
|
rxbitmask res 1
|
||
|
rxbuff res 1
|
||
|
rxdata res 1
|
||
|
rxbits res 1
|
||
|
rxcnt res 1
|
||
|
rxcode res 1
|
||
|
rxmask res 1
|
||
|
|
||
|
txbitmask res 1
|
||
|
txbuff res 1
|
||
|
txdata res 1
|
||
|
txbits res 1
|
||
|
txcnt res 1
|
||
|
txcode res 1
|
||
|
txmask res 1
|
||
|
|
||
|
tmclkticks res 1
|
||
|
tmseconds res 1
|
||
|
clcode res 1
|
||
|
|
||
|
fit $100
|
||
|
|
||
|
{{
|
||
|
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||
|
│ TERMS OF USE: MIT License │
|
||
|
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||
|
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
|
||
|
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
|
||
|
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
|
||
|
│is furnished to do so, subject to the following conditions: │
|
||
|
│ │
|
||
|
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
|
||
|
│ │
|
||
|
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
|
||
|
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
|
||
|
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
|
||
|
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
|
||
|
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||
|
}}
|