The driver should allow as many threads as possible into the driver: this leads to fine-grained locking.
However, it should not spend too much time executing the locking primitives: this approach leads to coarse-grained locking.
Driver code should be simple and maintainable.
Avoid lock contention for shared data.
Write re-entrant code wherever possible. This makes it possible for many threads to execute without grabbing any locks.
Use locks to protect the data and not the code path.
Keep in mind the level of concurrency provided by the device; if the controller can only handle one request at a time, there is no point in spending excessive time making the driver handle multiple threads.
A little thought in reorganizing the ordering and types of locks around such data can lead to considerable savings.