Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
95e40cc5
Commit
95e40cc5
authored
Jun 26, 2013
by
Mark Brown
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'spi/topic/mpc512x' into spi-next
parents
11e91689
85085898
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
186 additions
and
155 deletions
+186
-155
drivers/spi/spi-mpc512x-psc.c
drivers/spi/spi-mpc512x-psc.c
+186
-155
No files found.
drivers/spi/spi-mpc512x-psc.c
View file @
95e40cc5
...
...
@@ -21,7 +21,6 @@
#include <linux/interrupt.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/io.h>
#include <linux/delay.h>
...
...
@@ -33,24 +32,15 @@
struct
mpc512x_psc_spi
{
void
(
*
cs_control
)(
struct
spi_device
*
spi
,
bool
on
);
u32
sysclk
;
/* driver internal data */
struct
mpc52xx_psc
__iomem
*
psc
;
struct
mpc512x_psc_fifo
__iomem
*
fifo
;
unsigned
int
irq
;
u8
bits_per_word
;
u8
busy
;
u32
mclk
;
u8
eofbyte
;
struct
workqueue_struct
*
workqueue
;
struct
work_struct
work
;
struct
list_head
queue
;
spinlock_t
lock
;
/* Message queue lock */
struct
completion
done
;
struct
completion
txisrdone
;
};
/* controller state */
...
...
@@ -136,145 +126,223 @@ static int mpc512x_psc_spi_transfer_rxtx(struct spi_device *spi,
struct
spi_transfer
*
t
)
{
struct
mpc512x_psc_spi
*
mps
=
spi_master_get_devdata
(
spi
->
master
);
struct
mpc52xx_psc
__iomem
*
psc
=
mps
->
psc
;
struct
mpc512x_psc_fifo
__iomem
*
fifo
=
mps
->
fifo
;
size_t
len
=
t
->
len
;
size_t
tx_len
=
t
->
len
;
size_t
rx_len
=
t
->
len
;
u8
*
tx_buf
=
(
u8
*
)
t
->
tx_buf
;
u8
*
rx_buf
=
(
u8
*
)
t
->
rx_buf
;
if
(
!
tx_buf
&&
!
rx_buf
&&
t
->
len
)
return
-
EINVAL
;
/* Zero MR2 */
in_8
(
&
psc
->
mode
);
out_8
(
&
psc
->
mode
,
0x0
);
/* enable transmiter/receiver */
out_8
(
&
psc
->
command
,
MPC52xx_PSC_TX_ENABLE
|
MPC52xx_PSC_RX_ENABLE
);
while
(
len
)
{
int
count
;
int
i
;
while
(
rx_len
||
tx_len
)
{
size_t
txcount
;
u8
data
;
size_t
fifosz
;
int
rxcount
;
size_t
rxcount
;
int
rxtries
;
/*
*
The number of bytes that can be sent at a tim
e
*
depends on the fifo size.
*
send the TX bytes in as large a chunk as possibl
e
*
but neither exceed the TX nor the RX FIFOs
*/
fifosz
=
MPC512x_PSC_FIFO_SZ
(
in_be32
(
&
fifo
->
txsz
));
count
=
min
(
fifosz
,
len
);
for
(
i
=
count
;
i
>
0
;
i
--
)
{
data
=
tx_buf
?
*
tx_buf
++
:
0
;
if
(
len
==
EOFBYTE
&&
t
->
cs_change
)
setbits32
(
&
fifo
->
txcmd
,
MPC512x_PSC_FIFO_EOF
);
out_8
(
&
fifo
->
txdata_8
,
data
);
len
--
;
txcount
=
min
(
fifosz
,
tx_len
);
fifosz
=
MPC512x_PSC_FIFO_SZ
(
in_be32
(
&
fifo
->
rxsz
));
fifosz
-=
in_be32
(
&
fifo
->
rxcnt
)
+
1
;
txcount
=
min
(
fifosz
,
txcount
);
if
(
txcount
)
{
/* fill the TX FIFO */
while
(
txcount
--
>
0
)
{
data
=
tx_buf
?
*
tx_buf
++
:
0
;
if
(
tx_len
==
EOFBYTE
&&
t
->
cs_change
)
setbits32
(
&
fifo
->
txcmd
,
MPC512x_PSC_FIFO_EOF
);
out_8
(
&
fifo
->
txdata_8
,
data
);
tx_len
--
;
}
/* have the ISR trigger when the TX FIFO is empty */
INIT_COMPLETION
(
mps
->
txisrdone
);
out_be32
(
&
fifo
->
txisr
,
MPC512x_PSC_FIFO_EMPTY
);
out_be32
(
&
fifo
->
tximr
,
MPC512x_PSC_FIFO_EMPTY
);
wait_for_completion
(
&
mps
->
txisrdone
);
}
INIT_COMPLETION
(
mps
->
done
);
/*
* consume as much RX data as the FIFO holds, while we
* iterate over the transfer's TX data length
*
* only insist in draining all the remaining RX bytes
* when the TX bytes were exhausted (that's at the very
* end of this transfer, not when still iterating over
* the transfer's chunks)
*/
rxtries
=
50
;
do
{
/*
* grab whatever was in the FIFO when we started
* looking, don't bother fetching what was added to
* the FIFO while we read from it -- we'll return
* here eventually and prefer sending out remaining
* TX data
*/
fifosz
=
in_be32
(
&
fifo
->
rxcnt
);
rxcount
=
min
(
fifosz
,
rx_len
);
while
(
rxcount
--
>
0
)
{
data
=
in_8
(
&
fifo
->
rxdata_8
);
if
(
rx_buf
)
*
rx_buf
++
=
data
;
rx_len
--
;
}
/* interrupt on tx fifo empty */
out_be32
(
&
fifo
->
txisr
,
MPC512x_PSC_FIFO_EMPTY
);
out_be32
(
&
fifo
->
tximr
,
MPC512x_PSC_FIFO_EMPTY
);
/*
* come back later if there still is TX data to send,
* bail out of the RX drain loop if all of the TX data
* was sent and all of the RX data was received (i.e.
* when the transmission has completed)
*/
if
(
tx_len
)
break
;
if
(
!
rx_len
)
break
;
wait_for_completion
(
&
mps
->
done
);
/*
* TX data transmission has completed while RX data
* is still pending -- that's a transient situation
* which depends on wire speed and specific
* hardware implementation details (buffering) yet
* should resolve very quickly
*
* just yield for a moment to not hog the CPU for
* too long when running SPI at low speed
*
* the timeout range is rather arbitrary and tries
* to balance throughput against system load; the
* chosen values result in a minimal timeout of 50
* times 10us and thus work at speeds as low as
* some 20kbps, while the maximum timeout at the
* transfer's end could be 5ms _if_ nothing else
* ticks in the system _and_ RX data still wasn't
* received, which only occurs in situations that
* are exceptional; removing the unpredictability
* of the timeout either decreases throughput
* (longer timeouts), or puts more load on the
* system (fixed short timeouts) or requires the
* use of a timeout API instead of a counter and an
* unknown inner delay
*/
usleep_range
(
10
,
100
);
}
while
(
--
rxtries
>
0
);
if
(
!
tx_len
&&
rx_len
&&
!
rxtries
)
{
/*
* not enough RX bytes even after several retries
* and the resulting rather long timeout?
*/
rxcount
=
in_be32
(
&
fifo
->
rxcnt
);
dev_warn
(
&
spi
->
dev
,
"short xfer, missing %zd RX bytes, FIFO level %zd
\n
"
,
rx_len
,
rxcount
);
}
mdelay
(
1
);
/*
* drain and drop RX data which "should not be there" in
* the first place, for undisturbed transmission this turns
* into a NOP (except for the FIFO level fetch)
*/
if
(
!
tx_len
&&
!
rx_len
)
{
while
(
in_be32
(
&
fifo
->
rxcnt
))
in_8
(
&
fifo
->
rxdata_8
);
}
/* rx fifo should have count bytes in it */
rxcount
=
in_be32
(
&
fifo
->
rxcnt
);
if
(
rxcount
!=
count
)
mdelay
(
1
);
}
return
0
;
}
rxcount
=
in_be32
(
&
fifo
->
rxcnt
);
if
(
rxcount
!=
count
)
{
dev_warn
(
&
spi
->
dev
,
"expected %d bytes in rx fifo "
"but got %d
\n
"
,
count
,
rxcount
);
static
int
mpc512x_psc_spi_msg_xfer
(
struct
spi_master
*
master
,
struct
spi_message
*
m
)
{
struct
spi_device
*
spi
;
unsigned
cs_change
;
int
status
;
struct
spi_transfer
*
t
;
spi
=
m
->
spi
;
cs_change
=
1
;
status
=
0
;
list_for_each_entry
(
t
,
&
m
->
transfers
,
transfer_list
)
{
if
(
t
->
bits_per_word
||
t
->
speed_hz
)
{
status
=
mpc512x_psc_spi_transfer_setup
(
spi
,
t
);
if
(
status
<
0
)
break
;
}
rxcount
=
min
(
rxcount
,
count
);
for
(
i
=
rxcount
;
i
>
0
;
i
--
)
{
data
=
in_8
(
&
fifo
->
rxdata_8
);
if
(
rx_buf
)
*
rx_buf
++
=
data
;
}
while
(
in_be32
(
&
fifo
->
rxcnt
))
{
in_8
(
&
fifo
->
rxdata_8
);
}
if
(
cs_change
)
mpc512x_psc_spi_activate_cs
(
spi
);
cs_change
=
t
->
cs_change
;
status
=
mpc512x_psc_spi_transfer_rxtx
(
spi
,
t
);
if
(
status
)
break
;
m
->
actual_length
+=
t
->
len
;
if
(
t
->
delay_usecs
)
udelay
(
t
->
delay_usecs
);
if
(
cs_change
)
mpc512x_psc_spi_deactivate_cs
(
spi
);
}
/* disable transmiter/receiver and fifo interrupt */
out_8
(
&
psc
->
command
,
MPC52xx_PSC_TX_DISABLE
|
MPC52xx_PSC_RX_DISABLE
);
out_be32
(
&
fifo
->
tximr
,
0
);
return
0
;
m
->
status
=
status
;
m
->
complete
(
m
->
context
);
if
(
status
||
!
cs_change
)
mpc512x_psc_spi_deactivate_cs
(
spi
);
mpc512x_psc_spi_transfer_setup
(
spi
,
NULL
);
spi_finalize_current_message
(
master
);
return
status
;
}
static
void
mpc512x_psc_spi_work
(
struct
work_struct
*
work
)
static
int
mpc512x_psc_spi_prep_xfer_hw
(
struct
spi_master
*
master
)
{
struct
mpc512x_psc_spi
*
mps
=
container_of
(
work
,
struct
mpc512x_psc_spi
,
work
);
spin_lock_irq
(
&
mps
->
lock
);
mps
->
busy
=
1
;
while
(
!
list_empty
(
&
mps
->
queue
))
{
struct
spi_message
*
m
;
struct
spi_device
*
spi
;
struct
spi_transfer
*
t
=
NULL
;
unsigned
cs_change
;
int
status
;
m
=
container_of
(
mps
->
queue
.
next
,
struct
spi_message
,
queue
);
list_del_init
(
&
m
->
queue
);
spin_unlock_irq
(
&
mps
->
lock
);
spi
=
m
->
spi
;
cs_change
=
1
;
status
=
0
;
list_for_each_entry
(
t
,
&
m
->
transfers
,
transfer_list
)
{
if
(
t
->
bits_per_word
||
t
->
speed_hz
)
{
status
=
mpc512x_psc_spi_transfer_setup
(
spi
,
t
);
if
(
status
<
0
)
break
;
}
struct
mpc512x_psc_spi
*
mps
=
spi_master_get_devdata
(
master
);
struct
mpc52xx_psc
__iomem
*
psc
=
mps
->
psc
;
if
(
cs_change
)
mpc512x_psc_spi_activate_cs
(
spi
);
cs_change
=
t
->
cs_change
;
dev_dbg
(
&
master
->
dev
,
"%s()
\n
"
,
__func__
);
status
=
mpc512x_psc_spi_transfer_rxtx
(
spi
,
t
);
if
(
status
)
break
;
m
->
actual_length
+=
t
->
len
;
/* Zero MR2 */
in_8
(
&
psc
->
mode
);
out_8
(
&
psc
->
mode
,
0x0
);
if
(
t
->
delay_usecs
)
udelay
(
t
->
delay_usecs
);
/* enable transmitter/receiver */
out_8
(
&
psc
->
command
,
MPC52xx_PSC_TX_ENABLE
|
MPC52xx_PSC_RX_ENABLE
);
if
(
cs_change
)
mpc512x_psc_spi_deactivate_cs
(
spi
);
}
return
0
;
}
m
->
status
=
status
;
m
->
complete
(
m
->
context
);
static
int
mpc512x_psc_spi_unprep_xfer_hw
(
struct
spi_master
*
master
)
{
struct
mpc512x_psc_spi
*
mps
=
spi_master_get_devdata
(
master
);
struct
mpc52xx_psc
__iomem
*
psc
=
mps
->
psc
;
struct
mpc512x_psc_fifo
__iomem
*
fifo
=
mps
->
fifo
;
if
(
status
||
!
cs_change
)
mpc512x_psc_spi_deactivate_cs
(
spi
);
dev_dbg
(
&
master
->
dev
,
"%s()
\n
"
,
__func__
);
mpc512x_psc_spi_transfer_setup
(
spi
,
NULL
);
/* disable transmitter/receiver and fifo interrupt */
out_8
(
&
psc
->
command
,
MPC52xx_PSC_TX_DISABLE
|
MPC52xx_PSC_RX_DISABLE
);
out_be32
(
&
fifo
->
tximr
,
0
);
spin_lock_irq
(
&
mps
->
lock
);
}
mps
->
busy
=
0
;
spin_unlock_irq
(
&
mps
->
lock
);
return
0
;
}
static
int
mpc512x_psc_spi_setup
(
struct
spi_device
*
spi
)
{
struct
mpc512x_psc_spi
*
mps
=
spi_master_get_devdata
(
spi
->
master
);
struct
mpc512x_psc_spi_cs
*
cs
=
spi
->
controller_state
;
unsigned
long
flags
;
int
ret
;
if
(
spi
->
bits_per_word
%
8
)
...
...
@@ -303,28 +371,6 @@ static int mpc512x_psc_spi_setup(struct spi_device *spi)
cs
->
bits_per_word
=
spi
->
bits_per_word
;
cs
->
speed_hz
=
spi
->
max_speed_hz
;
spin_lock_irqsave
(
&
mps
->
lock
,
flags
);
if
(
!
mps
->
busy
)
mpc512x_psc_spi_deactivate_cs
(
spi
);
spin_unlock_irqrestore
(
&
mps
->
lock
,
flags
);
return
0
;
}
static
int
mpc512x_psc_spi_transfer
(
struct
spi_device
*
spi
,
struct
spi_message
*
m
)
{
struct
mpc512x_psc_spi
*
mps
=
spi_master_get_devdata
(
spi
->
master
);
unsigned
long
flags
;
m
->
actual_length
=
0
;
m
->
status
=
-
EINPROGRESS
;
spin_lock_irqsave
(
&
mps
->
lock
,
flags
);
list_add_tail
(
&
m
->
queue
,
&
mps
->
queue
);
queue_work
(
mps
->
workqueue
,
&
mps
->
work
);
spin_unlock_irqrestore
(
&
mps
->
lock
,
flags
);
return
0
;
}
...
...
@@ -407,12 +453,12 @@ static irqreturn_t mpc512x_psc_spi_isr(int irq, void *dev_id)
struct
mpc512x_psc_spi
*
mps
=
(
struct
mpc512x_psc_spi
*
)
dev_id
;
struct
mpc512x_psc_fifo
__iomem
*
fifo
=
mps
->
fifo
;
/* clear interrupt and wake up the
work queu
e */
/* clear interrupt and wake up the
rx/tx routin
e */
if
(
in_be32
(
&
fifo
->
txisr
)
&
in_be32
(
&
fifo
->
tximr
)
&
MPC512x_PSC_FIFO_EMPTY
)
{
out_be32
(
&
fifo
->
txisr
,
MPC512x_PSC_FIFO_EMPTY
);
out_be32
(
&
fifo
->
tximr
,
0
);
complete
(
&
mps
->
done
);
complete
(
&
mps
->
txisr
done
);
return
IRQ_HANDLED
;
}
return
IRQ_NONE
;
...
...
@@ -444,18 +490,18 @@ static int mpc512x_psc_spi_do_probe(struct device *dev, u32 regaddr,
if
(
pdata
==
NULL
)
{
mps
->
cs_control
=
mpc512x_spi_cs_control
;
mps
->
sysclk
=
0
;
master
->
bus_num
=
bus_num
;
}
else
{
mps
->
cs_control
=
pdata
->
cs_control
;
mps
->
sysclk
=
pdata
->
sysclk
;
master
->
bus_num
=
pdata
->
bus_num
;
master
->
num_chipselect
=
pdata
->
max_chipselect
;
}
master
->
mode_bits
=
SPI_CPOL
|
SPI_CPHA
|
SPI_CS_HIGH
|
SPI_LSB_FIRST
;
master
->
setup
=
mpc512x_psc_spi_setup
;
master
->
transfer
=
mpc512x_psc_spi_transfer
;
master
->
prepare_transfer_hardware
=
mpc512x_psc_spi_prep_xfer_hw
;
master
->
transfer_one_message
=
mpc512x_psc_spi_msg_xfer
;
master
->
unprepare_transfer_hardware
=
mpc512x_psc_spi_unprep_xfer_hw
;
master
->
cleanup
=
mpc512x_psc_spi_cleanup
;
master
->
dev
.
of_node
=
dev
->
of_node
;
...
...
@@ -473,31 +519,18 @@ static int mpc512x_psc_spi_do_probe(struct device *dev, u32 regaddr,
"mpc512x-psc-spi"
,
mps
);
if
(
ret
)
goto
free_master
;
init_completion
(
&
mps
->
txisrdone
);
ret
=
mpc512x_psc_spi_port_config
(
master
,
mps
);
if
(
ret
<
0
)
goto
free_irq
;
spin_lock_init
(
&
mps
->
lock
);
init_completion
(
&
mps
->
done
);
INIT_WORK
(
&
mps
->
work
,
mpc512x_psc_spi_work
);
INIT_LIST_HEAD
(
&
mps
->
queue
);
mps
->
workqueue
=
create_singlethread_workqueue
(
dev_name
(
master
->
dev
.
parent
));
if
(
mps
->
workqueue
==
NULL
)
{
ret
=
-
EBUSY
;
goto
free_irq
;
}
ret
=
spi_register_master
(
master
);
if
(
ret
<
0
)
goto
unreg_master
;
goto
free_irq
;
return
ret
;
unreg_master:
destroy_workqueue
(
mps
->
workqueue
);
free_irq:
free_irq
(
mps
->
irq
,
mps
);
free_master:
...
...
@@ -513,8 +546,6 @@ static int mpc512x_psc_spi_do_remove(struct device *dev)
struct
spi_master
*
master
=
spi_master_get
(
dev_get_drvdata
(
dev
));
struct
mpc512x_psc_spi
*
mps
=
spi_master_get_devdata
(
master
);
flush_workqueue
(
mps
->
workqueue
);
destroy_workqueue
(
mps
->
workqueue
);
spi_unregister_master
(
master
);
free_irq
(
mps
->
irq
,
mps
);
if
(
mps
->
psc
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment