commit c01277861f259d9a60f33a0cb093bb203261a357
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon May 30 14:39:11 2011 +0200

    dect: ccf: remove unsused timer functions
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 0401bad3f2e7efda3908e0fe14254d5276ce55a2
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 20 16:22:20 2011 +0200

    dect: add P640j packet definitions and get rid of DECT_B_FIELD_SIZE
    
    Add DECT_PACKET_P640j definitions and get rid of the constant
    DECT_B_FIELD_SIZE, which is only valid for P32 packets.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 463f33bcc1a25bf80cc0977e5c6beb85dbe3f948
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 5 12:02:39 2011 +0200

    dect: coa: fix syncying in PP/slave mode
    
    The SyncLoop needs to manually reinitialize the radio since the
    individual receive functions don't do this anymore.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a5bec753cce7558f3363006e55058b26242f694e
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 20 02:15:33 2011 +0200

    dect: coa: optimize per-slot memory layout
    
    - merge DCS state and DCS IV/Key space: the IV and key are reinialized
      on every slot, so we can re-use their space for the DCS state at the
      end of the slot.
    
    - pack radio configuration and BMC control data and move adjacent to
      the DCS space.
    
    This results in 87 bytes of free space for the B-Field data.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e783777ac8fb9956b99f30a41e8b79aa60106c68
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 20 01:37:18 2011 +0200

    dect: coa: write BMC configuration when setting channel mode
    
    Write the BMC configuration when setting the channel mode. This allows
    to reserve memory for only a single configuration for both RX and TX.
    
    Additionally it will in the future be used to configure S-field error
    parameters dynamically.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 34b95935ff0344d2dec3ccfa03cb65ef7911ea8a
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 20 01:15:08 2011 +0200

    dect: coa: reduce A-field receive size by one bit for ReceiveSync
    
    Since a JMP to the generic P00 receive function is done, we need to
    wait one bit less than usually.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 16faf2d44c0d99bfa1eca62cffa66d739c29efed
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Apr 7 02:07:32 2011 +0200

    dect: coa: support operation on all slots
    
    Support operation on uneven slot numbers. The firmware is rewritten to
    follow the following scheme:
    
    - each slot has an entry in the SlotTable consisting of two
      intructions
    
    - idle slots use WT 1 + WNT 1
    
    - for active slots the previous slot is programmed to switch
      to the proper memory bank and initialize the BMC/radio
    
    - the actual slot is programmed to jump to one the receive or
      transmit functions
    
    The transceiver layer takes care of scheduling operation only
    on slots that are surrounded by idle slots based on the
    DECT_TRANSCEIVER_SLOW_HOPPING feature flag.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 2bcb0143d1e7b2e5d4527105907023f3a46ae54f
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Apr 7 02:07:30 2011 +0200

    dect: coa: fix firmware array size in Makefile
    
    The size is 510 not 509 bytes. Also fix range argument to p2bin.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 77a5606155f7e31d80941ecbf546b3bbcb462313
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Apr 7 02:07:29 2011 +0200

    dect: coa: reduce banktable size
    
    Reduce the size of the bank table and make it usable for uneven
    slots as well.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 648ed7b1c1b1f58268e7e700ecc24f62e9265643
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Apr 7 02:07:28 2011 +0200

    dect: replace transceiver slotmask by features flag and use to compute blind slots
    
    Use DECT_TRANSCEIVER_SLOW_HOPPING feature to calculate blind slots.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8e819940f09e9f8f753de5a368349f5b5b3eeddf
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Apr 7 02:07:27 2011 +0200

    dect: coa: don't load BMC config twice on TX
    
    Move RX BMC configuration from the radio initialization to the
    receive functions to avoid double configuration on TX.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit cbf1cafc33d30e952f04cd64f52e15d9ac74d121
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Apr 12 02:06:53 2011 +0200

    dect: csf: fix RFPI RPN parsing
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 4edecd8551285999f7b3a584d03e6f134db5712c
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 6 21:41:54 2011 +0200

    dect: coa: firmware: save two instructions
    
    Fall through to the main RX/TX function in the encrypted variants
    instead of branching.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a8d83344b03e8150a48bad8fc675fdaf5c7d4e4e
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 6 21:32:38 2011 +0200

    dect: coa: fix oops on module unload
    
    Copy sc1442x_base to a local variable to unmap it since we have to
    free the transceiver that contains the pointer before that.
    
    Additionally (in case of the PCMCIA driver) call pcmcia_disable_device()
    before freeing the transceiver since we may still receive interrupts until
    that point.
    
    Signed-off- by: Patrick McHardy <kaber@gtrash.net>ZZ

commit 2e21102c9c14efe0b52980d34c08df91cc461262
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 6 15:34:09 2011 +0200

    dect: coa: firmware: use symbolic constants for P_SC argument
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 83463f3ec34ab017f76d403bcf5cd6a463a73aa4
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 6 15:22:51 2011 +0200

    dect: coa: reduce firmware size
    
    Save a couple of instructions by moving the protected B-field transmission
    and reception code directly into the calling functions. With this change all
    receive and transmit functions call their respective B-field processing
    functions in a similar fashion, allowing to remove the WT 1 for the last
    A-field bit after Receive/Transmit, saving a total of 6 instructions.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 2d0e4f37b5085cb7fd5ce4de0a70be424eb99a8c
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Apr 6 09:24:32 2011 +0200

    dect: csf: don't set transmit slots to scanning when disabling a bearer
    
    Don't set transmit slots to scanning mode when disabling a TBC unless
    the cell is in monitor mode.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 892371898dab97eb249d440581a4bf9eee0bdfc7
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Mar 21 19:38:10 2011 +0100

    dect: csf: check B-field identification before passing frame to upper layers
    
    Also add missing B identification for bearer setup requests.
    
    Signed-off-by; Patrick McHardy <kaber@trash.net>

commit 88785738303e564c82f34b263a1244787b2a3abf
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Mar 20 17:07:56 2011 +0100

    dect: dlc_s_sap: move SAPI/LLN identifiers to public header
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 9f8a80fa36ecba48ad278dbf38a9fc61b9192578
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Mar 20 16:49:53 2011 +0100

    dect: csf: update CSF TX timers before CCF timers
    
    The CCF timers are updated based on the CSF timers, so the CSF
    timer needs to be updated first since the framenumber might be
    increased when slot 0 is reached.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 7bff85f26321debb2e602d77b5c97a76dad0093b
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Mar 19 02:34:29 2011 +0100

    Revert "dect: ccf: add support for software ciphering"
    
    This reverts commit 3253b4dd51ad28c2402fee9d74ca0a67eea3b9e1.
    
    Accidentally committed, code is not ready yet and only works
    occasionaly.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 61f483321134d601f80dd9ac281df850f606ed0f
Author: Erik Tews <e_tews@cdc.informatik.tu-darmstadt.de>
Date:   Fri Mar 18 18:43:46 2011 +0100

    dect: implemented setting the MFN from userspace.
    
    Patch from Erik, moved MFN assignment further down to avoid having it
    overwritten by the current value.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 3253b4dd51ad28c2402fee9d74ca0a67eea3b9e1
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Sep 7 19:28:00 2010 +0200

    dect: ccf: add support for software ciphering
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d7d20e77fbf88dc22bf03455e986432e2eda8c28
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Feb 28 18:56:32 2011 +0100

    dect: com_on_air_pci: fix oops on module unload
    
    BUG: unable to handle kernel paging request at ffffc9000007da08
    IP: [<ffffffffa00bb089>] sc1442x_write_cmd+0x29/0xa0 [com_on_air]
    PGD 4cc36067 PUD 4cc37067 PMD 4cc38067 PTE 0
    Oops: 0002 [#1] PREEMPT SMP
    last sysfs file: /sys/module/dect_csf/initstate
    CPU 0
    Modules linked in: dect_raw dect_ccf com_on_air_pci(-) com_on_air dect_csf dect loop ide_cd_mod i2c_i801 processor rng_core i2c_core evdev serio_raw cdrom fan thermal uhci_hcd rtc_cmos thermal_sys ehci_hcd button [last unloaded: dect]
    
    Pid: 4850, comm: rmmod Not tainted 2.6.38-rc6+ #8
    RIP: 0010:[<ffffffffa00bb089>]  [<ffffffffa00bb089>] sc1442x_write_cmd+0x29/0xa0 [com_on_air]
    RSP: 0018:ffff88004b669c98  EFLAGS: 00010082
    RAX: 0000000000000000 RBX: ffff88004acc6758 RCX: 0000000000000002
    RDX: 0000000000000008 RSI: 0000000000000008 RDI: ffff88004acc6758
    RBP: ffff88004b669c98 R08: 0000000000001a00 R09: ffffc9000007da08
    R10: 000000000000298a R11: 0000000000000046 R12: ffff88004acc6898
    R13: ffff88004acc604c R14: 0000000000000000 R15: 0000000000000000
    FS:  00007fe3f3340700(0000) GS:ffff88004ec00000(0000) knlGS:0000000000000000
    CS:  0010 DS: 0000 ES: 0000 CR0: 000000008005003b
    CR2: ffffc9000007da08 CR3: 000000004b51d000 CR4: 00000000000006f0
    DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
    DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
    Process rmmod (pid: 4850, threadinfo ffff88004b668000, task ffff88004b0650c0)
    Stack:
     ffff88004b669cf8 ffffffffa00bc002 ffff88004b669cc8 00ffffffa008a3a7
     ffffffff00000000 ffffffffa008d040 ffff88004b669ce8 0000000000000040
     0000000000000001 ffff88004acc6000 ffff88004acc6000 0000000000000001
    Call Trace:
     [<ffffffffa00bc002>] sc1442x_set_mode+0x162/0x190 [com_on_air]
     [<ffffffffa00a740b>] dect_irc_disable+0x9b/0x110 [dect_csf]
     [<ffffffffa00a749e>] dect_cell_detach_transceiver+0x1e/0x70 [dect_csf]
     [<ffffffffa00af32b>] dect_transceiver_notify+0x4b/0x70 [dect_csf]
     [<ffffffffa00af37b>] dect_unregister_transceiver+0x2b/0x50 [dect_csf]
     [<ffffffffa001a159>] coa_remove+0x7e/0x86 [com_on_air_pci]
     [<ffffffff811eebd7>] pci_device_remove+0x37/0x70
     [<ffffffff8127d2c6>] __device_release_driver+0x66/0xc0
     [<ffffffff8127d3f0>] driver_detach+0xd0/0xe0
     [<ffffffff8127c1d1>] bus_remove_driver+0x91/0x100
     [<ffffffff8127dbc2>] driver_unregister+0x62/0xa0
     [<ffffffff811eee84>] pci_unregister_driver+0x44/0xa0
     [<ffffffffa001a0d9>] coa_pci_exit+0x15/0x17 [com_on_air_pci]
     [<ffffffff810891b0>] sys_delete_module+0x1a0/0x2a0
     [<ffffffff8139edd9>] ? trace_hardirqs_on_thunk+0x3a/0x3f
     [<ffffffff81002fab>] system_call_fastpath+0x16/0x1b
    Code: 00 00 55 48 89 e5 0f 1f 44 00 00 44 0f b7 87 8c 01 00 00 8b 47 0c 01 f6 85 c0 46 8d 0c 06 75 52 45 0f b7 c9 4c 03 8f 80 01 00 00 <41> 88 11 44 0f b7 87 8c 01 00 00 8b 47 0c 85 c0 41 8d 74 30 01
    RIP  [<ffffffffa00bb089>] sc1442x_write_cmd+0x29/0xa0 [com_on_air]
     RSP <ffff88004b669c98>
    CR2: ffffc9000007da08
    
    Unregister the transceiver before unmapping the IO memory.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 9fc3a1e19a5fe588a74a4b4e13e68f9a821aa1f1
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Jan 30 04:44:43 2011 +0100

    dect: ccf: fix RX timer scheduling
    
    The data is supposed to be delivered to the DLC at the end of the
    receive half frame, not at the beginning, at which point there's
    obviously no data available from the current frame.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d28c985e22b74fde620f2eb3776629633ec971ea
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Jan 12 19:12:21 2011 +0100

    dect: csf: fix alignment warning and A-Field parsing
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d98393aecf61f976334e8cbd9bf35fb21c50a8e5
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Jan 11 22:23:40 2011 +0100

    leak

commit 5bc402cbce171de995bf67b7c9b2bf0550f18ad1
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Jan 3 16:22:06 2011 +0100

    dect: csf: don't use get_unaligned()
    
    Now that the entire T-Field is parsed, data is well aligned,
    don't use get_unaligned() anymore.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit b3312157fa75d2d33056a2cbf7fb3ad1947fe329
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Dec 15 22:48:46 2010 +0100

    dect: csf: parse entire T-field
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 60b1d5af1dc75bcbe6ae31300c1f54b692a4120b
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Dec 15 22:48:45 2010 +0100

    dect: csf: remove unused bearer types
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 4521f18caea9650d79fcacb290641c1fcd6897be
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Dec 28 19:16:23 2010 +0100

    dect: com_on_air_cs: fix compilation on recent kernel versions
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit b4fb9a79547b67b000431265ac614ae8c3f71883
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Dec 8 14:54:16 2010 +0100

    dect: dlc_b_sap: fix skb memory leak in page delivery
    
    skbs delivered to B-SAP sockets are cloned before delivery, the
    original skb leaks. Fix in a similar fashion to ptype delivery,
    clone the skb for all but the last socket and deliver the original
    skb to the last socket.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 4a31844f31b932c544a5e9e962b2d95d16a24118
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Nov 16 22:04:39 2010 +0100

    csf: monitor bearer: add watchdog timer
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 374896d80276bafe0dc4324d5e94ae68f9b45a12
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Nov 16 09:49:21 2010 +0100

    dect: csf: fix WARN_ON in dect_next_carrier()
    
    WARNING: at include/net/dect/transceiver.h:49 dect_next_carrier+0x1a/0x3d [dect_csf]()
    Modules linked in: dect_raw dect_ccf com_on_air_pci com_on_air dect_csf dect loop processor ide_cd_mod uhci_hcd i2c_i801 thermal fan ehci_hcd evdev i2c_core cdrom rng_core thermal_sys serio_raw rtc_cmos button [last unloaded: dect]
    Pid: 15840, comm: make Tainted: G        W   2.6.37-rc1+ #59
    Call Trace:
     <IRQ>  [<ffffffff8103275e>] warn_slowpath_common+0x80/0x98
     [<ffffffff8103278b>] warn_slowpath_null+0x15/0x17
     [<ffffffffa021b8b8>] dect_next_carrier+0x1a/0x3d [dect_csf]
     [<ffffffffa02233a6>] dect_mac_tx_tick+0xc5e/0xcba [dect_csf]
     [<ffffffff8104ea21>] ? sched_clock_cpu+0xca/0xd8
     [<ffffffffa022441b>] dect_transceiver_tasklet+0x2eb/0x5fa [dect_csf]
     [<ffffffff8103712a>] tasklet_hi_action+0xa7/0x10f
     [<ffffffff81037803>] __do_softirq+0xc4/0x16e
     [<ffffffff8100398c>] call_softirq+0x1c/0x28
     [<ffffffff81005350>] do_softirq+0x38/0x80
    
    The IRC doesn't validate that tail message parsing was successful,
    thus missing invalid frames containing rfcars of 0. This later
    triggers a warning in dect_next_carrier() when the static system
    information is copied to the cell.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 558f49f2a7ef11100573d5cd88196b17cce1da60
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Nov 16 09:49:20 2010 +0100

    dect: ccf: add MAC_ME_INFO-res to dynamically lock to a FP
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit bb7a44cbb443192d3c73cc564387d36dea41c5e9
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Nov 6 21:17:56 2010 +0100

    dect: seperate CSF and CCF modules from core
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e1874168f9d95aa3e3e4b49eafb2dfa45dcc0b97
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Nov 3 22:56:33 2010 +0100

    dect: allow to disable CCP
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d7ddb983e25f055df0fa67b3a0f36985281771b5
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Oct 29 18:47:13 2010 +0200

    dect: csf: remove unused function
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 9fd2c84ce3a19cf5b865e45f2732ca68c119843e
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Oct 13 21:30:17 2010 +0200

    dect: csf: fix cell slave mode
    
    When locking to a remote dummy bearer, no local one exists, ignore
    this case when in slave mode.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 2b34bd41b8eead1e1825c208e85f08adae85597b
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Oct 13 18:10:28 2010 +0200

    dect: csf: improve raw mode reception
    
    Don't pass every crap received on scan bearers to raw sockets,
    instead only pass frames deamed valid and for the configured PARI
    to raw sockets.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 4b709d2fa76c2462d847849eac2d88458147df4e
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Oct 8 15:13:10 2010 +0200

    dect: ccf: enable ciphering for handover bearers in PP mode
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 7819c618e93c7061242005ae4b86f4fea1f441dc
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Oct 6 19:17:27 2010 +0200

    dect: coa: warn on unsupported slots
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 14e1e0f29856e091bb95cd6350c4f3fa062bcedb
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Oct 6 18:52:01 2010 +0200

    dect: csf: fix scan receive slot configuration
    
    Don't configure slots unavailable on the transceiver to SCANNING mode
    since this might invalidly patch the firmware.
    
    Also fix the transmit slot configuration to use
    dect_transceiver_channel_available() instead of dect_slot_available(),
    which doesn't check for reserved slots.
    
    Fixes TBC establishment on setups with <= 3 transceivers.
    
    Reported-by: Erik Tews <e_tews@cdc.informatik.tu-darmstadt.de>
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8c881b172ead2fe7edb7b42866829ffae19e8066
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Oct 6 17:43:49 2010 +0200

    dect: csf: don't update handover state in FP mode
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit cc8372a3021538f8c63e935ec06fd3ac5d81c723
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Oct 6 17:43:12 2010 +0200

    dect: csf: slightly improve debugging output
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit b6caf52128347d74ccd94d0e001c7423ffe61edd
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Sep 28 22:38:11 2010 +0200

    dect: dlc_s_sap: free correct socket on error in dect_ssap_rcv_request()
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit de173d4e509d10e4f302581e962e5a9ffccce08f
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Sep 22 03:15:25 2010 +0200

    dect: remove unused file
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d921b4183803c1b9310198c4a752a77b12fc4d19
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Sep 21 01:14:00 2010 +0200

    dect: coa: fix compilation of pcmcia driver
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c79694cbb268e1ff15bd98a697d624623ed29454
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Sep 14 22:05:01 2010 +0200

    dect: csf: fix two bugs
    
    - clear the data area of the cloned sk_buff instead of the original in
      dect_queue_page_segments()
    
    - only trigger handover in PP mode
    
    Additionally add some more information to the MAC receive debugging message.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a4fa2535e4f5bfa596b690431fb9280e39de6d96
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Sep 14 22:05:01 2010 +0200

    dect: fix use-after-free in DECT socket modules
    
    Use sk_common_release or sock_orphan to make sure the socket is orphaned
    when the process closes it. Fixes crashes when the userspace process is
    terminated before all packets in flight are freed.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit f70adc43821bc3c7ecec68c48dd8eee26e6bdb93
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Sep 13 20:14:28 2010 +0200

    net: fix merge error in net/core/sock.c
    
    Remove duplicated line in af_family_key_strings initializer.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit cca4ba902fb15bda93992b503cf12027f5135583
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Sep 10 16:58:57 2010 +0200

    dect: mac: implement PP handover
    
    Trigger handover based on indicated slot errors as per ETSI EN 300 175-3
    section 11.4.5. Sliding collision indications based on the Q1/Q2 bits is
    currently ignores since there appears to be a bug in the sc1442x firmware
    in transmitting the Z-field. Additionally bearer-reattempt rate-limiting
    is currently missing.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 6845d317a84d8bcc99c5fe17c42ef0529dfa608a
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Sep 10 13:47:14 2010 +0200

    dect: transceiver: fix off-by-one-window in sequence number resynchronization
    
    When resynchronizing the sequence number for a window in which a different
    transceiver has already reported events, the sequence number is incremented
    twice by the event rate.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit ca002d1355fcf7ac5feab54390c90b2cfc8cb930
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Sep 10 13:12:15 2010 +0200

    dect: csf: embed struct dect_bearer directly into its controllers
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e3d313541fae94a863eb70065b0feb07150fe9c0
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Sep 8 13:33:57 2010 +0200

    dect: lu1: implement seamless handover
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 7e159cbda199ed4d54f1495e7e978cab66f976be
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Sep 5 08:46:22 2010 +0200

    dect: ccf: implement bearer handover procedures
    
    Signed-off-by: Patirck McHardy <kaber@trash.net>

commit cd0e1d9c0910ff645ac8ab62b20ef80b023aa49b
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Sep 5 06:22:50 2010 +0200

    dect: mac: move normal receive/transmit and slot based timers to CCF
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit ac6eb8218e4755ae557cff3ca10c781d9d740301
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Sep 7 22:56:55 2010 +0200

    dect: dlc_cplane: fix use-after-free in dect_cplane_mac_dis_ind()
    
    When the last LAPC is released, the Lc entity is destroyed and iteration
    over the remaining LAPCs might access freed data. Grab a reference to
    the Lc entity for the duration of the iteration.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e895f9c972adcdc67513869e6ab376fb90760dfb
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 31 06:25:29 2010 +0200

    dect: move dect_normal_{transmit,receive}_base to transceiver.h
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 02ed73dc4853495dd3578ce6f59941d808888f56
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 31 02:47:14 2010 +0200

    dect: ccf: add CCF-layer timers
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 7793f90521d0d876feecd33563df5aa705e539ab
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Sep 3 05:54:21 2010 +0200

    dect: mac: make timer infrastructure usable by CSF and CCF
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a26e763512fdae7f5d74ed8f7e8639e83802fa4c
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Sep 6 14:27:05 2010 +0200

    dect: csf: fix primary scan carrier
    
    The offset is supposed to be 0, not 6.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 3a5d82dee0b664dcf5df7caa9ca40242fff0cb88
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Sep 4 09:28:44 2010 +0200

    dect: coa: clear interrupts on shutdown
    
    Clear interrupt on shutdown instead of on init to avoid spurious interrupts
    when probing the device.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 751e325c3ff5438a06b82329841379dbb9ba2119
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Sep 3 18:57:49 2010 +0200

    dect: csf: clear system information when unlocking from FP
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 70302b07b1b6f08c5ffc9ee9d002a2d480ad368f
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Sep 2 17:45:17 2010 +0200

    dect: csf: show A-CRC errors in debug output
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 908cefe93f5f67db32a1e9f715a3726196a93b46
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Sep 2 17:47:34 2010 +0200

    dect: csf: make sure to lock secondary transceivers to the proper channel
    
    If a TBC exists before the secondary transceivers are locked, they might accidentally
    lock to the TBC.
    
    This is only temporary though since the DBC should actually be disabled and handled
    by the TBC while it is active, however this is not done yet.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 143685767b7a8dbeb6c7ef246c3e44eea1199cd3
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Sep 2 01:10:55 2010 +0200

    dect: csf: fix and improve scanning sequence of the 2nd+ transceiver
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 34486a5122a0a01de1a63116db59870145da3f1f
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 17 22:55:50 2009 +0200

    dect: csf: transmit the release reason when releasing a TBC
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit f663907b7b95350f62fe87c9dd4e7b3a4dfc7abb
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 10 21:18:09 2010 +0200

    dect: csf: add missing attributes to ATTRIBUTES_T messages
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit ec15ddfabbadfe36df9c8f8afcdfdd6c56ae65bc
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Jul 24 21:57:25 2010 +0200

    dect: add DECT standard cipher
    
    Not integrated yet though.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 4f684ea37dfcb4ea621dbc0baad50dd9d8f736a7
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Aug 20 14:41:55 2010 +0200

    dect: fix a few monitor mode bugs
    
    Don't pass the packet to raw sockets twice, release the transceiver reference
    of the clone instead of the original packet when queuing packets to a raw
    socket.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8cfafd8baa2d2a14ec6f12cea56499a57b581fa0
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 20 18:18:41 2010 +0200

    dect: csf: restore monitor mode
    
    Introduce a "monitor" bearer type that watches for TBC ACCESS_REQUEST messages
    and allows to monitor all data exchanged by the TBCs.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c5081c4f34cb3453ccf70f539f0c63bf81f0d51b
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Aug 20 13:59:19 2010 +0200

    dect: csf: make sure timer is initially synced correctly
    
    Delay locking to the FP until a MFN frame has been received so we know for
    sure that the frame number is 8. Until then, manually synchronize the PSCN
    so the IRC is set up correctly.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit ad6d189af57c5945d62c3ca69645cb69d7e04bca
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 18 04:18:03 2010 +0200

    dect: rename primitives to match the ETSI standards
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit b9d1a5f4fc8eae9f84f26539f7857158bbd8480e
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 18 01:20:31 2010 +0200

    dect: make debugging better readable
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit acce50071bf0ace7ce60acd110391fa0bfbc2152
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 17 22:52:15 2010 +0200

    dect: mac: fix compilation
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 83f020838ce17007179ead8e4d17c74532d5c9d7
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 17 22:49:25 2010 +0200

    dect: mac: fix page distribution to multiple cells
    
    The inner loop should deliver the page to the previous cell,
    not the current one.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 5cec4d53f37c12c140f2e1aba57d6046deb781b4
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 17 22:41:55 2010 +0200

    dect: netlink: include RPN in MAC information
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 022afdaed73932a65701a5e0467ee1765447de8e
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 17 17:40:53 2010 +0200

    dect: csf: don't skip Q-channel transmissions for unavailable capabilities
    
    Send some other Q-channel information instead of skipping transmission.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit b4b93a19e89532715c8eecf1586e29363a29b7c2
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 17 17:39:53 2010 +0200

    dect: coa: clear all pending interrupts on init
    
    Occasionally after an error the card will deliver spurious interrupts when
    reloading the driver. Clear all pending interrupts to avoid this.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit b1e8712628577a5047e1ba990f41b1af11cb2765
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 17 08:13:24 2010 +0200

    dect: mac: add two helpers to get the cell/cluster from their handle
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 5bd10db031fac7b0dcbcd5f221c9487b60f6e0a7
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Aug 16 22:51:16 2010 +0200

    dect: mac: fix RFP preloading not to overwrite the PSCN
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 38eb830806f8c0a2d3c530218b85e05cd490f869
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Aug 13 10:35:54 2010 +0200

    dect: dlc_bsap: catch zero length page messages
    
    Zero-length pages are invalid, refuse them.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e95b0c3c73d8f0fa4cc44f84b237553162359612
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Aug 12 22:34:57 2010 +0200

    dect: csf: fix a few paging bugs
    
    - clear the page header area in the message data before ORing in
      the length to make sure its valid
    
    - fix skb leaks on receive error conditions
    
    - clear the long_page flag in the page CB after cloning the message
      since it contains information from the transceiver
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 6b2d51c22fcc5786afc004a1cfa604d2c937addc
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 11 18:45:20 2010 +0200

    dect: properly support long pages
    
    Add ancillary data to indicate whether a page message is a long page on
    sendmsg() and recvmsg(). Additionally fix long page segmentation, the
    page length was incorrectly stored in the message header.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit fbfe5601135ce0128b71eeedb1f69a1217b4de60
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 11 18:45:20 2010 +0200

    dect: csf: send blind-full-slots and dummy-bearer-postition pages
    
    Periodically send blind-full-slots and dummy-bearer-position pages.
    Additionally send blind-full-slots pages when the slot visibility state
    changes.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a691181203cc7b134753ce8206de17ef54816cea
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 11 18:45:19 2010 +0200

    dect: maintain cell blind full slots properly in FP mode
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 3221a798f59c7ff3aa88e8f06d9d0d0b95443bab
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 11 18:45:18 2010 +0200

    dect: csf: print TBC state names in debug messages
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 4b22db81c9ead3d2e42c544e843e713de81a7058
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 11 03:59:11 2010 +0200

    dect: coa: use register definitions for clarity
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e4ed1a352915c959c769ebcd49268d5a59bece47
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Mar 2 08:43:49 2010 +0100

    dect: add virtual transceiver driver
    
    Add a driver for virtual DECT transceivers. Multiple transceivers can
    be joined together in a group, forming a virtual DECT network. Signal
    strength and interference are calculated based on the relative positions
    of the transceivers, which can be controlled through sysfs.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c06cee2d91fa4ca746a369a092083e869305c969
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 4 22:41:23 2010 +0200

    dect: ccf: support RFP MAC preload
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c07b09c99fb08950a9ccfc10f613dbf4411f5367
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Aug 4 22:46:45 2010 +0200

    dect: rename DECT_HLC_ACCESS_RIGHT_REQUESTS to DECT_HLC_ACCESS_RIGHTS_REQUESTS
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 766d637d970597f8a7d33f88c235a0f44bdd8322
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 3 19:12:22 2010 +0200

    dect: csf: fix logic error in encryption control message processing
    
    Comparing the message type with the encryption state does obviously not
    make any sense. The intention was to retransmit the START_GRANT message
    if a retransmitted START_CONFIRM is received, so do that.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8b686acbd2f0689129c178e964104b11680a54bf
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 3 08:55:31 2010 +0200

    dect: mac: fix DECT_ENCCTRL_STOP_* values
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8cd6d34690babd639d244239ca8308920d5d760f
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Aug 3 03:07:55 2010 +0200

    dect: add DECT sockaddr build-time size checks
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 0f17d0c0937ddba08b94ef7d18927e65696c1977
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Aug 2 23:04:43 2010 +0200

    dect: notify userspace of mac info changes
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit dde02dbeb7f9eb0ececb12b9fe7d35cbda43bf89
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Jul 28 19:28:06 2010 +0200

    dect: ccp: add support for missing primitives
    
    At some point, it needs to be tested :)
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 3626ceae245233f6849a4500e14de3a9f0160e68
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Mar 4 01:04:07 2010 +0100

    dect: support specifying cluster instance by index
    
    Specifying cluster by ARI introduces conflicts in case both
    a PP and a FP for the same ARI are running locally.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c25a57df0e19532aa79f3740c5d7de8c6e6a7e67
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Jul 24 21:57:26 2010 +0200

    dect: csf: rename dect_tbc_build_bcctrl()
    
    It's used for both basic and advanced connection control, rename to
    dect_tbc_build_cctrl() to reflect that fact.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a341570d6af0328d32acc56640adeb02b6f7cc84
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Jul 5 18:53:04 2010 +0200

    dect: fix ciphering in PP mode
    
    Complete CSF procedures, fix segfault in dect_ssap_setsockopt().
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 9064dc69e2e51fbbaa87085eb89d5c77d687d670
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Jun 17 04:48:46 2010 +0200

    dect: coa: remove unnecessary pcmcia_release_window()
    
    Everything is cleaned up by pcmcia_disable_device().
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 2b8885083af9b9c93ece1cc5d5fdaff8c2e321b7
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Jun 17 01:21:19 2010 +0200

    dect: csf: fix TBC accounting
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8f74a2300cd2746f934041071ab5126ef0a29dca
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Jun 17 00:34:41 2010 +0200

    dect: fix up for 2.6.35-rc changes
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 78996a5f2c30439c8e51b2bcabd90aa3003460c8
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 20 18:18:41 2010 +0200

    dect: csf: beginning of proper PP state maintenance
    
    Keep track of the A/Nt/resync timeouts and leave idle-locked
    for active-unlocked once they expire. Additionally disable
    active DBCs when entering active-locked state and enable them
    again when transitioning back to idle-locked.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a8d07fb03acfaa1ea24c666790e1a2e21f9b36b6
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Jun 16 04:29:05 2010 +0200

    dect: csf: add helper function to change TBC state
    
    Will be used to keep track of established TBCs for PT state maintenance.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 265376893f41d966cfcb2f699b30aa1bc86300de
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Jun 16 04:17:57 2010 +0200

    dect: csf: move dect_lock_fp() up
    
    Preparation to keep the next patch more readable.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 62b76e295706af1f341f3b6a707e2e8eb945605f
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 27 04:37:09 2010 +0200

    dect: ccf: fix indicating disconnects of never connected connections
    
    [2376746.821041] WARNING: at net/dect/dlc.c:203 dect_dlc_mac_dis_indicate+0x77/0x7e [dect]()
    [2376746.821045] Hardware name: To Be Filled By O.E.M.
    [2376746.821048] Modules linked in: ...
    [2376746.821167] Pid: 1974, comm: ulogd Tainted: G        W  2.6.34-rc5 #2
    [2376746.821171] Call Trace:
    [2376746.821183]  [<c012aa70>] warn_slowpath_common+0x6c/0x95
    [2376746.821194]  [<f94d7eee>] ? dect_dlc_mac_dis_indicate+0x77/0x7e [dect]
    [2376746.821202]  [<f94d7eee>] ? dect_dlc_mac_dis_indicate+0x77/0x7e [dect]
    [2376746.821208]  [<c012aab3>] warn_slowpath_null+0x1a/0x1c
    [2376746.821216]  [<f94d7eee>] dect_dlc_mac_dis_indicate+0x77/0x7e [dect]
    [2376746.821224]  [<f94d7427>] dect_mbc_timeout+0x2d/0x38 [dect]
    [2376746.821231]  [<c01371a6>] run_timer_softirq+0x137/0x2fe
    [2376746.821240]  [<f94d73fa>] ? dect_mbc_timeout+0x0/0x38 [dect]
    [2376746.821247]  [<c013006e>] __do_softirq+0x75/0x1a2
    [2376746.821254]  [<c012fff9>] ? __do_softirq+0x0/0x1a2
    [2376746.821258]  <IRQ>  [<c0130325>] ? irq_exit+0x74/0x76
    [2376746.821270]  [<c03f003f>] ? smp_apic_timer_interrupt+0x4b/0x7c
    [2376746.821276]  [<c03eb92d>] ? apic_timer_interrupt+0x31/0x38
    [2376746.821280] ---[ end trace 3018db64542d7bb0 ]---
    
    Connections initiated by the remote side have their MBC timer started,
    but are not yet indicated to the DLC. Don't indicate disconnect as well.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 043090cc700fc4d94a2aad213c18ae1ff59ee5a6
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 20 18:18:40 2010 +0200

    dect: coa: support switching to scanning mode from locked state
    
    Patch the slot table to jump to the scanning functions when unlocking
    the transceiver.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit fdfac4129dbaf82cdb38ea805a36541621a5b496
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 27 03:44:28 2010 +0200

    dect: coa: toggle LEDs based on frames received and transmitted
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 3351aedc65a97cb625d5b2da54a6ba51376b4f8e
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Mar 25 15:36:17 2010 +0100

    dect: csf: disable IRC when detaching a transceiver
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 29b3b7cf496eeea7b9e99e9208f5a7dd1e6619be
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Mar 25 15:32:59 2010 +0100

    dect: csf: fix CBC compiler warning
    
    Disable CBC functions for now until it is actually supported.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 6cff6c7350b338c5c333e38794c4751701209f7d
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Mar 26 16:12:23 2010 +0100

    dect: csf: ignore invalid number of rfcarriers values
    
    Avoid a warning in the carrier calculation functions.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8c9d13a6e29d03a35b808e390ffab866501aef3d
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Jan 13 09:46:14 2010 +0100

    dect: add missing capabilities, add netlink MAC_INFO-ind notifications
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 80e7e5eb553e112ac5ac12549b54c57bd72e31bd
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Mar 6 16:13:11 2010 +0100

    luiq module

commit 94c4dc337406de3141bb025fc065324d6b68424f
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Feb 25 17:50:09 2010 +0100

    dect: ccf: properly handle MBC timeouts
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 39a784e61c8d0d08cb3d77a7e41799254e03646b
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Feb 25 17:50:07 2010 +0100

    dect: make debugging a compile time option
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e13ab57fb0b95c6871ca648188a95ad5739420fd
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Mar 4 16:09:25 2010 +0100

    dect: csf: fix ARQ acknowledgements for small transceiver event rates
    
    For small event rate values, the normal TX timer might trigger before any
    replies are received and the C-plane data transmission state is invalidated.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 166a23971c8b461e56cae06c7612eda4512dc88d
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Jan 22 11:14:33 2010 +0100

    dect: csf: fix warning on 64 bit
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8f72a53f91fe068c97d09952c1f4e10b28878b9f
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Jan 19 19:49:07 2010 +0100

    dect: csf: don't reserve entire T-field in dect_t_skb_alloc()
    
    Only reserve room for the Header Field to have T-skbs and page skbs
    in a consistent state, allowing to use skb_put() on both.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit ca6b193ae51c999f61a8b0523a5dddce0caac7b0
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Jan 19 19:48:19 2010 +0100

    dect: csf: parse and verify RFPI in zero length pages
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 173068afbe14c14b77ebc9367d86a5095c6efe69
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Jan 19 19:46:53 2010 +0100

    dect: csf: rename short page B_S constants
    
    Zero length pages don't carry B_S data, rename the constants to reflect that.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 912ef7a21d23e100c2ebf345a3d9df1e8ae18e65
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Jan 17 13:37:48 2010 +0100

    Merge upstream
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a9228d4d4571eeba891852e225b25fb63ed33e0c
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Jan 15 10:58:01 2010 +0100

    Merge branches 'master' and 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6

commit c14b22bdf4f662d0b6a8fc85071cdd247da2ddfc
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Jan 13 09:44:05 2010 +0100

    dect: dlc_s_sap: fix socket leak on connect() error
    
    Properly release all resources when MAC connection setup fails.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 06d4e06c5015dbda20825b60b01c59d22901ed00
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Jan 10 22:48:47 2010 +0100

    dect: csf: disable debugging
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit ee4666b5b9911adaf7857ecb198e2b91a78490ca
Author: Patrick McHardy <kaber@gw.localnet>
Date:   Thu Dec 31 17:28:30 2009 +0100

    dect: coa: fix firmware patching for secondary FP transceivers
    
    Ciphering and synchronization options are similar to the primary
    transceiver (the sync bearer is handled seperately), only the
    entry points of primary and secondaries differ.
    
    Signed-off-by: Patrick McHardy <kaber@gw.localnet>

commit e2bb619e4efb1b25f221a572a803e0997c7ad97d
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Dec 30 13:03:03 2009 +0100

    dect: netlink: properly set NLMSG_DONE on single messages
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit f553782a249ad2fbae68d106916c358988d9db20
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Dec 29 13:52:52 2009 +0100

    dect: csf: fix locking to slot number 0
    
    The patch to check transceiver slot availability broke locking to slot
    number 0 since its not in state IDLE while the check is performed.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 4b49f01c79fa411a170e7ba1ef7090a8b20ca240
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Dec 28 16:55:30 2009 +0100

    dect: csf: restart scan if channel is unavailable
    
    When the channel (most likely the slot) is not supported by the transceiver,
    restart the scan.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 7c084cd51a5eadbf631b13cf6117133e779ba7d1
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Dec 28 15:48:12 2009 +0100

    dect: csf: synchronize timer when locking to FP
    
    Fix synchronization to long running FPs: when the multiframe number is
    above 2^23, the overflow-aware timer multiframe comparison will consider
    timers meant to run immediately to be scheduled far in the future,
    causing the channel list updates to fail, which in turn makes any
    attempt to open a connection fail.
    
    Roughly synchronize when locking to an FP to avoid this. Full synchronization
    will be achieved once locked.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 74189d6e5380c26b5735acc81f7599906ec23745
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Dec 28 15:48:11 2009 +0100

    dect: csf: fix static system information slotnumber range check
    
    Valid values are 0-11.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 5026d4fc571ccbe618bbeadbcc54d5c9b8f058c7
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Dec 20 21:01:26 2009 +0100

    dect: fix skb_over_panic when appending RA-Field to C_S channel frame
    
    The frames are allocated without sufficient tail room, causing a
    skb_over_panic().
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 2e152cbcdaf0f0ac8eaa44aea45771f3e4d8b779
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Dec 20 20:08:45 2009 +0100

    dect: csf: fix TX bearer activation
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 7606be42841da42871860185321d617838416a6f
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Dec 20 19:18:13 2009 +0100

    dect: mbc: fix setup request retransmission
    
    Don't increment the counter twice.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 74d494e388992e3b7e3350cebcbd050bbfa7962c
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Dec 17 17:08:03 2009 +0100

    dect: add ciphering support
    
    Currently only the FP side is properly supported.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 81aaaa8f2c6f9b7cc2ae7cc5d6ec177278fb2c97
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Dec 17 17:06:35 2009 +0100

    dect: add socket notification support
    
    Add socket notifications using sk->sk_error_queue to report primitives
    that can't be mapped to the socket layer functions directly.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c3211a5a9df8ddc00cbe1127b4931eb9fcac4c4d
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed Dec 9 06:33:14 2009 +0100

    dect: mac_csf: allow raw sockets to transmit on RX bearers
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c86af8975d2c453f7c731a9dff1cf3b269d1f3c6
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Dec 5 12:28:50 2009 +0100

    dect: transceiver: improve dect_channel_desc layout
    
    Use smaller data types for pkt/b_fmt to plug some holes.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 7c1dd59dd26466c89ad5ab170f01c8156cd3ee1d
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Dec 5 11:59:32 2009 +0100

    dect: raw: don't send packets back to the originating raw socket
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit bf5764ea5cfdce4939a5ab443618e502167728c4
Author: Patrick McHardy <kaber@gw.localnet>
Date:   Fri Dec 4 11:04:13 2009 +0100

    dect: mac_csf: add support for cells "slave" mode
    
    A cell in slave mode is similar to a regular cell, except that *all*
    transceivers are slaves.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 647ee2ecbb0d277e87357ed653f752ce297485d3
Author: Patrick McHardy <kaber@gw.localnet>
Date:   Fri Dec 4 11:04:12 2009 +0100

    dect: coa: add clock sync support for secondary transceivers
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d183201d516174a4eb30fd3fa700fe8fef061efd
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Nov 28 14:47:32 2009 +0100

    dect: coa: fix P00 reception
    
    Apparently the sc1442x doesn't properly update the BMC status information unless
    reception of the B-field is begun. This causes P00 slots to be incorrectly detected
    as out of sync.
    
    Add two (otherwise unnecessary) commands to finish the A-field and begin reception
    of the B-field before disabling the receiver.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit cffb7063e19eeb689494b2dcfd21c8e3629943a1
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Nov 27 18:32:11 2009 +0100

    dect: mac_csf: add encryption control message definitions and helpers
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 53bb9793bc43c79b4cf9b4d79bb091071f98291d
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri Nov 27 18:32:05 2009 +0100

    dect: coa: add ciphering support
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 1cf568130fb6fe1aecb3565faa2942d7b7c79a92
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Nov 26 10:03:17 2009 +0100

    dect: clean up firmware a bit and add P00 receive function
    
    - rename Receive and Transmit functions to RX_ and TX_
    
    - add P00 receive function. This is necessary for channel measurement,
      which should measure only over the real packet length.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d11b2d6f9ed55217377e14e09265ed96108bd89a
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Nov 26 10:03:16 2009 +0100

    dect: coa: don't reinitialize radio configuration
    
    There's no need to reinitialize the radio configuration when changing
    channel modes, just initialize it once in sc1442x_init_slot().
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 634dac446d61d7b4c10d903fa755ccdd46f28211
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Nov 26 03:16:22 2009 +0100

    dect: maintain X-crc error statistics
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 80f8beda34c96fe1b440c222a163605908479c5a
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue Nov 24 02:38:40 2009 +0100

    dect: coa: change memory pointers in sc1442x_{from,to}_dmem() to void *
    
    void * is the proper type to use for a memcpy-like function.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 5d2ca23e0ea808c8de1c477eb6baa315ffa4999a
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Nov 23 23:52:05 2009 +0100

    dect: fix use-after-free in dect_transceiver_tx()
    
    The skb is freed in ->tx and must not be used afterwards.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 5feebd67f5e25a55e63f2589c551ac59f1da834c
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Nov 22 16:09:59 2009 +0100

    dect: af_dect: add protocol getsockopt/setsockopt support
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit bce4512e45f2fced25a7c3f9b20a8bf9fbff186d
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Nov 22 02:15:10 2009 +0100

    dect: dlc_s_sap: properly propagate errors after disconnect
    
    The S-SAP sockets currently return ENOTCONN for recvmsg() on any
    unconnected socket. This is wrong for two reasons:
    
    - packets remaining in the rcvqueue should still be received
    - socket errors indicating the reason for disconnecting aren't
      delivered properly
    
    Deliver all remaining packets and errors beforing indicating
    that the socket is disconnected.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 295a52d3752d80d073d7426b57775bfeb46f5230
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Nov 21 13:59:15 2009 +0100

    dect: mac_csf: don't drop B-field data when A-Field contains CRC errors
    
    Its up to the upper layers to decide what to do in this case.
    I_N_minimal_delay services (audio) still accept the data.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 73667d77735b1a74089727ccf65cf9e1463750ca
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon Nov 16 05:46:10 2009 +0100

    dect: dlc_cplane: fix dect_cplane_mac_dis_indicate() error signedness
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d7f3f644e71f843c049bd3afbcf3f51b9bc5bd1e
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Nov 15 19:20:17 2009 +0100

    dlc: WARN_ON() when routing a DLC connection to a closed MAC connection
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 38e19271469864bd2df518d44aa9e6cdb76c658f
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Nov 15 18:43:13 2009 +0100

    dect: add MAINTAINERS entry
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c23dbf41aabb3c43a22293068fb9e60146626627
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun Nov 15 18:09:02 2009 +0100

    dect: dlc_cplane: fix mac connection leaks
    
    Usually unbinding of the LC from the MAC connection and LC destruction
    is triggered by the last LAPC unbinding from the LC. When no LAPCs are
    bound to the LC, destruction is never triggered and the LC and MAC
    connection leak.
    
    Even worse, when trying to route the U-Plane to the MAC connection for
    a call later on, the old disconnected MAC connection is found and used.
    
    Fix by triggering LC destruction manually when no LAPCs are bound.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 53638abde094f1b8e36c1fb99a8a7e45464752d1
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Nov 12 13:16:35 2009 +0100

    dect: dlc_s_sap: propagate dect_lapc_establish() errors
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e8c862200301aa3c2954c03f425cec25d822eac8
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu Nov 12 13:11:50 2009 +0100

    dect: mac_csf: fix initial blind full slot mask for PP mode
    
    Assume all slots are usable Until the cell has received the first blind
    full slots message.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 58403119ccaf1b327e5aa7185925a18b1dd8e1b5
Author: Patrick McHardy <kaber@gw.localnet>
Date:   Sat Sep 12 17:43:13 2009 +0200

    dect: mac_ccf: clean up MBCs on module unload
    
    Should fix crash when unloading with active MBCs caused by timers.
    
    Signed-off-by: Patrick McHardy <kaber@gw.localnet>

commit a531b996140a3d35893cf9aa507d71973b476863
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Jul 18 07:49:33 2009 +0200

    dect: add A-field CRC error statistics counter
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e3ef9413e3ee4db7dd09fd42b098dc03a10f9784
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat Jul 18 07:21:20 2009 +0200

    dect: ccp: add some unimplemented callbacks
    
    Still not fully functional currently.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit b7f515d2bcc1209f16ad79bbeb59e4844ba32479
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 31 17:03:48 2009 +0200

    dect: dlc_uplane: fix skb leak
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a3e905288a6970f060aba8230302e29ca11a8243
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat May 30 11:50:24 2009 +0200

    dect: coa: fix IRQ mask to include timer
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 856cc64a10b980ac4f59da526bb108fb793fb6f3
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 29 19:18:01 2009 +0200

    dect: mac_csf: avoid pr_debug redefinition
    
    Just redefine KERN_DEBUG to avoid <7> in continuation lines, but still
    be able to use dynamic debugging.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 064c5a71dd833666dde3a38955597fec5fdbeea2
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 29 10:27:31 2009 +0200

    dect: mac_csf: initalize blind full slots mask
    
    Initialize the blind full slots mask to "no blind slots" to make
    sure it contains a valid value until the first update is received.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 2f73d018c5db719b5c8041de840fdc8fd114c05a
Author: Patrick McHardy <kaber@gw.localnet>
Date:   Fri May 29 04:17:02 2009 +0200

    dect: coa: disable phase adjustment in FP mode
    
    Signed-off-by: Patrick McHardy <kaber@gw.localnet>

commit fc542b4100cb6b7943d6a5ed829e5d521d60e6eb
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 29 02:33:12 2009 +0200

    dect: coa: fix typo
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit f2cc810b91eb92024d46e930dd36f037010cb982
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 28 23:28:57 2009 +0200

    dect: dlc: fix MCEI format in debugging output
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 752302d367f594d1d4ae2c9a6ff938c02fa6f6a8
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 28 18:20:38 2009 +0200

    dect: mac_csf: fix secondary transceiver sychronization
    
    Ignore unlocked transceivers when performing slot maintenance.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 76e2a79484112a402f197d70db724b94117e8b09
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 28 17:52:17 2009 +0200

    dect: coa: move LED toggling out of loop
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 1449f2c27954b7c336d44052c489a22c952796fa
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 28 17:06:46 2009 +0200

    dect: coa: add some symbolic constants, LED support
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 1b50e058150650e3e293fa7a88a249df7f6c0cb6
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon May 25 17:52:50 2009 +0200

    mac: add a few more higher layer capabilities for testing
    
    This really needs to be moved to a proper API ...
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 47bec29fe9fba2d08a22a7a1e0b5859258b732cf
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon May 25 17:51:50 2009 +0200

    dlc: fix mac connection cleanup for unused connections
    
    When the connection is not used by higher layer entities, nothing will
    trigger a release through unbinding, perform destruction immediately.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 1876a909dfd3c7579462d337c3effe6a15464eba
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 22 14:49:38 2009 +0200

    dect: mbc: release MBC after indicating disconnect
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 6a9f9d590bedf1d07f14ed05a10e272fd9d6ab54
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 22 11:56:01 2009 +0200

    dect: dlc: release mac connections when no longer needed
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 3601e59674c59807582a8849ded4ea72f784afd4
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 22 11:56:00 2009 +0200

    dect: dlc: release Lc entity when no longer needed
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 609da1eb733cc9dddf6b08dc196a863b5e540280
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 22 02:53:56 2009 +0200

    dect: ssap/lapc: implement normal release procedure
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 3e5c308d51fa48a5c9d656dfa0179ec415df1d95
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 22 02:21:01 2009 +0200

    dect: lapc: renames
    
    Rename a few members and constants to better suited names.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 067eac103250c2f3369347c50b8ed6e4406672f0
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 22 02:20:59 2009 +0200

    dect: ssap: fix request socket initialization
    
    Move reference grabbing after sock_init_data() since it uses atomic_set(),
    replacing the original value. Also need to initialize sk_type manually.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 2053a3cb84c5098fd650a5aeb622a6d6b4c099bf
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 20 17:23:57 2009 +0200

    dect: mac: propagate release reason to MBC
    
    Parse and propagate remote and local release reasons to MBC.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c3b4586997ab94b25ef45e1f688535869ed12bd5
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 20 12:03:11 2009 +0200

    dect: raw: convert to dect_dgram_ops
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 37374be45da7caba4240bcc174a4940e29391d24
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 20 11:16:44 2009 +0200

    dect: raw: add raw TX support
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8a01a4c56e4d42a90eece00f4618784aaeafdde6
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 20 10:03:54 2009 +0200

    dect: mac_csf: fix DBC quality control
    
    In addition to switching the channel mode, the carrier needs to be reprogrammed
    afterwards.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 772a7818d57dcb30e21e105fc9621c0c6a33f7a5
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 20 09:57:01 2009 +0200

    dect: mac_csf: add dummy bearer quality control
    
    Periodically switch the DBC to RX mode to measure the noise value on the
    chosen channel. No action is performed yet on bad quality since bearer
    migration is not implemented yet.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 96f4600c2b4a0fdd2ea5b5cd884af64cd3768192
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 20 08:23:58 2009 +0200

    dect: bsap: add proper locking for socket BSAP list
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 61b8c29e5e7d54352239e92dfd484b1dd6d7ce17
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue May 19 07:17:34 2009 +0200

    dect: socket layer: add shutdown() support
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 5a1db56095a109e2c7b27c0f67caa53ce67add07
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue May 19 06:08:17 2009 +0200

    dect: ssap: fix socket unlink locking
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 441f818aebe854efba6c9f1d4b9d273d00d578d4
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue May 19 03:16:23 2009 +0200

    dect: lapc: fix lsig provisioning
    
    Really fix it this time:
    
    [ 9124.757810] TBC (MCEI 2): PMID: assigned 10005 FMID: 610 ECN: 0: Normal RX timer
    [ 9124.757818] Lc (MCEI 2): csum verify: lsig 0005 c0: ff c1: ff
    ...
    
    link 9 (ESTABLISHED): new link: PMID: 10005 LCN: 0 LLN: 1 SAPI: 0
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 93ce1179be3891026df9879565afaf375f22c8f5
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue May 19 03:16:08 2009 +0200

    dect: fix dlc compiler warning and mac TBC debugging format
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e2a994a484b9ab4945a66cf7bfcdb9c480d0c95b
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue May 19 02:59:39 2009 +0200

    dect: lapc: report connection reset to S-SAP
    
    Needs to perform real cleanup. For now, just give userspace a hint.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 365c9cc748d96efad88764aae24193ea02436e18
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue May 19 01:23:33 2009 +0200

    dect: mac: temporary disable accepting handover requests
    
    Not handled yet. One of my phones immediately opens a normal and a handover
    bearer, which breaks everything.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 830fa5c4f2321ca104dd0f7eea09136bc4a863f8
Author: Patrick McHardy <kaber@trash.net>
Date:   Tue May 19 01:19:43 2009 +0200

    dect: mac: fix C-Channel preambles
    
    Move setting of mac_len directly before transmission. The C-channel
    packets loose their mac_len during skb_copy().
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c2a99d16ffbb433d79f5f7699dd34ea817f800d5
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon May 18 13:04:13 2009 +0200

    dect: mac: set Q2 bit in first TBC response
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 9ed89108d2504324d84b8ab40ae26d6bff97041c
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon May 18 03:08:18 2009 +0200

    dect: dlc: fix checksumming with lsig != 0
    
    Remove the checksum verification after checksumming outgoing packets
    since the lsig removal changes the checksum, resulting in a positive
    check and an incorrect checksum. D'oh :)
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 61ef5ce8ded445547051ea9149e90cf60ffd0cb1
Author: Patrick McHardy <kaber@trash.net>
Date:   Mon May 18 01:14:23 2009 +0200

    dect: coa: fix preamble transmission
    
    The first byte of the transmitted preamble is currently zero. For some
    reason the firmware transmits 5 bytes of firmware, my assumption is that
    the first byte is used during ramp-up of the radio.
    
    According to ETS 175 300-02 Chapter 4.9, the ramp-up pattern must be
    either the natural extension of the preamble pattern, or the value 0.
    Both appear to work equally good, which seems to confirm my assumption.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8589db4503b2619ba46855dc9f54ab4f0bb6ebea
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 17 20:07:34 2009 +0200

    dect: mac: support advanced connection control
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a9363ec735e3f88a68de2729a99106e5dd1e8469
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 17 19:28:50 2009 +0200

    dect: handle A-field checksum errors manually
    
    Accept non-P00 packets with A-field CRC errors and filter them in
    software. I_N minimum delay bearers may still accept the B-field.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit aa3fe98a28f799205c90f20dec07bab7c0ce52db
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 17 18:48:16 2009 +0200

    dect: mac: move C-channel sequencing and retransmissions up to MBC
    
    Move all sequence and retransmission related logic up to the MBC. This
    is necessary for bearer handover, where data needs to be duplicated
    synchronously to multiple TBCs.
    
    While at it, move the entire acknowledgement detection, data and DTR
    indication logic to timers.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d2bc0f06d3bf4ab7ef386633a5b3b39edbe11423
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 17 12:29:28 2009 +0200

    dect: mac_csf: retransmit unacknowledged C_S segments
    
    Retransmit C_S segments when no acknowledgement was received. Mid term
    this all needs to move up to the MBC level to deal with handover.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 9f7c68b6013eb99f4286b10e066fefaef9d69a04
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 17 12:04:30 2009 +0200

    dect: mac_csf: properly handle dropped C_S segments
    
    Reset the TBC's Q2-bit after each transmission to make sure it only reflects
    the state of the last data burst, whether it was received or not.  Currently
    the state is only updated when a packet was received, meaning dropped C_S
    segmented might accidentally get acknowledged.
    
    This allows to keep having the driver drop frames with invalid A-field CRCs
    without having to run an additional timer for verifying whether packets were
    actually received.
    
    Additionally verify that a received segment is in sequence before queuing
    it to avoid queueing duplicated segments after an acknowledgment got lost.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 19d2306a3b3cc21a5a3543f83a8504f8f2280e01
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 17 11:49:27 2009 +0200

    dect: coa: change timing to match behaviour before clock sync change
    
    Previously there was a delay of 5 symbol clocks between LDH DC_THRESHOLD
    and S-field receive, reorder the code so that delay is still there.
    Not sure if it makes any difference, just being cautious.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit be838ce33b4a8a2f290f583e17fb3c987b81a19f
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 17 11:49:13 2009 +0200

    dect: coa: improve IO speed
    
    Use 16 bit writes when copying larger amounts of data. This improves
    both read and write speed by roughly 30% in case of the PCMCIA device.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 8e40dc8f9fd16baca400a3c63634342617de943e
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat May 16 18:07:29 2009 +0200

    dect: revert accidentally committed crap
    
    stacked git messed something up, revert unfinished advanced connection control
    support.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 0177cdf10e2a8d4c8ba8cf09cf132c03ee9dd79c
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat May 16 18:03:59 2009 +0200

    dect: coa: continously synchronize clock with FP in PP mode
    
    Use P_SC on reception in PP mode to keep the clock synchronized to the FP.
    This reduces the phase offset between two coa devices from 15kHz to < 0.5kHz.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 99e9540403352d4b4d568c4b8534f8a2837f986f
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat May 16 13:58:11 2009 +0200

    dect: mac_csf: fix TBC double free
    
    Delete the release timer on destruction to avoid a double free in case
    destruction was triggered by both packet reception and the watchdog
    timer.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 97b9e82acd781c8c897194f7dae1df7096f7ee4a
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 15 17:21:27 2009 +0200

    dect: mac: support advanced connection control
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit a5e6a087ccbfc28035519423e682b94b4dc075c7
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 15 17:21:03 2009 +0200

    dect: coa: rate-limit interrupt-lost notice
    
    During a crash in softirq context the message makes the oops scroll off the
    display.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 648c95c26440ff7e73bb5b62d5d6ea15c84525cc
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 15 17:20:48 2009 +0200

    dect: lapc: fix typo in comment
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 059202f24a9a1ba48e504b11c19df9ffbfe51d46
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 15 15:51:16 2009 +0200

    dect: mac_csf: fix multiframe number system information message
    
    Broadcast the real multiframe number instead of zero.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit ccf1815fcb90006298880da7b0abc752830f4b47
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 15 11:15:47 2009 +0200

    dect: lapc: handle link establishment message retransmissions
    
    Reset the state variables when receiving an I-frame with the NLF flag to
    avoid sequence errors and have the NLF flag set in the response.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 87bd802c444bd9275bfa13c6de547b69b122b76f
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 15 10:35:13 2009 +0200

    dect: lapc: properly retransmit last I-frame on acknowledgment timeout
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 93a96ee4413e49646e6a9340a1e7c7a40f52c4de
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 15 09:41:21 2009 +0200

    dect: lapc: remove tx frame dumping
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 2049b9ed4519187e392b056b5adf131de4c92e3e
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 15 09:15:57 2009 +0200

    dect: lapc: purge retransmission queue on destruction
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit debcb9d12e48ce110e095b31521eb63d7667e795
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 14 21:32:18 2009 +0200

    dect: add missing timer.h includes
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 84ff8a455ae7f06d0232813d3d8461e0347b1911
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 13 14:54:47 2009 +0200

    dect: mac_csf: fix incorrect initialization to self
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d15e9bfc8829b3b8e25b742d3751e66884282248
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 10 18:37:34 2009 +0200

    dect: com-on-air: phase offset calculation
    
    Calculate the phase offset of RX slots and maintain averaged value.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 31a4af0d57931f70433d4fb670bbeb71ccbedc01
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 10 15:21:12 2009 +0200

    dect: phase offset tracking: core part
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 023ab80408770d12810fbcc1b3bd9c2eae27afdd
Author: Patrick McHardy <kaber@trash.net>
Date:   Sat May 9 17:25:55 2009 +0200

    dect: fix frequency formatting strings
    
    The positions after the decimal point should be left-padded with zeros.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 40cfaddc0f53ed7506dc731170438094b0d38a8d
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 8 16:02:39 2009 +0200

    dect: mac: fix RSSI type in struct dect_scan_result
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit db07bac5946da5f6ac2d525db3a9f87416ecca73
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 8 15:15:43 2009 +0200

    dect: ccp: fix function signature mismatch
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 03178200da5152011dd33ca4589d5ae12d91ade6
Author: Patrick McHardy <kaber@trash.net>
Date:   Fri May 8 12:57:22 2009 +0200

    dect: transceiver: release transceiver event structure before processing events
    
    Release the structure before processing the queued events to avoid
    blocking it unnecessarily from re-use the transceiver.
    
    In combination with MAC-layer debugging (which increases latency by
    quite a lot) this decreases busy events to almost none on my notebook.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 9f7de46d4990ba7d2584bccf974f4e938522554e
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 7 17:30:44 2009 +0200

    dect: mac: fix system information definitions
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit c8235a0a0e18b4bd1021401c16d4da586932f6d3
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 7 17:21:24 2009 +0200

    dect: dlc: silence some sparse endianness warnings
    
    The link signature is a big endian value, annotate it and silence the
    remaining warnings.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit cdf5f258eafc301c2a2c863d2ba1b76592eb248a
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 7 16:48:44 2009 +0200

    dect: com-on-air: sparse locking annotations
    
    Add proper annotations to fix sparse warning:
    
    drivers/dect/coa/sc1442x.c:234:13: warning: context imbalance in 'sc1442x_lock_mem' - wrong count at exit
    drivers/dect/coa/sc1442x.c:239:13: warning: context imbalance in 'sc1442x_unlock_mem' - unexpected unlock
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 1e1e4e350080192d9834589a9c689c9b6ba47ba2
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 7 16:23:08 2009 +0200

    dect: com-on-air: fix PCMCIA Type II cards
    
    Apparently the Type II cards don't work properly unless the Codec is
    disabled - scanning, channel hopping etc. is performed, but no FPs
    are found at all.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit d212d4b5c54636f3b78eeb74dcdc17bd69c64459
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 7 15:10:47 2009 +0200

    dect: com-on-air: lock chip memory accesses
    
    Access to the chip memory needs to be serialized to avoid races between
    switch to a bank and actually using it.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 26d0d967632553ebebe2474a7bef420ba423945d
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 7 02:29:36 2009 +0200

    dect: com-on-air: fix up rename
    
    The symbolic names should use an upper case 'X'.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 136a3a280104677955bb8004685a8939335f69c4
Author: Patrick McHardy <kaber@trash.net>
Date:   Thu May 7 02:23:35 2009 +0200

    dect: com-on-air: rename sc14421 to sc1442x
    
    Rename all files, constants and functions to reflect the fact that the
    driver handles the 14424 as well.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e49efc3856707ab6c30bb810b2d74a14be73a197
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 6 19:34:18 2009 +0200

    dect: com-on-air: add PCI driver
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 6cb11b0113bbca415af50b6f4296e3b263481fc5
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 6 19:34:16 2009 +0200

    dect: com-on-air: add PCMCIA driver
    
    Add Com-on-Air PCMCIA driver, based on the original dedected.org driver.
    
    Main changes to the original driver:
      - removal of all DECT protocol specific stuff, use of new driver core
      - cleanup, adjustment to kernel coding style
      - suspend/resume support (needs some more support from the DECT
    			    protocol stack though)
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 87abf8cec493db07887e4277aff860f28dca6cfc
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 6 19:33:52 2009 +0200

    dect: com-on-air: NSC LXM3161 radio support
    
    Add unfinished support for the NSC LMX3161 Single Chip Radio Transceiver.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 3f22863f322475ca1adf0f4435c2972df753b1da
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 6 19:33:52 2009 +0200

    dect: com-on-air: atmel u2785 radio support
    
    Add support for the ATMEL U2785 RF IC.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit e2bc8bd43a421a816024b9ae29d4feb3f6136b5e
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 6 19:33:52 2009 +0200

    dect: com-on-air: support for rebuilding firmware
    
    Add the Com-on-Air firmware source and support for rebuilding it during
    the kernel build process.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit dfdc90c5210bae349a4168b5053ecf7de0065a9a
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 6 19:33:51 2009 +0200

    dect: com-on-air: device driver core
    
    Add the chip-specific parts of the com-on-air driver.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 28c157ecb4bacb660fdda5f47290ae89c90dcb0d
Author: Patrick McHardy <kaber@trash.net>
Date:   Wed May 6 19:30:29 2009 +0200

    dect: add DECT protocol core
    
    Add an (so far unfunctional) DECT protocol core and a basic DECT netlink
    socket family.
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>

commit 6c2fa86bf41e8201dbe71922ce9c2d3ca77614cf
Author: Patrick McHardy <kaber@trash.net>
Date:   Sun May 3 12:17:08 2009 +0200

    netlink: constify nlh argument to nlmsg_report()
    
    Signed-off-by: Patrick McHardy <kaber@trash.net>
diff --git a/MAINTAINERS b/MAINTAINERS
index 69f19f1..f9541f6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2080,6 +2080,14 @@ S:	Orphan
 F:	Documentation/networking/decnet.txt
 F:	net/decnet/
 
+DECT NETWORK PROTOCOL
+M:	Patrick McHardy <kaber@trash.net>
+S:	Maintained
+F:	net/dect
+F:	drivers/dect
+F:	include/net/dect
+F:	include/linux/dect*.h
+
 DEFXX FDDI NETWORK DRIVER
 M:	"Maciej W. Rozycki" <macro@linux-mips.org>
 S:	Maintained
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 177c7d1..3d25063 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -40,6 +40,8 @@ source "drivers/net/Kconfig"
 
 source "drivers/isdn/Kconfig"
 
+source "drivers/dect/Kconfig"
+
 source "drivers/telephony/Kconfig"
 
 # input before char - char/joystick depends on it. As does USB.
diff --git a/drivers/Makefile b/drivers/Makefile
index 3f135b6..60807cf 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -86,6 +86,7 @@ obj-$(CONFIG_MD)		+= md/
 obj-$(CONFIG_BT)		+= bluetooth/
 obj-$(CONFIG_ACCESSIBILITY)	+= accessibility/
 obj-$(CONFIG_ISDN)		+= isdn/
+obj-$(CONFIG_DECT)		+= dect/
 obj-$(CONFIG_EDAC)		+= edac/
 obj-$(CONFIG_MCA)		+= mca/
 obj-$(CONFIG_EISA)		+= eisa/
diff --git a/drivers/dect/Kconfig b/drivers/dect/Kconfig
new file mode 100644
index 0000000..baba4c1
--- /dev/null
+++ b/drivers/dect/Kconfig
@@ -0,0 +1,11 @@
+menuconfig DECTDEVICES
+	bool "DECT device support"
+	help
+	  Say Y here to show DECT device driver options.
+
+if DECTDEVICES
+
+source "drivers/dect/vtrx/Kconfig"
+source "drivers/dect/coa/Kconfig"
+
+endif
diff --git a/drivers/dect/Makefile b/drivers/dect/Makefile
new file mode 100644
index 0000000..c58af58
--- /dev/null
+++ b/drivers/dect/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_DECT_VTRX)	+= vtrx/
+obj-$(CONFIG_DECT_COA)	+= coa/
diff --git a/drivers/dect/coa/.gitignore b/drivers/dect/coa/.gitignore
new file mode 100644
index 0000000..7890f99
--- /dev/null
+++ b/drivers/dect/coa/.gitignore
@@ -0,0 +1,4 @@
+bin2c
+*.p
+*.h.tmp
+*.bin
diff --git a/drivers/dect/coa/Kconfig b/drivers/dect/coa/Kconfig
new file mode 100644
index 0000000..8773ad7
--- /dev/null
+++ b/drivers/dect/coa/Kconfig
@@ -0,0 +1,38 @@
+config DECT_COA_PCI
+	tristate "Com-on-Air PCI DECT support"
+	depends on DECT
+	depends on PCI
+	select DECT_COA
+	select DECT_COA_U2785
+	help
+	  This option enables support for the Com-on-Air DECT PCI devices.
+
+config DECT_COA_CS
+	tristate "Com-on-Air PCMCIA DECT support"
+	depends on DECT
+	depends on PCMCIA
+	select DECT_COA
+	select DECT_COA_U2785
+	select DECT_COA_LMX3161
+	select CRC32
+	help
+	  This option enables support for the Com-on-Air DECT PCMCIA devices.
+
+config DECT_COA
+	tristate
+
+config DECT_COA_U2785
+	bool
+
+config DECT_COA_LMX3161
+	bool
+
+config DECT_COA_FIRMWARE
+	bool "Build Com-on-Air firmware (requires ASL macro assembler)"
+	depends on DECT_COA
+	help
+	  This option enables rebuild of the firmware for the Com-on-Air
+	  devices during the kernel build process. The ASL macro compiler
+	  is required for this.
+
+	  If unsure, say N.
diff --git a/drivers/dect/coa/Makefile b/drivers/dect/coa/Makefile
new file mode 100644
index 0000000..8ac23c3
--- /dev/null
+++ b/drivers/dect/coa/Makefile
@@ -0,0 +1,41 @@
+com_on_air-objs				:= sc1442x_firmware.o sc1442x.o
+com_on_air-$(CONFIG_DECT_COA_U2785)	+= radio_u2785.o
+com_on_air-$(CONFIG_DECT_COA_LMX3161)	+= radio_lmx3161.o
+
+obj-$(CONFIG_DECT_COA)			+= com_on_air.o
+obj-$(CONFIG_DECT_COA_PCI)		+= com_on_air_pci.o
+obj-$(CONFIG_DECT_COA_CS)		+= com_on_air_cs.o
+
+$(obj)/sc1442x.o:			$(obj)/sc1442x_firmware.c
+$(obj)/sc1442x_firmware.c:		NAME=sc1442x
+clean-files				+= sc1442x_firmware.p
+clean-files				+= sc1442x_firmware.bin
+clean-files				+= sc1442x_firmware.h.tmp
+
+hostprogs-$(CONFIG_DECT_COA_FIRMWARE)	+= bin2c
+
+ifeq ($(CONFIG_DECT_COA_FIRMWARE),y)
+
+ASL		= asl
+P2BIN		= p2bin
+BIN2C		= $(obj)/bin2c
+
+quiet_cmd_asl	= ASL     $<
+      cmd_asl	= $(ASL) -q -c $< -o $(<:.asm=.p) -shareout $(<:.asm=.h.tmp); \
+		  $(P2BIN) $(<:.asm=.p) $(<:.asm=.bin) -r 0-509; \
+		  $(BIN2C) $(<:.asm=.bin) $(NAME)_firmware > $@; \
+		  ( \
+			echo "\#ifndef $$(echo $(NAME) | tr a-z A-Z)_FIRMWARE"; \
+			echo "\#define $$(echo $(NAME) | tr a-z A-Z)_FIRMWARE"; \
+			echo;\
+			echo "extern const unsigned char $(NAME)_firmware[510];"; \
+			echo;\
+			grep define $(<:.asm=.h.tmp); \
+			echo;\
+			echo "\#endif /* $$(echo $(NAME) | tr a-z A-Z)_FIRMWARE */"; \
+		  ) > $(@:.c=.h)
+
+$(obj)/%_firmware.c: $(src)/%_firmware.asm $(BIN2C)
+	$(call if_changed,asl)
+
+endif
diff --git a/drivers/dect/coa/bin2c.c b/drivers/dect/coa/bin2c.c
new file mode 100644
index 0000000..bab08fa
--- /dev/null
+++ b/drivers/dect/coa/bin2c.c
@@ -0,0 +1,57 @@
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define HEADER_FMT \
+	"/*\n"						\
+	" * automatically generated file\n"		\
+	" * DO NOT EDIT\n"				\
+	" * edit firmware/filename.asm instead\n"	\
+	" */\n"						\
+	"\n"						\
+	"#include \"%s.h\"\n"				\
+	"\n"						\
+	"const unsigned char %s[] = {\n"
+
+#define FOOTER "};\n"
+
+int main(int argc, char *argv[])
+{
+	uint32_t wordcount = 0;
+	uint16_t w;
+	int f;
+
+	if (argc < 3) {
+		printf("usage: bin2c bin-file varname > c-file\n");
+		exit(1);
+	}
+
+	f = open(argv[1], O_RDONLY);
+	if (f < 0) {
+		printf("cant open(\"%s\"): %s\n", argv[1], strerror(errno));
+		exit(1);
+	}
+
+	printf(HEADER_FMT, argv[2], argv[2]);
+
+	while (2 == read(f, &w, 2)) {
+		if (!wordcount)
+			printf("\t");
+		else
+			if (!(wordcount % 4))
+				printf(",\n\t");
+			else
+				printf(", ");
+		printf("0x%.2x, 0x%.2x", (w & 0xff00) >> 8, w & 0xff);
+		wordcount++;
+	}
+	printf(FOOTER);
+	close(f);
+	return 0;
+}
diff --git a/drivers/dect/coa/com_on_air.h b/drivers/dect/coa/com_on_air.h
new file mode 100644
index 0000000..37c2b0a
--- /dev/null
+++ b/drivers/dect/coa/com_on_air.h
@@ -0,0 +1,99 @@
+/*
+ * com_on_air - basic driver for the Dosch and Amand "com on air" cards
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * authors:
+ * (C) 2008  Andreas Schuler <krater at badterrorist dot com>
+ * (C) 2008  Matthias Wenzel <dect at mazzoo dot de>
+ * (C) 2009  Patrick McHardy <kaber@trash.net>
+ *
+ */
+
+#ifndef COM_ON_AIR_H
+#define COM_ON_AIR_H
+
+#include <linux/types.h>
+
+struct coa_freq_map_entry {
+	struct {
+		u8	divisor;
+		u8	swcnt;
+	} rx, tx;
+};
+
+struct coa_freq_map {
+	struct coa_freq_map_entry	carrier[DECT_CARRIER_NUM];
+};
+
+struct coa_device;
+struct coa_radio_ops {
+	void		(*rx_init)(const struct coa_device *dev, u16 offset);
+	void		(*tx_init)(const struct coa_device *dev, u16 offset);
+	void		(*set_carrier)(const struct coa_device *dev, u16 offset,
+				       enum dect_slot_states mode, u8 carrier);
+	u64		(*map_band)(struct coa_device *dev,
+				    const struct dect_band *band);
+	const char	*type;
+};
+
+extern const struct coa_radio_ops coa_u2785_radio_ops;
+extern const struct coa_radio_ops coa_lmx3161_radio_ops;
+
+/**
+ * struct sc1442x_phase_state - per-slot phase offset state
+ *
+ * @framenum:	frame number the information was last updated
+ * @tap:	sc1442x internal clock cycle which sampled the data
+ * @phase:	offset of number of symbol periods to nominal 11520 symbols per frame
+ *
+ * This structure is used to store the measured values for one particular
+ * frame. The actual phase offset is calculated from the differences of two
+ * consequitive frames.
+ */
+struct sc1442x_phase_state {
+	u8	framenum;
+	u8	tap;
+	s8	phase;
+};
+
+enum coa_device_types {
+	COA_TYPE_PCI,
+	COA_TYPE_PCMCIA,
+};
+
+struct coa_device {
+	const struct device		*dev;
+	unsigned int			irq;
+
+	enum coa_device_types		type;
+
+	const struct coa_radio_ops	*radio_ops;
+	struct coa_freq_map		freq_map;
+	struct sc1442x_phase_state	phase_state[DECT_FRAME_SIZE / 2];
+
+	spinlock_t			lock;
+	uint				config_base;
+	u8 __iomem			*sc1442x_base;
+	u16				cfg_reg;
+	u16				irq_reg;
+	u16				code_base;
+	u16				data_base;
+	u16				data_mask;
+
+	u8				ctrl;
+	u8				led;
+};
+
+extern irqreturn_t sc1442x_interrupt(int irq, void *dev_id);
+extern const struct dect_transceiver_ops sc1442x_transceiver_ops;
+
+extern int sc1442x_init_device(struct coa_device *dev);
+extern void sc1442x_shutdown_device(struct coa_device *dev);
+
+extern void sc1442x_rfdesc_write(const struct coa_device *dev, u16 offset,
+				 const u8 *src, u16 length);
+
+#endif
diff --git a/drivers/dect/coa/com_on_air_cs.c b/drivers/dect/coa/com_on_air_cs.c
new file mode 100644
index 0000000..e1f2231
--- /dev/null
+++ b/drivers/dect/coa/com_on_air_cs.c
@@ -0,0 +1,271 @@
+/*
+ * com_on_air_cs - basic driver for the Dosch and Amand "com on air" cards
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * authors:
+ * (C) 2008  Andreas Schuler <krater at badterrorist dot com>
+ * (C) 2008  Matthias Wenzel <dect at mazzoo dot de>
+ * (C) 2009  Patrick McHardy <kaber@trash.net>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/crc32.h>
+#include <net/dect/transceiver.h>
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ciscode.h>
+#include <pcmcia/ds.h>
+#include <pcmcia/cisreg.h>
+
+#include "com_on_air.h"
+
+MODULE_AUTHOR("Matthias Wenzel comonair<a>mazzoo.de;"
+              "Andreas Schuler dect<a>badterrorist.com");
+MODULE_DESCRIPTION("Dosch&Amand COM-ON-AIR PCMCIA driver");
+MODULE_LICENSE("GPL");
+
+static int get_card_id(const struct pcmcia_device *link);
+
+static int com_on_air_probe(struct pcmcia_device *link)
+{
+	struct dect_transceiver *trx;
+	struct coa_device *dev;
+	int err;
+
+	trx = dect_transceiver_alloc(&sc1442x_transceiver_ops, sizeof(*dev));
+	if (!trx) {
+		err = -ENOMEM;
+		goto err1;
+	}
+
+	link->priv = trx;
+	dev = dect_transceiver_priv(trx);
+	dev->type      = COA_TYPE_PCMCIA;
+	dev->code_base = 0x0;
+	dev->data_base = 0x0;
+	dev->data_mask = 0x0ff;
+	dev->cfg_reg   = 0x1ff;
+	dev->irq_reg   = 0x0;
+	dev->dev       = &link->dev;
+
+	dev_info(dev->dev, "%s %s %s %s\n", link->prod_id[0], link->prod_id[1],
+		 link->prod_id[2] ? : "", link->prod_id[3] ? : "");
+
+	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+	link->resource[0]->end	  = 16;
+	link->resource[1]->flags |= 0;
+
+	link->config_flags	  = CONF_ENABLE_IRQ;
+	link->config_index	  = 1;
+	link->config_regs	  = PRESENT_OPTION;
+	link->config_base	  = 0x1020;
+
+	link->resource[2]->flags  = WIN_DATA_WIDTH_16 | WIN_ENABLE;
+	link->resource[2]->start  = 0;
+	link->resource[2]->end	  = 0x1000;
+
+	err = pcmcia_request_window(link, link->resource[2], 500);
+	if (err < 0) {
+		dev_err(dev->dev, "failed to obtain PCMCIA window\n");
+		goto err2;
+	}
+
+	dev->sc1442x_base = ioremap_nocache(link->resource[2]->start,
+					    resource_size(link->resource[2]));
+	if (!dev->sc1442x_base) {
+		dev_err(dev->dev, "failed to remap PCMCIA resource\n");
+		err = -EIO;
+		goto err3;
+	}
+
+	link->socket->functions = 0;
+
+	err = pcmcia_request_irq(link, sc1442x_interrupt);
+	if (err < 0) {
+		dev_err(dev->dev, "failed to request IRQ%d\n", link->irq);
+		goto err4;
+	}
+
+	err = pcmcia_enable_device(link);
+	if (err < 0) {
+		dev_err(dev->dev, "failed to enable PCMCIA device\n");
+		goto err5;
+	}
+
+	dev_dbg(dev->dev, "%svalid client.\n", (link->config_flags) ? "":"in");
+	dev_dbg(dev->dev, "Type          0x%x\n", link->socket->state);
+	dev_dbg(dev->dev, "Function      0x%x\n", link->func);
+	dev_dbg(dev->dev, "config_flags  %d\n", link->config_flags);
+	dev_dbg(dev->dev, "config_base   0x%x\n", link->config_base);
+	dev_dbg(dev->dev, "config_regs   %d\n", link->config_regs);
+	dev_dbg(dev->dev, "IRQ           0x%x\n", link->irq);
+	dev_dbg(dev->dev, "BasePort1     0x%llx\n", link->resource[0]->start);
+	dev_dbg(dev->dev, "NumPorts1     0x%llx\n", link->resource[0]->end);
+	dev_dbg(dev->dev, "Attributes1   0x%lx\n", link->resource[0]->flags);
+	dev_dbg(dev->dev, "BasePort2     0x%llx\n", link->resource[1]->start);
+	dev_dbg(dev->dev, "NumPorts2     0x%llx\n", link->resource[1]->end);
+	dev_dbg(dev->dev, "Attributes2   0x%lx\n", link->resource[1]->flags);
+	dev_dbg(dev->dev, "IOAddrLines   0x%x\n", link->io_lines);
+	dev_dbg(dev->dev, "has%s function_config\n",
+		link->function_config ? "":" no");
+
+	switch (get_card_id(link)) {
+	case 0:
+	case 3:
+		dev->radio_ops = &coa_u2785_radio_ops;
+		break;
+	case 1:
+	case 2:
+		dev->radio_ops = &coa_lmx3161_radio_ops;
+		break;
+	default:
+		dev_err(dev->dev, "unknown radio type\n");
+		err = -EINVAL;
+		goto err5;
+	}
+
+	dev_info(dev->dev, "Radio type %s\n", dev->radio_ops->type);
+
+	dev->irq	 = link->irq;
+	dev->config_base = link->config_base;
+	err = sc1442x_init_device(dev);
+	if (err < 0)
+		goto err5;
+
+	err = dect_register_transceiver(trx);
+	if (err < 0)
+		goto err6;
+
+	return 0;
+
+err6:
+	sc1442x_shutdown_device(dev);
+err5:
+	pcmcia_disable_device(link);
+err4:
+	iounmap(dev->sc1442x_base);
+err3:
+	pcmcia_release_window(link, link->resource[2]);
+err2:
+	dect_transceiver_free(trx);
+err1:
+	return err;
+}
+
+static void com_on_air_remove(struct pcmcia_device *link)
+{
+	struct dect_transceiver *trx = link->priv;
+	struct coa_device *dev = dect_transceiver_priv(trx);
+	u8 __iomem *sc1442x_base = dev->sc1442x_base;
+
+	sc1442x_shutdown_device(dev);
+	pcmcia_disable_device(link);
+	dect_unregister_transceiver(trx);
+	iounmap(sc1442x_base);
+}
+
+static int com_on_air_suspend(struct pcmcia_device *link)
+{
+	struct dect_transceiver *trx = link->priv;
+	struct coa_device *dev = dect_transceiver_priv(trx);
+
+	sc1442x_shutdown_device(dev);
+	return 0;
+}
+
+static int com_on_air_resume(struct pcmcia_device *link)
+{
+	struct dect_transceiver *trx = link->priv;
+	struct coa_device *dev = dect_transceiver_priv(trx);
+
+	return sc1442x_init_device(dev);
+}
+
+static struct pcmcia_device_id com_on_air_ids[] = {
+	/*
+	 * The crc32 hashes below are generated by the tool in
+	 * Documentation/pcmcia/devicetable.txt
+	 */
+	PCMCIA_DEVICE_PROD_ID12  ("DECTDataDevice", "PCMCIA F22",
+			           0x11fe69e9,       0x253670b2),
+	PCMCIA_DEVICE_PROD_ID12  ("DECTDataDevice", "PCMCIA",
+			           0x11fe69e9,       0x281f1c5d),
+	PCMCIA_DEVICE_PROD_ID1234("DOSCH-AMAND",    "MMAP PCMCIA",
+			          "MXM500",         "V1.00",
+				   0x4bc552e7,       0x0df519bb,
+				   0x09e43c7c,       0x3488c81a),
+	PCMCIA_DEVICE_PROD_ID12  ("DECTVoIPDevice", "PCMCIA DA099",
+				   0xeabb0be4,       0xd7b915fe),
+#if 0
+	There are more devices out there, I only own the above three.
+	an excerpt from win32 dna.inf:
+
+%String1%=pcmcia.install,PCMCIA\DOSCH-AMAND-MMAP_PCMCIA-C7D7
+%String1%=pcmcia.install,PCMCIA\Dosch-Amand-DECT_MultiMedia-BD0D
+%String1%=pcmcia.install,PCMCIA\DOSCH_&_AMAND-DECT_MULTIMEDIA-1A9F
+%String1%=pcmcia.install,PCMCIA\DECTDataDevice-F13-6433
+%String1%=pcmcia.install,PCMCIA\DECTDataDevice-PCMCIA-0EF8
+%String4%=pci.install,PCI\VEN_11E3&DEV_0001&SUBSYS_000111E3&REV_00
+%String4%=pci.install,PCI\VEN_11E3&DEV_0001&SUBSYS_00011786&REV_32
+%String4%=pci.install,PCI\VEN_1786&DEV_0001&SUBSYS_000111E3&REV_00
+%String5%=freekey2.install,PCMCIA\DECTDataDevice-PCMCIA-FEF2
+%String6%=freekey2.install,PCMCIA\DECTDataDevice-PCMCIA_F22-4BD3
+%String6%=freekey2.install,PCMCIA\DECTDataDevice-PCMCIA_F22-BBD9
+
+#endif
+	PCMCIA_DEVICE_NULL
+};
+
+MODULE_DEVICE_TABLE(pcmcia, com_on_air_ids);
+
+/* returns an index into com_on_air_ids[] */
+static int get_card_id(const struct pcmcia_device *link)
+{
+	u32 hash[4] = {};
+	unsigned int i;
+
+	for (i = 0; i < 4; i++) {
+		if (link->prod_id[i] == NULL)
+			continue;
+		hash[i] = crc32(0, link->prod_id[i], strlen(link->prod_id[i]));
+	}
+
+	for (i = 0; i < ARRAY_SIZE(com_on_air_ids) - 1; i++) {
+		if ((hash[0] == com_on_air_ids[i].prod_id_hash[0]) &&
+		    (hash[1] == com_on_air_ids[i].prod_id_hash[1]) &&
+		    (hash[2] == com_on_air_ids[i].prod_id_hash[2]) &&
+		    (hash[3] == com_on_air_ids[i].prod_id_hash[3]))
+			return i;
+	}
+	return -1;
+}
+
+static struct pcmcia_driver coa_driver = {
+	.owner		= THIS_MODULE,
+	.name		= KBUILD_MODNAME,
+	.probe		= com_on_air_probe,
+	.remove		= com_on_air_remove,
+	.suspend	= com_on_air_suspend,
+	.resume		= com_on_air_resume,
+	.id_table	= com_on_air_ids,
+};
+
+static int __init init_com_on_air_cs(void)
+{
+	return pcmcia_register_driver(&coa_driver);
+}
+
+static void __exit exit_com_on_air_cs(void)
+{
+	pcmcia_unregister_driver(&coa_driver);
+}
+
+module_init(init_com_on_air_cs);
+module_exit(exit_com_on_air_cs);
diff --git a/drivers/dect/coa/com_on_air_pci.c b/drivers/dect/coa/com_on_air_pci.c
new file mode 100644
index 0000000..5c79ba0
--- /dev/null
+++ b/drivers/dect/coa/com_on_air_pci.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <net/dect/transceiver.h>
+
+#include "com_on_air.h"
+
+#define PCI_VENDOR_ID_QUICKLOGIC	0x11e3
+#define PCI_DEVICE_ID_COA		0x0001
+
+static int __devinit coa_probe(struct pci_dev *pdev,
+			       const struct pci_device_id *ent)
+{
+	struct dect_transceiver *trx;
+	struct coa_device *dev;
+	void __iomem *base;
+	int err;
+
+	err = pci_enable_device(pdev);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to enable PCI device\n");
+		goto err1;
+	}
+	pci_set_master(pdev);
+
+	err = pci_request_regions(pdev, KBUILD_MODNAME);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to obtain PCI resources\n");
+		goto err2;
+	}
+
+	base = ioremap_nocache(pci_resource_start(pdev, 0),
+			       pci_resource_len(pdev, 0));
+	if (base == NULL) {
+		dev_err(&pdev->dev, "failed to remap PCI resource\n");
+		err = -EIO;
+		goto err3;
+	}
+
+	trx = dect_transceiver_alloc(&sc1442x_transceiver_ops, sizeof(*dev));
+	if (trx == NULL) {
+		err = -ENOMEM;
+		goto err4;
+	}
+	pci_set_drvdata(pdev, trx);
+
+	dev = dect_transceiver_priv(trx);
+	dev->type	  = COA_TYPE_PCI;
+	dev->dev	  = &pdev->dev;
+	dev->sc1442x_base = base;
+	dev->radio_ops    = &coa_u2785_radio_ops;
+	dev->data_base	  = 0x0a00;
+	dev->data_mask	  = 0x7ff;
+	dev->cfg_reg	  = 0x1fe2;
+	dev->code_base	  = 0x1a00;
+
+	err = sc1442x_init_device(dev);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to initialize chip\n");
+		goto err5;
+	}
+
+	err = request_irq(pdev->irq, sc1442x_interrupt, IRQF_SHARED,
+			  KBUILD_MODNAME, trx);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to request IRQ%d\n", pdev->irq);
+		goto err6;
+	}
+
+	dev->irq = pdev->irq;
+	err = dect_register_transceiver(trx);
+	if (err < 0)
+		goto err7;
+
+	return 0;
+
+err7:
+	free_irq(pdev->irq, trx);
+err6:
+	sc1442x_shutdown_device(dev);
+err5:
+	dect_transceiver_free(trx);
+err4:
+	iounmap(base);
+err3:
+	pci_release_regions(pdev);
+err2:
+	pci_disable_device(pdev);
+err1:
+	return err;
+}
+
+static void __devexit coa_remove(struct pci_dev *pdev)
+{
+	struct dect_transceiver *trx = pci_get_drvdata(pdev);
+	struct coa_device *dev = dect_transceiver_priv(trx);
+	u8 __iomem *sc1442x_base = dev->sc1442x_base;
+
+	sc1442x_shutdown_device(dev);
+	free_irq(pdev->irq, trx);
+	dect_unregister_transceiver(trx);
+	iounmap(sc1442x_base);
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+}
+
+static int coa_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	struct dect_transceiver *trx = pci_get_drvdata(pdev);
+	struct coa_device *dev = dect_transceiver_priv(trx);
+
+	sc1442x_shutdown_device(dev);
+	pci_save_state(pdev);
+	return 0;
+}
+
+static int coa_resume(struct pci_dev *pdev)
+{
+	struct dect_transceiver *trx = pci_get_drvdata(pdev);
+	struct coa_device *dev = dect_transceiver_priv(trx);
+
+	pci_restore_state(pdev);
+	return sc1442x_init_device(dev);
+}
+
+static DEFINE_PCI_DEVICE_TABLE(coa_pci_tbl) = {
+	{PCI_DEVICE(PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_COA)},
+	{}
+};
+
+static struct pci_driver coa_driver = {
+	.name		= KBUILD_MODNAME,
+	.id_table	= coa_pci_tbl,
+	.probe		= coa_probe,
+	.remove		= __devexit_p(coa_remove),
+	.suspend	= coa_suspend,
+	.resume		= coa_resume,
+};
+
+static int __init coa_pci_init(void)
+{
+	return pci_register_driver(&coa_driver);
+}
+
+static void __exit coa_pci_exit(void)
+{
+	pci_unregister_driver(&coa_driver);
+}
+
+module_init(coa_pci_init);
+module_exit(coa_pci_exit);
+
+MODULE_DESCRIPTION("Dosch&Amand COM-ON-AIR PCI driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, coa_pci_tbl);
diff --git a/drivers/dect/coa/dip_opcodes.h b/drivers/dect/coa/dip_opcodes.h
new file mode 100644
index 0000000..bd50056
--- /dev/null
+++ b/drivers/dect/coa/dip_opcodes.h
@@ -0,0 +1,157 @@
+/*
+ * com_on_air - basic driver for the Dosch and Amand "com on air" cards
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * authors:
+ * (C) 2008  Andreas Schuler <krater at badterrorist dot com>
+ * (C) 2008  Matthias Wenzel <dect at mazzoo dot de>
+ *
+ */
+
+#ifndef DIP_OPCODE_H
+#define DIP_OPCODE_H
+
+#define	BR		0x01
+#define	JMP		0x02
+#define	JMP1		0x03
+#define	RTN		0x04
+#define	BK_A1		0x05
+#define	WNTM1		0x06
+#define	WNTP1		0x07
+#define	WNT		0x08
+#define	WT		0x09
+#define	RFDIS		0x0a
+#define	RFEN		0x0b
+#define	LD_PTR		0x0c
+#define	SLOTZERO	0x0d
+#define	BK_A		0x0e
+#define	BK_C		0x0f
+
+
+#define	B_RST		0x20
+#define	B_ST2		0x21
+#define	B_XT		0x24
+#define	B_BT2		0x25
+#define	B_BTFU		0x25
+#define	B_XOFF		0x26
+#define	B_ON		0x27
+#define	B_XON		0x27
+#define	UNLCK		0x28
+#define	B_SR		0x29
+#define	B_XR		0x2b
+#define	EN_SL_ADJ	0x2c
+#define	B_BR2		0x2d
+#define	B_BRFU		0x2d
+#define	B_RINV		0x2e
+#define	B_RON		0x2f
+
+
+#define	B_ST		0x31
+#define	B_TX		0x31
+#define	B_AT		0x32
+#define	B_RC		0x33
+#define	B_BT		0x34
+#define	B_BTFP		0x35
+#define	B_BTP		0x35
+#define	B_AT2		0x37
+#define	B_WRS		0x39
+#define	B_AR		0x3a
+#define	B_BR		0x3c
+#define	B_BRP		0x3d
+#define	B_BRFP		0x3d
+#define	B_AR2		0x3f
+
+
+#define	D_RST		0x40
+#define	D_ON		0x42
+#define	D_OFF		0x43
+#define	D_PREP		0x44
+#define	WSC		0x48
+
+
+#define	D_LDK		0x50
+#define	D_LDS		0x57
+#define	D_WRS		0x5f
+
+
+#define	U_PSC		0x60
+#define	U_INT0		0x61
+#define	RCK_INT		0x62
+#define	RCK_EXT		0x63
+#define	B_WB_OFF	0x64
+#define	B_WB_ON		0x65
+#define	CLK1		0x66
+#define	CLK3		0x67
+#define	U_CK8		0x68
+#define	U_CK4		0x69
+#define	U_CK2		0x6a
+#define	U_INT1		0x6b
+#define	U_CK1		0x6c
+#define	U_INT2		0x6d
+#define	U_INT3		0x6f
+
+
+#define	A_RCV0		0x80
+#define	A_RCV36		0x82
+#define	A_RCV30		0x83
+#define	A_RCV24		0x84
+#define	A_RCV18		0x85
+#define	A_RCV12		0x86
+#define	A_RCV6		0x87
+#define	A_RCV33		0x8a
+#define	A_RCV27		0x8b
+#define	A_RCV21		0x8c
+#define	A_RCV15		0x8d
+#define	A_RCV9		0x8e
+#define	A_RCV3		0x8f
+
+
+#define	MEN3N		0xa2
+#define	MEN3		0xa3
+#define	MEN1N		0xa4
+#define	MEN1		0xa5
+#define	MEN2N		0xa6
+#define	MEN2		0xa7
+#define	M_RD		0xa8
+#define	M_RST		0xa9
+
+
+#define	M_WRS		0xb8
+#define	M_WR		0xb9
+
+
+#define	A_RST		0xc0
+#define	A_MUTE		0xc1
+#define	A_STOFF		0xc2
+#define	A_ALAW		0xc3
+#define	A_DT		0xc4
+#define	A_NORM		0xc5
+#define	A_LDR		0xc6
+#define	A_LDW		0xc7
+#define	A_LIN		0xc8
+#define	A_MTOFF		0xc9
+#define	A_MUTE1		0xca
+#define	A_MTOFF1	0xcb
+#define	A_STON		0xcc
+#define	A_DT1		0xcd
+#define	A_LDR1		0xce
+#define	A_LDW1		0xcf
+
+
+#define	A_STRN		0xe0
+#define	P_LD		0xe8
+#define	P_EN		0xe9
+#define	P_SC		0xea
+#define	A_RST1		0xeb
+#define	P_LDL		0xec
+#define	P_LDH		0xed
+#define	C_ON		0xee
+#define	C_OFF		0xef
+
+
+#define	C_LD		0xfa
+
+#endif
diff --git a/drivers/dect/coa/radio_lmx3161.c b/drivers/dect/coa/radio_lmx3161.c
new file mode 100644
index 0000000..9f6d5ae
--- /dev/null
+++ b/drivers/dect/coa/radio_lmx3161.c
@@ -0,0 +1,84 @@
+/*
+ * radio_lmx3161 - NSC LMX3161 Single Chip Radio Transceiver radio operations
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+#include <net/dect/transceiver.h>
+
+#include "com_on_air.h"
+
+/* Intermediate frequency */
+#define RADIO_LMX3161_FREQ_IF	110592	/* kHz */
+
+/*
+ * Control Bits
+ */
+
+/* N-counter */
+#define RADIO_LMX3161_CTRL_N	0x0
+/* R-counter */
+#define RADIO_LMX3161_CTRL_R	0x2
+/* F-latch */
+#define RADIO_LMX3161_CTRL_F	0x1
+
+/*
+ * Function Register (18 bit F-latch)
+ */
+
+/* Prescaler modules select */
+#define RADIO_LMX3161_PRESCALER_32_33
+#define RADIO_LMX3161_PRESCALER_64_65
+/* Phase detector polarity: 0 = negative, 1 = positive */
+#define RADIO_LMX3161_PD		(1 << 3)
+/* Charge pump current gain select: 0 = LOW (1*I_cpo), 1 = high (4*I_cpo) */
+#define RADIO_LMX3161_CP		(1 << 4)
+/* tri-state charge pump output: 0 = normal, 1 = tri-state */
+#define RADIO_LMX3161_CP_TRISTATE	(1 << 5)
+/* Receive chain power down control: 0 = power up, 1 = power down */
+#define RADIO_LMX3161_RX_POWER		(1 << 7)
+/* Transmit chain power down control: 0 = power up, 1 = power down */
+#define RADIO_LMX3161_TX_POWER		(1 << 8)
+/* Out 0 CMOS output: 0 = low, 1 = high */
+#define RADIO_LMX3161_CMOS0A		(1 << 9)
+/* Out 1 CMOS output: 0 = low, 1 = high */
+#define RADIO_LMX3161_CMOS1		(1 << 10)
+/* Out 2 CMOS output: 0 = low, 1 = high */
+#define RADIO_LMX3161_CMOS2		(1 << 11)
+/* Power down mode select: */
+#define RADIO_LMX3161_POWER_DOWN_MASK		(0x3 << 12)
+#define RADIO_LMX3161_POWER_DOWN_SW		0
+#define RADIO_LMX3161_POWER_DOWN_HARDWIRE	(0x3 << 12)
+/* Demodulator gain select */
+/* Demodulator DC level shifting polarity */
+/* Demodulator DC level shift */
+
+static u64 lmx3161_map_band(struct coa_device *dev, const struct dect_band *band)
+{
+	struct coa_freq_map_entry *fe;
+	u32 frequency;
+	u8 carrier;
+
+	for (carrier = 0; carrier < band->carriers; carrier++) {
+		frequency = band->frequency[carrier];
+		fe = &dev->freq_map.carrier[carrier];
+	}
+	return 0;
+}
+
+const struct coa_radio_ops coa_lmx3161_radio_ops = {
+	.type		= "LMX3161",
+	.rx_init	= NULL,
+	.tx_init	= NULL,
+	.set_carrier	= NULL,
+	.map_band	= lmx3161_map_band,
+};
+EXPORT_SYMBOL_GPL(coa_lmx3161_radio_ops);
diff --git a/drivers/dect/coa/radio_u2785.c b/drivers/dect/coa/radio_u2785.c
new file mode 100644
index 0000000..dab53a0
--- /dev/null
+++ b/drivers/dect/coa/radio_u2785.c
@@ -0,0 +1,261 @@
+/*
+ * radio_u2785 - ATMEL U2785 RF IC radio operations
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ */
+
+//#define DEBUG
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+#include <net/dect/transceiver.h>
+
+#include "com_on_air.h"
+
+#define u2785_debug(dev, fmt, args...) \
+	dev_dbg(dev->dev, "u2785: " fmt, ## args)
+
+/* Intermediate frequencies */
+#define RADIO_U2785_FREQ_IF1	110592	/* kHz */
+#define RADIO_U2785_FREQ_IF2	112320	/* kHz */
+
+/*
+ *  RC (Reference Divider)
+ */
+#define RADIO_U2785_RC_SHIFT	22
+#define RADIO_U2785_RC_12	(0x1 << RADIO_U2785_RC_SHIFT)
+#define RADIO_U2785_RC_16	(0x2 << RADIO_U2785_RC_SHIFT)
+#define RADIO_U2785_RC_24	(0x3 << RADIO_U2785_RC_SHIFT)
+
+/*
+ * SC (Swallow Counter) 0-31
+ */
+#define RADIO_U2785_SC_SHIFT	17
+#define RADIO_U2785_SC_MAX	31
+#define RADIO_U2785_SC_MASK	(0x1F << RADIO_U2785_SC_SHIFT)
+
+/*
+ *  MC (Main Divider)
+ */
+#define RADIO_U2785_MC_SHIFT	15
+#define RADIO_U2785_MC_MIN	31
+#define RADIO_U2785_MC_MAX	34
+#define RADIO_U2785_MC_31	(0x0 << RADIO_U2785_MC_SHIFT)
+#define RADIO_U2785_MC_32	(0x1 << RADIO_U2785_MC_SHIFT)
+#define RADIO_U2785_MC_33	(0x2 << RADIO_U2785_MC_SHIFT)
+#define RADIO_U2785_MC_34	(0x3 << RADIO_U2785_MC_SHIFT)
+
+/*
+ *  PS (Phase Settings)
+ */
+
+/* Phase of GF_DATA */
+#define RADIO_U2785_PS_GF	(0x1 << 14)
+/* Phase of MCC Internal Connection */
+#define RADIO_U2785_PS_MCC	(0x1 << 13)
+/* Phase of Charge Pump */
+#define RADIO_U2785_PS_CP	(0x1 << 12)
+
+/*
+ * Current-Saving Power-up/down Settings
+ */
+
+/* Gaussian Filter */
+#define RADIO_U2785_GF		(0x1 << 11)
+/* Modulation Compensation Circuit */
+#define RADIO_U2785_MCC		(0x1 << 10)
+/* Frequency Doubler */
+#define RADIO_U2785_FD		(0x1 << 8)
+/* OP1 + OP2 (Op Amps) */
+#define RADIO_U2785_OP		(0x1 << 7)
+
+/*
+ *  Current Gain Settings (in percent)
+ */
+#define RADIO_U2785_CGS_60	0x0
+#define RADIO_U2785_CGS_70	0x1
+#define RADIO_U2785_CGS_80	0x2
+#define RADIO_U2785_CGS_90	0x3
+#define RADIO_U2785_CGS_100	0x4
+#define RADIO_U2785_CGS_110	0x5
+#define RADIO_U2785_CGS_120	0x6
+#define RADIO_U2785_CGS_130	0x7
+
+/* GFCS (Gaussian-Filter Current Settings) */
+#define RADIO_U2785_GFCS_SHIFT	7
+/* CPCS (Charge-Pump Current Settings) */
+#define RADIO_U2785_CPCS_SHIFT	1
+/* MCCS (Modulation-Compensation Current Settings) */
+#define RADIO_U2785_MCCS_SHIFT	4
+
+/*
+ * Pretune DAC Voltage
+ */
+#define RADIO_U2785_DAC_SHIFT	4
+#define RADIO_U2785_DAC_300mV	(0x0 << RADIO_U2785_DAC_SHIFT)
+#define RADIO_U2785_DAC_600mV	(0x1 << RADIO_U2785_DAC_SHIFT)
+#define RADIO_U2785_DAC_900mV	(0x2 << RADIO_U2785_DAC_SHIFT)
+#define RADIO_U2785_DAC_1200mV	(0x3 << RADIO_U2785_DAC_SHIFT)
+#define RADIO_U2785_DAC_1400mV	(0x4 << RADIO_U2785_DAC_SHIFT)
+#define RADIO_U2785_DAC_1700mV	(0x5 << RADIO_U2785_DAC_SHIFT)
+#define RADIO_U2785_DAC_2000mV	(0x6 << RADIO_U2785_DAC_SHIFT)
+#define RADIO_U2785_DAC_2300mV	(0x7 << RADIO_U2785_DAC_SHIFT)
+
+/*
+ * Address bit
+ */
+#define RADIO_U2785_ADDRESS_BIT	0x1
+
+static void u2785_write_config(const struct coa_device *dev, u16 offset,
+			       u32 init1, u32 init2)
+{
+	u8 init[5] = {
+		/* first word: 24 bits */
+		[0]	= init1 >> 16,
+		[1]	= init1 >> 8,
+		[2]	= init1 | RADIO_U2785_ADDRESS_BIT,
+		/* second word: 9 bits */
+		[3]	= init2 >> 1,
+		[4]	= 0,
+	};
+
+	sc1442x_rfdesc_write(dev, offset, init, sizeof(init));
+}
+
+static void u2785_rx_init(const struct coa_device *dev, u16 offset)
+{
+	u32 init1 = 0, init2 = 0;
+
+	init1 |= RADIO_U2785_RC_12;
+	init1 |= RADIO_U2785_MC_32;
+	init1 |= 10 << RADIO_U2785_SC_SHIFT;
+
+	init1 |= RADIO_U2785_CGS_100 << RADIO_U2785_CPCS_SHIFT;
+
+	init2 |= RADIO_U2785_FD;
+	init2 |= RADIO_U2785_CGS_100 << RADIO_U2785_MCCS_SHIFT;
+
+	u2785_write_config(dev, offset, init1, init2);
+}
+
+static void u2785_tx_init(const struct coa_device *dev, u16 offset)
+{
+	u32 init1 = 0, init2 = 0;
+
+	init1 |= RADIO_U2785_RC_12;
+	init1 |= RADIO_U2785_MC_34;
+	init1 |= 7 << RADIO_U2785_SC_SHIFT;
+
+	init1 |= RADIO_U2785_GF;
+	init1 |= RADIO_U2785_MCC;
+	init1 |= RADIO_U2785_CGS_120 << RADIO_U2785_GFCS_SHIFT;
+	init1 |= RADIO_U2785_DAC_1400mV;
+	init1 |= RADIO_U2785_CGS_60 << RADIO_U2785_CPCS_SHIFT;
+
+	init2 |= RADIO_U2785_FD;
+	init2 |= RADIO_U2785_CGS_130 << RADIO_U2785_MCCS_SHIFT;
+
+	u2785_write_config(dev, offset, init1, init2);
+}
+
+static void u2785_write_carrier(const struct coa_device *dev, u16 offset,
+			        u32 init1)
+{
+	u8 init[3] = {
+		/* first word: 24 bits */
+		[0]	= init1 >> 16,
+		[1]	= init1 >> 8,
+		[2]	= init1 | RADIO_U2785_ADDRESS_BIT,
+	};
+
+	sc1442x_rfdesc_write(dev, offset, init, sizeof(init));
+}
+
+static void u2785_set_carrier(const struct coa_device *dev, u16 offset,
+			      enum dect_slot_states mode, u8 carrier)
+{
+	const struct coa_freq_map_entry *fe = &dev->freq_map.carrier[carrier];
+	u32 init1 = 0;
+
+	init1 |= RADIO_U2785_RC_12;
+
+	switch (mode) {
+	case DECT_SLOT_SCANNING:
+	case DECT_SLOT_RX:
+		init1 |= (fe->rx.divisor - RADIO_U2785_MC_MIN) <<
+			 RADIO_U2785_MC_SHIFT;
+		init1 |= fe->rx.swcnt << RADIO_U2785_SC_SHIFT;
+
+		init1 |= RADIO_U2785_CGS_100 << RADIO_U2785_CPCS_SHIFT;
+		break;
+	case DECT_SLOT_TX:
+		init1 |= (fe->tx.divisor - RADIO_U2785_MC_MIN) <<
+			 RADIO_U2785_MC_SHIFT;
+		init1 |= fe->tx.swcnt << RADIO_U2785_SC_SHIFT;
+
+		init1 |= RADIO_U2785_GF;
+		init1 |= RADIO_U2785_MCC;
+		init1 |= RADIO_U2785_CGS_120 << RADIO_U2785_GFCS_SHIFT;
+		init1 |= RADIO_U2785_DAC_1400mV;
+		init1 |= RADIO_U2785_CGS_60 << RADIO_U2785_CPCS_SHIFT;
+		break;
+	default:
+		return;
+	}
+
+	u2785_write_carrier(dev, offset, init1);
+}
+
+static int u2785_map_freq(u32 frequency, u8 *s_mc, u8 *s_sc)
+{
+	frequency /= DECT_CARRIER_WIDTH;
+
+	*s_mc = frequency / 32;
+	if (*s_mc < RADIO_U2785_MC_MIN || *s_mc > RADIO_U2785_MC_MAX)
+		return false;
+	*s_sc = frequency % 32;
+	return true;
+}
+
+static u64 u2785_map_band(struct coa_device *dev, const struct dect_band *band)
+{
+	struct coa_freq_map_entry *fe;
+	u64 carriers = 0;
+	u32 frequency;
+	u8 carrier;
+
+	for (carrier = 0; carrier < band->carriers; carrier++) {
+		frequency = band->frequency[carrier];
+		fe = &dev->freq_map.carrier[carrier];
+
+		if (!u2785_map_freq(frequency - RADIO_U2785_FREQ_IF1,
+				    &fe->rx.divisor, &fe->rx.swcnt))
+			continue;
+		if (!u2785_map_freq(frequency,
+				    &fe->tx.divisor, &fe->tx.swcnt))
+			continue;
+
+		carriers |= 1 << carrier;
+		u2785_debug(dev, "carrier %u (%u.%03uMHz) => "
+			    "rx: div: %u sw: %u tx: div: %u sw: %u\n",
+			    carrier, frequency / 1000, frequency % 1000,
+			    fe->rx.divisor, fe->rx.swcnt,
+			    fe->tx.divisor, fe->tx.swcnt);
+	}
+
+	return carriers;
+}
+
+const struct coa_radio_ops coa_u2785_radio_ops = {
+	.type		= "U2785B",
+	.rx_init	= u2785_rx_init,
+	.tx_init	= u2785_tx_init,
+	.set_carrier	= u2785_set_carrier,
+	.map_band	= u2785_map_band,
+};
+EXPORT_SYMBOL_GPL(coa_u2785_radio_ops);
diff --git a/drivers/dect/coa/sc1442x.c b/drivers/dect/coa/sc1442x.c
new file mode 100644
index 0000000..39d8c58
--- /dev/null
+++ b/drivers/dect/coa/sc1442x.c
@@ -0,0 +1,1024 @@
+/*
+ * com_on_air - basic driver for the Dosch and Amand "com on air" cards
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * authors:
+ * (C) 2008  Andreas Schuler <krater at badterrorist dot com>
+ * (C) 2008  Matthias Wenzel <dect at mazzoo dot de>
+ * (C) 2009  Patrick McHardy <kaber@trash.net>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+#include <net/dect/mac_csf.h>
+#include <net/dect/dsc.h>
+#include <net/dect/transceiver.h>
+#include <asm/io.h>
+
+#include "com_on_air.h"
+#include "sc1442x_firmware.h"
+#include "dip_opcodes.h"
+
+/*
+ * The sc1442x contain a 2k data RAM and 512b code RAM. The two primary
+ * methods for memory access are direct and indirect access. In indirect
+ * mode, the access goes through the DIP and the memory bank needs to be
+ * mapped by writting its number to the control register. In direct mode
+ * the memory can be accessed directly, the three modes differ only in
+ * the address space layout. The choice between direct and indirect mode
+ * is made by the device vendor.
+ *
+ * The address space is layed out as follows:
+ *
+ * PCI - size 8k:
+ *
+ * 0x0a00 - 0x11ff:	data memory
+ * 0x1a00 - 0x1bff:	code memory
+ * 0x1f00 - 0x1fff:	DIP control and status registers
+ *
+ * PCMCIA - size 1k:
+ *
+ * 0x0000 - 0x01ff:	256 bytes memory
+ * 0x0200 - 0x02ff:	DIP control and status registers
+ *
+ * Memory of the PCMCIA device is addressed in 16 bit little endian quantities.
+ *
+ * The first bank of the data memory contains DIP specific control data,
+ * the remaining banks are used to store packet and slot configuration data,
+ * with each slot having one half memory bank assigned.
+ *
+ * The per slot data of 128 bytes is layed out as follows:
+ *
+ * Offset		RX		TX
+ *
+ * 0x00 - 0x05:		Status		Preamble
+ * 0x06 - 0x0d:		A-Field		A-Field
+ * 0x0e - 0x35:		B-Field		B-Field
+ *
+ * 0x65 - 0x68:		Radio Cfg	Radio Cfg
+ * 0x69 - 0x6f:		BMC Ctrl	BMC Ctrl
+ * 0x70 - 0x7f:		DCS IV/Key	DCS IV/Key
+ * 0x70 - 0x7a:		DCS state	DCS state
+ */
+
+#define SC1442X_DIPSTOPPED		0x80
+#define SC1442X_PRESCALER_ENABLED	0x40
+#define SC1442X_TIMER_INTERRUPT_ENABLED	0x02
+
+/* Memory access modes */
+#define SC1442X_LINEAR_MODE		0x01
+#define SC1442X_LINEAR_MODE_0		(SC14421_LINEAR_MODE | 0x0)
+#define SC1442X_LINEAR_MODE_1		(SC14421_LINEAR_MODE | 0x2)
+#define SC1442X_LINEAR_MODE_2		(SC14421_LINEAR_MODE | 0x3)
+
+/* Indirect mode RAM bank select */
+#define SC1442X_RAMBANK0		0x00
+#define SC1442X_RAMBANK1		0x04
+#define SC1442X_RAMBANK2		0x08
+#define SC1442X_RAMBANK3		0x0c
+#define SC1442X_RAMBANK4		0x10
+#define SC1442X_RAMBANK5		0x14
+#define SC1442X_RAMBANK6		0x18
+#define SC1442X_RAMBANK7		0x1c
+#define SC1442X_CODEBANK		0x20
+#define SC1442X_BANKSIZE		0x100
+
+/* Interrupts 0-3 */
+#define SC1442X_IRQ_SLOT_0_5		0x01
+#define SC1442X_IRQ_SLOT_6_11		0x02
+#define SC1442X_IRQ_SLOT_12_17		0x04
+#define SC1442X_IRQ_SLOT_18_23		0x08
+#define SC1442X_IRQ_TIMER		0x10
+#define SC1442X_IRQ_MASK		0x1f
+
+/* Interrupt status 1: DIP/CLK100/TIM1/TIM0/SPI/UART/P10/KEYB */
+#define SC14424_RESET_INT_PENDING_1	0x1f02
+/* Interrupt status 2: CLK8K/TONE */
+#define SC14424_RESET_INT_PENDING_2	0x1f03
+
+/* DIP_INT and CLK100_INT priority level */
+#define SC14424_INT_PRIORITY_1		0x1f06
+
+/* P1 output control */
+#define SC14424_P1_SET_OUTPUT_DATA	0x1f21
+#define SC14424_P1_RESET_OUTPUT_DATA	0x1f22
+
+/* P1 input/output direction */
+#define SC14424_P1_DIR_REG		0x1f23
+
+/*
+ * Burst Mode Controller control information
+ */
+
+/* Maximum number of unmasked errors in S-field bits 8 to 31 */
+#define SC1442X_BC0_S_ERR_SHIFT		4
+/* Invert incoming data (RDI) */
+#define SC1442X_BC0_INV_RDI		0x08
+/* Invert outgoing data (TDO) */
+#define SC1442X_BC0_INV_TDO		0x04
+/* Disable writing B-field on A-field CRC error */
+#define SC1442X_BC0_SENS_A		0x02
+/* PP/FP mode */
+#define SC1442X_BC0_PP_MODE		0x01
+
+/* Error test mask for S-field bits 15-8 */
+#define SC1442X_BC1_MASK_MASK		0xff
+
+/* Sliding error test mask for S-field bits 15-8 */
+#define SC1442X_BC2_SLIDE_MASK		0xff
+
+/* DAC output value when BCM is active (for frequency control?) */
+#define SC1442X_BC3_DAC_MASK		0x1f
+
+/* Only perform phase jump for correct A-field CRC + SL_EN_ADJ command */
+#define SC1442X_BC4_ADP			0x10
+/* Window in which S-field is accepted */
+#define SC1442X_BC4_WIN_MASK		0x0f
+
+/* Amplitude-trimming of gaussian shape */
+#define SC1442X_BC5_VOL_SHIFT		4
+/* Disable scrambling */
+#define SC1442X_BC5_SC_OFF		0x08
+/* PD1 synchronization pattern:
+ * 0 = S-field received, 1 = preamble + first 2 bits of synchronization word */
+#define SC1442X_BC5_DO_FR		0x04
+/* TDO output shape */
+#define SC1442X_BC5_TDO_DIGITAL		0x00
+#define SC1442X_BC5_TDO_GAUSIAN		0x01
+#define SC1442X_BC5_TDO_POWER_DOWN	0x02
+#define SC1442X_BC5_TDO_MID_LEVEL	0x03
+
+/* Low 4 bits of multiframe number */
+#define SC1442X_BC6_MFR_SHIFT		4
+#define SC1442X_BC6_MFR_MASK		0xf0
+/* Frame number */
+#define SC1442X_BC6_FR_MASK		0x0f
+
+/*
+ * Burst Mode Controller status information
+ */
+
+/* Peak binary value of ADC (RSSI) */
+#define SC1442X_ST0_ADC_MASK		0x3f
+
+/* S-pattern recognized according to BMC configuration */
+#define SC1442X_ST1_IN_SYNC		0x80
+
+/* A-field R-CRC correct */
+#define SC1442X_ST1_A_CRC		0x40
+
+/* Protected Bn-subfield R-CRC correct */
+#define SC1442X_ST1_B_CRC_MASK		0x3c
+#define SC1442X_ST1_B1_CRC		0x20
+#define SC1442X_ST1_B2_CRC		0x10
+#define SC1442X_ST1_B3_CRC		0x08
+#define SC1442X_ST1_B4_CRC		0x04
+
+/* B-field X-CRC correct */
+#define SC1442X_ST1_X_CRC		0x02
+
+/* Z-field equals X-CRC */
+#define SC1442X_ST1_Z_CRC		0x01
+
+/* Phase offset of received S-field: which of the nine internal clock cycles
+ * per symbol sampled the incoming data. The frequency deviation can be
+ * calculated from the difference of the offsets of two consequitive frames as:
+ *
+ * K * (T / 9) / 10m = K * 96ns / 10m = K * 9.6ppm
+ */
+#define SC1442X_ST2_TAP_SHIFT		4
+#define SC1442X_ST2_TAP_MASK		0xf0
+#define SC1442X_ST2_TAP_SCALE		(DECT_PHASE_OFFSET_SCALE * 96 / 10)
+
+/* Number of unmasked S-field errors according to BMC configuration */
+#define SC1442X_ST2_S_ERR_SHIFT		0
+#define SC1442X_ST2_S_ERR_MASK		0x0f
+
+/* Phase offset of received S-field: difference of number of symbol periods
+ * between nominal 11520 symbols per frame and actual number of symbols. The
+ * frequency deviation can be calculated from the difference of two
+ * consequitive frames as:
+ *
+ * N * T / 10m = N * 870ns / 10m = N * 87ppm
+ */
+#define SC1442X_ST3_PHASE_MASK		0xff
+#define SC1442X_ST3_PHASE_SCALE		(DECT_PHASE_OFFSET_SCALE * 87)
+
+/* DC offset of received data to comparator reference input (DAC) */
+#define SC1442X_ST4_DC_MASK		0x3f
+
+/*
+ * Codec configuration
+ */
+
+#define SC1442X_CC_SIZE			6
+
+#define SC1442X_CC0_STANDBY		0xc2
+#define SC1442X_CC0_POWERDOWN		0x3d
+
+/* Logical memory banks */
+#define SC1442X_BANK_UNITS		8
+#define SC1442X_SLOT_BANK_SIZE		128
+
+static const u8 banktable[] = {
+	SC1442X_RAMBANK1,
+	SC1442X_RAMBANK2,
+	SC1442X_RAMBANK3,
+	SC1442X_RAMBANK4,
+	SC1442X_RAMBANK5,
+	SC1442X_RAMBANK6,
+};
+
+static const u8 slottable[] = {
+	Slot00, Slot01, Slot02, Slot03, Slot04, Slot05, Slot06, Slot07,
+	Slot08, Slot09, Slot10, Slot11, Slot12, Slot13, Slot14, Slot15,
+	Slot16, Slot17, Slot18, Slot19, Slot20, Slot21,	Slot22, Slot23,
+};
+
+static const u8 sc1442x_rx_funcs[DECT_PACKET_MAX + 1][DECT_B_MAX + 1][2][2] = {
+	[DECT_PACKET_P00][DECT_B_NONE][0][0]		= RX_P00,
+	[DECT_PACKET_P00][DECT_B_NONE][0][1]		= RX_P00_Sync,
+	[DECT_PACKET_P32][DECT_B_UNPROTECTED][0][0]	= RX_P32U,
+	[DECT_PACKET_P32][DECT_B_UNPROTECTED][1][0]	= RX_P32U_Enc,
+	[DECT_PACKET_P32][DECT_B_PROTECTED][0][0]	= RX_P32P,
+};
+
+static const u8 sc1442x_tx_funcs[DECT_PACKET_MAX + 1][DECT_B_MAX + 1][2] = {
+	[DECT_PACKET_P00][DECT_B_NONE][0]		= TX_P00,
+	[DECT_PACKET_P32][DECT_B_UNPROTECTED][0]	= TX_P32U,
+	[DECT_PACKET_P32][DECT_B_UNPROTECTED][1]	= TX_P32U_Enc,
+	[DECT_PACKET_P32][DECT_B_PROTECTED][0]		= TX_P32P,
+};
+
+/*
+ * Raw IO functions
+ */
+
+static void sc1442x_lock_mem(struct coa_device *dev) __acquires(dev->lock)
+{
+	spin_lock_irq(&dev->lock);
+}
+
+static void sc1442x_unlock_mem(struct coa_device *dev) __releases(dev->lock)
+{
+	mmiowb();
+	spin_unlock_irq(&dev->lock);
+}
+
+static u8 sc1442x_readb(const struct coa_device *dev, u16 offset)
+{
+	switch (dev->type) {
+	case COA_TYPE_PCI:
+		return readb(dev->sc1442x_base + offset);
+	case COA_TYPE_PCMCIA:
+		return le16_to_cpu(readw(dev->sc1442x_base + 2 * offset));
+	default:
+		BUG();
+	}
+}
+
+static u16 sc1442x_readw(const struct coa_device *dev, u16 offset)
+{
+	u32 tmp;
+
+	switch (dev->type) {
+	case COA_TYPE_PCI:
+		return le16_to_cpu(readw(dev->sc1442x_base + offset));
+	case COA_TYPE_PCMCIA:
+		tmp = le32_to_cpu(readl(dev->sc1442x_base + 2 * offset));
+		return (tmp >> 8) | (tmp & 0xff);
+	default:
+		BUG();
+	}
+}
+
+static void sc1442x_writeb(const struct coa_device *dev, u16 offset, u8 value)
+{
+	switch (dev->type) {
+	case COA_TYPE_PCI:
+		writeb(value, dev->sc1442x_base + offset);
+		break;
+	case COA_TYPE_PCMCIA:
+		writew(cpu_to_le16(value), dev->sc1442x_base + 2 * offset);
+		break;
+	}
+}
+
+static void sc1442x_writew(const struct coa_device *dev, u16 offset, u16 value)
+{
+	u32 tmp;
+
+	switch (dev->type) {
+	case COA_TYPE_PCI:
+		writew(cpu_to_le16(value), dev->sc1442x_base + offset);
+		break;
+	case COA_TYPE_PCMCIA:
+		tmp = ((value & 0xff00) << 8) | (value & 0xff);
+		writel(cpu_to_le32(tmp), dev->sc1442x_base + 2 * offset);
+		break;
+	}
+}
+
+static void sc1442x_stop_dip(struct coa_device *dev)
+{
+	/* Prevent the interrupt handler from restarting the DIP */
+	dev->ctrl = SC1442X_DIPSTOPPED;
+
+	/* Stop the DIP and wait for interrupt handler to complete */
+	sc1442x_writeb(dev, dev->cfg_reg, SC1442X_DIPSTOPPED);
+	synchronize_irq(dev->irq);
+}
+
+static void sc1442x_start_dip(struct coa_device *dev)
+{
+	dev->ctrl = 0;
+	sc1442x_writeb(dev, dev->cfg_reg, 0x00);
+}
+
+static void sc1442x_switch_to_bank(const struct coa_device *dev, u8 bank)
+{
+	if (dev->type != COA_TYPE_PCMCIA)
+		return;
+	sc1442x_writeb(dev, dev->cfg_reg, bank | dev->ctrl);
+	/* need to wait for 4 IO cycles */
+	inb_p(dev->config_base);
+	inb_p(dev->config_base);
+	inb_p(dev->config_base);
+	inb_p(dev->config_base);
+}
+
+static void sc1442x_toggle_led(struct coa_device *dev)
+{
+	if (dev->type != COA_TYPE_PCI)
+		return;
+
+	if ((dev->led & 0xf) > 0x7)
+		sc1442x_writeb(dev, SC14424_P1_SET_OUTPUT_DATA, 0x40);
+	else
+		sc1442x_writeb(dev, SC14424_P1_RESET_OUTPUT_DATA, 0x40);
+	dev->led++;
+}
+
+/*
+ * Code memory IO functions
+ */
+static void sc1442x_write_cmd(const struct coa_device *dev, u16 label,
+			      u8 opcode, u8 operand)
+{
+	sc1442x_writeb(dev, dev->code_base + 2 * label + 0, opcode);
+	sc1442x_writeb(dev, dev->code_base + 2 * label + 1, operand);
+}
+
+static void sc1442x_to_cmem(const struct coa_device *dev,
+			    const u8 *src, u16 length)
+{
+	u16 i;
+
+	for (i = 0; i < length; i++)
+		sc1442x_writeb(dev, dev->code_base + i, src[i]);
+}
+
+/*
+ * Data memory IO functions
+ */
+static inline u8 sc1442x_dreadb(const struct coa_device *dev, u16 offset)
+{
+	return sc1442x_readb(dev, dev->data_base + (offset & dev->data_mask));
+}
+
+static inline u16 sc1442x_dreadw(const struct coa_device *dev, u16 offset)
+{
+	return sc1442x_readw(dev, dev->data_base + (offset & dev->data_mask));
+}
+
+static inline void sc1442x_dwriteb(const struct coa_device *dev,
+				  u16 offset, u8 value)
+{
+	sc1442x_writeb(dev, dev->data_base + (offset & dev->data_mask), value);
+}
+
+static inline void sc1442x_dwritew(const struct coa_device *dev,
+				   u16 offset, u16 value)
+{
+	sc1442x_writew(dev, dev->data_base + (offset & dev->data_mask), value);
+}
+
+static void sc1442x_to_dmem(const struct coa_device *dev, u16 offset,
+			    const void *src, u16 length)
+{
+	u16 i = 0;
+
+	for (; length >= 2; length -= 2, i += 2)
+		sc1442x_dwritew(dev, offset + i, *(u16 *)(src + i));
+	for (; length >= 1; length -= 1, i += 1)
+		sc1442x_dwriteb(dev, offset + i, *(u8 *)(src + i));
+}
+
+static void sc1442x_from_dmem(const struct coa_device *dev, void *dst,
+			      u16 offset, u16 length)
+{
+	u16 i = 0;
+
+	for (; length >= 2; length -= 2, i += 2)
+		*(u16 *)(dst +  i) = sc1442x_dreadw(dev, offset + i);
+	for (; length >= 1; length -= 1, i += 1)
+		*(u8 *)(dst + i) = sc1442x_dreadb(dev, offset + i);
+}
+
+static u8 sc1442x_dip_bankaddress(u8 slot)
+{
+	return (slot / 2 + 2) * SC1442X_SLOT_BANK_SIZE / SC1442X_BANK_UNITS;
+}
+
+static u8 sc1442x_slot_bank(u8 slot)
+{
+	return banktable[slot / 4];
+}
+
+static u16 sc1442x_slot_offset(u8 slot)
+{
+	u16 offset;
+
+	offset = SC1442X_BANKSIZE + slot / 4 * SC1442X_BANKSIZE;
+	if (slot & 0x2)
+		offset += SC1442X_BANKSIZE / 2;
+	return offset;
+}
+
+void sc1442x_rfdesc_write(const struct coa_device *dev, u16 offset,
+			  const u8 *src, u16 length)
+{
+	sc1442x_to_dmem(dev, offset + RF_DESC, src, length);
+}
+
+/*
+ * Ciphering
+ */
+
+static void sc1442x_dcs_init(const struct coa_device *dev,
+			     const struct dect_transceiver *trx,
+			     u8 slot, u32 mfn, u8 framenum)
+{
+	const struct dect_transceiver_slot *ts = &trx->slots[slot];
+	u16 off = sc1442x_slot_offset(slot);
+	__le64 iv;
+
+	iv = dect_dsc_iv(mfn, framenum);
+	sc1442x_to_dmem(dev, off + DCS_IV, &iv, 8);
+	sc1442x_to_dmem(dev, off + DCS_CK, &ts->ck, 8);
+}
+
+/* Transfer DCS cipher state between two TDD slots of a MAC connection */
+static void sc1442x_transfer_dcs_state(struct coa_device *dev,
+				       struct dect_transceiver *trx,
+				       u8 slot)
+{
+	u8 slot2 = slot + DECT_HALF_FRAME_SIZE;
+	struct dect_transceiver_slot *ts1 = &trx->slots[slot];
+	struct dect_transceiver_slot *ts2 = &trx->slots[slot2];
+	u8 dcs_state[DCS_STATE_SIZE];
+	u16 off;
+
+	if (!(ts1->flags & DECT_SLOT_CIPHER) ||
+	    !(ts2->flags & DECT_SLOT_CIPHER))
+		return;
+
+	sc1442x_switch_to_bank(dev, sc1442x_slot_bank(slot));
+	off = sc1442x_slot_offset(slot);
+	sc1442x_from_dmem(dev, dcs_state, off + DCS_STATE, DCS_STATE_SIZE);
+
+	sc1442x_switch_to_bank(dev, sc1442x_slot_bank(slot2));
+	off = sc1442x_slot_offset(slot2);
+	sc1442x_to_dmem(dev, off + DCS_STATE, dcs_state, DCS_STATE_SIZE);
+}
+
+/*
+ * Transceiver operations
+ */
+
+static void sc1442x_disable(const struct dect_transceiver *trx)
+{
+	sc1442x_stop_dip(dect_transceiver_priv(trx));
+}
+
+static void sc1442x_enable(const struct dect_transceiver *trx)
+{
+	const struct coa_device *dev = dect_transceiver_priv(trx);
+	u8 slot;
+
+	/* Restore slot table to a pristine state */
+	sc1442x_switch_to_bank(dev, SC1442X_CODEBANK);
+	for (slot = 0; slot < DECT_FRAME_SIZE; slot++) {
+		sc1442x_write_cmd(dev, slottable[slot] + 0, WT, 1);
+		sc1442x_write_cmd(dev, slottable[slot] + 1, WNT, 1);
+	}
+
+	if (trx->cell->mode == DECT_MODE_FP) {
+		sc1442x_write_cmd(dev, ClockSyncOn, WT, 1);
+		sc1442x_write_cmd(dev, ClockAdjust, WT, 1);
+		sc1442x_write_cmd(dev, ClockSyncOff, WT, 1);
+
+		sc1442x_write_cmd(dev, TX_P32U_Enc, JMP, LoadEncKey);
+		sc1442x_write_cmd(dev, RX_P32U_Enc, JMP, LoadEncState);
+	} else {
+		sc1442x_write_cmd(dev, ClockSyncOn, P_SC, PSC_S_SYNC_ON);
+		sc1442x_write_cmd(dev, ClockAdjust, EN_SL_ADJ, 1);
+		sc1442x_write_cmd(dev, ClockSyncOff, P_SC, 0x00);
+
+		sc1442x_write_cmd(dev, RX_P32U_Enc, JMP, LoadEncKey);
+		sc1442x_write_cmd(dev, TX_P32U_Enc, JMP, LoadEncState);
+	}
+
+	if (trx->mode == DECT_TRANSCEIVER_MASTER)
+		sc1442x_write_cmd(dev, RFStart, BR, SlotTable);
+	else {
+		sc1442x_write_cmd(dev, RFStart, BR, SyncInit);
+		sc1442x_write_cmd(dev, SyncLoop, BR, Sync);
+	}
+
+	sc1442x_start_dip(dect_transceiver_priv(trx));
+}
+
+static void sc1442x_confirm(const struct dect_transceiver *trx)
+{
+	struct coa_device *dev = dect_transceiver_priv(trx);
+
+	/*
+	 * This locks the firmware into a cycle where it will receive every
+	 * 24th slot. This must happen within the time it takes to transmit
+	 * 22 slots after the interrupt to lock to the correct signal.
+	 */
+	sc1442x_lock_mem(dev);
+	sc1442x_switch_to_bank(dev, SC1442X_CODEBANK);
+	sc1442x_write_cmd(dev, SyncLoop, BR, SyncLock);
+	sc1442x_unlock_mem(dev);
+}
+
+static void sc1442x_unlock(const struct dect_transceiver *trx)
+{
+	struct coa_device *dev = dect_transceiver_priv(trx);
+
+	/* Restore jump into Sync loop */
+	sc1442x_lock_mem(dev);
+	sc1442x_switch_to_bank(dev, SC1442X_CODEBANK);
+	sc1442x_write_cmd(dev, SyncLoop, BR, Sync);
+	sc1442x_write_cmd(dev, SlotTable, BR, SyncInit);
+	sc1442x_unlock_mem(dev);
+}
+
+static void sc1442x_lock(const struct dect_transceiver *trx, u8 slot)
+{
+	struct coa_device *dev = dect_transceiver_priv(trx);
+
+	/*
+	 * We're receiving the single slot "slot". Adjust the firmware so it
+	 * will jump into the correct slottable position on the next receive
+	 * event. This will automagically establish the correct slot numbers
+	 * and thereby interrupt timing for all slots.
+	 */
+	sc1442x_lock_mem(dev);
+	sc1442x_switch_to_bank(dev, SC1442X_CODEBANK);
+	sc1442x_write_cmd(dev, SlotTable, SLOTZERO, 0);
+	sc1442x_write_cmd(dev, SyncLoop, BR, slottable[slot]);
+	sc1442x_unlock_mem(dev);
+}
+
+static void sc1442x_write_bmc_config(const struct coa_device *dev,
+				     u8 slot, bool tx)
+{
+	u16 off;
+	u8 cfg;
+
+	off = sc1442x_slot_offset(slot) + BMC_CTRL;
+
+	cfg  = 2 << SC1442X_BC0_S_ERR_SHIFT;
+	cfg |= SC1442X_BC0_INV_TDO;
+	cfg |= SC1442X_BC0_SENS_A;
+	if (slot < 12 && !tx)
+		cfg |= SC1442X_BC0_PP_MODE;
+	sc1442x_dwriteb(dev, off + 0, cfg);
+
+	/* S-field error mask */
+	sc1442x_dwriteb(dev, off + 1, 0);
+	/* S-field sliding window error mask */
+	sc1442x_dwriteb(dev, off + 2, 0x3f);
+
+	/* DAC output */
+	sc1442x_dwriteb(dev, off + 3, 0);
+
+	cfg  = SC1442X_BC4_ADP;
+	cfg |= 0xf & SC1442X_BC4_WIN_MASK;
+	cfg |= 0x80;
+	sc1442x_dwriteb(dev, off + 4, cfg);
+
+	cfg  = SC1442X_BC5_DO_FR;
+	cfg |= tx ? SC1442X_BC5_TDO_DIGITAL : SC1442X_BC5_TDO_POWER_DOWN;
+	sc1442x_dwriteb(dev, off + 5, cfg);
+
+	/* Frame number */
+	sc1442x_dwriteb(dev, off + 6, 0);
+}
+
+static void sc1442x_set_mode(const struct dect_transceiver *trx,
+			     const struct dect_channel_desc *chd,
+			     enum dect_slot_states mode)
+{
+	struct coa_device *dev = dect_transceiver_priv(trx);
+	bool cipher = trx->slots[chd->slot].flags & DECT_SLOT_CIPHER;
+	bool sync = trx->slots[chd->slot].flags & DECT_SLOT_SYNC;
+	u8 slot = chd->slot, prev = dect_slot_sub(slot, 1);
+
+	sc1442x_lock_mem(dev);
+	sc1442x_switch_to_bank(dev, SC1442X_CODEBANK);
+
+	switch (mode) {
+	case DECT_SLOT_IDLE:
+		sc1442x_write_cmd(dev, slottable[prev] + 0, WT, 1);
+		sc1442x_write_cmd(dev, slottable[prev] + 1, WNT, 1);
+		sc1442x_write_cmd(dev, slottable[slot] + 0, WT, 1);
+		sc1442x_write_cmd(dev, slottable[slot] + 1, WNT, 1);
+		break;
+	case DECT_SLOT_SCANNING:
+	case DECT_SLOT_RX:
+		sc1442x_write_cmd(dev, slottable[prev] + 0, BK_C,
+				  sc1442x_dip_bankaddress(slot));
+		sc1442x_write_cmd(dev, slottable[prev] + 1, JMP, RFInit);
+		sc1442x_write_cmd(dev, slottable[slot] + 0, WT, 1);
+		sc1442x_write_cmd(dev, slottable[slot] + 1, JMP,
+				  sc1442x_rx_funcs[chd->pkt][chd->b_fmt][cipher][sync]);
+
+		sc1442x_switch_to_bank(dev, sc1442x_slot_bank(slot));
+		sc1442x_write_bmc_config(dev, slot, false);
+		break;
+	case DECT_SLOT_TX:
+		sc1442x_write_cmd(dev, slottable[prev] + 0, BK_C,
+				  sc1442x_dip_bankaddress(slot));
+		sc1442x_write_cmd(dev, slottable[prev] + 1, JMP, RFInit);
+		sc1442x_write_cmd(dev, slottable[slot] + 0, WT, 1);
+		sc1442x_write_cmd(dev, slottable[slot] + 1, JMP,
+				  sc1442x_tx_funcs[chd->pkt][chd->b_fmt][cipher]);
+
+		sc1442x_switch_to_bank(dev, sc1442x_slot_bank(slot));
+		sc1442x_write_bmc_config(dev, slot, true);
+		break;
+	}
+	sc1442x_unlock_mem(dev);
+}
+
+static void sc1442x_set_carrier(const struct dect_transceiver *trx,
+				u8 slot, u8 carrier)
+{
+	const struct dect_transceiver_slot *ts = &trx->slots[slot];
+	struct coa_device *dev = dect_transceiver_priv(trx);
+	u16 off;
+
+	WARN_ON(ts->state == DECT_SLOT_IDLE);
+
+	sc1442x_lock_mem(dev);
+	sc1442x_switch_to_bank(dev, sc1442x_slot_bank(slot));
+	off = sc1442x_slot_offset(slot);
+	dev->radio_ops->set_carrier(dev, off, ts->state, carrier);
+	sc1442x_unlock_mem(dev);
+}
+
+static u64 sc1442x_set_band(const struct dect_transceiver *trx,
+			    const struct dect_band *band)
+{
+	struct coa_device *dev = dect_transceiver_priv(trx);
+
+	return dev->radio_ops->map_band(dev, band);
+}
+
+static void sc1442x_tx(const struct dect_transceiver *trx, struct sk_buff *skb)
+{
+	struct coa_device *dev = dect_transceiver_priv(trx);
+	const struct dect_skb_trx_cb *cb = DECT_TRX_CB(skb);
+	const struct dect_transceiver_slot *ts = &trx->slots[cb->slot];
+	u8 slot = cb->slot;
+	u16 off;
+
+	sc1442x_lock_mem(dev);
+	sc1442x_switch_to_bank(dev, sc1442x_slot_bank(slot));
+	off = sc1442x_slot_offset(slot);
+
+	/* Duplicate first byte for transmission during ramp-up */
+	sc1442x_dwriteb(dev, off + SD_PREAMBLE_OFF - 1, *skb_mac_header(skb));
+	sc1442x_to_dmem(dev, off + SD_PREAMBLE_OFF,
+			skb_mac_header(skb), skb->mac_len);
+	sc1442x_to_dmem(dev, off + SD_DATA_OFF, skb->data, skb->len);
+	sc1442x_dwriteb(dev, off + BMC_CTRL + BMC_CTRL_MFR_OFF, cb->frame);
+
+	/* Init DCS for slots in the first half frame */
+	if (ts->flags & DECT_SLOT_CIPHER && slot < DECT_HALF_FRAME_SIZE)
+		sc1442x_dcs_init(dev, trx, slot, cb->mfn, cb->frame);
+
+	sc1442x_toggle_led(dev);
+	sc1442x_unlock_mem(dev);
+	kfree_skb(skb);
+}
+
+const struct dect_transceiver_ops sc1442x_transceiver_ops = {
+	.name			= "sc1442x",
+	.features		= DECT_TRANSCEIVER_SLOW_HOPPING,
+	.eventrate		= 6,
+	.latency		= 6,
+	.disable		= sc1442x_disable,
+	.enable			= sc1442x_enable,
+	.confirm		= sc1442x_confirm,
+	.unlock			= sc1442x_unlock,
+	.lock			= sc1442x_lock,
+	.set_mode		= sc1442x_set_mode,
+	.set_carrier		= sc1442x_set_carrier,
+	.set_band		= sc1442x_set_band,
+	.tx			= sc1442x_tx,
+	.destructor		= dect_transceiver_free,
+};
+EXPORT_SYMBOL_GPL(sc1442x_transceiver_ops);
+
+static u8 sc1442x_clear_interrupt(const struct coa_device *dev)
+{
+	u8 int1, int2, cnt = 0;
+
+	int1 = sc1442x_readb(dev, dev->cfg_reg);
+	/* is the card still plugged? */
+	if (int1 == 0xff)
+		return 0;
+
+	int2 = int1 & SC1442X_IRQ_MASK;
+
+	/* Clear interrupt status before checking for any remaining events */
+	if (int2 && dev->type == COA_TYPE_PCI)
+		sc1442x_writeb(dev, SC14424_RESET_INT_PENDING_1, 0x80);
+
+	while (int1) {
+		cnt++;
+		if (cnt > 254) {
+			int2 = 0;
+			break;
+		}
+
+		int1 = sc1442x_readb(dev, dev->cfg_reg) & SC1442X_IRQ_MASK;
+		int2 |= int1;
+	}
+
+	return int2 & SC1442X_IRQ_MASK;
+}
+
+static void sc1442x_update_phase_offset(struct coa_device *dev,
+					struct dect_transceiver_slot *ts,
+					u8 framenum)
+{
+	struct sc1442x_phase_state *ps = &dev->phase_state[ts->chd.slot / 2];
+	u16 off = sc1442x_slot_offset(ts->chd.slot);
+	s32 phaseoff;
+	s8 phase;
+	u8 tap;
+
+	/* The phase offset is calculated from the differences of the tap and
+	 * phase status of two consequitive frames. The tap field contains
+	 * which of the nine internal clock cycles per symbol sampled the
+	 * incoming data and measures small scale frequency deviations up to
+	 * +-8 * 9.6ppm == +-86.4ppm. The phase field contains the absolute
+	 * phase offset in multiples of 87ppm.
+	 */
+	tap   = sc1442x_dreadb(dev, off + 2) >> SC1442X_ST2_TAP_SHIFT;
+	phase = sc1442x_dreadb(dev, off + 3);
+
+	if (dect_next_framenum(ps->framenum) == framenum) {
+		phaseoff = (tap - ps->tap) * SC1442X_ST2_TAP_SCALE;
+		phaseoff += (phase - ps->phase) * SC1442X_ST3_PHASE_SCALE;
+
+		ts->phaseoff = dect_average_phase_offset(ts->phaseoff, phaseoff);
+	}
+
+	ps->framenum = framenum;
+	ps->tap      = tap;
+	ps->phase    = phase;
+}
+
+static void sc1442x_process_slot(struct coa_device *dev,
+				 struct dect_transceiver *trx,
+				 struct dect_transceiver_event *event,
+				 u8 slot)
+{
+	struct dect_transceiver_slot *ts = &trx->slots[slot];
+	struct sk_buff *skb;
+	u8 status, framenum, csum, rssi;
+	u32 mfn;
+	u16 off;
+
+	if (ts->state == DECT_SLOT_IDLE || ts->state == DECT_SLOT_TX)
+		return;
+
+	mfn = trx->cell->timer_base[DECT_TIMER_RX].mfn;
+	framenum = trx->cell->timer_base[DECT_TIMER_RX].framenum;
+
+	sc1442x_switch_to_bank(dev, sc1442x_slot_bank(slot));
+	off = sc1442x_slot_offset(slot);
+
+	/*
+	 * The SC1442X contains a 6 bit ADC for RSSI measurement, convert to
+	 * units used by the stack.
+	 */
+	status = sc1442x_dreadb(dev, off + SD_RSSI_OFF);
+	rssi = (status & SC1442X_ST0_ADC_MASK) * DECT_RSSI_RANGE / 63;
+
+	/* validate and clear checksum */
+	status = sc1442x_dreadb(dev, off + SD_CSUM_OFF);
+	if (!(status & SC1442X_ST1_IN_SYNC))
+		goto out;
+	sc1442x_dwriteb(dev, off + SD_CSUM_OFF, 0);
+
+	if (!(status & SC1442X_ST1_A_CRC)) {
+		ts->rx_a_crc_errors++;
+		if (ts->chd.pkt == DECT_PACKET_P00)
+			goto out;
+		csum = 0;
+	} else
+		csum = DECT_CHECKSUM_A_CRC_OK;
+
+	if (ts->chd.pkt != DECT_PACKET_P00) {
+		if (!(status & SC1442X_ST1_X_CRC))
+			ts->rx_x_crc_errors++;
+		else
+			csum |= DECT_CHECKSUM_X_CRC_OK;
+
+		if (!(status & SC1442X_ST1_Z_CRC))
+			ts->rx_z_crc_errors++;
+		else
+			csum |= DECT_CHECKSUM_Z_CRC_OK;
+	}
+
+	/* calculate phase offset */
+	sc1442x_update_phase_offset(dev, ts, framenum);
+
+	skb = dect_transceiver_alloc_skb(trx, slot);
+	if (skb == NULL)
+		goto out;
+	sc1442x_from_dmem(dev, skb->data, off + SD_DATA_OFF, skb->len);
+	DECT_TRX_CB(skb)->csum = csum;
+	DECT_TRX_CB(skb)->rssi = rssi;
+	__skb_queue_tail(&event->rx_queue, skb);
+
+	ts->rx_bytes += skb->len;
+	ts->rx_packets++;
+
+	sc1442x_toggle_led(dev);
+out:
+	ts->rssi = dect_average_rssi(ts->rssi, rssi);
+	dect_transceiver_record_rssi(event, slot, rssi);
+
+	/* Update frame number for next reception */
+	sc1442x_dwriteb(dev, off + BMC_CTRL + BMC_CTRL_MFR_OFF, framenum + 1);
+
+	/* Init DCS for slots in the first half frame */
+	if (ts->flags & DECT_SLOT_CIPHER && slot < DECT_HALF_FRAME_SIZE)
+		sc1442x_dcs_init(dev, trx, slot, mfn, framenum + 1);
+}
+
+irqreturn_t sc1442x_interrupt(int irq, void *dev_id)
+{
+	struct dect_transceiver *trx = dev_id;
+	struct coa_device *dev = dect_transceiver_priv(trx);
+	struct dect_transceiver_event *event;
+	u8 slot, i;
+
+	irq = sc1442x_clear_interrupt(dev);
+	if (!irq)
+		return IRQ_NONE;
+
+	if (unlikely(hweight8(irq) != 1 && net_ratelimit()))
+		dev_info(dev->dev, "lost some interrupts\n");
+
+	for (i = 0; i < 4; i++) {
+		if (!(irq & (1 << i)))
+			continue;
+
+		event = dect_transceiver_event(trx, i % 2, i * 6);
+		if (event == NULL)
+			goto out;
+
+		spin_lock(&dev->lock);
+		for (slot = 6 * i; slot < 6 * (i + 1); slot++) {
+			sc1442x_process_slot(dev, trx, event, slot);
+			if (slot < DECT_HALF_FRAME_SIZE)
+				sc1442x_transfer_dcs_state(dev, trx, slot);
+		}
+		spin_unlock(&dev->lock);
+
+		dect_transceiver_queue_event(trx, event);
+	}
+out:
+	return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(sc1442x_interrupt);
+
+static void sc1442x_init_slot(const struct coa_device *dev, u8 slot)
+{
+	u16 off;
+
+	sc1442x_switch_to_bank(dev, sc1442x_slot_bank(slot));
+	off = sc1442x_slot_offset(slot);
+	dev->radio_ops->rx_init(dev, off);
+	dev->radio_ops->tx_init(dev, off);
+}
+
+static int sc1442x_check_dram(const struct coa_device *dev)
+{
+	unsigned int bank, i;
+	unsigned int cnt;
+	u16 off;
+	u8 val;
+
+	for (bank = 0; bank < 8; bank++) {
+		sc1442x_switch_to_bank(dev, 4 * bank);
+
+		off = bank * SC1442X_BANKSIZE;
+		for (i = 0; i < SC1442X_BANKSIZE - 2; i++)
+			sc1442x_dwriteb(dev, off + i, bank + i);
+	}
+
+	cnt = 0;
+	for (bank = 0; bank < 8; bank++) {
+		sc1442x_switch_to_bank(dev, 4 * bank);
+
+		off = bank * SC1442X_BANKSIZE;
+		for (i = 0; i < SC1442X_BANKSIZE - 2; i++) {
+			val = sc1442x_dreadb(dev, off + i);
+			if (val != ((bank + i) & 0xff)) {
+				dev_err(dev->dev,
+					"memory error bank %.2x offset %.2x: "
+					"%.2x != %.2x\n", bank, i,
+					val, (bank + i) & 0xff);
+				cnt++;
+			}
+			sc1442x_dwriteb(dev, off + i, 0);
+		}
+	}
+
+	if (cnt > 0)
+		dev_err(dev->dev, "found %u memory r/w errors\n", cnt);
+	return cnt ? -1 : 0;
+}
+
+int sc1442x_init_device(struct coa_device *dev)
+{
+	unsigned int i;
+	u8 slot;
+
+	spin_lock_init(&dev->lock);
+	dev->ctrl = SC1442X_DIPSTOPPED;
+
+	if (sc1442x_check_dram(dev) < 0)
+		return -EIO;
+
+	dev_info(dev->dev, "Loading firmware ...\n");
+	sc1442x_switch_to_bank(dev, SC1442X_CODEBANK);
+	sc1442x_to_cmem(dev, sc1442x_firmware, sizeof(sc1442x_firmware));
+
+	sc1442x_clear_interrupt(dev);
+
+	/* Init DIP */
+	sc1442x_switch_to_bank(dev, SC1442X_RAMBANK0);
+
+	/* Disable Codec */
+	sc1442x_dwriteb(dev, DIP_CC_INIT, SC1442X_CC0_STANDBY);
+	for (i = 1; i < SC1442X_CC_SIZE; i++)
+		sc1442x_dwriteb(dev, DIP_CC_INIT + i, 0);
+
+	sc1442x_write_bmc_config(dev, 0, false);
+	for (slot = 0; slot < DECT_FRAME_SIZE; slot += 2)
+		sc1442x_init_slot(dev, slot);
+
+	if (dev->type == COA_TYPE_PCI) {
+		/* Enable DIP interrupt */
+		sc1442x_writeb(dev, SC14424_INT_PRIORITY_1, 0x70);
+		/* Enable SPI for LED control */
+		sc1442x_writeb(dev, SC14424_P1_DIR_REG, 0xd6);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(sc1442x_init_device);
+
+void sc1442x_shutdown_device(struct coa_device *dev)
+{
+	sc1442x_stop_dip(dev);
+
+	if (dev->type == COA_TYPE_PCI) {
+		/* Clear pening interrupts */
+		sc1442x_writeb(dev, SC14424_RESET_INT_PENDING_1, 0xff);
+		sc1442x_writeb(dev, SC14424_RESET_INT_PENDING_2, 0xff);
+		/* Reset LED */
+		sc1442x_writeb(dev, SC14424_P1_RESET_OUTPUT_DATA, 0x40);
+	}
+}
+EXPORT_SYMBOL_GPL(sc1442x_shutdown_device);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/dect/coa/sc1442x_firmware.asm b/drivers/dect/coa/sc1442x_firmware.asm
new file mode 100644
index 0000000..05c7de8
--- /dev/null
+++ b/drivers/dect/coa/sc1442x_firmware.asm
@@ -0,0 +1,442 @@
+		CPU     SC14421
+		ORG	0
+
+		BR      Start
+
+PB_LED          EQU     0x80
+PB_RX_ON        EQU     0x40
+PB_TX_ON        EQU     0x10
+PB_RADIOPOWER   EQU     0x04
+PB_DCTHRESHOLD  EQU     0x02
+PB_RSSI		EQU	0x01
+
+; synchronisation control
+PSC_ARPD1	EQU	0x80
+PSC_S_SYNC	EQU	0x40
+PSC_S_SYNC_ON	EQU	0x20
+PSC_EOPSM	EQU	0x10
+
+; memory banks 0-7, lower and upper halfs (128 bytes each)
+BANK0_LOW	EQU	0x00
+BANK0_HIGH	EQU	0x10
+BANK1_LOW	EQU	0x20
+BANK1_HIGH	EQU	0x30
+BANK2_LOW	EQU	0x40
+BANK2_HIGH	EQU	0x50
+BANK3_LOW	EQU	0x60
+BANK3_HIGH	EQU	0x70
+BANK4_LOW	EQU	0x80
+BANK4_HIGH	EQU	0x90
+BANK5_LOW	EQU	0xa0
+BANK5_HIGH	EQU	0xb0
+BANK6_LOW	EQU	0xc0
+BANK6_HIGH	EQU	0xd0
+BANK7_LOW	EQU	0xe0
+BANK7_HIGH	EQU	0xf0
+
+; Codec Control
+DIP_CC_INIT	EQU	0x10
+
+; Radio configuration word
+RF_DESC		EQU	0x65
+
+; BMC control information
+BMC_CTRL_SIZE	EQU	7
+BMC_CTRL	EQU	0x69
+
+; (multi) frame number for scambler and DCS
+BMC_CTRL_MFR_OFF EQU	6
+
+; Cipher IV/Key
+DCS_DESC	EQU	0x70
+DCS_IV		EQU	DCS_DESC
+DCS_CK		EQU	DCS_DESC + 0x8
+
+; Cipher state
+DCS_STATE	EQU	0x70
+DCS_STATE_SIZE	EQU	11
+
+SD_PREAMBLE_OFF	EQU	0x01
+SD_A_FIELD_OFF	EQU	0x06
+SD_B_FIELD_OFF	EQU	0x0E
+
+; status descriptor
+SD_BASE_OFF	EQU	0x00
+SD_RSSI_OFF	EQU	0x00
+SD_CSUM_OFF	EQU	0x01
+SD_DATA_OFF	EQU	0x06
+
+; U2785 radio
+U2785_CFG1_LEN	EQU	24
+U2785_CFG2_LEN	EQU	9
+
+;-------------------------------------------------------------
+
+Start:		BR	InitDIP
+;-------------------------------------------------------------
+
+SlotTable:	SLOTZERO
+
+Slot00:		WT	1
+		WNT	1
+Slot01:		WT	1
+		WNT	1
+Slot02:		WT	1
+		WNT	1
+Slot03:		WT	1
+		WNT	1
+Slot04:		WT	1
+		WNT	1
+Slot05:		WT	1
+		WNT	1
+		U_INT0
+
+Slot06:		WT	1
+		WNT	1
+Slot07:		WT	1
+		WNT	1
+Slot08:		WT	1
+		WNT	1
+Slot09:		WT	1
+		WNT	1
+Slot10:		WT	1
+		WNT	1
+Slot11:		WT	1
+		WNT	1
+		U_INT1
+
+Slot12:		WT	1
+		WNT	1
+Slot13:		WT	1
+		WNT	1
+Slot14:		WT	1
+		WNT	1
+Slot15:		WT	1
+		WNT	1
+Slot16:		WT	1
+		WNT	1
+Slot17:		WT	1
+		WNT	1
+		U_INT2
+
+Slot18:		WT	1
+		WNT	1
+Slot19:		WT	1
+		WNT	1
+Slot20:		WT	1
+		WNT	1
+Slot21:		WT	1
+		WNT	1
+Slot22:		WT	1
+		WNT	1
+Slot23:		WT	1
+		WNT	1
+		U_INT3
+
+		BR	SlotTable
+
+;-------------------------------------------------------------------------------
+; Receive a P00 packet
+;
+RX_P00:		JMP	Receive		; Receive S- and beginning of A-field		|
+RX_P00_End:	B_BRFU	SD_B_FIELD_OFF	; Receive unprotected full-slot B-field		| p: 95		A: 63
+		JMP	ReceiveEnd	; End reception					| p: 96		B:  0
+		BR	WriteBMC1	;
+
+RX_P00_Sync:	JMP	ReceiveSync	; Receive S- and beginning of A-field		|
+		BR	RX_P00_End
+
+; Receive a P32 packet using the the unprotected full slot B-field format in
+; the D32-field
+;
+RX_P32U_Enc:	JMP	LoadEncKey
+RX_P32U:	JMP	Receive
+		B_BRFU	SD_B_FIELD_OFF	; Receive unprotected full-slot B-field		| p: 95		A: 63
+		JMP	RX_P32U_BZ	; Receive B-field				| p: 96		B:  0
+		BR	WriteBMC2
+
+; Receive a P32 packet using the protected full slot B-field format in the
+; D32-field
+;
+RX_P32P:	JMP	Receive
+		B_BRFP	SD_B_FIELD_OFF	; Receive protected full slot B-field data	| p:  95	A:  63
+		JMP	TransferP32P_B	; Receive the B-subfields			| p:  96-411	B:   0-315
+		WT	3		;	 					| p: 412-414	B: 316-318
+		B_XR			; Receive X-CRC					| p: 415	B: 319
+		WT	6		;						| p: 416-419	B: 320-323	X: 0-3
+					;						| p: 420-421	Z:   0-  1
+		JMP	ReceiveEnd	;						| p: 422	Z: 2
+		B_WRS	SD_BASE_OFF	; write status
+		WT	6
+		BR	label_58
+
+;-------------------------------------------------------------------------------
+; Transmit a P00 packet
+;
+TX_P00:		JMP	Transmit	; Transmit S- and beginning of A-field		|
+		JMP	TransmitEnd	; End transmission				| p: 94		A: 62
+		BR	label_53	;
+
+; Transmit a P32 packet using the unprotected full slot B-field format in the
+; D32-field
+;
+TX_P32U_Enc:	JMP	LoadEncKey
+TX_P32U:	JMP	Transmit	; Transmit S- and beginning of A-field		|
+		B_BTFU	SD_B_FIELD_OFF	; Transmit unprotected full-slot B-field data	| p: 95		A: 63
+		JMP	TX_P32U_BZ	; Transmit the B- and Z-fields			| p: 96		B: 0
+		BR	label_54	;
+
+; Transmit a P32 packet using the protected full slot B-field format in the
+; D32-field
+;
+TX_P32P:	JMP	Transmit	; Transmit S- and beginning of A-field		|
+		B_BTFP	SD_B_FIELD_OFF	; Transmit protect fulls-slot B-field data	| p:  95	A:  63
+		JMP	TransferP32P_B	; Transmit the B-subfields			| p:  96-411	B:   0-315
+		WT	3		; 						| p: 412-414	B: 316-318
+		B_XT			; Transmit X-CRC				| p: 415	B: 319
+		WT	13		;						| p: 416-419	B: 320-323	X: 0-3
+					;						| p: 420-423	Z: 0-3
+					;						| p: 424-428	???
+		B_RST			;						| p: 429
+		JMP	TransmitEnd	; End transmission				| p: 430
+		BR	label_58	;
+
+;-------------------------------------------------------------------------------
+WriteBMC1:	B_WRS	SD_BASE_OFF	; write status
+		WT	6
+
+label_53:	B_RST
+label_54:	P_LDL	PB_RX_ON | PB_TX_ON
+		WT	5
+		WNT	1
+		RTN
+
+;-------------------------------------------------------------------------------
+WriteBMC2:	B_WRS	SD_BASE_OFF	; write status
+		WT	6
+label_58:	B_RST
+		P_LDL	PB_RX_ON | PB_TX_ON
+		RTN
+;-------------------------------------------------------------------------------
+; Enable the receiver, receive the S-field and the first 61 bits of the D-field
+; (93 bits total)
+;
+Receive:	B_RST
+		B_RC	BMC_CTRL
+		WT	BMC_CTRL_SIZE + 1
+		P_LDH	PB_RX_ON
+		P_LDL	PB_RSSI		; enable RSSI measurement
+		WT	25
+		WNT	1		; Wait until beginning of slot			|
+		WT	8		;						| p: -33--26
+		B_XON			;						| p: -25
+ClockSyncOn:	P_SC	PSC_S_SYNC_ON	;						| p: -24
+		P_LDH	PB_DCTHRESHOLD	;						| p: -23
+		WT	5		;						| p: -22--16
+		B_SR			; Receive S-field				| p: -17
+ClockAdjust:	EN_SL_ADJ		;						| p: -16	S: 0
+		WT	12		;						| p: -15--4	S: 1-12
+		P_LDL	PB_DCTHRESHOLD	;						| p:  -3	S: 13
+		WT	32		;						| p:  -2-29	S: 14-45
+ClockSyncOff:	P_SC	0x00		;						| p:  30	S: 46
+		B_AR2	SD_A_FIELD_OFF	; Start reception of A-field/A-field CRC	| p:  31	S: 47
+		WT	62		; Receive first 61 bits of A-field		| p:  32-92	A:  0-60
+		RTN			; Return					| p:  93	A: 61
+
+ReceiveSync:	B_RST
+		B_RC	BMC_CTRL
+		WT	BMC_CTRL_SIZE + 1
+		P_LDH	PB_RX_ON
+		P_LDL	PB_RSSI		; enable RSSI measurement
+		WT	25
+		WNT	1		; Wait until beginning of slot			|
+		WT	8		;						| p: -33--26
+		B_XON			;						| p: -25
+		P_SC	PSC_S_SYNC_ON	;						| p: -24
+		P_LDH	PB_DCTHRESHOLD	;						| p: -23
+		WT	5		;						| p: -22--16
+		B_SR			; Receive S-field				| p: -17
+		EN_SL_ADJ		;						| p: -16	S: 0
+		WT	12		;						| p: -15--4	S: 1-12
+		P_LDL	PB_DCTHRESHOLD	;						| p:  -3	S: 13
+		WT	32		;						| p:  -2-29	S: 14-45
+		P_SC	0x00		;						| p:  30	S: 46
+		B_AR2	SD_A_FIELD_OFF	; Start reception of A-field/A-field CRC	| p:  31	S: 47
+		WT	61		; Receive first 61 bits of A-field		| p:  32-92	A:  0-60
+		RTN			; Return					| p:  93	A: 61
+
+; Receive the B- and Z-fields of a P32 packet using the protected full slot
+; B-field format in the D32-field
+RX_P32U_BZ:	WT	249		;						| p:  97-345	B:   1-249
+		WT	79		;						| p: 346-415	B: 250-319
+					;						| p: 416-419	B: 320-323	X: 0-3
+					;						| p: 420-423	Z:   0-  3
+					;						| p: 424	??
+ReceiveEnd:	P_LDH	PB_RSSI		;						|
+		P_LDL	PB_RX_ON
+		BR	SaveEncState
+;-------------------------------------------------------------------------------
+; Enable transmitter, transmit the S-field and the first 61 bits of the D-field
+; (93 bits total)
+;
+Transmit:	P_LDH	0x00		;
+		WT	40		;
+		B_RST			;
+		B_RC	BMC_CTRL	;
+		WNT	1		; Wait until beginning of slot
+		B_ST	0x00		; Start transmission of S-field data		|
+		WT	1		; Wait one bit					| p: -8		S:  0
+		P_LDH	PB_TX_ON	; Enable transmitter				| p: -7		S:  1
+		WT	37		; Transmit 29 bits S-field			| p: -6-30	S:  2-38
+		B_AT2	SD_A_FIELD_OFF	; Start transission of A-field data/A-field CRC	| p: 31		S: 39
+		WT	62		; Transmit first 61 bits of A-field		| p: 32-92	A:  0-60
+		RTN			; Return					| p: 93		A: 61
+
+;-------------------------------------------------------------------------------
+;
+;
+TX_P32U_BZ:	WT	249		; 						| p:  97-345	B:   1-249
+		WT	84		; Last bits of B-field data			| p: 346-415	B: 250-319
+					; X-field					| p: 416-419	B: 320-323	X: 0-3
+					; Z-field (?)					| p: 420-424	Z:   0-  3
+					; 5 bits of crap?				| p: 425-429
+		B_RST			; Reset BMC					| p: 430
+
+TransmitEnd:	P_LDL	PB_TX_ON	; Disable transmitter				|
+		WT	8		; Wait until transmitter is disabled		|
+		P_LDL	0x00		;
+		BR	SaveEncState
+
+;-------------------------------------------------------------------------------
+; Transfer a protected B-field subfield
+;
+; The X-CRC includes the R-CRC in its calculation, which is contained in the
+; last 16 bits of the subfield.
+;
+TransferBP:  	B_XON			; Enable X-CRC calculation			| p: 159+i*80			  Bi: 63
+		WT	15		; Transfer 15 bits R-CRC, include in X-CRC	| p: 160+i*80-164+i*80		  Bi: 64-78	R_Bi: 0-14
+		B_XOFF			; Disable X-CRC	calculation			| p: 175+i*80			  Bi: 79	R_Bi: 15
+		WT	61		; Transfer first 61 bits of next B-field	| p: 176+i*80-236+i*80		  Bi:  0-60
+		RTN			; Return					| p: 237+i*80			  Bi: 61
+
+; Transfer the B-field subfields of a P32 packet using protected full slot
+; format
+TransferP32P_B:	WT	61		; Transfer 61 bits of B-field			| p:  97-157	B:   1- 61	  B0: 1-61
+		JMP	TransferBP	; End subfield, begin next subfield		| p: 158-237	B:  62-141	  B0: 62-79	B1: 0-61
+		JMP	TransferBP	; End subfield, begin next subfield		| p: 238-317	B: 142-221	  B1: 62-79	B2: 0-61
+		JMP	TransferBP	; End subfield, begin next subfield		| p: 318-397	B: 222-301	  B2: 62-79	B3: 0-61
+		WT	1		;						| p: 398	B: 302		  B3: 62
+		B_XON			; 						| p: 399	B: 303		  B3: 63
+		WT	11		;						| p: 400-410	B: 304-314	R_B3:  0-10
+		RTN			;						| p: 411	B: 315		R_B3: 11
+
+;-------------------------------------------------------------------------------
+
+RFInit:		RFEN			; Enable RF-clock
+		WT	2
+
+		MEN1N			; Transfer first radio configuration word
+		M_WR	RF_DESC
+		WT	U2785_CFG1_LEN + 1
+		M_RST
+		MEN1
+
+		MEN1N			; Transfer second radio configuration word
+		M_WR	RF_DESC + U2785_CFG1_LEN / 8
+		WT	U2785_CFG2_LEN + 1
+		M_RST
+		MEN1
+		;WT	1
+
+		P_LDL	0x20
+		WT	10
+		MEN2
+		WT	182
+		MEN2N
+		WT	16
+		RTN
+;--------------------------------------------------------------
+;
+LoadEncKey:	D_RST
+		D_LDK	DCS_DESC	; load IV (64 bits) and cipher key (64 bits)
+		WT	16
+		D_LDK	0
+		D_PREP	0
+		WT	39
+		D_PREP	0
+		RTN
+
+SaveEncState:	D_WRS   DCS_STATE
+		WT	DCS_STATE_SIZE	; actually should be -1, but does not work
+		D_WRS	0
+		D_RST
+		RTN
+
+LoadEncState:	D_RST
+		D_LDS	DCS_STATE
+		WT	DCS_STATE_SIZE	; actually should be -1, but does not work
+		D_LDS	0
+		RTN
+;-------------------------------------------------------------
+
+SyncInit:	BK_C	BANK1_LOW
+Sync:		JMP	RFInit
+		WT	250
+		P_SC	PSC_S_SYNC_ON
+		P_LDH	PB_RX_ON | PB_DCTHRESHOLD
+		UNLCK
+		WT	64
+		B_XOFF
+		B_SR
+		WNT	20
+		JMP1	SFieldFound
+		B_RST
+		U_INT1
+		WNT	23
+		BR	Sync
+;-------------------------------------------------------------
+
+SFieldFound:	WNT	23
+		P_SC	0x00
+SyncLock:	JMP	RFInit
+		JMP	RX_P00
+		U_INT0
+		WNT	22
+SyncLoop:	BR	Sync
+;-------------------------------------------------------------
+
+InitDIP:	;B_RST
+		BK_C	BANK0_LOW
+		C_LD	DIP_CC_INIT
+		WT	10
+		B_RC	BMC_CTRL
+		WT	BMC_CTRL_SIZE + 1
+		B_RST
+		;C_ON
+		WT	10
+		P_EN
+		P_LD	0x04
+		RCK_INT
+		RFEN
+RFStart:	BR	SyncInit
+;-------------------------------------------------------------
+
+		SHARED	DIP_CC_INIT,RF_DESC
+		SHARED	BMC_CTRL,BMC_CTRL_MFR_OFF
+		SHARED	SD_RSSI_OFF,SD_CSUM_OFF,SD_PREAMBLE_OFF,SD_DATA_OFF
+
+		SHARED	SlotTable
+		SHARED	Slot00,Slot01,Slot02,Slot03,Slot04,Slot05,Slot06,Slot07
+		SHARED	Slot08,Slot09,Slot10,Slot11,Slot12,Slot13,Slot14,Slot15
+		SHARED	Slot16,Slot17,Slot18,Slot19,Slot20,Slot21,Slot22,Slot23
+
+		SHARED	RFStart,RFInit
+		SHARED	SyncInit,Sync,SyncLock,SyncLoop
+		SHARED	ClockSyncOn,ClockSyncOff,ClockAdjust
+		SHARED	PSC_ARPD1,PSC_S_SYNC,PSC_S_SYNC_ON,PSC_EOPSM
+
+		SHARED	RX_P00,RX_P00_Sync,RX_P32U,RX_P32P,RX_P32U_Enc
+		SHARED	TX_P00,TX_P32U,TX_P32P,TX_P32U_Enc
+
+		SHARED	DCS_IV,DCS_CK,DCS_STATE,DCS_STATE_SIZE
+		SHARED	LoadEncKey,LoadEncState
diff --git a/drivers/dect/coa/sc1442x_firmware.c b/drivers/dect/coa/sc1442x_firmware.c
new file mode 100644
index 0000000..f90962b
--- /dev/null
+++ b/drivers/dect/coa/sc1442x_firmware.c
@@ -0,0 +1,73 @@
+/*
+ * automatically generated file
+ * DO NOT EDIT
+ * edit firmware/filename.asm instead
+ */
+
+#include "sc1442x_firmware.h"
+
+const unsigned char sc1442x_firmware[] = {
+	0x01, 0x01, 0x01, 0xf4, 0x0d, 0x00, 0x09, 0x01,
+	0x08, 0x01, 0x09, 0x01, 0x08, 0x01, 0x09, 0x01,
+	0x08, 0x01, 0x09, 0x01, 0x08, 0x01, 0x09, 0x01,
+	0x08, 0x01, 0x09, 0x01, 0x08, 0x01, 0x61, 0x00,
+	0x09, 0x01, 0x08, 0x01, 0x09, 0x01, 0x08, 0x01,
+	0x09, 0x01, 0x08, 0x01, 0x09, 0x01, 0x08, 0x01,
+	0x09, 0x01, 0x08, 0x01, 0x09, 0x01, 0x08, 0x01,
+	0x6b, 0x00, 0x09, 0x01, 0x08, 0x01, 0x09, 0x01,
+	0x08, 0x01, 0x09, 0x01, 0x08, 0x01, 0x09, 0x01,
+	0x08, 0x01, 0x09, 0x01, 0x08, 0x01, 0x09, 0x01,
+	0x08, 0x01, 0x6d, 0x00, 0x09, 0x01, 0x08, 0x01,
+	0x09, 0x01, 0x08, 0x01, 0x09, 0x01, 0x08, 0x01,
+	0x09, 0x01, 0x08, 0x01, 0x09, 0x01, 0x08, 0x01,
+	0x09, 0x01, 0x08, 0x01, 0x6f, 0x00, 0x01, 0x02,
+	0x02, 0x6a, 0x2d, 0x0e, 0x02, 0x96, 0x01, 0x5e,
+	0x02, 0x7f, 0x01, 0x39, 0x02, 0xcc, 0x02, 0x6a,
+	0x2d, 0x0e, 0x02, 0x94, 0x01, 0x65, 0x02, 0x6a,
+	0x3d, 0x0e, 0x02, 0xb1, 0x09, 0x03, 0x2b, 0x00,
+	0x09, 0x06, 0x02, 0x96, 0x39, 0x00, 0x09, 0x06,
+	0x01, 0x67, 0x02, 0x99, 0x02, 0xa8, 0x01, 0x60,
+	0x02, 0xcc, 0x02, 0x99, 0x25, 0x0e, 0x02, 0xa5,
+	0x01, 0x61, 0x02, 0x99, 0x35, 0x0e, 0x02, 0xb1,
+	0x09, 0x03, 0x24, 0x00, 0x09, 0x0d, 0x20, 0x00,
+	0x02, 0xa8, 0x01, 0x67, 0x39, 0x00, 0x09, 0x06,
+	0x20, 0x00, 0xec, 0x50, 0x09, 0x05, 0x08, 0x01,
+	0x04, 0x00, 0x39, 0x00, 0x09, 0x06, 0x20, 0x00,
+	0xec, 0x50, 0x04, 0x00, 0x20, 0x00, 0x33, 0x69,
+	0x09, 0x08, 0xed, 0x40, 0xec, 0x01, 0x09, 0x19,
+	0x08, 0x01, 0x09, 0x08, 0x27, 0x00, 0xea, 0x20,
+	0xed, 0x02, 0x09, 0x05, 0x29, 0x00, 0x2c, 0x00,
+	0x09, 0x0c, 0xec, 0x02, 0x09, 0x20, 0xea, 0x00,
+	0x3f, 0x06, 0x09, 0x3e, 0x04, 0x00, 0x20, 0x00,
+	0x33, 0x69, 0x09, 0x08, 0xed, 0x40, 0xec, 0x01,
+	0x09, 0x19, 0x08, 0x01, 0x09, 0x08, 0x27, 0x00,
+	0xea, 0x20, 0xed, 0x02, 0x09, 0x05, 0x29, 0x00,
+	0x2c, 0x00, 0x09, 0x0c, 0xec, 0x02, 0x09, 0x20,
+	0xea, 0x00, 0x3f, 0x06, 0x09, 0x3d, 0x04, 0x00,
+	0x09, 0xf9, 0x09, 0x4f, 0xed, 0x01, 0xec, 0x40,
+	0x01, 0xd4, 0xed, 0x00, 0x09, 0x28, 0x20, 0x00,
+	0x33, 0x69, 0x08, 0x01, 0x31, 0x00, 0x09, 0x01,
+	0xed, 0x10, 0x09, 0x25, 0x37, 0x06, 0x09, 0x3e,
+	0x04, 0x00, 0x09, 0xf9, 0x09, 0x54, 0x20, 0x00,
+	0xec, 0x10, 0x09, 0x08, 0xec, 0x00, 0x01, 0xd4,
+	0x27, 0x00, 0x09, 0x0f, 0x26, 0x00, 0x09, 0x3d,
+	0x04, 0x00, 0x09, 0x3d, 0x02, 0xac, 0x02, 0xac,
+	0x02, 0xac, 0x09, 0x01, 0x27, 0x00, 0x09, 0x0b,
+	0x04, 0x00, 0x0b, 0x00, 0x09, 0x02, 0xa4, 0x00,
+	0xb9, 0x65, 0x09, 0x19, 0xa9, 0x00, 0xa5, 0x00,
+	0xa4, 0x00, 0xb9, 0x68, 0x09, 0x0a, 0xa9, 0x00,
+	0xa5, 0x00, 0xec, 0x20, 0x09, 0x0a, 0xa7, 0x00,
+	0x09, 0xb6, 0xa6, 0x00, 0x09, 0x10, 0x04, 0x00,
+	0x40, 0x00, 0x50, 0x70, 0x09, 0x10, 0x50, 0x00,
+	0x44, 0x00, 0x09, 0x27, 0x44, 0x00, 0x04, 0x00,
+	0x5f, 0x70, 0x09, 0x0b, 0x5f, 0x00, 0x40, 0x00,
+	0x04, 0x00, 0x40, 0x00, 0x57, 0x70, 0x09, 0x0b,
+	0x57, 0x00, 0x04, 0x00, 0x0f, 0x20, 0x02, 0xb9,
+	0x09, 0xfa, 0xea, 0x20, 0xed, 0x42, 0x28, 0x00,
+	0x09, 0x40, 0x26, 0x00, 0x29, 0x00, 0x08, 0x14,
+	0x03, 0xed, 0x20, 0x00, 0x6b, 0x00, 0x08, 0x17,
+	0x01, 0xdf, 0x08, 0x17, 0xea, 0x00, 0x02, 0xb9,
+	0x02, 0x38, 0x61, 0x00, 0x08, 0x16, 0x01, 0xdf,
+	0x0f, 0x00, 0xfa, 0x10, 0x09, 0x0a, 0x33, 0x69,
+	0x09, 0x08, 0x20, 0x00, 0x09, 0x0a, 0xe9, 0x00,
+	0xe8, 0x04, 0x62, 0x00, 0x0b, 0x00, 0x01, 0xde};
diff --git a/drivers/dect/coa/sc1442x_firmware.h b/drivers/dect/coa/sc1442x_firmware.h
new file mode 100644
index 0000000..826db2d
--- /dev/null
+++ b/drivers/dect/coa/sc1442x_firmware.h
@@ -0,0 +1,68 @@
+#ifndef SC1442X_FIRMWARE
+#define SC1442X_FIRMWARE
+
+extern const unsigned char sc1442x_firmware[510];
+
+#define DIP_CC_INIT 0x10
+#define RF_DESC 0x65
+#define BMC_CTRL 0x69
+#define BMC_CTRL_MFR_OFF 0x6
+#define SD_RSSI_OFF 0x0
+#define SD_CSUM_OFF 0x1
+#define SD_PREAMBLE_OFF 0x1
+#define SD_DATA_OFF 0x6
+#define SlotTable 0x2
+#define Slot00 0x3
+#define Slot01 0x5
+#define Slot02 0x7
+#define Slot03 0x9
+#define Slot04 0xB
+#define Slot05 0xD
+#define Slot06 0x10
+#define Slot07 0x12
+#define Slot08 0x14
+#define Slot09 0x16
+#define Slot10 0x18
+#define Slot11 0x1A
+#define Slot12 0x1D
+#define Slot13 0x1F
+#define Slot14 0x21
+#define Slot15 0x23
+#define Slot16 0x25
+#define Slot17 0x27
+#define Slot18 0x2A
+#define Slot19 0x2C
+#define Slot20 0x2E
+#define Slot21 0x30
+#define Slot22 0x32
+#define Slot23 0x34
+#define RFStart 0xFF
+#define RFInit 0xB9
+#define SyncInit 0xDE
+#define Sync 0xDF
+#define SyncLock 0xEF
+#define SyncLoop 0xF3
+#define ClockSyncOn 0x73
+#define ClockSyncOff 0x7B
+#define ClockAdjust 0x77
+#define PSC_ARPD1 0x80
+#define PSC_S_SYNC 0x40
+#define PSC_S_SYNC_ON 0x20
+#define PSC_EOPSM 0x10
+#define RX_P00 0x38
+#define RX_P00_Sync 0x3C
+#define RX_P32U 0x3F
+#define RX_P32P 0x43
+#define RX_P32U_Enc 0x3E
+#define TX_P00 0x4D
+#define TX_P32U 0x51
+#define TX_P32P 0x55
+#define TX_P32U_Enc 0x50
+#define DCS_IV 0x70
+#define DCS_CK 0x78
+#define DCS_STATE 0x70
+#define DCS_STATE_SIZE 0xB
+#define LoadEncKey 0xCC
+#define LoadEncState 0xD9
+
+#endif /* SC1442X_FIRMWARE */
diff --git a/drivers/dect/vtrx/Kconfig b/drivers/dect/vtrx/Kconfig
new file mode 100644
index 0000000..fb86eee
--- /dev/null
+++ b/drivers/dect/vtrx/Kconfig
@@ -0,0 +1,5 @@
+config DECT_VTRX
+	tristate "DECT virtual transceiver support"
+	depends on DECT
+	help
+	  This option enables support for the virtual DECT transceiver.
diff --git a/drivers/dect/vtrx/Makefile b/drivers/dect/vtrx/Makefile
new file mode 100644
index 0000000..08abbf5
--- /dev/null
+++ b/drivers/dect/vtrx/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_DECT_VTRX)		+= dect-vtrx.o
+dect-vtrx-objs			:= vtrx.o vtrx-sysfs.o mw_to_dbm.o
diff --git a/drivers/dect/vtrx/mw_to_dbm.c b/drivers/dect/vtrx/mw_to_dbm.c
new file mode 100644
index 0000000..55122a8
--- /dev/null
+++ b/drivers/dect/vtrx/mw_to_dbm.c
@@ -0,0 +1,164 @@
+/*
+ * DECT virtual transceiver
+ *
+ * Copyright (c) 2010 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <net/dect/transceiver.h>
+#include "vtrx.h"
+
+static const struct {
+	u64		mw;
+	int		dbm;
+} mw_to_dbm_tbl[] = {
+	{ 5ULL,			-93 },
+	{ 6ULL,			-92 },
+	{ 7ULL,			-91 },
+	{ 10ULL,		-90 },
+	{ 12ULL,		-89 },
+	{ 15ULL,		-88 },
+	{ 19ULL,		-87 },
+	{ 25ULL,		-86 },
+	{ 31ULL,		-85 },
+	{ 39ULL,		-84 },
+	{ 50ULL,		-83 },
+	{ 63ULL,		-82 },
+	{ 79ULL,		-81 },
+	{ 100ULL,		-80 },
+	{ 125ULL,		-79 },
+	{ 158ULL,		-78 },
+	{ 199ULL,		-77 },
+	{ 251ULL,		-76 },
+	{ 316ULL,		-75 },
+	{ 398ULL,		-74 },
+	{ 501ULL,		-73 },
+	{ 630ULL,		-72 },
+	{ 794ULL,		-71 },
+	{ 1000ULL,		-70 },
+	{ 1258ULL,		-69 },
+	{ 1584ULL,		-68 },
+	{ 1995ULL,		-67 },
+	{ 2511ULL,		-66 },
+	{ 3162ULL,		-65 },
+	{ 3981ULL,		-64 },
+	{ 5011ULL,		-63 },
+	{ 6309ULL,		-62 },
+	{ 7943ULL,		-61 },
+	{ 10000ULL,		-60 },
+	{ 12589ULL,		-59 },
+	{ 15848ULL,		-58 },
+	{ 19952ULL,		-57 },
+	{ 25118ULL,		-56 },
+	{ 31622ULL,		-55 },
+	{ 39810ULL,		-54 },
+	{ 50118ULL,		-53 },
+	{ 63095ULL,		-52 },
+	{ 79432ULL,		-51 },
+	{ 100000ULL,		-50 },
+	{ 125892ULL,		-49 },
+	{ 158489ULL,		-48 },
+	{ 199526ULL,		-47 },
+	{ 251188ULL,		-46 },
+	{ 316227ULL,		-45 },
+	{ 398107ULL,		-44 },
+	{ 501187ULL,		-43 },
+	{ 630957ULL,		-42 },
+	{ 794328ULL,		-41 },
+	{ 1000000ULL,		-40 },
+	{ 1258925ULL,		-39 },
+	{ 1584893ULL,		-38 },
+	{ 1995262ULL,		-37 },
+	{ 2511886ULL,		-36 },
+	{ 3162277ULL,		-35 },
+	{ 3981071ULL,		-34 },
+	{ 5011872ULL,		-33 },
+	{ 6309573ULL,		-32 },
+	{ 7943282ULL,		-31 },
+	{ 10000000ULL,		-30 },
+	{ 12589254ULL,		-29 },
+	{ 15848931ULL,		-28 },
+	{ 19952623ULL,		-27 },
+	{ 25118864ULL,		-26 },
+	{ 31622776ULL,		-25 },
+	{ 39810717ULL,		-24 },
+	{ 50118723ULL,		-23 },
+	{ 63095734ULL,		-22 },
+	{ 79432823ULL,		-21 },
+	{ 100000000ULL,		-20 },
+	{ 125892541ULL,		-19 },
+	{ 158489319ULL,		-18 },
+	{ 199526231ULL,		-17 },
+	{ 251188643ULL,		-16 },
+	{ 316227766ULL,		-15 },
+	{ 398107170ULL,		-14 },
+	{ 501187233ULL,		-13 },
+	{ 630957344ULL,		-12 },
+	{ 794328234ULL,		-11 },
+	{ 1000000000ULL,	-10 },
+	{ 1258925411ULL,	-9 },
+	{ 1584893192ULL,	-8 },
+	{ 1995262314ULL,	-7 },
+	{ 2511886431ULL,	-6 },
+	{ 3162277660ULL,	-5 },
+	{ 3981071705ULL,	-4 },
+	{ 5011872336ULL,	-3 },
+	{ 6309573444ULL,	-2 },
+	{ 7943282347ULL,	-1 },
+	{ 10000000000ULL,	0 },
+	{ 12589254117ULL,	1 },
+	{ 15848931924ULL,	2 },
+	{ 19952623149ULL,	3 },
+	{ 25118864315ULL,	4 },
+	{ 31622776601ULL,	5 },
+	{ 39810717055ULL,	6 },
+	{ 50118723362ULL,	7 },
+	{ 63095734448ULL,	8 },
+	{ 79432823472ULL,	9 },
+	{ 100000000000ULL,	10 },
+	{ 125892541179ULL,	11 },
+	{ 158489319246ULL,	12 },
+	{ 199526231496ULL,	13 },
+	{ 251188643150ULL,	14 },
+	{ 316227766016ULL,	15 },
+	{ 398107170553ULL,	16 },
+	{ 501187233627ULL,	17 },
+	{ 630957344480ULL,	18 },
+	{ 794328234724ULL,	19 },
+	{ 1000000000000ULL,	20 },
+	{ 1258925411794ULL,	21 },
+	{ 1584893192461ULL,	22 },
+	{ 1995262314968ULL,	23 },
+	{ 2511886431509ULL,	24 },
+};
+
+int dect_mw_to_dbm(u64 mw)
+{
+	unsigned int min, max, mid;
+	u64 val;
+
+	min = 0;
+	max = ARRAY_SIZE(mw_to_dbm_tbl) - 1;
+
+	while (min < max) {
+		mid = min + (max - min) / 2;
+
+		val = mw_to_dbm_tbl[mid].mw;
+		if (val < mw)
+			min = mid + 1;
+		else
+			max = mid;
+	}
+
+	if (val > mw) {
+		if (mid == 0)
+			return 0;
+		mid--;
+	}
+
+	return mw_to_dbm_tbl[mid].dbm;
+}
diff --git a/drivers/dect/vtrx/vtrx-sysfs.c b/drivers/dect/vtrx/vtrx-sysfs.c
new file mode 100644
index 0000000..d65beab
--- /dev/null
+++ b/drivers/dect/vtrx/vtrx-sysfs.c
@@ -0,0 +1,228 @@
+/*
+ * DECT virtual transceiver
+ *
+ * Copyright (c) 2010 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <net/dect/transceiver.h>
+#include "vtrx.h"
+
+static struct class *dect_class;
+
+/*
+ * Transceivers
+ */
+
+#define VTRX_ATTR(_name, _mode, _show, _store)						\
+	struct device_attribute vtrx_attr_##_name = __ATTR(_name, _mode, _show, _store)
+
+#define VTRX_NUMERIC_ATTR(name, field, scale)						\
+static ssize_t vtrx_show_##name(struct device *dev, struct device_attribute *attr,	\
+				char *buf)						\
+{											\
+	struct dect_vtrx *vtrx = dev_get_drvdata(dev);					\
+	return sprintf(buf, "%llu\n",							\
+		       (unsigned long long)div64_u64(vtrx->field, scale));		\
+}											\
+											\
+static ssize_t vtrx_store_##name(struct device *dev, struct device_attribute *attr,	\
+				 const char *buf, size_t count)				\
+{											\
+	struct dect_vtrx *vtrx = dev_get_drvdata(dev);					\
+	char *ptr;									\
+	u32 val;									\
+											\
+	val = simple_strtoul(buf, &ptr, 10);						\
+	if (ptr == buf)									\
+		return -EINVAL;								\
+	vtrx->field = val * scale;							\
+	return count;									\
+}											\
+static VTRX_ATTR(name, S_IRUGO | S_IWUSR, vtrx_show_##name, vtrx_store_##name)
+
+VTRX_NUMERIC_ATTR(tx_power, tx_power, DECT_VTRX_POWER_SCALE);
+VTRX_NUMERIC_ATTR(pos_x, pos_x, 1000);
+VTRX_NUMERIC_ATTR(pos_y, pos_y, 1000);
+
+static ssize_t vtrx_store_remove(struct device *dev, struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct dect_vtrx *vtrx = dev_get_drvdata(dev);
+
+	dect_vtrx_free(vtrx);
+	return count;
+}
+
+static VTRX_ATTR(remove, S_IWUSR, NULL, vtrx_store_remove);
+
+static struct attribute *vtrx_attrs[] = {
+	&vtrx_attr_tx_power.attr,
+	&vtrx_attr_pos_x.attr,
+	&vtrx_attr_pos_y.attr,
+	&vtrx_attr_remove.attr,
+	NULL
+};
+
+static struct attribute_group vtrx_attr_group = {
+	.attrs		= vtrx_attrs,
+};
+
+static const struct attribute_group *vtrx_attr_groups[] = {
+	&vtrx_attr_group,
+	NULL,
+};
+
+static void dect_vtrx_release(struct device *dev)
+{
+	printk("%s\n", __func__);
+}
+
+static struct device_type dect_vtrx_group = {
+	.name		= "vtrx",
+	.groups		= vtrx_attr_groups,
+	.release	= dect_vtrx_release,
+};
+
+int dect_vtrx_register_sysfs(struct dect_vtrx *vtrx)
+{
+	struct device *dev = &vtrx->dev;
+
+	dev->type   = &dect_vtrx_group;
+	dev->class  = dect_class;
+	dev->parent = &vtrx->group->dev;
+
+	dev_set_name(dev, "%s", vtrx->trx->name);
+	dev_set_drvdata(dev, vtrx);
+
+	return device_register(dev);
+}
+
+void dect_vtrx_unregister_sysfs(struct dect_vtrx *vtrx)
+{
+	device_del(&vtrx->dev);
+}
+
+/*
+ * Groups
+ */
+
+#define GROUP_ATTR(_name, _mode, _show, _store)						\
+	struct device_attribute group_attr_##_name = __ATTR(_name, _mode, _show, _store)
+
+#define GROUP_SHOW(name, field, fmt)							\
+static ssize_t group_show_##name(struct device *dev, struct device_attribute *attr,	\
+				 char *buf)						\
+{											\
+	struct dect_vtrx_group *group = dev_get_drvdata(dev);				\
+	return sprintf(buf, fmt, group->field);						\
+}											\
+											\
+static ssize_t group_store_##name(struct device *dev, struct device_attribute *attr,	\
+				  const char *buf, size_t count)			\
+{											\
+	struct dect_vtrx_group *group = dev_get_drvdata(dev);				\
+	char *ptr;									\
+	u32 val;									\
+											\
+	val = simple_strtoul(buf, &ptr, 10);						\
+	if (ptr == buf)									\
+		return -EINVAL;								\
+	group->field = val;								\
+	return count;									\
+}											\
+static GROUP_ATTR(name, S_IRUGO | S_IWUSR, group_show_##name, group_store_##name)
+
+static ssize_t group_store_new(struct device *dev, struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct dect_vtrx_group *group = dev_get_drvdata(dev);
+	int err;
+
+	err = dect_vtrx_init(group);
+	return err ? err : count;
+}
+
+static GROUP_ATTR(new_trx, S_IWUSR, NULL, group_store_new);
+
+static struct attribute *group_attrs[] = {
+	&group_attr_new_trx.attr,
+	NULL
+};
+
+static struct attribute_group group_attr_group = {
+	.attrs		= group_attrs,
+};
+
+static const struct attribute_group *group_attr_groups[] = {
+	&group_attr_group,
+	NULL,
+};
+
+static void dect_vtrx_group_release(struct device *dev)
+{
+	printk("%s\n", __func__);
+}
+
+static struct device_type dect_vtrx_group_group = {
+	.name		= "vtrx-group",
+	.groups		= group_attr_groups,
+	.release	= dect_vtrx_group_release,
+};
+
+int dect_vtrx_group_register_sysfs(struct dect_vtrx_group *group)
+{
+	struct device *dev = &group->dev;
+
+	dev->type   = &dect_vtrx_group_group;
+	dev->class  = dect_class;
+	dev->parent = 0;
+
+	dev_set_name(dev, "%s", group->name);
+	dev_set_drvdata(dev, group);
+
+	return device_register(dev);
+}
+
+static ssize_t store_new_group(struct class *dev, struct class_attribute *attr,
+			       const char *buf, size_t count)
+{
+	char name[16];
+
+	sscanf(buf, "%16s", name);
+	if (!dect_vtrx_group_init(name))
+		return -ENOMEM;
+	return count;
+}
+
+static CLASS_ATTR(new_group, S_IWUSR, NULL, store_new_group);
+
+void dect_vtrx_group_unregister_sysfs(struct dect_vtrx_group *group)
+{
+	device_del(&group->dev);
+}
+
+int dect_vtrx_sysfs_init(void)
+{
+	int err;
+
+	dect_class = class_create(THIS_MODULE, "dect");
+	if (dect_class == NULL)
+		return -ENOMEM;
+
+	err = class_create_file(dect_class, &class_attr_new_group);
+	if (err < 0)
+		class_destroy(dect_class);
+
+	return err;
+}
+
+void dect_vtrx_sysfs_exit(void)
+{
+	class_remove_file(dect_class, &class_attr_new_group);
+	class_destroy(dect_class);
+}
diff --git a/drivers/dect/vtrx/vtrx.c b/drivers/dect/vtrx/vtrx.c
new file mode 100644
index 0000000..6636ba3
--- /dev/null
+++ b/drivers/dect/vtrx/vtrx.c
@@ -0,0 +1,397 @@
+/*
+ * DECT virtual transceiver
+ *
+ * Copyright (c) 2010 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define DEBUG
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kobject.h>
+#include <linux/hrtimer.h>
+#include <linux/skbuff.h>
+#include <net/dect/transceiver.h>
+#include "vtrx.h"
+
+#define vtrx_debug(vtrx, fmt, args...) \
+	pr_debug("vtrx %s: " fmt, (vtrx)->trx->name, ## args)
+
+#define DECT_SLOTS_PER_SECOND	(DECT_FRAMES_PER_SECOND * DECT_FRAME_SIZE)
+#define DECT_VTRX_RATE		(NSEC_PER_SEC / DECT_SLOTS_PER_SECOND)
+#define DECT_VTRX_DEFAULT_TRX	2
+
+#define DECT_WAVELEN_SCALE	13
+#define DECT_WAVELEN		160 /* mm */
+
+struct dect_skb_vtrx_cb {
+	struct dect_vtrx	*vtrx;
+	u8			rssi;
+	u8			carrier;
+};
+
+static LIST_HEAD(vtrx_groups);
+
+static inline struct dect_skb_vtrx_cb *DECT_VTRX_CB(const struct sk_buff *skb)
+{
+	BUILD_BUG_ON(sizeof(struct dect_skb_vtrx_cb) > sizeof(skb->cb));
+	return (struct dect_skb_vtrx_cb *)skb->cb;
+}
+
+static unsigned int dect_vtrx_distance(const struct dect_vtrx *vtrx1,
+				       const struct dect_vtrx *vtrx2)
+{
+	int dx, dy;
+
+	dx = vtrx1->pos_x - vtrx2->pos_x;
+	dy = vtrx1->pos_y - vtrx2->pos_y;
+
+	return int_sqrt(dx * dx + dy * dy);
+}
+
+static u8 dect_vtrx_receive_rssi(const struct dect_vtrx *rx_vtrx,
+				 const struct sk_buff *skb)
+{
+	const struct dect_vtrx *tx_vtrx = DECT_VTRX_CB(skb)->vtrx;
+	unsigned int distance;
+	u64 rx_power, tmp;
+	int dbm = 0;
+
+	distance = dect_vtrx_distance(rx_vtrx, tx_vtrx);
+	if (distance == 0)
+		goto out;
+
+	tmp = 1000 * (DECT_WAVELEN << DECT_WAVELEN_SCALE) / (4 * 3141 * distance);
+	rx_power = (tx_vtrx->tx_power * tmp * tmp) >> (2 * DECT_WAVELEN_SCALE);
+	dbm = dect_mw_to_dbm(rx_power);
+out:
+	if (dbm > -33)
+		dbm = -33;
+
+	return dect_dbm_to_rssi(dbm);
+}
+
+static void dect_vtrx_process_slot(struct dect_vtrx_group *group,
+				   struct dect_vtrx *vtrx)
+{
+	struct dect_transceiver_event *event;
+	struct dect_transceiver_slot *ts;
+	struct dect_transceiver *trx = vtrx->trx;
+	struct sk_buff *skb, *best;
+	u8 slot = group->slot, rcvslot;
+	u8 rssi, best_rssi;
+
+	event = dect_transceiver_event(trx, slot % 12, slot);
+	if (event == NULL)
+		return;
+
+	if (trx->state == DECT_TRANSCEIVER_UNLOCKED ||
+	    trx->state == DECT_TRANSCEIVER_LOCK_PENDING)
+		rcvslot = DECT_SCAN_SLOT;
+	else
+		rcvslot = slot;
+
+	rssi = dect_dbm_to_rssi(-80);
+	best = NULL;
+
+	ts = &trx->slots[rcvslot];
+	if (ts->state != DECT_SLOT_RX &&
+	    ts->state != DECT_SLOT_SCANNING)
+		goto queue;
+
+	skb_queue_walk(&group->txq[slot], skb) {
+		if (DECT_VTRX_CB(skb)->carrier != ts->chd.carrier)
+			continue;
+
+		rssi = dect_vtrx_receive_rssi(vtrx, skb);
+		if (best == NULL || rssi > best_rssi) {
+			best	  = skb;
+			best_rssi = rssi;
+		}
+	}
+
+	if (best == NULL)
+		goto rssi;
+	rssi = best_rssi;
+
+	skb = skb_clone(best, GFP_ATOMIC);
+	if (skb == NULL)
+		goto rssi;
+
+	DECT_TRX_CB(skb)->trx  = trx;
+	DECT_TRX_CB(skb)->slot = rcvslot;
+	DECT_TRX_CB(skb)->csum = DECT_CHECKSUM_A_CRC_OK | DECT_CHECKSUM_X_CRC_OK;
+	DECT_TRX_CB(skb)->rssi = rssi;
+	__skb_queue_tail(&event->rx_queue, skb);
+
+	ts->rx_bytes += skb->len;
+	ts->rx_packets++;
+rssi:
+	ts->rssi = dect_average_rssi(ts->rssi, rssi);
+	dect_transceiver_record_rssi(event, rcvslot, rssi);
+queue:
+	if (rcvslot != slot && best == NULL)
+		dect_release_transceiver_event(event);
+	else
+		dect_transceiver_queue_event(trx, event);
+}
+
+static enum hrtimer_restart dect_vtrx_timer(struct hrtimer *timer)
+{
+	struct dect_vtrx_group *group = container_of(timer, struct dect_vtrx_group, timer);
+	struct dect_vtrx *vtrx;
+	ktime_t time;
+
+	list_for_each_entry(vtrx, &group->act_list, list)
+		dect_vtrx_process_slot(group, vtrx);
+
+	skb_queue_purge(&group->txq[group->slot]);
+	group->slot = dect_next_slotnum(group->slot);
+
+	time = ktime_set(0, DECT_VTRX_RATE);
+	hrtimer_forward(timer, hrtimer_cb_get_time(timer), time);
+
+	return HRTIMER_RESTART;
+}
+
+/*
+ * Transceiver operations
+ */
+
+static void dect_vtrx_enable(const struct dect_transceiver *trx)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+	struct dect_vtrx_group *group = vtrx->group;
+	ktime_t time;
+
+	vtrx_debug(vtrx, "enable");
+	if (list_empty(&group->act_list)) {
+		time = ktime_set(0, DECT_VTRX_RATE);
+		hrtimer_start(&group->timer, time, HRTIMER_MODE_ABS);
+	}
+	list_move_tail(&vtrx->list, &group->act_list);
+}
+
+static void dect_vtrx_disable(const struct dect_transceiver *trx)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+	struct dect_vtrx_group *group = vtrx->group;
+
+	vtrx_debug(vtrx, "disable");
+	list_move_tail(&vtrx->list, &group->trx_list);
+	if (list_empty(&group->act_list))
+		hrtimer_cancel(&group->timer);
+}
+
+static void dect_vtrx_confirm(const struct dect_transceiver *trx)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+
+	vtrx_debug(vtrx, "confirm");
+}
+
+static void dect_vtrx_unlock(const struct dect_transceiver *trx)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+
+	vtrx_debug(vtrx, "unlock");
+}
+
+static void dect_vtrx_lock(const struct dect_transceiver *trx, u8 slot)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+
+	vtrx_debug(vtrx, "lock");
+}
+
+static void dect_vtrx_set_mode(const struct dect_transceiver *trx,
+			       const struct dect_channel_desc *chd,
+			       enum dect_slot_states mode)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+
+	vtrx_debug(vtrx, "set_mode: slot: %u mode: %u",
+		   chd->slot, mode);
+}
+
+static void dect_vtrx_set_carrier(const struct dect_transceiver *trx,
+				  u8 slot, u8 carrier)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+
+	vtrx_debug(vtrx, "set carrier: slot: %u carrier: %u\n",
+		   slot, carrier);
+}
+
+static u64 dect_vtrx_set_band(const struct dect_transceiver *trx,
+			      const struct dect_band *band)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+
+	vtrx_debug(vtrx, "set band: %u\n", band->band);
+	return band->carriers;
+}
+
+static void dect_vtrx_tx(const struct dect_transceiver *trx, struct sk_buff *skb)
+{
+	struct dect_vtrx *vtrx = dect_transceiver_priv(trx);
+	struct dect_vtrx_group *group = vtrx->group;
+	u8 slot = DECT_TRX_CB(skb)->slot;
+
+	vtrx_debug(vtrx, "vtrx tx: slot: %u skb: %p\n", slot, skb);
+	DECT_VTRX_CB(skb)->vtrx    = vtrx;
+	DECT_VTRX_CB(skb)->rssi    = vtrx->tx_power;
+	DECT_VTRX_CB(skb)->carrier = trx->slots[slot].chd.carrier;
+	skb_queue_tail(&group->txq[slot], skb);
+}
+
+static const struct dect_transceiver_ops vtrx_transceiver_ops = {
+	.name			= "vtrx",
+	.eventrate		= 1,
+	.latency		= 1,
+	.enable			= dect_vtrx_enable,
+	.disable		= dect_vtrx_disable,
+	.confirm		= dect_vtrx_confirm,
+	.unlock			= dect_vtrx_unlock,
+	.lock			= dect_vtrx_lock,
+	.set_mode		= dect_vtrx_set_mode,
+	.set_carrier		= dect_vtrx_set_carrier,
+	.set_band		= dect_vtrx_set_band,
+	.tx			= dect_vtrx_tx,
+	.destructor		= dect_transceiver_free,
+};
+
+int dect_vtrx_init(struct dect_vtrx_group *group)
+{
+	struct dect_transceiver *trx;
+	struct dect_vtrx *vtrx;
+	int err;
+
+	trx = dect_transceiver_alloc(&vtrx_transceiver_ops, sizeof(*vtrx));
+	if (trx == NULL)
+		return -ENOMEM;
+
+	err = dect_register_transceiver(trx);
+	if (err < 0)
+		goto err1;
+
+	vtrx = dect_transceiver_priv(trx);
+	vtrx->group	= group;
+	vtrx->trx	= trx;
+	vtrx->tx_power	= 2 * DECT_VTRX_POWER_SCALE;
+	list_add_tail(&vtrx->list, &group->trx_list);
+
+	dect_vtrx_register_sysfs(vtrx);
+	return 0;
+
+err1:
+	dect_transceiver_free(trx);
+	return err;
+}
+
+void dect_vtrx_free(struct dect_vtrx *vtrx)
+{
+	dect_vtrx_unregister_sysfs(vtrx);
+	dect_unregister_transceiver(vtrx->trx);
+}
+
+struct dect_vtrx_group *dect_vtrx_group_init(const char *name)
+{
+	struct dect_vtrx_group *group;
+	unsigned int i;
+	int err;
+
+	group = kzalloc(sizeof(*group), GFP_KERNEL);
+	if (group == NULL)
+		goto err1;
+
+	strlcpy(group->name, name, sizeof(group->name));
+	INIT_LIST_HEAD(&group->trx_list);
+	INIT_LIST_HEAD(&group->act_list);
+	hrtimer_init(&group->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+	group->timer.function = dect_vtrx_timer;
+
+	for (i = 0; i < ARRAY_SIZE(group->txq); i++)
+		skb_queue_head_init(&group->txq[i]);
+
+	err = dect_vtrx_group_register_sysfs(group);
+	if (err < 0)
+		goto err2;
+
+	list_add_tail(&group->list, &vtrx_groups);
+	return group;
+
+err2:
+	kfree(group);
+err1:
+	return NULL;
+}
+
+void dect_vtrx_group_free(struct dect_vtrx_group *group)
+{
+	struct dect_vtrx *vtrx, *next;
+	unsigned int i;
+
+	list_for_each_entry_safe(vtrx, next, &group->act_list, list)
+		dect_vtrx_free(vtrx);
+	list_for_each_entry_safe(vtrx, next, &group->trx_list, list)
+		dect_vtrx_free(vtrx);
+
+	dect_vtrx_group_unregister_sysfs(group);
+
+	for (i = 0; i < ARRAY_SIZE(group->txq); i++)
+		__skb_queue_purge(&group->txq[i]);
+
+	kfree(group);
+}
+
+static int __init vtrx_init(void)
+{
+	struct dect_vtrx_group *group;
+	unsigned int i;
+	int err;
+
+	err = dect_vtrx_sysfs_init();
+	if (err < 0)
+		goto err1;
+
+	group = dect_vtrx_group_init("group-1");
+	if (group == NULL) {
+		err = -ENOMEM;
+		goto err2;
+	}
+
+	for (i = 0; i < DECT_VTRX_DEFAULT_TRX; i++) {
+		err = dect_vtrx_init(group);
+		if (err < 0)
+			goto err3;
+	}
+
+	return 0;
+
+err3:
+	dect_vtrx_group_free(group);
+err2:
+	dect_vtrx_sysfs_exit();
+err1:
+	return err;
+}
+
+static void __exit vtrx_exit(void)
+{
+	struct dect_vtrx_group *group, *next;
+
+	list_for_each_entry_safe(group, next, &vtrx_groups, list)
+		dect_vtrx_group_free(group);
+
+	dect_vtrx_sysfs_exit();
+}
+
+module_init(vtrx_init);
+module_exit(vtrx_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/dect/vtrx/vtrx.h b/drivers/dect/vtrx/vtrx.h
new file mode 100644
index 0000000..67aaba6
--- /dev/null
+++ b/drivers/dect/vtrx/vtrx.h
@@ -0,0 +1,42 @@
+#ifndef _DECT_VTRX_H
+#define _DECT_VTRX_H
+
+struct dect_vtrx_group {
+	struct list_head	list;
+	struct device		dev;
+	char			name[16];
+	struct hrtimer		timer;
+	struct list_head	trx_list;
+	struct list_head	act_list;
+	struct sk_buff_head	txq[DECT_FRAME_SIZE];
+	unsigned int		slot;
+};
+
+struct dect_vtrx {
+	struct list_head	list;
+	struct device		dev;
+	struct dect_vtrx_group	*group;
+	struct dect_transceiver	*trx;
+	u64			tx_power;
+	unsigned int		pos_x;
+	unsigned int		pos_y;
+};
+
+extern struct dect_vtrx_group *dect_vtrx_group_init(const char *name);
+extern void	dect_vtrx_group_free(struct dect_vtrx_group *group);
+extern int	dect_vtrx_group_register_sysfs(struct dect_vtrx_group *group);
+extern void	dect_vtrx_group_unregister_sysfs(struct dect_vtrx_group *group);
+
+extern int	dect_vtrx_register_sysfs(struct dect_vtrx *vtrx);
+extern void	dect_vtrx_unregister_sysfs(struct dect_vtrx *vtrx);
+extern int	dect_vtrx_init(struct dect_vtrx_group *group);
+extern void	dect_vtrx_free(struct dect_vtrx *vtrx);
+
+extern int	dect_vtrx_sysfs_init(void);
+extern void	dect_vtrx_sysfs_exit(void);
+
+#define DECT_VTRX_POWER_SCALE		10000000000ULL
+
+extern int	dect_mw_to_dbm(u64 mw);
+
+#endif /* _DECT_VTRX_H */
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index 75cf611..ad9a13d 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -100,6 +100,8 @@ header-y += cyclades.h
 header-y += cycx_cfm.h
 header-y += dcbnl.h
 header-y += dccp.h
+header-y += dect.h
+header-y += dect_netlink.h
 header-y += dlm.h
 header-y += dlm_device.h
 header-y += dlm_netlink.h
diff --git a/include/linux/dect.h b/include/linux/dect.h
new file mode 100644
index 0000000..03bcaa1
--- /dev/null
+++ b/include/linux/dect.h
@@ -0,0 +1,154 @@
+#ifndef _LINUX_DECT_H
+#define _LINUX_DECT_H
+
+#define DECTNAMSIZ	16
+
+#include <linux/types.h>
+#include <linux/socket.h>
+
+/* these have to be macros in order to be usable for module aliases */
+#define DECT_RAW	0	/* raw frames */
+#define DECT_B_SAP	1	/* DLC Broadcast Service */
+#define DECT_S_SAP	2	/* DLC Data Link Service */
+#define DECT_LU1_SAP	3	/* LU1 sockets */
+#define DECT_PROTO_NUM	4
+
+/**
+ * struct sockaddr_dect
+ *
+ * @dect_family:	address family (AF_DECT)
+ * @dect_index:		cluster index
+ */
+struct sockaddr_dect {
+	sa_family_t	dect_family;
+	int		dect_index;
+};
+
+/* raw sockets */
+
+#define DECT_RAW_AUXDATA	0
+
+/**
+ * struct dect_raw_auxdata - raw socket auxiliary frame data
+ *
+ * @mfn:	multi-frame number
+ * @frame:	frame number
+ * @slot:	slot numer
+ * @rssi:	receive signal strength indicator
+ */
+struct dect_raw_auxdata {
+	__u32		mfn;
+	__u8		frame;
+	__u8		slot;
+	__u8		rssi;
+};
+
+#define DECT_BSAP_AUXDATA	0
+
+/**
+ * struct dect_bsap_auxdata
+ *
+ * @long_page:	message contains a long page
+ */
+struct dect_bsap_auxdata {
+	__u8		long_page;
+};
+
+/**
+ * enum dect_sapis - S SAP Identifier
+ *
+ * @DECT_SAPI_CO_SIGNALLING:	connection oriented signalling
+ * @DECT_SAPI_CL_SIGNALLING:	connectionless signalling
+ * @DECT_SAPI_ANY:		wildcard
+ */
+enum dect_sapis {
+	DECT_SAPI_CO_SIGNALLING = 0,
+	DECT_SAPI_CL_SIGNALLING = 3,
+	DECT_SAPI_ANY		= 7,
+};
+
+/**
+ * enum dect_llns - Logical Link Numbers
+ *
+ * @DECT_LLN_CLASS_U:		Class U operation
+ * @DECT_LLN_CLASS_A:		Class A operation
+ * @DECT_LLN_ASSIGNABLE*:	Assignable LLN (class B operation)
+ * @DECT_LLN_UNASSIGNED:	LLN unassigned (class B operation
+ * @DECT_LLN_ANY:		wildcard
+ */
+enum dect_llns {
+	DECT_LLN_CLASS_U	= 0,
+	DECT_LLN_CLASS_A	= 1,
+	DECT_LLN_ASSIGNABLE_MIN	= 2,
+	DECT_LLN_ASSIGNABLE_MAX	= 6,
+	DECT_LLN_UNASSIGNED	= 7,
+	DECT_LLN_ANY		= 15,
+};
+
+/**
+ * struct sockaddr_dect_ssap
+ *
+ * @dect_family:	family (AF_DECT)
+ * @dect_lln:		logical link number
+ * @dect_sapi:		service access point identifier
+ * @dect_class:		class A/B
+ * @dect_index:		cluster index
+ * @dect_ari:		ARI
+ * @dect_pmid:		PMID
+ * @dect_lcn:		logical connection number
+ */
+struct sockaddr_dect_ssap {
+	sa_family_t	dect_family;
+	__u8		dect_lln:4,
+			dect_sapi:3;
+	__u8		dect_class;
+	int		dect_index;
+	__u64		dect_ari:40,
+			dect_pmid:20,
+			dect_lcn:3;
+};
+
+/* S-SAP primitives */
+#define DECT_DL_ENC_KEY	1
+#define DECT_DL_ENCRYPT	2
+
+enum dect_cipher_states {
+	DECT_CIPHER_DISABLED,
+	DECT_CIPHER_ENABLED,
+};
+
+/**
+ * struct dect_dl_encrypt - DL_ENCRYPT primitive arguments
+ *
+ * @status:		desired/achieved encryption status
+ */
+struct dect_dl_encrypt {
+	enum dect_cipher_states	status;
+};
+
+/**
+ * struct sockaddr_dect_lu - DLC U-plane LUx service instance address
+ *
+ * @dect_family:	address family (AF_DECT)
+ * @dect_mci:		MAC Connection Identifier
+ */
+struct sockaddr_dect_lu {
+	sa_family_t	dect_family;
+	int		dect_index;
+	__u64		dect_ari:40,
+			dect_pmid:20,
+			dect_lcn:3;
+};
+
+/* LU1 SAP */
+
+#define DECT_LU1_QUEUE_STATS	0
+
+struct dect_lu1_queue_stats {
+	__u32		rx_bytes;
+	__u32		rx_underflow;
+	__u32		tx_bytes;
+	__u32		tx_underflow;
+};
+
+#endif /* _LINUX_DECT_H */
diff --git a/include/linux/dect_netlink.h b/include/linux/dect_netlink.h
new file mode 100644
index 0000000..956c57a
--- /dev/null
+++ b/include/linux/dect_netlink.h
@@ -0,0 +1,331 @@
+#ifndef _LINUX_DECT_NETLINK_H
+#define _LINUX_DECT_NETLINK_H
+
+struct dectmsg {
+	int	dm_index;
+};
+
+enum dect_nlgroups {
+	DECTNLGRP_NONE,
+	DECTNLGRP_TRANSCEIVER,
+	DECTNLGRP_CELL,
+	DECTNLGRP_CLUSTER,
+	DECTNLGRP_LLME,
+	__DECTNLGRP_MAX
+};
+#define DECTNLGRP_MAX		(__DECTNLGRP_MAX - 1)
+
+enum dect_netlink_msg_types {
+	DECT_MSG_BASE = 0x10,
+	DECT_NEW_TRANSCEIVER,
+	DECT_DEL_TRANSCEIVER,
+	DECT_GET_TRANSCEIVER,
+	DECT_NEW_CELL,
+	DECT_DEL_CELL,
+	DECT_GET_CELL,
+	DECT_NEW_CLUSTER,
+	DECT_DEL_CLUSTER,
+	DECT_GET_CLUSTER,
+	DECT_LLME_MSG,
+	__DECT_MSG_MAX
+};
+#define DECT_MSG_MAX		(__DECT_MSG_MAX - 1)
+
+#define DECT_NR_MSGTYPES	(DECT_MSG_MAX + 1 - DECT_MSG_BASE)
+
+enum dect_list_attrs {
+	DECTA_LIST_UNSPEC,
+	DECTA_LIST_ELEM,
+	__DECTA_LIST_MAX
+};
+#define DECTA_LIST_MAX		(__DECTA_LIST_MAX - 1)
+
+enum dect_slot_states {
+	DECT_SLOT_IDLE,
+	DECT_SLOT_SCANNING,
+	DECT_SLOT_RX,
+	DECT_SLOT_TX,
+};
+
+enum dect_slot_flags {
+	DECT_SLOT_SYNC			= 0x1,
+	DECT_SLOT_CIPHER		= 0x2,
+};
+
+#define DECT_PHASE_OFFSET_SCALE		1024
+
+enum dect_slot_attrs {
+	DECTA_SLOT_UNSPEC,
+	DECTA_SLOT_NUM,
+	DECTA_SLOT_STATE,
+	DECTA_SLOT_FLAGS,
+	DECTA_SLOT_CARRIER,
+	DECTA_SLOT_FREQUENCY,
+	DECTA_SLOT_PHASEOFF,
+	DECTA_SLOT_RSSI,
+	DECTA_SLOT_RX_PACKETS,
+	DECTA_SLOT_RX_BYTES,
+	DECTA_SLOT_RX_A_CRC_ERRORS,
+	DECTA_SLOT_RX_X_CRC_ERRORS,
+	DECTA_SLOT_RX_Z_CRC_ERRORS,
+	DECTA_SLOT_TX_PACKETS,
+	DECTA_SLOT_TX_BYTES,
+	__DECTA_SLOT_MAX
+};
+#define DECTA_SLOT_MAX			(__DECTA_SLOT_MAX - 1)
+
+enum dect_transceiver_stats_attrs {
+	DECTA_TRANSCEIVER_STATS_UNSPEC,
+	DECTA_TRANSCEIVER_STATS_EVENT_BUSY,
+	DECTA_TRANSCEIVER_STATS_EVENT_LATE,
+	__DECTA_TRANSCEIVER_STATS_MAX
+};
+#define DECTA_TRANSCEIVER_STATS_MAX	(__DECTA_TRANSCEIVER_STATS_MAX - 1)
+
+/**
+ * @DECT_TRANSCEIVER_SLOW_HOPPING:	transceiver has slow hopping radio
+ */
+enum dect_transceiver_features {
+	DECT_TRANSCEIVER_SLOW_HOPPING	= 0x1,
+};
+
+enum dect_transceiver_attrs {
+	DECTA_TRANSCEIVER_UNSPEC,
+	DECTA_TRANSCEIVER_NAME,
+	DECTA_TRANSCEIVER_TYPE,
+	DECTA_TRANSCEIVER_FEATURES,
+	DECTA_TRANSCEIVER_LINK,
+	DECTA_TRANSCEIVER_STATS,
+	DECTA_TRANSCEIVER_BAND,
+	DECTA_TRANSCEIVER_SLOTS,
+	__DECTA_TRANSCEIVER_MAX
+};
+#define DECTA_TRANSCEIVER_MAX		(__DECTA_TRANSCEIVER_MAX - 1)
+
+enum dect_cell_flags {
+	DECT_CELL_CCP		= (1 << 0),
+	DECT_CELL_SLAVE		= (1 << 1),
+	DECT_CELL_MONITOR	= (1 << 2),
+};
+
+enum dect_cell_attrs {
+	DECTA_CELL_UNSPEC,
+	DECTA_CELL_NAME,
+	DECTA_CELL_FLAGS,
+	DECTA_CELL_TRANSCEIVERS,
+	DECTA_CELL_CLUSTER,
+	__DECTA_CELL_MAX
+};
+#define DECTA_CELL_MAX		(__DECTA_CELL_MAX - 1)
+
+enum dect_cluster_attrs {
+	DECTA_CLUSTER_UNSPEC,
+	DECTA_CLUSTER_NAME,
+	DECTA_CLUSTER_MODE,
+	DECTA_CLUSTER_PARI,
+	DECTA_CLUSTER_CELLS,
+	__DECTA_CLUSTER_MAX
+};
+#define DECTA_CLUSTER_MAX	(__DECTA_CLUSTER_MAX - 1)
+
+enum dect_cluster_modes {
+	DECT_MODE_FP,
+	DECT_MODE_PP,
+};
+
+/**
+ * DECT ARI classes
+ *
+ * @DECT_ARC_A:	Residential and private (PBX) single- and small multiple cell systems
+ * @DECT_ARC_B:	Private (PABXs) multiple cell
+ * @DECT_ARC_C: Public single and multiple cell systems
+ * @DECT_ARC_D: Public DECT access to a GSM network
+ * @DECT_ARC_E: PP to PP direct communication (private)
+ */
+enum dect_ari_classes {
+	DECT_ARC_A,
+	DECT_ARC_B,
+	DECT_ARC_C,
+	DECT_ARC_D,
+	DECT_ARC_E,
+};
+
+enum dect_ari_attrs {
+	DECTA_ARI_UNSPEC,
+	DECTA_ARI_CLASS,
+	DECTA_ARI_FPN,
+	DECTA_ARI_FPS,
+	DECTA_ARI_EMC,
+	DECTA_ARI_EIC,
+	DECTA_ARI_POC,
+	DECTA_ARI_GOP,
+	DECTA_ARI_FIL,
+	__DECTA_ARI_MAX
+};
+#define DECTA_ARI_MAX		(__DECTA_ARI_MAX - 1)
+
+enum decta_sari_attrs {
+	DECTA_SARI_UNSPEC,
+	DECTA_SARI_ARI,
+	DECTA_SARI_BLACK,
+	DECTA_SARI_TARI,
+	__DECTA_SARI_MAX
+};
+#define DECTA_SARI_MAX		(__DECTA_SARI_MAX - 1)
+
+enum dect_fixed_part_capabilities {
+	DECT_FPC_EXTENDED_FP_INFO		= 0x80000,
+	DECT_FPC_DOUBLE_DUPLEX_BEARER_CONNECTION= 0x40000,
+	DECT_FPC_RESERVED			= 0x20000,
+	DECT_FPC_DOUBLE_SLOT			= 0x10000,
+	DECT_FPC_HALF_SLOT			= 0x08000,
+	DECT_FPC_FULL_SLOT			= 0x04000,
+	DECT_FPC_FREQ_CONTROL			= 0x02000,
+	DECT_FPC_PAGE_REPETITION		= 0x01000,
+	DECT_FPC_CO_SETUP_ON_DUMMY		= 0x00800,
+	DECT_FPC_CL_UPLINK			= 0x00400,
+	DECT_FPC_CL_DOWNLINK			= 0x00200,
+	DECT_FPC_BASIC_A_FIELD_SETUP		= 0x00100,
+	DECT_FPC_ADV_A_FIELD_SETUP		= 0x00080,
+	DECT_FPC_B_FIELD_SETUP			= 0x00040,
+	DECT_FPC_CF_MESSAGES			= 0x00020,
+	DECT_FPC_IN_MIN_DELAY			= 0x00010,
+	DECT_FPC_IN_NORM_DELAY			= 0x00008,
+	DECT_FPC_IP_ERROR_DETECTION		= 0x00004,
+	DECT_FPC_IP_ERROR_CORRECTION		= 0x00002,
+	DECT_FPC_MULTIBEARER_CONNECTIONS	= 0x00001,
+};
+
+enum dect_higher_layer_capabilities {
+	DECT_HLC_ADPCM_G721_VOICE		= 0x8000,
+	DECT_HLC_GAP_PAP_BASIC_SPEECH		= 0x4000,
+	DECT_HLC_NON_VOICE_CIRCUIT_SWITCHED	= 0x2000,
+	DECT_HLC_NON_VOICE_PACKET_SWITCHED	= 0x1000,
+	DECT_HLC_STANDARD_AUTHENTICATION	= 0x0800,
+	DECT_HLC_STANDARD_CIPHERING		= 0x0400,
+	DECT_HLC_LOCATION_REGISTRATION		= 0x0200,
+	DECT_HLC_SIM_SERVICES			= 0x0100,
+	DECT_HLC_NON_STATIC_FIXED_PART		= 0x0080,
+	DECT_HLC_CISS_SERVICE			= 0x0040,
+	DECT_HLC_CLMS_SERVICE			= 0x0020,
+	DECT_HLC_COMS_SERVICE			= 0x0010,
+	DECT_HLC_ACCESS_RIGHTS_REQUESTS		= 0x0008,
+	DECT_HLC_EXTERNAL_HANDOVER		= 0x0004,
+	DECT_HLC_CONNECTION_HANDOVER		= 0x0002,
+	DECT_HLC_RESERVED			= 0x0001,
+};
+
+enum dect_extended_fixed_part_capabilities {
+	DECT_EFPC_WRS_MASK			= 0x1f80,
+	DECT_EFPC_WRS_CRFP_HOPS_MASK		= 0x1800,
+	DECT_EFPC_WRS_CRFP_HOPS_1		= 0x0000,
+	DECT_EFPC_WRS_CRFP_HOPS_2		= 0x0800,
+	DECT_EFPC_WRS_CRFP_HOPS_3		= 0x1000,
+	DECT_EFPC_WRS_CRFP_HOPS_NONE		= 0x1800,
+	DECT_EFPC_WRS_CRFP_ENCRYPTION		= 0x0400,
+	DECT_EFPC_WRS_REP_HOPS_MASK		= 0x0300,
+	DECT_EFPC_WRS_REP_HOPS_NONE		= 0x0000,
+	DECT_EFPC_WRS_REP_HOPS_1		= 0x0100,
+	DECT_EFPC_WRS_REP_HOPS_2		= 0x0200,
+	DECT_EFPC_WRS_REP_HOPS_3		= 0x0300,
+	DECT_EFPC_WRS_REP_INTERLACING		= 0x0080,
+	DECT_EFPC_SYNC_MASK			= 0x0060,
+	DECT_EFPC_SYNC_PROLONGED_PREAMBLE	= 0x0020,
+	DECT_EFPC_SYNC_RESERVED1		= 0x0010,
+	DECT_EFPC_MAC_SUSPEND_RESUME		= 0x0008,
+	DECT_EFPC_MAC_IP_Q_SERVICE		= 0x0004,
+	DECT_EFPC_EXTENDED_FP_INFO2		= 0x0002,
+	DECT_EFPC_RESERVED2			= 0x0001,
+};
+
+enum dect_extended_higher_layer_capabilities {
+	DECT_EHLC_ISDN_DATA_SERVICE		= 0x000001,
+	DECT_EHLC_DPRS_FREL			= 0x000002,
+	DECT_EHLC_DPRS_STREAM			= 0x000004,
+	DECT_EHLC_DATA_SERVICE_PROFILE_D	= 0x000008,
+	DECT_EHLC_LRMS				= 0x000010,
+	DECT_EHLC_ASYMETRIC_BEARERS		= 0x000040,
+	DECT_EHLC_EMERGENCY_CALLS		= 0x000080,
+	DECT_EHLC_TPUI_LOCATION_REGISTRATION	= 0x000100,
+	DECT_EHLC_GPS_SYNCHRONIZED		= 0x000200,
+	DECT_EHLC_ISDN_INTERMEDIATE_SYSTEM	= 0x000400,
+	DECT_EHLC_RAP_PART_1_PROFILE		= 0x000800,
+	DECT_EHLC_V_24				= 0x004000,
+	DECT_EHLC_PPP				= 0x008000,
+	DECT_EHLC_IP				= 0x010000,
+	DECT_EHLC_TOKEN_RING			= 0x020000,
+	DECT_EHLC_ETHERNET			= 0x040000,
+	DECT_EHLC_IP_ROAMING			= 0x080000,
+	DECT_EHLC_GENERIC_MEDIA_ENCAPSULATION	= 0x100000,
+	DECT_EHLC_BASIC_ODAP			= 0x200000,
+	DECT_EHLC_F_MMS_INTERWORKING_PROFILE	= 0x400000,
+};
+
+enum dect_extended_fixed_part_capabilities2 {
+	DECT_EFPC2_LONG_SLOT_J640		= 0x800,
+	DECT_EFPC2_LONG_SLOT_J672		= 0x400,
+	DECT_EFPC2_IP_F				= 0x200,
+	DECT_EFPC2_SI_PF			= 0x100,
+	DECT_EFPC2_GF				= 0x080,
+	DECT_EFPC2_NO_EMISSION_CARRIER		= 0x001,
+};
+
+enum dect_extended_higher_layer_capabilities2 {
+	DECT_EHLC2_NG_DECT_PERMANENT_CLIR	= 0x000100,
+	DECT_EHLC2_NG_DECT_MULTIPLE_CALLS	= 0x000200,
+	DECT_EHLC2_NG_DECT_MULTIPLE_LINES	= 0x000400,
+	DECT_EHLC2_EASY_PAIRING			= 0x000800,
+	DECT_EHLC2_LIST_ACCESS_FEATURES		= 0x001000,
+	DECT_EHLC2_NO_EMISSION_MODE		= 0x002000,
+	DECT_EHLC2_NG_DECT_CALL_DEFLECTION	= 0x004000,
+	DECT_EHLC2_NG_DECT_INTRUSION_CALL	= 0x008000,
+	DECT_EHLC2_NG_DECT_CONFERENCE_CALL	= 0x010000,
+	DECT_EHLC2_NG_DECT_PARALLEL_CALLS	= 0x020000,
+	DECT_EHLC2_NG_DECT_CALL_TRANSFER	= 0x040000,
+	DECT_EHLC2_NG_DECT_EXTENDED_WIDEBAND	= 0x080000,
+	DECT_EHLC2_PACKET_DATA_CATEGORY_MASK	= 0x700000,
+	DECT_EHLC2_NG_DECT_WIDEBAND		= 0x800000,
+};
+
+enum dect_mac_info_attrs {
+	DECTA_MAC_INFO_UNSPEC,
+	DECTA_MAC_INFO_PARI,
+	DECTA_MAC_INFO_RPN,
+	DECTA_MAC_INFO_RSSI,
+	DECTA_MAC_INFO_SARI_LIST,
+	DECTA_MAC_INFO_FPC,
+	DECTA_MAC_INFO_HLC,
+	DECTA_MAC_INFO_EFPC,
+	DECTA_MAC_INFO_EHLC,
+	DECTA_MAC_INFO_EFPC2,
+	DECTA_MAC_INFO_EHLC2,
+	DECTA_MAC_INFO_MFN,
+	__DECTA_MAC_INFO_MAX
+};
+#define DECTA_MAC_INFO_MAX	(__DECTA_MAC_INFO_MAX - 1)
+
+enum dect_llme_ops {
+	DECT_LLME_REQUEST,
+	DECT_LLME_INDICATE,
+	DECT_LLME_RESPONSE,
+	DECT_LLME_CONFIRM,
+};
+
+enum dect_llme_msg_types {
+	DECT_LLME_SCAN,
+	DECT_LLME_MAC_INFO,
+	DECT_LLME_MAC_RFP_PRELOAD,
+	__DECT_LLME_MAX
+};
+#define DECT_LLME_MAX		(__DECT_LLME_MAX - 1)
+
+enum dect_llme_msg_attrs {
+	DECTA_LLME_UNSPEC,
+	DECTA_LLME_OP,
+	DECTA_LLME_TYPE,
+	DECTA_LLME_DATA,
+	__DECTA_LLME_MAX
+};
+#define DECTA_LLME_MAX		(__DECTA_LLME_MAX - 1)
+
+#endif /* _LINUX_DECT_NETLINK_H */
diff --git a/include/linux/netlink.h b/include/linux/netlink.h
index 4c4ac3f..3f0d0fc 100644
--- a/include/linux/netlink.h
+++ b/include/linux/netlink.h
@@ -24,6 +24,7 @@
 /* leave room for NETLINK_DM (DM Events) */
 #define NETLINK_SCSITRANSPORT	18	/* SCSI Transports */
 #define NETLINK_ECRYPTFS	19
+#define NETLINK_DECT		20	/* DECT */
 
 #define MAX_LINKS 32		
 
diff --git a/include/linux/socket.h b/include/linux/socket.h
index d2b5e98..942ee5c 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -192,7 +192,8 @@ struct ucred {
 #define AF_IEEE802154	36	/* IEEE802154 sockets		*/
 #define AF_CAIF		37	/* CAIF sockets			*/
 #define AF_ALG		38	/* Algorithm sockets		*/
-#define AF_MAX		39	/* For now.. */
+#define AF_DECT		39	/* DECT sockets			*/
+#define AF_MAX		40	/* For now.. */
 
 /* Protocol families, same as address families. */
 #define PF_UNSPEC	AF_UNSPEC
@@ -234,6 +235,7 @@ struct ucred {
 #define PF_IEEE802154	AF_IEEE802154
 #define PF_CAIF		AF_CAIF
 #define PF_ALG		AF_ALG
+#define PF_DECT		AF_DECT
 #define PF_MAX		AF_MAX
 
 /* Maximum queue length specifiable by listen.  */
@@ -308,6 +310,7 @@ struct ucred {
 #define SOL_IUCV	277
 #define SOL_CAIF	278
 #define SOL_ALG		279
+#define SOL_DECT	280
 
 /* IPX options */
 #define IPX_TYPE	1
diff --git a/include/net/dect/ccp.h b/include/net/dect/ccp.h
new file mode 100644
index 0000000..5234c7d
--- /dev/null
+++ b/include/net/dect/ccp.h
@@ -0,0 +1,110 @@
+/*
+ * DECT MAC Layer - Cell Control Protocol (CCP)
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ */
+
+#ifndef _NET_DECT_CCP
+#define _NET_DECT_CCP
+
+#define DECT_CCP_TIPC_TYPE		TIPC_RESERVED_TYPES
+#define DECT_CCP_CELL_PORT		1000
+#define DECT_CCP_CLUSTER_PORT_BASE	1000
+
+enum dect_ccp_primitives {
+	/* CCF -> CSF */
+	DECT_CCP_SET_MODE,
+	DECT_CCP_SCAN,
+	DECT_CCP_ENABLE,
+	DECT_CCP_PRELOAD,
+	DECT_CCP_MAC_INFO_IND,
+	DECT_CCP_PAGE_REQ,
+	DECT_CCP_TBC_ESTABLISH_REQ,
+	DECT_CCP_TBC_ESTABLISH_RES,
+	DECT_CCP_TBC_DATA_REQ,
+	DECT_CCP_TBC_DIS_REQ,
+	DECT_CCP_TBC_ENC_KEY_REQ,
+	DECT_CCP_TBC_ENC_EKS_REQ,
+	/* CSF -> CCF */
+	DECT_CCP_TBC_ESTABLISH_IND,
+	DECT_CCP_TBC_ESTABLISH_CFM,
+	DECT_CCP_TBC_EVENT_IND,
+	DECT_CCP_TBC_DATA_IND,
+	DECT_CCP_TBC_DIS_IND,
+};
+
+struct dect_ccp_msg_hdr {
+	u8		primitive;
+} __attribute__((packed));
+
+struct dect_ccp_ari {
+	__be64		ari;
+};
+
+struct dect_ccp_mode_msg {
+	u8		mode;
+} __attribute__((packed));
+
+struct dect_ccp_scan_msg {
+	__be64		ari;
+	__be64		ari_mask;
+} __attribute__((packed));
+
+struct dect_ccp_sysinfo_msg {
+	__be64		pari;
+	__be64		sari[DECT_SARI_CYCLE_MAX];
+	__be64		fpc;
+	__be64		hlc;
+	__be64		efpc;
+	__be32		mfn;
+	u8		num_saris;
+	u8		rpn;
+} __attribute__((packed));
+
+struct dect_ccp_page_msg {
+	u8		fast_page;
+	u8		long_page;
+} __attribute__((packed));
+
+struct dect_ccp_tbc_msg {
+	__be32		tbei;
+	__be32		pmid;
+	__be64		ari;
+	u8		ecn;
+	u8		data;
+} __attribute__((packed));
+
+struct dect_ccp_enc_key_msg {
+	__be64		key;
+} __attribute__((packed));
+
+struct dect_ccp_data_msg {
+	u8		channel;
+	u8		data[];
+} __attribute__((packed));
+
+#ifdef CONFIG_DECT_CCP
+extern int dect_ccp_cluster_init(struct dect_cluster *cl);
+extern void dect_ccp_cluster_shutdown(struct dect_cluster *cl);
+
+extern struct dect_cluster_handle *dect_ccp_cell_init(struct dect_cell *cell,
+						      u8 clindex);
+#else
+static inline int dect_ccp_cluster_init(struct dect_cluster *cl)
+{
+	return 0;
+}
+
+static inline void dect_ccp_cluster_shutdown(struct dect_cluster *cl)
+{
+	return;
+}
+
+static inline struct dect_cluster_handle *
+dect_ccp_cell_init(struct dect_cell *cell, u8 clindex)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+#endif
+
+#endif /* _NET_DECT_CPP */
diff --git a/include/net/dect/dect.h b/include/net/dect/dect.h
new file mode 100644
index 0000000..e253308
--- /dev/null
+++ b/include/net/dect/dect.h
@@ -0,0 +1,319 @@
+#ifndef _NET_DECT_DECT_H
+#define _NET_DECT_DECT_H
+
+#define DECT_FRAMES_PER_MULTIFRAME	16
+
+static inline u8 dect_next_framenum(u8 framenum)
+{
+	if (++framenum == DECT_FRAMES_PER_MULTIFRAME)
+		framenum = 0;
+	return framenum;
+}
+
+static inline u8 dect_framenum_add(u8 f1, u8 f2)
+{
+	return (f1 + f2) % DECT_FRAMES_PER_MULTIFRAME;
+}
+
+#define DECT_MULTIFRAME_MASK		0x00ffffff
+
+static inline u32 dect_next_mfn(u32 mfn)
+{
+	if (++mfn == (1 << 24) - 1)
+		mfn = 0;
+	return mfn;
+}
+
+static inline u32 dect_mfn_add(u32 mfn1, u32 mfn2)
+{
+	return (mfn1 + mfn2) & DECT_MULTIFRAME_MASK;
+}
+
+/* Compare multiframe numbers, considering overflows */
+static inline bool dect_mfn_before(u32 mfn1, u32 mfn2)
+{
+	return (s32)((mfn2 << 8) - (mfn1 << 8)) > 0;
+}
+
+static inline bool dect_mfn_after(u32 mfn1, u32 mfn2)
+{
+	return dect_mfn_before(mfn2, mfn1);
+}
+
+#include <linux/list.h>
+
+/**
+ * enum dect_timer_bases - timer bases for DECT timers
+ *
+ * @DECT_TIMER_RX:	receive time base
+ * @DECT_TIMER_TX:	send time base
+ */
+enum dect_timer_bases {
+	DECT_TIMER_RX,
+	DECT_TIMER_TX,
+	__DECT_TIMER_BASE_MAX
+};
+#define DECT_TIMER_BASE_MAX	(__DECT_TIMER_BASE_MAX - 1)
+
+/**
+ * struct dect_timer_base - timer base
+ *
+ * @timers:		list of active timers
+ * @slot:		slot position
+ * @framenum:		frame number
+ * @mfn:		multiframe number
+ */
+struct dect_timer_base {
+	struct list_head	timers;
+	u8			base;
+	u8			slot;
+	u8			framenum;
+	u32			mfn;
+};
+
+static inline void dect_timer_base_init(struct dect_timer_base base[],
+					enum dect_timer_bases b)
+{
+	INIT_LIST_HEAD(&base[b].timers);
+	base->base = b;
+}
+
+static inline u8 __dect_slotnum(const struct dect_timer_base *base)
+{
+	return base->slot;
+}
+
+static inline u8 __dect_framenum(const struct dect_timer_base *base)
+{
+	return base->framenum;
+}
+
+static inline u32 __dect_mfn(const struct dect_timer_base *base)
+{
+	return base->mfn;
+}
+
+extern void __dect_run_timers(const char *name, struct dect_timer_base *base);
+
+/**
+ * struct dect_timer - DECT TDMA frame timer
+ *
+ * @list:		timer list node
+ * @base:		timer base
+ * @mfn:		expiration time: multiframe number
+ * @frame:		expiration time: frame number
+ * @slot:		expiration time: slot number
+ * @func:		timer function
+ * @data:		timer data
+ */
+struct dect_cell;
+struct dect_cluster;
+
+struct dect_timer {
+	struct list_head		list;
+
+	enum dect_timer_bases		base;
+	u32				mfn;
+	u8				frame;
+	u8				slot;
+
+	union {
+		void			(*cell)(struct dect_cell *, void *);
+		void			(*cluster)(struct dect_cluster *, void *);
+		void			(*cb)(void *, void *);
+	} cb;
+	union {
+		struct dect_cell	*cell;
+		struct dect_cluster	*cluster;
+		void			*obj;
+	};
+	void				*data;
+};
+
+static inline void dect_timer_init(struct dect_timer *timer)
+{
+	INIT_LIST_HEAD(&timer->list);
+}
+
+static inline void dect_timer_del(struct dect_timer *timer)
+{
+	list_del_init(&timer->list);
+}
+
+extern void __dect_timer_add(const char *name, struct dect_timer_base *base,
+			     struct dect_timer *timer, u32 frame, u8 slot);
+
+#include <linux/dect.h>
+#include <net/dect/identities.h>
+#include <net/dect/mac_ccf.h>
+#include <net/dect/dlc.h>
+
+extern void __acquires(dect_cfg_mutex) dect_lock(void);
+extern void __releases(dect_cfg_mutex) dect_unlock(void);
+
+/**
+ * struct dect_cluster - DECT cluster of up to 8/256 cells
+ *
+ * @list:		device list node
+ * @name:		device identifier
+ * @index:		unique numeric cluster identifier
+ * @mode:		device mode (FP/PP/monitor)
+ * @pari:		primary access rights identifier
+ * @si:			system information
+ * @bmc:		Broadcast Message Control
+ * @cmc:		Connectionless Message Control
+ * @mbcs:		Multi-Bearer Controllers
+ * @cells:		DECT cells
+ */
+struct dect_cluster {
+	struct list_head		list;
+	char				name[DECTNAMSIZ];
+	int				index;
+
+	u32				tipc_id;
+	u32				tipc_portref;
+	struct dect_cluster_handle	handle;
+
+	enum dect_cluster_modes		mode;
+
+	spinlock_t			lock;
+
+	struct dect_ari			pari;
+	struct dect_si			si;
+	u8				rpn;
+
+	u32				pmid;
+
+	struct list_head		cells;
+	struct dect_bmc			bmc;
+	struct dect_cmc			cmc;
+	struct list_head		mbcs;
+
+	u32				mcei_rover;
+	struct list_head		mac_connections;
+
+	struct dect_timer_base		timer_base[DECT_TIMER_BASE_MAX + 1];
+};
+
+extern struct list_head dect_cluster_list;
+extern struct dect_cluster *dect_cluster_get_by_index(int index);
+
+struct dect_netlink_handler {
+	int (*doit)(const struct sk_buff *, const struct nlmsghdr *,
+		    const struct nlattr *[]);
+	int (*dump)(struct sk_buff *, struct netlink_callback *);
+	int (*done)(struct netlink_callback *);
+	const struct nla_policy *policy;
+	unsigned int maxtype;
+};
+
+extern void dect_netlink_register_handlers(const struct dect_netlink_handler *handler,
+					   unsigned int base, unsigned int n);
+extern void dect_netlink_unregister_handlers(unsigned int base, unsigned int n);
+
+extern struct sock *dect_nlsk;
+
+/**
+ * struct dect_llme_req - LLME netlink request
+ *
+ * @nlh:		netlink header
+ * @nlpid:		netlink socket PID
+ */
+struct dect_llme_req {
+	struct nlmsghdr		nlh;
+	u32			nlpid;
+};
+
+#include <net/sock.h>
+
+extern const struct proto_ops dect_stream_ops;
+extern const struct proto_ops dect_dgram_ops;
+
+struct dect_proto {
+	unsigned int		type;
+	unsigned int		protocol;
+	int			capability;
+	const struct proto_ops	*ops;
+	int			(*getname)(struct sock *sk,
+					   struct sockaddr *uaddr, int *len,
+					   int peer);
+	struct proto		proto;
+};
+
+#include <net/tcp_states.h>
+
+enum {
+	DECT_SK_ESTABLISHED		= TCP_ESTABLISHED,
+	DECT_SK_ESTABLISH_PENDING	= TCP_SYN_SENT,
+	DECT_SK_RELEASED		= TCP_CLOSE,
+	DECT_SK_RELEASE_PENDING		= TCP_CLOSING,
+	DECT_SK_LISTEN			= TCP_LISTEN,
+};
+
+struct dect_csk {
+	struct sock		sk;
+	struct hlist_head	accept_queue;
+};
+
+static inline struct dect_csk *dect_csk(const struct sock *sk)
+{
+	return (struct dect_csk *)sk;
+}
+
+extern int dect_proto_register(struct dect_proto *proto);
+extern void dect_proto_unregister(struct dect_proto *proto);
+
+struct dect_skb_sk_cb {
+	//struct dect_skb_trx_cb	cb;
+	int			index;
+};
+
+#define DECT_SK_CB(skb)		((struct dect_skb_sk_cb *)(skb)->cb)
+
+static inline int dect_sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
+{
+	/*
+	 * Release the transceiver reference, it is only valid in IRQ and
+	 * softirq context.
+	 */
+	//FIXME
+	//DECT_SK_CB(skb)->index = DECT_CB(skb)->trx->dev->index;
+	return sock_queue_rcv_skb(sk, skb);
+}
+
+struct dect_notification {
+	u32		type;
+};
+
+#define DECT_NOTIFY_CB(skb)	((struct dect_notification *)(skb)->cb)
+
+extern struct sk_buff *dect_alloc_notification(u32 type, const void *data,
+					       unsigned int size);
+
+extern void (*dect_raw_rcv_hook)(struct sk_buff *skb);
+static inline void dect_raw_rcv(struct sk_buff *skb)
+{
+	typeof(dect_raw_rcv_hook) dect_raw_rcv;
+
+	rcu_read_lock();
+	dect_raw_rcv = dect_raw_rcv_hook;
+	if (dect_raw_rcv != NULL)
+		dect_raw_rcv(skb);
+	rcu_read_unlock();
+}
+
+extern int dect_af_module_init(void);
+extern void dect_af_module_exit(void);
+
+extern int dect_bsap_module_init(void);
+extern void dect_bsap_module_exit(void);
+extern int dect_ssap_module_init(void);
+extern void dect_ssap_module_exit(void);
+
+extern int dect_netlink_module_init(void);
+extern void dect_netlink_module_exit(void);
+
+extern struct sk_buff *skb_append_frag(struct sk_buff *head, struct sk_buff *skb);
+extern unsigned int skb_queue_pull(struct sk_buff_head *list, unsigned int len);
+
+#endif /* _NET_DECT_DECT_H */
diff --git a/include/net/dect/dlc.h b/include/net/dect/dlc.h
new file mode 100644
index 0000000..f38f4d4
--- /dev/null
+++ b/include/net/dect/dlc.h
@@ -0,0 +1,462 @@
+/*
+ * DECT DLC Layer
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ */
+
+#ifndef _NET_DECT_DLC_H
+#define _NET_DECT_DLC_H
+
+#include <linux/timer.h>
+
+/*
+ * C-Plane data link service
+ */
+
+/*
+ * FA-Frame
+ */
+
+#define DECT_FA_HDR_SIZE	3
+
+struct dect_fa_hdr {
+	u8	addr;
+	u8	ctrl;
+	u8	li;
+};
+
+/*
+ * Address field
+ */
+
+#define DECT_FA_ADDR_OFF	0
+
+/* New link flag */
+#define DECT_FA_ADDR_NLF_FLAG	0x80
+
+/* Logical Link Number */
+#define DECT_FA_ADDR_LLN_MASK	0x70
+#define DECT_FA_ADDR_LLN_SHIFT	4
+
+/* Service Access Point Identifier */
+#define DECT_FA_ADDR_SAPI_MASK	0x0c
+#define DECT_FA_ADDR_SAPI_SHIFT	2
+
+/* Command/Response flag */
+#define DECT_FA_ADDR_CR_FLAG	0x02
+
+/* Reserved bit */
+#define DECT_FA_ADDR_RES_BIT	0x01
+
+/*
+ * Control field
+ */
+
+#define DECT_FA_CTRL_OFF	1
+
+/*
+ * I-Format: numbered information
+ */
+
+#define DECT_FA_CTRL_I_FMT_MASK	0x01
+#define DECT_FA_CTRL_I_FMT_ID	0x00
+
+/* Receive sequence number */
+#define DECT_FA_CTRL_I_NR_MASK	0xe0
+#define DECT_FA_CTRL_I_NR_SHIFT	5
+
+/* Poll bit */
+#define DECT_FA_CTRL_I_P_FLAG	0x10
+
+/* Send sequence number */
+#define DECT_FA_CTRL_I_NS_MASK	0x0e
+#define DECT_FA_CTRL_I_NS_SHIFT	1
+
+/* Command */
+#define DECT_FA_CTRL_I_CMD_I	(0x0)
+
+/*
+ * S-Format: supervisory functions
+ */
+
+#define DECT_FA_CTRL_S_FMT_MASK	0x03
+#define DECT_FA_CTRL_S_FMT_ID	0x01
+
+/* Receive sequence number */
+#define DECT_FA_CTRL_S_NR_MASK	0xe0
+#define DECT_FA_CTRL_S_NR_SHIFT	5
+
+/* Poll/final bit */
+#define DECT_FA_CTRL_S_PF_FLAG	0x10
+
+/* Command/Response */
+#define DECT_FA_CTRL_S_CR_MASK	0x0c
+
+#define DECT_FA_CTRL_S_CR_RR	0x00
+#define DECT_FA_CTRL_S_CR_RNR	0x40
+#define DECT_FA_CTRL_S_CR_REJ	0x80
+
+/*
+ *  U-Format: unnumbered information
+ */
+
+#define DECT_FA_CTRL_U_FMT_MASK	0x03
+#define DECT_FA_CTRL_U_FMT_ID	0x03
+
+/* Unnumbered function bits */
+#define DECT_FA_CTRL_U_U1_MASK	0xec
+
+/* Poll/final bit */
+#define DECT_FA_CTRL_U_PF_FLAG	0x10
+
+/* Command/Response */
+#define DECT_FA_CTRL_U_CR_MASK	0xef
+
+#define DECT_FA_CTRL_U_CR_SABM	0x2c
+#define DECT_FA_CTRL_U_CR_DM	0x0c
+#define DECT_FA_CTRL_U_CR_UI	0x00
+#define DECT_FA_CTRL_U_CR_DISC	0x40
+#define DECT_FA_CTRL_U_CR_UA	0x60
+
+/*
+ * Length Indicator
+ */
+
+#define DECT_FA_LI_OFF		2
+
+/* Length (octets) */
+#define DECT_FA_LI_LENGTH_MASK	0xfc
+#define DECT_FA_LI_LENGTH_SHIFT	2
+
+/* More data flag */
+#define DECT_FA_LI_M_FLAG	0x02
+
+/* Extended length indicator bit */
+#define DECT_FA_LI_EXT_FLAG	0x01
+
+/* maximum length value */
+#define DECT_FA_LI_MAX		63
+
+/*
+ * Extended Length indicator
+ */
+
+#define DECT_FA_ELI_OFF		3
+
+/* Length (octets) */
+#define DECT_FA_ELI_LENGTH_MASK	0xfc
+#define DECT_FA_ELI_LENGTH_SHIFT 2
+
+struct dect_fa_len {
+	u8		len;
+	bool		more;
+};
+
+/*
+ * Fill Field
+ */
+
+#define DECT_FA_FILL_PATTERN	0xf0
+
+/*
+ * Checksum field
+ */
+
+#define DECT_FA_CSUM_SIZE	2
+
+/*
+ * Information field
+ */
+
+#define DECT_FA_I_MAX		(DECT_FA_LI_MAX - DECT_FA_HDR_SIZE - DECT_FA_CSUM_SIZE)
+
+
+/**
+ * struct dect_dli - DECT Data Link Identifier (DLI)
+ *
+ * @lln:	Logical Link Number
+ * @mci:	Mac Connection Identifier
+ */
+struct dect_dli {
+	enum dect_llns	lln;
+	struct dect_mci	mci;
+};
+
+/**
+ * @DECT_LAPC_ULI:	unassigned link identifier state (class U/A)
+ * @DECT_LAPC_ALI:	assigned link identifier state (class B established)
+ * @DECT_LAPC_ASM:	assigned Link Identifier/multiple frame state (class B suspended)
+ */
+enum dect_lapc_states {
+	DECT_LAPC_ULI,
+	DECT_LAPC_ALI,
+	DECT_LAPC_ASM,
+};
+
+/**
+ * struct dect_lapc - DECT LAPC entity
+ *
+ * @lc:			Associated Lc entity
+ * @dli:		Data Link Identifier
+ * @sapi:		Service Access Point Identifier
+ * @cmd:		CR bit setting for commands (PT: 1, FT: 0)
+ * @nlf:		New link flag
+ * @v_s:		Send state Variable V(S): sequence number of next I-frame
+ * @v_a:		Acknowledge state Variable V(A): last I-frame that has been acknowledged
+ * @v_r:		Receive state Variable V(R): next expected sequence number
+ * busy:		LAPC is in receiver busy condition
+ * @peer_busy:		Peer is in receiver busy condition
+ * @window:		maximum number of oustanding unacknowledged I-frames
+ * @mod:		modulus for sequence number calculations
+ * @retransmit_cnt:	Retransmission counter
+ * @retransmit_queue:	Retransmission queue
+ * @timer:		Retransmission timer (DL.04)
+ */
+struct dect_lapc {
+	struct sock		*sk;
+	struct dect_lc		*lc;
+	struct dect_dli		dli;
+	enum dect_sapis		sapi;
+
+	bool			cmd;
+
+	enum dect_lapc_states	state;
+	bool			nlf;
+	u8			v_s;
+	u8			v_a;
+	u8			v_r;
+
+	bool			busy;
+	bool			peer_busy;
+
+	u8			window;
+	u8			mod;
+
+	u8			retransmit_cnt;
+	struct sk_buff_head	retransmit_queue;
+	struct timer_list	timer;
+
+	struct sk_buff		*rcv_head;
+};
+
+/* class A window size and sequence number modulus */
+#define DECT_LAPC_CLASS_A_WINDOW		1
+#define DECT_LAPC_CLASS_A_MOD			2
+
+/* class B window size and sequence number modulus */
+#define DECT_LAPC_CLASS_B_INITIAL_WINDOW	1
+#define DECT_LAPC_CLASS_B_WINDOW		3
+#define DECT_LAPC_CLASS_B_MOD			8
+
+/* maximum number of retransmissions */
+#define DECT_LAPC_RETRANSMIT_MAX		3
+
+/* various timer parameters specified in Annex A */
+#define DECT_LAPC_CLASS_A_ESTABLISH_TIMEOUT	(2 * HZ)
+#define DECT_LAPC_CLASS_B_ESTABLISH_TIMEOUT	(2 * HZ)
+#define DECT_LAPC_RETRANSMISSION_TIMEOUT	(1 * HZ)
+#define DECT_LAPC_LINK_RELEASE_TIMEOUT		(2 * HZ)
+#define DECT_LAPC_LINK_SUSPEND_TIMEOUT		(2 * HZ)
+#define DECT_LAPC_LINK_RESUME_TIMEOUT		(2 * HZ)
+#define DECT_LAPC_CONNECTION_HANDOVER_TIMEOUT	(10 * HZ)
+#define DECT_LAPC_CONNECTION_HANDOVER_INTERVAL	(4 * HZ)
+
+extern struct dect_lapc *dect_lapc_init(struct sock *sk, const struct dect_dli *dli,
+					enum dect_sapis sapi, struct dect_lc *lc,
+					gfp_t gfp);
+extern void dect_lapc_destroy(struct dect_lapc *lapc);
+
+extern int dect_lapc_establish(struct dect_lapc *lapc);
+extern void dect_lapc_release(struct dect_lapc *lapc, bool normal);
+extern int dect_lapc_transmit(struct dect_lapc *lapc);
+
+extern struct dect_lapc *dect_ssap_rcv_request(struct dect_lc *lc,
+					       const struct dect_dli *dli,
+					       enum dect_sapis sapi);
+
+/**
+ * struct dect_lc - DECT Lc entity
+ *
+ * @mc:		MAC connection
+ * @lsig:	link signature for checksumming (lower 16 bits of PMID or 0)
+ * @rx_head:	reassembly queue head
+ * @rx_len:	target length of current reassembly buffer
+ * @txq:	transmit queue
+ * @tx_head:	current TX LAPC frame
+ * @tx_len:	TX target fragment length
+ * @use:	usage count
+ * @lapcs:	LAPC entities associated with the Lc
+ * @e_lapc:	LAPC performing establishment procedures
+ *
+ * The Lc entity is responsible for framing, logical channel selection and
+ * fragmenting of LAPC PDUs. There is one Lc entity per MAC connection.
+ */
+struct dect_lc {
+	struct dect_mac_conn	*mc;
+	u16			lsig;
+
+	struct sk_buff		*rx_head;
+	u8			rx_len;
+
+	struct sk_buff_head	txq;
+	struct sk_buff		*tx_head;
+	u8			tx_len;
+
+	u8			use;
+	struct dect_lapc	*lapcs[DECT_LLN_UNASSIGNED + 1];
+	struct dect_lapc	*elapc;
+};
+
+#define DECT_LC_LSIG_MASK	0xffff
+
+extern struct dect_lc *dect_lc_init(struct dect_mac_conn *mc, gfp_t gfp);
+extern void dect_lc_destroy(struct dect_lc *lc);
+
+extern void dect_lc_bind(struct dect_lc *lc, struct dect_lapc *lapc);
+extern void dect_lc_unbind(struct dect_lc *lc, struct dect_lapc *lapc);
+
+/**
+ * struct dect_lb - DECT Lb entity (C-plane broadcast service)
+ *
+ *
+ */
+struct dect_lb {
+};
+
+#define DECT_LB_SHORT_FRAME_SIZE	3
+#define DECT_LB_LONG_FRAME_SIZE		5
+#define DECT_LB_EXTENDED_FRAME_SIZE_MAX	(6 * DECT_LB_LONG_FRAME_SIZE)
+
+#include <net/sock.h>
+
+/**
+ * struct dect_dlc_fbx_ops - DLC U-plane lower (FBx) entity ops
+ *
+ */
+struct dect_fbx;
+struct dect_fbx_ops {
+	struct sk_buff			*(*dequeue)(struct dect_fbx *fbx);
+	void				(*enqueue)(struct dect_fbx *fbx,
+						   struct sk_buff *skb);
+};
+
+struct dect_fbx {
+	const struct dect_fbx_ops	*ops;
+};
+
+extern const struct dect_fbx_ops dect_fbn_ops;
+
+struct dect_lux;
+struct dect_lux_ops {
+	struct sk_buff			*(*dequeue)(struct dect_lux *lux);
+	void				(*enqueue)(struct dect_lux *lux,
+						   struct sk_buff *skb);
+	void				(*disconnect)(struct dect_lux *lux);
+};
+
+/**
+ * struct dect_lux - DLC U-plane upper (LUx) entity
+ *
+ * @fpx:	FBx entity
+ */
+struct dect_lux {
+	const struct dect_lux_ops	*ops;
+	struct dect_fbx			fbx;
+};
+
+/**
+ * dect_mac_connection_states - DECT MAC connection states as viewed by the DLC
+ *
+ * @DECT_MAC_CONN_CLOSED:
+ * @DECT_MAC_CONN_OPEN_PENDING:
+ * @DECT_MAC_CONN_OPEN:
+ */
+enum dect_mac_conn_states {
+	DECT_MAC_CONN_CLOSED,
+	DECT_MAC_CONN_OPEN_PENDING,
+	DECT_MAC_CONN_OPEN,
+};
+
+/**
+ * struct dect_mac_conn - DECT MAC connection as viewed by the DLC
+ *
+ * @list:	Cluster connection list node
+ * @cl:		Cluster
+ * @mcei:	MAC Connection Endpoint Identification
+ * @mci:	MAC Connection Identifier (BMCI or AMCI)
+ * @state:	Connection state
+ * @service:	Service offered by the connection
+ * @ck:		cipher key
+ */
+struct dect_mac_conn {
+	struct list_head		list;
+	struct dect_cluster		*cl;
+
+	u32				mcei;
+	struct dect_mci			mci;
+	enum dect_mac_conn_states	state;
+	enum dect_mac_service_types	service;
+	u64				ck;
+
+	u8				use;
+	struct dect_lc			*lc;
+	struct dect_fbx			*fbx;
+};
+
+extern struct dect_mac_conn *dect_mac_conn_init(struct dect_cluster *cl,
+						const struct dect_mci *mci,
+						const struct dect_mbc_id *id);
+extern void dect_dlc_mac_conn_destroy(struct dect_mac_conn *mc);
+extern struct dect_mac_conn *dect_mac_conn_get_by_mci(const struct dect_cluster *cl,
+						      const struct dect_mci *mci);
+
+extern void dect_dlc_mac_conn_bind(struct dect_mac_conn *mc);
+extern void dect_dlc_mac_conn_unbind(struct dect_mac_conn *mc);
+extern int dect_dlc_mac_conn_establish(struct dect_mac_conn *mc);
+
+extern int dect_mac_con_cfm(struct dect_cluster *cl, u32 mcei,
+			    enum dect_mac_service_types service);
+extern int dect_mac_con_ind(struct dect_cluster *cl,
+			    const struct dect_mbc_id *id,
+			    enum dect_mac_service_types service);
+
+extern int dect_dlc_mac_conn_enc_key_req(struct dect_mac_conn *mc, u64 key);
+extern int dect_dlc_mac_conn_enc_eks_req(struct dect_mac_conn *mc,
+					 enum dect_cipher_states status);
+extern void dect_mac_enc_eks_cfm(struct dect_cluster *cl, u32 mcei,
+				 enum dect_cipher_states status);
+extern void dect_mac_enc_eks_ind(struct dect_cluster *cl, u32 mcei,
+				 enum dect_cipher_states status);
+
+extern void dect_dlc_mac_dis_req(struct dect_mac_conn *mc);
+extern int dect_mac_dis_ind(struct dect_cluster *cl, u32 mcei,
+			    enum dect_release_reasons reason);
+
+extern void dect_cplane_notify_state_change(struct dect_mac_conn *mc);
+extern void dect_cplane_mac_dis_ind(const struct dect_mac_conn *mc,
+				    enum dect_release_reasons reason);
+extern void dect_cplane_mac_enc_eks_ind(const struct dect_mac_conn *mc,
+					enum dect_cipher_states status);
+
+extern void dect_cplane_rcv(struct dect_mac_conn *mc,
+			    enum dect_data_channels chan,
+			    struct sk_buff *skb);
+extern struct sk_buff *dect_cplane_dtr(struct dect_mac_conn *mc,
+				       enum dect_data_channels chan);
+
+extern void dect_uplane_rcv(struct dect_mac_conn *mc,
+			    enum dect_data_channels chan,
+			    struct sk_buff *skb);
+extern struct sk_buff *dect_uplane_dtr(struct dect_mac_conn *mc,
+				       enum dect_data_channels chan);
+
+extern void dect_mac_co_data_ind(struct dect_cluster *cl, u32 mcei,
+				 enum dect_data_channels chan,
+				 struct sk_buff *skb);
+extern struct sk_buff *dect_mac_co_dtr_ind(struct dect_cluster *cl, u32 mcei,
+					   enum dect_data_channels chan);
+
+extern void dect_bsap_rcv(const struct dect_cluster *cl, struct sk_buff *skb);
+extern void dect_mac_page_ind(struct dect_cluster *cl, struct sk_buff *skb);
+
+#endif /* _NET_DECT_DLC_H */
diff --git a/include/net/dect/dsc.h b/include/net/dect/dsc.h
new file mode 100644
index 0000000..423a646
--- /dev/null
+++ b/include/net/dect/dsc.h
@@ -0,0 +1,12 @@
+#ifndef _NET_DECT_DSC_H
+#define _NET_DECT_DSC_H
+
+static inline __le64 dect_dsc_iv(u32 mfn, u8 framenum)
+{
+	return cpu_to_le64((mfn << 4) + framenum);
+}
+
+extern void dect_dsc_keystream(uint64_t iv, const uint8_t *key,
+			       uint8_t *output, unsigned int len);
+
+#endif /* _NET_DECT_DSC_H */
diff --git a/include/net/dect/identities.h b/include/net/dect/identities.h
new file mode 100644
index 0000000..a924d35
--- /dev/null
+++ b/include/net/dect/identities.h
@@ -0,0 +1,194 @@
+#ifndef _NET_DECT_IDENTITIES_H
+#define _NET_DECT_IDENTITIES_H
+
+/*
+ * Acess Rights Identity (ARI)
+ */
+
+#define DECT_ARI_ARC_MASK	0xe000000000000000ULL
+#define DECT_ARI_ARC_SHIFT	61
+
+/* Class A */
+#define DECT_ARI_A_EMC_MASK	0x1fffe00000000000ULL
+#define DECT_ARI_A_EMC_SHIFT	45
+
+#define DECT_ARI_A_FPN_MASK	0x00001ffff0000000ULL
+#define DECT_ARI_A_FPN_SHIFT	28
+
+/* Class B */
+#define DECT_ARI_B_EIC_MASK	0x1fffe00000000000ULL
+#define DECT_ARI_B_EIC_SHIFT	45
+
+#define DECT_ARI_B_FPN_MASK	0x00001fe000000000ULL
+#define DECT_ARI_B_FPN_SHIFT	37
+
+#define DECT_ARI_B_FPS_MASK	0x0000001e00000000ULL
+#define DECT_ARI_B_FPS_SHIFT	33
+
+/* Class C */
+#define DECT_ARI_C_POC_MASK	0x1fffe00000000000ULL
+#define DECT_ARI_C_POC_SHIFT	45
+
+#define DECT_ARI_C_FPN_MASK	0x00001fe000000000ULL
+#define DECT_ARI_C_FPN_SHIFT	37
+
+#define DECT_ARI_C_FPS_MASK	0x0000001e00000000ULL
+#define DECT_ARI_C_FPS_SHIFT	33
+
+/* Class D */
+#define DECT_ARI_D_GOP_MASK	0x1ffffe0000000000ULL
+#define DECT_ARI_D_GOP_SHIFT	41
+
+#define DECT_ARI_D_FPN_MASK	0x000001fe00000000ULL
+#define DECT_ARI_D_FPN_SHIFT	33
+
+/* Class E */
+#define DECT_ARI_E_FIL_MASK	0x1fffe00000000000ULL
+#define DECT_ARI_E_FIL_SHIFT	45
+
+#define DECT_ARI_E_FPN_MASK	0x00001ffe00000000ULL
+#define DECT_ARI_E_FPN_SHIFT	33
+
+#include <linux/dect_netlink.h>
+
+struct dect_ari {
+	enum dect_ari_classes	arc;
+	u32			fpn;
+	u32			fps;
+	union {
+		u16		emc;
+		u16		eic;
+		u16		poc;
+		u32		gop;
+		u16		fil;
+	};
+};
+
+enum dect_ari_lengths {
+	DECT_ARC_A_LEN		= 36,
+	DECT_ARC_B_LEN		= 31,
+	DECT_ARC_C_LEN		= 31,
+	DECT_ARC_D_LEN		= 31,
+	DECT_ARC_E_LEN		= 31,
+};
+
+extern bool dect_ari_masked_cmp(const struct dect_ari *a1,
+				const struct dect_ari *a2,
+				const struct dect_ari *m);
+extern bool dect_ari_cmp(const struct dect_ari *a1, const struct dect_ari *a2);
+extern u8 dect_parse_ari(struct dect_ari *ari, u64 a);
+extern u64 dect_build_ari(const struct dect_ari *ari);
+
+/*
+ * RFPI
+ */
+
+#define DECT_RFPI_E_FLAG	0x0080000000000000ULL
+#define DECT_RFPI_ARI_SHIFT	9
+#define DECT_RFPI_RPN_SHIFT	16
+
+struct dect_idi;
+extern bool dect_rfpi_cmp(const struct dect_idi *i1, const struct dect_idi *i2);
+extern u64 dect_build_rfpi(const struct dect_idi *idi);
+
+/*
+ * FMID (Fixed MAC Identifier)
+ */
+
+#define DECT_FMID_MASK		0x0fff
+#define DECT_FMID_SIZE		12
+
+extern u16 dect_build_fmid(const struct dect_idi *idi);
+
+/*
+ * PMID (Portable MAC Identifier)
+ */
+
+#define DECT_PMID_MASK			0x000fffff
+#define DECT_PMID_SIZE			20
+
+#define DECT_PMID_DEFAULT_ID_MASK	0x000f0000
+#define DECT_PMID_DEFAULT_ID		0x000e0000
+#define DECT_PMID_DEFAULT_NUM_MASK	0x0000ffff
+
+#define DECT_PMID_EMERGENCY_ID_MASK	0x000ff000
+#define DECT_PMID_EMERGENCY_ID		0x000f1000
+#define DECT_PMID_EMERGENCY_TPUI_MASK	0x00000fff
+
+#define DECT_PMID_ASSIGNED_TPUI_MASK	0x000fffff
+
+/**
+ * @DECT_PMID_DEFAULT:		1110 + arbitrary number (16 bits)
+ * @DECT_PMID_ASSIGNED:		Assigned individual TPUI
+ * @DECT_PMID_EMERGENCY:	1111 0001 + 12 bits of emergency TPUI
+ */
+enum dect_pmid_types {
+	DECT_PMID_DEFAULT,
+	DECT_PMID_ASSIGNED,
+	DECT_PMID_EMERGENCY,
+};
+
+struct dect_pmid {
+	enum dect_pmid_types	type;
+	union {
+		u32		tpui;
+		u32		num;
+	};
+};
+
+extern void dect_parse_pmid(struct dect_pmid *pmid, u32 p);
+extern u32 dect_build_pmid(const struct dect_pmid *pmid);
+extern bool dect_pmid_cmp(const struct dect_pmid *p1, const struct dect_pmid *p2);
+
+/*
+ * ECN (Exchanged Connection Number)
+ */
+
+#define DECT_ECN_MASK		0xf
+#define DECT_ECN_SIZE		4
+
+/*
+ * LCN (Logical Connection Number)
+ */
+
+#define DECT_LCN_MASK		0x7
+#define DECT_LCN_SIZE		3
+
+/**
+ * struct dect_mci - MAC connection identifier
+ *
+ * @ari:	DECT ARI
+ * @pmid:	Portable MAC Identity
+ * @lcn:	Logical Connection Number
+ */
+struct dect_mci {
+	struct dect_ari		ari;
+	struct dect_pmid	pmid;
+	u8			lcn;
+};
+
+extern int dect_parse_mci(struct dect_mci *mci, u64 m);
+extern u64 dect_build_mci(const struct dect_mci *mci);
+
+/*
+ * Data Link Identifier
+ */
+
+/**
+ * struct dect_dlei - DECT Data Link Endpoint Identifier (DLEI)
+ *
+ */
+struct dect_dlei {
+	struct dect_mci	mci;
+	enum dect_sapis	sapi;
+	enum dect_llns	lln;
+};
+
+/**
+ * struct dect_ulei - DECT U-Plane Link Endpoint Identifier
+ */
+struct dect_ulei {
+	struct dect_mci	mci;
+};
+
+#endif /* _NET_DECT_IDENTITIES_H */
diff --git a/include/net/dect/mac.h b/include/net/dect/mac.h
new file mode 100644
index 0000000..8fcac73
--- /dev/null
+++ b/include/net/dect/mac.h
@@ -0,0 +1,868 @@
+/*
+ * DECT MAC Layer - Header and global definitions
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ */
+
+#ifndef _NET_DECT_MAC_H
+#define _NET_DECT_MAC_H
+
+#include <net/dect/identities.h>
+
+/*
+ * A-Field
+ */
+
+#define DECT_A_FIELD_SIZE	8
+
+#define DECT_RA_FIELD_SIZE	2
+#define DECT_RA_FIELD_OFF	6
+
+/*
+ * Header field
+ */
+
+#define DECT_HDR_FIELD_SIZE	1
+#define DECT_HDR_FIELD_OFF	0
+
+#define DECT_HDR_TA_OFF		0
+#define DECT_HDR_TA_MASK	0xe0
+#define DECT_HDR_TA_SHIFT	5
+
+#define DECT_HDR_Q1_OFF		0
+#define DECT_HDR_Q1_FLAG	0x10
+
+#define DECT_HDR_BA_OFF		0
+#define DECT_HDR_BA_MASK	0x0e
+#define DECT_HDR_BA_SHIFT	1
+
+#define DECT_HDR_Q2_OFF		0
+#define DECT_HDR_Q2_FLAG	0x01
+
+/*
+ * T-Field
+ */
+
+#define DECT_T_FIELD_OFF	1
+#define DECT_T_FIELD_SIZE	5
+
+/**
+ * dect_tail_identification - MAC layer T-Field identification
+ *
+ * @DECT_TI_CT_PKT_0:		C_T data packet number 0
+ * @DECT_TI_CT_PKT_1:		C_T data packet number 1
+ * @DECT_TI_NT_CL:		Identities information on connectionless bearer
+ * @DECT_TI_NT:			Identities information
+ * @DECT_TI_QT:			Multiframe synchronisation und system information
+ * @DECT_TI_RESERVED:		Reserved
+ * @DECT_TI_MT:			MAC layer control
+ * @DECT_TI_PT:			Paging tail (RFP only)
+ * @DECT_TI_MT_PKT_0:		MAC layer control (first PP transmission, PP only)
+ */
+enum dect_tail_identifications {
+	DECT_TI_CT_PKT_0	= 0x0 << DECT_HDR_TA_SHIFT,
+	DECT_TI_CT_PKT_1	= 0x1 << DECT_HDR_TA_SHIFT,
+	DECT_TI_NT_CL		= 0x2 << DECT_HDR_TA_SHIFT,
+	DECT_TI_NT		= 0x3 << DECT_HDR_TA_SHIFT,
+	DECT_TI_QT		= 0x4 << DECT_HDR_TA_SHIFT,
+	DECT_TI_RESERVED	= 0x5 << DECT_HDR_TA_SHIFT,
+	DECT_TI_MT		= 0x6 << DECT_HDR_TA_SHIFT,
+	DECT_TI_PT		= 0x7 << DECT_HDR_TA_SHIFT,
+	DECT_TI_MT_PKT_0	= 0x7 << DECT_HDR_TA_SHIFT,
+};
+
+struct dect_skb_a_cb {
+	enum dect_tail_identifications	id;
+};
+
+#define DECT_A_CB(skb)		((struct dect_skb_a_cb *)(skb)->cb)
+
+/*
+ * Identities channel (N-channel)
+ */
+
+/* Identities information */
+#define DECT_NT_ID_RFPI_LEN	5
+
+/**
+ * @e:		indicates whether SARIs are available
+ * @pari:	primary access rights identifier
+ * @rpn:	radio part number
+ */
+struct dect_idi {
+	bool		e;
+	struct dect_ari	pari;
+	u8		rpn;
+};
+
+/*
+ * System information and multiframe marker (Q-channel)
+ */
+
+/* RFP Q-channel T-MUX rules: only frame 8 */
+#define DECT_Q_CHANNEL_FRAME	8
+
+/* System information header */
+#define DECT_QT_H_MASK		0x00f0000000000000ULL
+#define DECT_QT_H_SHIFT		52
+
+/**
+ * dect_system_information_types - codes for system information messages
+ *
+ * @DECT_QT_SI_SSI:		static system information
+ * @DECT_QT_SI_ERFC:		extended RF carriers
+ * @DECT_QT_SI_FPC:		fixed part capabilities
+ * @DECT_QT_SI_EFPC:		extended fixed part capabilities
+ * @DECT_QT_SI_SARI:		SARI list contents
+ * @DECT_QT_SI_MFN:		multi-frame number
+ * @DECT_QT_SI_ESC:		escape
+ * @DECT_QT_SI_ERFC2:		extended RF carriers part 2
+ * @DECT_QT_SI_TXI		transmit information
+ * @DECT_QT_SI_EFPC2:		extended fixed part capabilities part 2
+ */
+enum dect_mac_system_information_types {
+	DECT_QT_SI_SSI		= 0x0ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_SSI2		= 0x1ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_ERFC		= 0x2ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_FPC		= 0x3ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_EFPC		= 0x4ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_SARI		= 0x5ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_MFN		= 0x6ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_ESC		= 0x7ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_ERFC2	= 0x9ULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_TXI		= 0xbULL << DECT_QT_H_SHIFT,
+	DECT_QT_SI_EFPC2	= 0xcULL << DECT_QT_H_SHIFT,
+};
+
+/*
+ * Static system information - repeated every 8 multiframes
+ */
+
+#define DECT_QT_SSI_FREQ	8
+
+/* normal reverse */
+#define DECT_QT_SSI_NR_FLAG	0x0010000000000000ULL
+
+/* slot number */
+#define DECT_QT_SSI_SN_MASK	0x000f000000000000ULL
+#define DECT_QT_SSI_SN_SHIFT	48
+
+/* start position */
+#define DECT_QT_SSI_SP_MASK	0x0000c00000000000ULL
+#define DECT_QT_SSI_SP_SHIFT	46
+
+/* escape bit */
+#define DECT_QT_SSI_ESC_FLAG	0x0000200000000000ULL
+
+/* number of transceivers */
+#define DECT_QT_SSI_TXS_MASK	0x0000180000000000ULL
+#define DECT_QT_SSI_TXS_SHIFT	43
+
+/* extended RF carrier information available */
+#define DECT_QT_SSI_MC_FLAG	0x0000040000000000ULL
+
+/* RF carriers available */
+#define DECT_QT_SSI_RFCARS_MASK	0x000003ff00000000ULL
+#define DECT_QT_SSI_RFCARS_SHIFT 32
+
+/* carrier number */
+#define DECT_QT_SSI_CN_MASK	0x000000003f000000ULL
+#define DECT_QT_SSI_CN_SHIFT	24
+
+/* primary scan carrier number */
+#define DECT_QT_SSI_PSCN_MASK	0x00000000003f0000ULL
+#define DECT_QT_SSI_PSCN_SHIFT	16
+
+struct dect_ssi {
+	bool	nr;
+	bool	mc;
+	u16	rfcars;
+	u8	sn;
+	u8	sp;
+	u8	txs;
+	u8	cn;
+	u8	pscn;
+};
+
+/*
+ * Extended RF carrier information
+ */
+
+#define DECT_QT_ERFC_FREQ		8
+
+#define DECT_QT_ERFC_RFCARS_MASK 	0x000fffffe0000000ULL
+#define DECT_QT_ERFC_RFCARS_SHIFT	1
+
+#define DECT_QT_ERFC_RFBAND_MASK	0x000000001f000000ULL
+#define DECT_QT_ERFC_RFBAND_SHIFT	24
+
+#define DECT_QT_ERFC_ERFC2_FLAG		0x0000000000800000ULL
+
+#define DECT_QT_ERFC_NUM_RFCARS_MASK	0x00000000003f0000ULL
+#define DECT_QT_ERFC_NUM_RFCARS_SHIFT	16
+
+struct dect_erfc {
+	u32	rfcars;
+	u8	band;
+	u8	num_rfcars;
+	bool	erfc2;
+};
+
+/*
+ * Fixed Part capabilities
+ */
+
+#define DECT_QT_FPC_FREQ		8
+
+#define DECT_QT_FPC_CAPABILITY_MASK	0x000fffff00000000ULL
+#define DECT_QT_FPC_CAPABILITY_SHIFT	32
+
+#define DECT_QT_FPC_HLC_MASK		0x00000000ffff0000ULL
+#define DECT_QT_FPC_HLC_SHIFT		16
+
+struct dect_fpc {
+	u32	fpc;
+	u16	hlc;
+};
+
+/*
+ * Extended Fixed Part capabilities
+ */
+
+#define DECT_QT_EFPC_EFPC_MASK		0x000fff8000000000ULL
+#define DECT_QT_EFPC_EFPC_SHIFT		39
+
+#define DECT_QT_EFPC_EHLC_MASK		0x0000007fffff0000ULL
+#define DECT_QT_EFPC_EHLC_SHIFT		16
+
+struct dect_efpc {
+	u16	fpc;
+	u32	hlc;
+};
+
+#define DECT_QT_EFPC2_FPC_MASK		0x000fff0000000000ULL
+#define DECT_QT_EFPC2_FPC_SHIFT		40
+
+#define DECT_QT_EFPC2_HLC_MASK		0x000000ffffff0000ULL
+#define DECT_QT_EFPC2_HLC_SHIFT		16
+
+struct dect_efpc2 {
+	u16	fpc;
+	u32	hlc;
+};
+
+/*
+ * SARI message
+ */
+
+#define DECT_QT_SARI_FREQ		4
+
+#define DECT_QT_SARI_LIST_CYCLE_MASK	0x00000e0000000000ULL
+#define DECT_QT_SARI_LIST_CYCLE_SHIFT	41
+
+#define DECT_QT_SARI_TARI_FLAG		0x0000010000000000ULL
+
+#define DECT_QT_SARI_BLACK_FLAG		0x0000008000000000ULL
+
+#define DECT_QT_SARI_ARI_MASK		0x0000007fffffff00ULL
+#define DECT_QT_SARI_ARI_SHIFT		25
+
+struct dect_sari {
+	u8		list_cycle;
+	bool		tari;
+	bool		black;
+	struct dect_ari	ari;
+};
+
+#define DECT_SARI_CYCLE_MAX		16
+
+/*
+ * Multiframe number - repeated every 8 multiframes if supported
+ */
+
+#define DECT_QT_MFN_FREQ		8
+
+#define DECT_QT_MFN_MASK		0x000000ffffff0000ULL
+#define DECT_QT_MFN_SHIFT		16
+
+struct dect_mfn {
+	u32	num;
+};
+
+/*
+ * Extended RF carrier information part 2
+ */
+
+#define DECT_QT_TXI_ERFC2_FREQ		8
+
+#define DECT_QT_ERFC2_RFCARS_MASK	0x000fffffffe00000ULL
+#define DECT_QT_ERFC2_RFCARS_SHIFT	21
+
+struct dect_erfc2 {
+	u32	rfcars;
+};
+
+/*
+ * Transmit Information
+ */
+
+#define DECT_QT_TXI_FREQ		8
+
+#define DECT_QT_TXI_TYPE_MASK		0x000f000000000000ULL
+#define DECT_QT_TXI_TYPE_SHIFT		48
+
+#define DECT_QT_TXI_PWL_MASK		0x0000ff0000000000ULL
+#define DECT_QT_TXI_PWL_SHIFT		40
+
+/*
+ * Extended fixed part capabilitiees part 2
+ */
+
+/*
+ * Paging Tail (P-channel)
+ */
+
+#define DECT_PT_HDR_EXTEND_FLAG		0x0080000000000000ULL
+
+#define DECT_PT_HDR_LENGTH_MASK		0x0070000000000000ULL
+#define DECT_PT_HDR_LENGTH_SHIFT	52
+
+/**
+ * @DECT_PT_ZERO_PAGE:		zero length page
+ * @DECT_PT_SHORT_PAGE:		short page
+ * @DECT_PT_FULL_PAGE:		full page
+ * @DECT_PT_MAX_RESUME_PAGE:	MAC resume and control page
+ * @DECT_PT_LONG_PAGE:		not the last 36 bits of a long page
+ * @DECT_PT_LONG_PAGE_FIRST:	the first 36 bits of a long page
+ * @DECT_PT_LONG_PAGE_LAST:	the last 36 bits of a long page
+ * @DECT_PT_LONG_PAGE_ALL:	all of a long page (first and last)
+ *
+ */
+enum dect_page_lengths {
+	DECT_PT_ZERO_PAGE		= 0x0ULL << DECT_PT_HDR_LENGTH_SHIFT,
+	DECT_PT_SHORT_PAGE		= 0x1ULL << DECT_PT_HDR_LENGTH_SHIFT,
+	DECT_PT_FULL_PAGE		= 0x2ULL << DECT_PT_HDR_LENGTH_SHIFT,
+	DECT_PT_RESUME_PAGE		= 0x3ULL << DECT_PT_HDR_LENGTH_SHIFT,
+	DECT_PT_LONG_PAGE		= 0x4ULL << DECT_PT_HDR_LENGTH_SHIFT,
+	DECT_PT_LONG_PAGE_FIRST		= 0x5ULL << DECT_PT_HDR_LENGTH_SHIFT,
+	DECT_PT_LONG_PAGE_LAST		= 0x6ULL << DECT_PT_HDR_LENGTH_SHIFT,
+	DECT_PT_LONG_PAGE_ALL		= 0x7ULL << DECT_PT_HDR_LENGTH_SHIFT,
+};
+
+/* zero length pages */
+#define DECT_PT_ZP_RFPI_MASK		0x000fffff00000000ULL
+#define DECT_PT_ZP_RFPI_SHIFT		32
+
+/* short page B_S channel data */
+#define DECT_PT_SP_BS_DATA_MASK		0x000fffff00000000ULL
+#define DECT_PT_SP_BS_DATA_SHIFT	32
+#define DECT_PT_SP_BS_DATA_SIZE		3
+
+/* long and full page B_S channel data */
+#define DECT_PT_LFP_BS_DATA_MASK	0x000fffffffff0000ULL
+#define DECT_PT_LFP_BS_DATA_SHIFT	16
+#define DECT_PT_LFP_BS_DATA_SIZE	5
+
+struct dect_page {
+	bool			extend;
+	enum dect_page_lengths	length;
+	u32			rfpi;
+};
+
+/* MAC layer information */
+#define DECT_PT_INFO_TYPE_MASK		0x00000000f0000000ULL
+#define DECT_PT_INFO_TYPE_SHIFT		28
+#define DECT_PT_INFO_TYPE_SIZE		2
+
+/**
+ * @DECT_PT_IT_FILL_BITS_OR_BLIND_LONG_SLOTS:	fill bits/blind long slots if bit 47 set
+ * @DECT_PT_IT_BLIND_FULL_SLOT:			blind full slot information
+ * @DECT_PT_IT_OTHER_BEARER:
+ * @DECT_PT_IT_RECOMMENDED_OTHER_BEARER:
+ * @DECT_PT_IT_GOOD_RFP_BEARER:
+ * @DECT_PT_IT_DUMMY_OR_CL_BEARER_POSITION:
+ * @DECT_PT_IT_RFP_IDENTITY:
+ * @DECT_PT_IT_ESCAPE:
+ * @DECT_PT_IT_DUMMY_OR_CL_BEARER_MARKER:
+ * @DECT_PT_IT_BEARER_HANDOVER_INFO:
+ * @DECT_PT_IT_RFP_STATUS:
+ * @DECT_PT_IT_ACTIVE_CARRIERS:
+ * @DECT_PT_IT_CL_BEARER_POSITION:
+ * @DECT_PT_IT_RECOMMENDED_POWER_LEVEL:
+ * @DECT_PT_IT_BLIND_DOUBLE_SLOT:
+ * @DECT_PT_IT_BLIND_FULL_SLOT_PACKET_MODE:
+ *
+ */
+enum dect_pt_info_types {
+	DECT_PT_IT_FILL_BITS_OR_BLIND_LONG_SLOTS= 0x0ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_BLIND_FULL_SLOT		= 0x1ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_OTHER_BEARER			= 0x2ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_RECOMMENDED_OTHER_BEARER	= 0x3ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_GOOD_RFP_BEARER		= 0x4ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_DUMMY_OR_CL_BEARER_POSITION	= 0x5ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_RFP_IDENTITY			= 0x6ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_ESCAPE			= 0x7ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_DUMMY_OR_CL_BEARER_MARKER	= 0x8ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_BEARER_HANDOVER_INFO		= 0x9ULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_RFP_STATUS			= 0xaULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_ACTIVE_CARRIERS		= 0xbULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_CL_BEARER_POSITION		= 0xcULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_RECOMMENDED_POWER_LEVEL	= 0xdULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_BLIND_DOUBLE_SLOT		= 0xeULL << DECT_PT_INFO_TYPE_SHIFT,
+	DECT_PT_IT_BLIND_FULL_SLOT_PACKET_MODE	= 0xfULL << DECT_PT_INFO_TYPE_SHIFT,
+};
+
+/* blind full slot information */
+#define DECT_PT_BFS_MASK		0x000000000fff0000ULL
+#define DECT_PT_BFS_SHIFT		16
+
+struct dect_bfs {
+	struct dect_page	page;
+	u16			mask;
+};
+
+/* Bearer description */
+#define DECT_PT_BEARER_SN_MASK		0x000000000f000000ULL
+#define DECT_PT_BEARER_SN_SHIFT		24
+
+#define DECT_PT_BEARER_SP_MASK		0x0000000000c00000ULL
+#define DECT_PT_BEARER_SP_SHIFT		22
+
+#define DECT_PT_BEARER_CN_MASK		0x00000000003f0000ULL
+#define DECT_PT_BEARER_CN_SHIFT		16
+
+struct dect_bearer_desc {
+	struct dect_page	page;
+	enum dect_pt_info_types	bt;
+	u8			sn;
+	u8			sp;
+	u8			cn;
+};
+
+/* RFP identity */
+#define DECT_PT_RFP_ID_MASK		0x000000000fff0000ULL
+#define DECT_PT_RFP_ID_SHIFT		16
+
+struct dect_rfp_id {
+	struct dect_page	page;
+	u16			id;
+};
+
+/* RFP status */
+#define DECT_PT_RFPS_RFP_BUSY_FLAG	0x0000000001000000ULL
+#define DECT_PT_RFPS_SYS_BUSY_FLAG	0x0000000002000000ULL
+
+struct dect_rfp_status {
+	struct dect_page	page;
+	bool			rfp_busy;
+	bool			sys_busy;
+};
+
+/* Active carriers */
+#define DECT_PT_ACTIVE_CARRIERS_MASK	0x000000000ffc0000ULL
+#define DECT_PT_ACTIVE_CARRIERS_SHIFT	18
+
+struct dect_active_carriers {
+	struct dect_page	page;
+	u16			active;
+};
+
+/*
+ * MAC control (M-channel)
+ */
+
+#define DECT_MT_FRAME_RATE		2
+
+#define DECT_MT_HDR_MASK		0x00f0000000000000ULL
+#define DECT_MT_HDR_SHIFT		52
+
+#define DECT_MT_CMD_MASK		0x000f000000000000ULL
+#define DECT_MT_CMD_SHIFT		48
+
+/**
+ * enum dect_mt_hdr_type - MAC tail header types
+ */
+enum dect_mt_hdr_type {
+	DECT_MT_BASIC_CCTRL			= 0x0ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_ADV_CCTRL			= 0x1ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_MAC_TEST			= 0x2ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_QUALITY_CTRL			= 0x3ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_BRD_CL_SERVICE			= 0x4ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_ENC_CTRL			= 0x5ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_XYZ				= 0x6ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_ESC				= 0x7ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_TARI				= 0x8ULL << DECT_MT_HDR_SHIFT,
+	DECT_MT_REP_CCTRL			= 0x9ULL << DECT_MT_HDR_SHIFT,
+};
+
+/* advanced connection control */
+enum dect_cctrl_cmds {
+	DECT_CCTRL_ACCESS_REQ			= 0x0ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_BEARER_HANDOVER_REQ		= 0x1ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_CONNECTION_HANDOVER_REQ	= 0x2ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_UNCONFIRMED_ACCESS_REQ	= 0x3ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_BEARER_CONFIRM		= 0x4ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_WAIT				= 0x5ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_ATTRIBUTES_T_REQUEST		= 0x6ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_ATTRIBUTES_T_CONFIRM		= 0x7ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_BANDWIDTH_T_REQUEST		= 0x8ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_BANDWIDTH_T_CONFIRM		= 0x9ULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_CHANNEL_LIST			= 0xaULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_UNCONFIRMED_DUMMY		= 0xbULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_UNCONFIRMED_HANDOVER		= 0xcULL << DECT_MT_CMD_SHIFT,
+	DECT_CCTRL_RELEASE			= 0xfULL << DECT_MT_CMD_SHIFT,
+};
+
+/* Most messages */
+#define DECT_CCTRL_FMID_MASK			0x0000fff000000000ULL
+#define DECT_CCTRL_FMID_SHIFT			36
+
+#define DECT_CCTRL_PMID_MASK			0x0000000fffff0000ULL
+#define DECT_CCTRL_PMID_SHIFT			16
+
+/* Attributes-T request/confirm */
+#define DECT_CCTRL_ATTR_ECN_MASK		0x0000f00000000000ULL
+#define DECT_CCTRL_ATTR_ECN_SHIFT		44
+
+#define DECT_CCTRL_ATTR_LBN_MASK		0x00000f0000000000ULL
+#define DECT_CCTRL_ATTR_LBN_SHIFT		40
+
+#define DECT_CCTRL_ATTR_TYPE_MASK		0x000000c000000000ULL
+#define DECT_CCTRL_ATTR_TYPE_SHIFT		38
+
+enum dect_cctrl_connection_types {
+	DECT_CCTRL_TYPE_ASYMETRIC_UPLINK	= 0x0,
+	DECT_CCTRL_TYPE_ASYMETRIC_DOWNLINK	= 0x1,
+	DECT_CCTRL_TYPE_SYMETRIC_MULTIBEARER	= 0x2,
+	DECT_CCTRL_TYPE_SYMETRIC_BEARER		= 0x3,
+};
+
+#define DECT_CCTRL_ATTR_SERVICE_MASK		0x0000003f00000000ULL
+#define DECT_CCTRL_ATTR_SERVICE_SHIFT		32
+
+enum dect_mac_service_types {
+	DECT_SERVICE_IN_MIN_DELAY		= 0x0,
+	DECT_SERVICE_IPX_ENCODED_PROTECTED	= 0x1,
+	DECT_SERVICE_IN_NORM_DELAY		= 0x2,
+	DECT_SERVICE_UNKNOWN			= 0x4,
+	DECT_SERVICE_C_ONLY			= 0x5,
+	DECT_SERVICE_IP_ERROR_DETECTION		= 0x10,
+	DECT_SERVICE_IPQ_ERROR_DETECTION	= 0x14,
+	/* Lifetime encoded in low three bits */
+	DECT_SERVICE_IP_ERROR_CORRECTION	= 0x18,
+	DECT_SERVICE_IPQ_ERROR_CORRECTION	= 0x38,
+};
+
+#define DECT_CCTRL_ATTR_SLOT_MASK		0x00000000f0000000ULL
+#define DECT_CCTRL_ATTR_SLOT_SHIFT		28
+
+#define DECT_CCTRL_ATTR_CF_FLAG			0x0000000008000000ULL
+
+#define DECT_CCTRL_ATTR_BZ_EXT_MOD_MASK		0x0000000007000000ULL
+#define DECT_CCTRL_ATTR_BZ_EXT_MOD_SHIFT	24
+
+#define DECT_CCTRL_ATTR_ACR_MASK		0x0000000000f00000ULL
+#define DECT_CCTRL_ATTR_ACR_SHIFT		20
+
+enum dect_adaptive_code_rates {
+	DECT_ACR_NONE				= 0x0,
+};
+
+#define DECT_CCTRL_ATTR_A_MOD_MASK		0x00000000000c0000ULL
+#define DECT_CCTRL_ATTR_A_MOD_SHIFT		18
+
+#define DECT_CCTRL_ATTR_BZ_MOD_MASK		0x0000000000030000ULL
+#define DECT_CCTRL_ATTR_BZ_MOD_SHIFT		16
+
+enum dect_modulation_type {
+	DECT_MODULATION_2_LEVEL			= 0x3,
+	DECT_MODULATION_4_LEVEL			= 0x2,
+	DECT_MODULATION_8_LEVEL			= 0x1,
+};
+
+/* Release */
+
+#define DECT_CCTRL_RELEASE_INFO1_MASK		0x0000f00000000000ULL
+#define DECT_CCTRL_RELEASE_INFO1_SHIFT		44
+
+#define DECT_CCTRL_RELEASE_LBN_MASK		0x00000f0000000000ULL
+#define DECT_CCTRL_RELEASE_LBN_SHIFT		40
+
+#define DECT_CCTRL_RELEASE_REASON_MASK		0x000000f000000000ULL
+#define DECT_CCTRL_RELEASE_REASON_SHIFT		36
+
+enum dect_release_reasons {
+	DECT_REASON_UNKNOWN				= 0x0,
+	DECT_REASON_BEARER_RELEASE			= 0x1,
+	DECT_REASON_CONNECTION_RELEASE			= 0x2,
+	DECT_REASON_BEARER_SETUP_OR_HANDOVER_FAILED	= 0x3,
+	DECT_REASON_BEARER_HANDOVER_COMPLETED		= 0x4,
+	DECT_REASON_BEARER_HANDOVER_CLUSTER		= 0x5,
+	DECT_REASON_TIMEOUT_LOST_SIGNAL			= 0x6,
+	DECT_REASON_TIMEOUT_LOST_HANDSHAKE		= 0x7,
+	DECT_REASON_REQUESTED_UNACCEPTABLE_SLOT_TYPE	= 0x8,
+	DECT_REASON_REQUESTED_UNACCEPTABLE_MAC_SERVICE	= 0x9,
+	DECT_REASON_BASE_STATION_BUSY			= 0xa,
+	DECT_REASON_REVERSE_DIRECTION			= 0xb,
+	DECT_REASON_DUPLICATE_PMID			= 0xc,
+	DECT_REASON_UNACCEPTABLE_PMID			= 0xd,
+	DECT_REASON_STAY_ON_LISTEN			= 0xe,
+};
+
+#define DECT_CCTRL_RELEASE_PMID_MASK		0x0000000fffff0000ULL
+#define DECT_CCTRL_RELEASE_PMID_SHIFT		16
+
+struct dect_cctrl {
+	enum dect_cctrl_cmds		cmd;
+	union {
+		struct {
+			u16		fmid;
+			u32		pmid;
+		};
+		struct {
+			u8		lbn;
+			u8		ecn;
+			u8		type;
+			u8		service;
+			u8		slot;
+			bool		cf;
+			u8		a_mod;
+			u8		bz_mod;
+			u8		bz_ext_mod;
+			u8		acr;
+		};
+		struct {
+			u8		lbn;
+			u8		reason;
+			u32		pmid;
+		};
+	};
+};
+
+/* Encryption Control */
+
+#define DECT_ENCCTRL_FILL_MASK			0x0050000000000000ULL
+
+#define DECT_ENCCTRL_CMD_MASK			0x000f000000000000ULL
+#define DECT_ENCCTRL_CMD_SHIFT			48
+
+enum dect_encctrl_cmds {
+	DECT_ENCCTRL_START_REQUEST		= 0x0,
+	DECT_ENCCTRL_START_CONFIRM		= 0x1,
+	DECT_ENCCTRL_START_GRANT		= 0x2,
+	DECT_ENCCTRL_STOP_REQUEST		= 0x4,
+	DECT_ENCCTRL_STOP_CONFIRM		= 0x5,
+	DECT_ENCCTRL_STOP_GRANT			= 0x6,
+};
+
+#define DECT_ENCCTRL_FMID_MASK			0x0000fff000000000ULL
+#define DECT_ENCCTRL_FMID_SHIFT			36
+
+#define DECT_ENCCTRL_PMID_MASK			0x0000000fffff0000ULL
+#define DECT_ENCCTRL_PMID_SHIFT			16
+
+struct dect_encctrl {
+	enum dect_encctrl_cmds	cmd;
+	u32			pmid;
+	u16			fmid;
+};
+
+/* marker for T-MUX exceptions */
+#define DECT_MT_HIGH_PRIORITY		0x1
+
+/*
+ * C_T data
+ */
+
+#define DECT_C_S_SDU_SIZE		5
+
+struct dect_ct_data {
+	u8				seq;
+};
+
+/*
+ * Flat representation of tail message contents
+ */
+enum dect_tail_msg_types {
+	DECT_TM_TYPE_INVALID,
+	DECT_TM_TYPE_ID,
+	DECT_TM_TYPE_SSI,
+	DECT_TM_TYPE_ERFC,
+	DECT_TM_TYPE_FPC,
+	DECT_TM_TYPE_EFPC,
+	DECT_TM_TYPE_EFPC2,
+	DECT_TM_TYPE_SARI,
+	DECT_TM_TYPE_MFN,
+	DECT_TM_TYPE_PAGE,
+	DECT_TM_TYPE_BFS,
+	DECT_TM_TYPE_BD,
+	DECT_TM_TYPE_RFP_ID,
+	DECT_TM_TYPE_RFP_STATUS,
+	DECT_TM_TYPE_ACTIVE_CARRIERS,
+	DECT_TM_TYPE_BCCTRL,
+	DECT_TM_TYPE_ACCTRL,
+	DECT_TM_TYPE_ENCCTRL,
+	DECT_TM_TYPE_CT,
+};
+
+struct dect_tail_msg {
+	enum dect_tail_identifications		ti;
+	enum dect_tail_msg_types		type;
+	union {
+		struct dect_idi			idi;
+		struct dect_ssi			ssi;
+		struct dect_erfc		erfc;
+		struct dect_fpc			fpc;
+		struct dect_efpc		efpc;
+		struct dect_efpc2		efpc2;
+		struct dect_sari		sari;
+		struct dect_mfn			mfn;
+		struct dect_page		page;
+		struct dect_bfs			bfs;
+		struct dect_bearer_desc		bd;
+		struct dect_rfp_id		rfp_id;
+		struct dect_rfp_status		rfp_status;
+		struct dect_active_carriers	active_carriers;
+		struct dect_cctrl		cctl;
+		struct dect_encctrl		encctl;
+		struct dect_ct_data		ctd;
+	};
+};
+
+struct dect_si {
+	u32				mask;
+	struct dect_ssi			ssi;
+	struct dect_erfc		erfc;
+	struct dect_fpc			fpc;
+	struct dect_efpc		efpc;
+	struct dect_efpc2		efpc2;
+	struct dect_sari		sari[DECT_SARI_CYCLE_MAX];
+	struct dect_mfn			mfn;
+	u8				num_saris;
+};
+
+/*
+ * B-Field
+ */
+
+/**
+ * dect_b_identitifications - MAC layer B-Field Identification
+ *
+ * @DECT_BI_UTYPE_0:		U-Type, I_N, SI_N, SI_P or I_P packet number 0
+ * @DECT_BI_UTYPE_1:		U-Type, I_P error detect or I_P packet number 1
+ * @DECT_BI_ETYPE_CF_0:		E-Type, all C_F or CL_F, packet number 0
+ * @DECT_BI_ETYPE_CF_1:		E-Type, all C_F, packet number 1
+ * @DECT_BI_ETYPE_MAC:		E-Type, all MAC control (unnumbered)
+ * @DECT_BI_NONE:		no B-Field
+ */
+enum dect_b_identifications {
+	DECT_BI_UTYPE_0			= 0x0 << DECT_HDR_BA_SHIFT,
+	DECT_BI_UTYPE_1			= 0x1 << DECT_HDR_BA_SHIFT,
+	DECT_BI_ETYPE_CF_0		= 0x2 << DECT_HDR_BA_SHIFT,
+	DECT_BI_DOUBLE_SLOT_REQUIRED	= DECT_BI_ETYPE_CF_0,
+	DECT_BI_ETYPE_CF_1		= 0x3 << DECT_HDR_BA_SHIFT,
+	DECT_BI_ETYPE_NOT_ALL_CF_0	= 0x4 << DECT_HDR_BA_SHIFT,
+	DECT_BI_HALF_SLOT_REQUIRED	= DECT_BI_ETYPE_NOT_ALL_CF_0,
+	DECT_BI_ETYPE_NOT_ALL_CF_1	= 0x5 << DECT_HDR_BA_SHIFT,
+	DECT_BI_LONG_SLOT_640_REQUIRED	= DECT_BI_ETYPE_NOT_ALL_CF_1,
+	DECT_BI_ETYPE_MAC		= 0x6 << DECT_HDR_BA_SHIFT,
+	DECT_BI_LONG_SLOT_672_REQUIRED	= DECT_BI_ETYPE_MAC,
+	DECT_BI_NONE			= 0x7 << DECT_HDR_BA_SHIFT,
+};
+
+struct dect_skb_b_cb {
+	enum dect_b_identifications	id;
+};
+
+#define DECT_B_CB(skb)		((struct dect_skb_b_cb *)(skb)->cb)
+
+#define DECT_C_F_SDU_SIZE	8
+#define DECT_G_F_SDU_SIZE	8
+
+/**
+ * enum dect_mac_channels - internal MAC control channels
+ *
+ * @DECT_MC_Q:		System information and multiframe marker
+ * @DECT_MC_N:		Identities information
+ * @DECT_MC_M:		MAC control channel
+ * @DECT_MC_P:		MAC Paging channel
+ */
+enum dect_mac_channels {
+	DECT_MC_Q,
+	DECT_MC_N,
+	DECT_MC_M,
+	DECT_MC_P,
+};
+
+/**
+ * enum dect_data_channels - logical MAC data channels
+ *
+ * @DECT_MC_G_F:
+ * @DECT_MC_C_S:	Higher layer C-Plane channel (slow)
+ * @DECT_MC_C_F:	Higher layer C-Plane channel (fast)
+ * @DECT_MC_I_N:	Higher layer U-Plane channel (numbered)
+ * @DECT_MC_I_P:	Higher layer U-Plane channel (protected)
+ * @DECT_MC_SI_N:	Higher layer connectionless U-Plane channel (numbered)
+ * @DECT_MC_SI_P:	Higher layer connectionless U-Plane channel (protected)
+ */
+enum dect_data_channels {
+	DECT_MC_G_F,
+	DECT_MC_C_S,
+	DECT_MC_C_F,
+	DECT_MC_I_N,
+	DECT_MC_I_P,
+	DECT_MC_SI_N,
+	DECT_MC_SI_P,
+	__DECT_MC_MAX
+};
+#define DECT_MC_MAX		(__DECT_MC_MAX - 1)
+
+/**
+ * enum dect_mac_connection_types - MAC Connection types
+ *
+ * @DECT_MAC_CONN_BASIC:	Basic connection, always I_N_min_delay service
+ * @DECT_MAC_CONN_ADVANCED:	Advanced connection
+ * @DECT_MAC_CONN_COMPLEMENT:	Complementary connection
+ */
+enum dect_mac_connection_types {
+	DECT_MAC_CONN_BASIC,
+	DECT_MAC_CONN_ADVANCED,
+	DECT_MAC_CONN_COMPLEMENT,
+};
+
+/**
+ * struct dect_tbc_id
+ *
+ * @ari:		FT identifier
+ * @pmid:		Portable MAC identity
+ * @lbn:		Logical Bearer Number
+ * @enc:		Exchanged connection number
+ * @tbei:		Traffic Bearer Endpoint Identifier
+ */
+struct dect_tbc_id {
+	struct dect_ari			ari;
+	struct dect_pmid		pmid;
+	u8				lbn;
+	u8				ecn;
+	u32				tbei;
+};
+
+/**
+ * struct dect_mbc_id
+ *
+ * @mcei:		MAC Connection Endpoint Identifier
+ * @type:		Connection Type (Basic/Advanced)
+ * @ari:		FT identifier
+ * @pmid:		Portable MAC Identity
+ * @ecn:		Exchanged Connection Number
+ * @service:		Service type
+ */
+struct dect_mbc_id {
+	u32				mcei;
+	enum dect_mac_connection_types	type;
+	struct dect_ari			ari;
+	struct dect_pmid		pmid;
+	u8				ecn;
+};
+
+#endif /* _NET_DECT_MAC_H */
diff --git a/include/net/dect/mac_ccf.h b/include/net/dect/mac_ccf.h
new file mode 100644
index 0000000..b3622a0
--- /dev/null
+++ b/include/net/dect/mac_ccf.h
@@ -0,0 +1,247 @@
+/*
+ * DECT MAC Layer - Cluster Control Functions (CCF)
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ */
+
+#ifndef _NET_DECT_MAC_CCF_H
+#define _NET_DECT_MAC_CCF_H
+
+#include <linux/skbuff.h>
+#include <linux/timer.h>
+#include <net/dect/mac.h>
+
+/**
+ * struct dect_bmc_skb_cb
+ *
+ * @fast_page:		page message is a fast page
+ * @long_page:		page message is a long page
+ * @stamp:		multiframe number at time of TX request
+ * @repetitions:	number of page repetitions
+ */
+struct dect_bmc_skb_cb {
+	bool				fast_page;
+	bool				long_page;
+	u32				stamp;
+	u8				repetitions;
+};
+#define DECT_BMC_CB(skb)		((struct dect_bmc_skb_cb *)(skb)->cb)
+
+#define DECT_PAGE_LIFETIME		6	/* multiframes */
+
+/**
+ * struct dect_bmc - broadcast message control
+ *
+ * @bcs:		broadcast controller list
+ * @index:		system information round robin index
+ */
+struct dect_bmc {
+	struct list_head		bcs;
+	unsigned int			index;
+};
+
+struct dect_cmc {
+
+};
+
+struct dect_cs_skb_cb {
+	u8				seq;
+};
+#define DECT_CS_CB(skb)			((struct dect_cs_skb_cb *)(skb)->cb)
+
+enum dect_mbc_state {
+	DECT_MBC_NONE,
+	DECT_MBC_INITIATED,
+	DECT_MBC_ESTABLISHED,
+	DECT_MBC_RELEASED,
+};
+
+/**
+ * struct dect_tb - DECT Traffic Bearer
+ *
+ * @list:		MBC traffic bearer list node
+ * @mbc:		MBC controlling the traffic bearer
+ * @ch:			Cell handling the traffic bearer
+ * @id:			Traffic Bearer Controller ID
+ * @handover:		Handover yes/no
+ * @handover_timer:	Handover timer
+ * @rx_slot:		Receive slot
+ * @tx_slot:		Transmit slot
+ * @slot_rx_timer:	Receive slot timer
+ * @slot_tx_timer:	Transmit slot timer
+ * @b_rx_skb:		B-Field receive skb
+ */
+struct dect_tb {
+	struct list_head		list;
+	struct dect_mbc			*mbc;
+	const struct dect_cell_handle	*ch;
+	struct dect_tbc_id		id;
+	bool				handover;
+
+	/* FP: handover release timer */
+	struct dect_timer		handover_timer;
+
+	/* Slot transmit/receive timers */
+	u8				rx_slot;
+	u8				tx_slot;
+	struct dect_timer		slot_rx_timer;
+	struct dect_timer		slot_tx_timer;
+
+	/* I channel data */
+	struct sk_buff			*b_rx_skb;
+};
+
+/**
+ * struct dect_mbc - DECT Multi-Bearer Control
+ *
+ * @list:		Cluster connection list node
+ * @cl:			Cluster the MBC is contained in
+ * @refcnt:		Reference count
+ * @type:		connection type
+ * @id:			MBC identity
+ * @state:		MBC state
+ * @timer:		Connection setup timer (T200)
+ * @setup_cnt:		number of setup attempts (N200)
+ * @tbs:		List of traffic bearers
+ * @ho_stamp:		Handover token bucket refill timestamp
+ * @ho_tokens:		Handover token bucket tokens
+ * @normal_rx_timer:	Normal receive half frame timer
+ * @onrmal_tx_timer:	Normal transmit half frame timer
+ * @ck:			Cipher key
+ * @cipher_state:	Ciphering state
+ * @cs_rx_seq:		C_S receive sequence number
+ * @cs_tx_seq:		C_S transmit sequence number
+ * @cs_tx_ok:		C_S segment transmit OK
+ * @cs_rx_ok:		C_S segment reception OK
+ * @cs_tx_skb:		C_S segment queued for transmission
+ * @cs_tx_skb:		C_S segment queued for delivery to DLC
+ */
+struct dect_mbc {
+	struct list_head		list;
+	struct dect_cluster		*cl;
+	unsigned int			refcnt;
+
+	enum dect_mac_connection_types	type;
+	struct dect_mbc_id		id;
+	enum dect_mbc_state		state;
+	enum dect_mac_service_types	service;
+
+	struct timer_list		timer;
+	u8				setup_cnt;
+
+	struct list_head		tbs;
+
+	/* Handover rate limiting */
+	unsigned long			ho_stamp;
+	u8				ho_tokens;
+
+	/* Normal transmit/receive timers */
+	struct dect_timer		normal_rx_timer;
+	struct dect_timer		normal_tx_timer;
+
+	/* Encryption */
+	u64				ck;
+	enum dect_cipher_states		cipher_state;
+
+	/* C_S channel */
+	u8				cs_rx_seq;
+	u8				cs_tx_seq;
+	bool				cs_tx_ok;
+	bool				cs_rx_ok;
+	struct sk_buff			*cs_rx_skb;
+	struct sk_buff			*cs_tx_skb;
+};
+
+#define DECT_MBC_SETUP_TIMEOUT		(5 * HZ)	/* T200: 5 seconds */
+#define DECT_MBC_SETUP_MAX_ATTEMPTS	10		/* N200: 10 attempts */
+#define DECT_MBC_HANDOVER_TIMER		(3 * HZ)	/* T202: 3 seconds */
+#define DECT_MBC_TB_HANDOVER_TIMEOUT	16		/* T203: 16 frames */
+
+#define DECT_MBC_HANDOVER_LIMIT		2		/* per N202 seconds */
+#define DECT_MBC_HANDOVER_REATTEMPTS	15		/* N201: 15 */
+
+extern u32 dect_mbc_alloc_mcei(struct dect_cluster *cl);
+extern int dect_mac_con_req(struct dect_cluster *cl,
+			    const struct dect_mbc_id *id);
+extern void dect_mac_dis_req(struct dect_cluster *cl, u32 mcei);
+
+extern int dect_mac_enc_key_req(const struct dect_cluster *cl, u32 mcei, u64 ck);
+extern int dect_mac_enc_eks_req(const struct dect_cluster *cl, u32 mcei,
+				enum dect_cipher_states status);
+
+extern void dect_bmc_mac_page_req(struct dect_cluster *cl, struct sk_buff *skb);
+
+struct dect_llme_req;
+
+/**
+ * struct dect_ccf_ops - Cluster Control Ops
+ *
+ * @bind:			bind cell to cluster
+ * @unbind:			unbind cell from cluster
+ * @mac_info_indicate:		indicate FP mac layer information (PP only)
+ * @mbc_conn_indicate:		indicate a new TBC connection
+ * @mbc_conn_notify:		notify MBC of TBC events
+ * @mbc_data_indicate:		indicate new data to MBC
+ * @bmc_page_indicate:		indicate reception of a page message to the BMC
+ */
+struct dect_cluster_handle;
+struct dect_scan_result;
+enum dect_tbc_event;
+struct dect_ccf_ops {
+	int	(*bind)(struct dect_cluster_handle *,
+			struct dect_cell_handle *);
+	void	(*unbind)(struct dect_cluster_handle *,
+			  struct dect_cell_handle *);
+
+	void	(*time_ind)(struct dect_cluster_handle *,
+			    enum dect_timer_bases, u32, u8, u8);
+
+	void	(*scan_report)(const struct dect_cluster_handle *,
+			       const struct dect_scan_result *);
+
+	void	(*mac_info_ind)(const struct dect_cluster_handle *,
+				const struct dect_idi *,
+				const struct dect_si *);
+
+	int	(*tbc_establish_ind)(const struct dect_cluster_handle *,
+				     const struct dect_cell_handle *,
+				     const struct dect_tbc_id *,
+				     enum dect_mac_service_types, bool);
+	int	(*tbc_establish_cfm)(const struct dect_cluster_handle *,
+				     const struct dect_tbc_id *, bool, u8);
+	void	(*tbc_dis_ind)(const struct dect_cluster_handle *,
+			       const struct dect_tbc_id *,
+			       enum dect_release_reasons);
+	int	(*tbc_event_ind)(const struct dect_cluster_handle *,
+				 const struct dect_tbc_id *,
+				 enum dect_tbc_event);
+	void	(*tbc_data_ind)(const struct dect_cluster_handle *,
+				const struct dect_tbc_id *,
+				enum dect_data_channels chan,
+				struct sk_buff *);
+	int	(*tbc_handover_req)(const struct dect_cluster_handle *,
+				    const struct dect_tbc_id *);
+
+	void	(*bmc_page_ind)(const struct dect_cluster_handle *,
+				struct sk_buff *);
+};
+
+/**
+ * struct dect_cluster_handle - Cell's view of a cluster
+ *
+ * @ops:		Cluster Control Function ops
+ * @index:		Cluster index
+ * @tipc_id:		Cluster TIPC user ID
+ * @tportref:		Topology Service port reference (remote cluster only)
+ * @portref:		Cell Control Protocol port reference (remote cluster only)
+ */
+struct dect_cluster_handle {
+	const struct dect_ccf_ops	*ops;
+	u8				index;
+
+	u32				tipc_id;
+	u32				tportref;
+	u32				portref;
+};
+
+#endif /* _NET_DECT_MAC_CCF_H */
diff --git a/include/net/dect/mac_csf.h b/include/net/dect/mac_csf.h
new file mode 100644
index 0000000..4a182f3
--- /dev/null
+++ b/include/net/dect/mac_csf.h
@@ -0,0 +1,596 @@
+/*
+ * DECT MAC Layer - Cell Site Functions (CSF)
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ */
+
+#ifndef _NET_DECT_MAC_CSF_H
+#define _NET_DECT_MAC_CSF_H
+
+#include <net/dect/mac.h>
+#include <net/dect/transceiver.h>
+#define DECT_CHANNEL_LIST_DBM_RES	6
+#define DECT_CHANNEL_LIST_BINS		(DECT_RSSI_DBM_RANGE / DECT_CHANNEL_LIST_DBM_RES)
+
+/**
+ * struct dect_channel_list_entry
+ *
+ * @list:		channel list bin node
+ * @slot:		slot number
+ * @carrier:		RF-carrier
+ * @rssi:		measured RSSI value
+ */
+struct dect_channel_list_entry {
+	struct list_head	list;
+	u8			slot;
+	u8			carrier;
+	u8			rssi;
+};
+
+/**
+ * struct dect_channel_list - Basic channel list
+ *
+ * @list:		cell's channel lists list node
+ * @pkt:		packet type used for RSSI measurement
+ * @status:		bitmask of completed carriers
+ * @timer:		update timer
+ * @available:		number of available entries
+ * @bins:		channels ordered by RSSI value
+ * @entries:		channel list entries
+ *
+ * A channel list contains channel descriptions of all physical channels
+ * able to carry the packet type, sorted into multiple bins based on the
+ * maximum RSSI value of the TDD slot pair.
+ */
+struct dect_channel_list {
+	struct list_head		list;
+	enum dect_packet_types		pkt;
+	u64				status;
+
+	struct dect_timer		timer;
+	u16				available;
+	struct list_head		bins[DECT_CHANNEL_LIST_BINS];
+	struct dect_channel_list_entry	entries[];
+};
+
+#define DECT_CHANNEL_LIST_MAX_AGE	30	/* T209: 30 seconds */
+#define DECT_CHANNEL_LIST_MAX_DBM	-50	/* dBm */
+#define DECT_CHANNEL_LIST_LOW_WATERMARK	20	/* channels */
+
+#define DECT_CHANNEL_MIN_DELAY		2	/* frames */
+
+enum dect_bearer_states {
+	DECT_DUMMY_BEARER,
+	DECT_TRAFFIC_BEARER,
+	DECT_CL_BEARER,
+	DECT_MONITOR_BEARER,
+};
+
+enum dect_bearer_modes {
+	DECT_BEARER_RX,
+	DECT_BEARER_TX,
+};
+
+/**
+ * enum dect_bearer_state - DECT MAC bearer states
+ *
+ * @DECT_BEARER_INACTIVE:	bearer inactive
+ * @DECT_BEARER_SCHEDULED:	bearer is scheduled for activation
+ * @DECT_BEARER_RSSI_CONFIRM:	bearer is scheduled for RSSI confirmation
+ * @DECT_BEARER_RSSI_CONFIRMED:	RSSI is confirmed, bearer is scheduled for e
+ * @DECT_BEARER_ENABLED:	bearer is enabled
+ */
+enum dect_bearer_state {
+	DECT_BEARER_INACTIVE,
+	DECT_BEARER_SCHEDULED,
+	DECT_BEARER_RSSI_CONFIRM,
+	DECT_BEARER_RSSI_CONFIRMED,
+	DECT_BEARER_ENABLED,
+};
+
+struct dect_bearer;
+struct dect_bearer_ops {
+	enum dect_bearer_states	state;
+	void			(*enable)(struct dect_cell *, struct dect_bearer *);
+	void			(*report_rssi)(struct dect_cell *, struct dect_bearer *,
+					       u8 slot, u8 rssi);
+	void			(*rcv)(struct dect_cell *cell, struct dect_bearer *,
+				       struct sk_buff *);
+};
+
+/**
+ * struct dect_bearer - DECT MAC Bearer
+ *
+ * @type:		bearer type
+ * @state:		operational state
+ * @trx:		DECT transceiver
+ * @chd:		channel description
+ * @mode:		bearer mode (RX/TX)
+ * @tx_timer:		TX enable timer
+ * @rssi:		last measured RSSI of selected channel
+ * @m_tx_queue:		M-channel TX queue
+ * @q:			Hdr-field MUX for Q1/Q2 bit settings
+ * @union:		bearer type specific data
+ */
+struct dect_bearer {
+	const struct dect_bearer_ops	*ops;
+	struct dect_transceiver		*trx;
+	struct dect_channel_desc	chd;
+	enum dect_bearer_modes		mode;
+	enum dect_bearer_state		state;
+	struct dect_timer		tx_timer;
+	u8				rssi;
+
+	struct sk_buff_head		m_tx_queue;
+	u8				q;
+
+	union {
+		struct dect_dbc		*dbc;
+		struct dect_cbc		*cbc;
+		struct dect_tbc		*tbc;
+		struct dect_dmb		*dmb;
+		struct dect_irc		*irc;
+		void			*data;
+	};
+};
+
+/**
+ * struct dect_bc - broadcast controller
+ *
+ * @list:		broadcast message control BC list node
+ * @p_rx_skb:		current RX P-channel message
+ * @p_tx_mask:		bitmask of scheduled mac layer pages
+ */
+struct dect_bc {
+	struct list_head		list;
+	struct sk_buff			*p_rx_skb;
+	u32				p_tx_mask;
+};
+
+/*
+ * enum dect_bearer_qctrl_state - DECT bearer quality control state
+ *
+ * @DECT_BEARER_QCTRL_WAIT:	waiting for next quality control event
+ * @DECT_BEARER_QCTRL_CONFIRM:	performing quality control
+ */
+enum dect_bearer_qctrl_state {
+	DECT_BEARER_QCTRL_WAIT,
+	DECT_BEARER_QCTRL_CONFIRM,
+};
+
+#define DECT_BEARER_QCTRL_FRAMENUM	15	/* must not affect paging */
+#define DECT_BEARER_QCTRL_PERIOD	256	/* frames */
+
+/**
+ * struct dect_dbc - dummy bearer control
+ *
+ * @list:		cell dbc list node
+ * @cell:		DECT cell
+ * @bearer:		dummy bearer
+ * @qctrl_timer:	quality control timer
+ * @qctrl_state:	qaulity control state
+ * @bc:			broadcast controller
+ */
+struct dect_dbc {
+	struct list_head		list;
+	struct dect_cell		*cell;
+	struct dect_bearer		bearer;
+	struct dect_timer		qctrl_timer;
+	enum dect_bearer_qctrl_state	qctrl;
+	struct dect_bc			bc;
+};
+
+/*
+ * struct dect_cbc - connectionless bearer control
+ *
+ * @cell:		DECT cell
+ * @dl_bearer:		connectionless downlink bearer
+ * @ul_bearer:		connectionless uplink bearer, if present
+ * @bc:			broadcast controller
+ */
+struct dect_cbc {
+	struct dect_cell		*cell;
+	struct dect_bearer		dl_bearer;
+	struct dect_bearer		ul_bearer;
+	struct dect_bc			bc;
+};
+
+/**
+ * enum dect_tbc_state - DECT Traffic Bearer Controller state
+ *
+ * @DECT_TBC_NONE:		Initial state
+ * @DECT_TBC_REQ_SENT:		Initiator: bearer request sent
+ * @DECT_TBC_WAIT_RCVD:		Initiator: intermediate state
+ * @DECT_TBC_REQ_RCVD:		Responder: request received
+ * @DECT_TBC_RESPONSE_SENT:	Responder: immediate response to request sent
+ * @DECT_TBC_OTHER_WAIT:	Waiting for "other" message
+ * @DECT_TBC_ESTABLISHED	Established
+ * @DECT_TBC_RELEASING		First RELEASE message sent
+ * @DECT_TBC_RELEASED:		Second RELEASE message sent
+ */
+enum dect_tbc_state {
+	DECT_TBC_NONE,
+	DECT_TBC_REQ_SENT,
+	DECT_TBC_WAIT_RCVD,
+	DECT_TBC_REQ_RCVD,
+	DECT_TBC_RESPONSE_SENT,
+	DECT_TBC_OTHER_WAIT,
+	DECT_TBC_ESTABLISHED,
+	DECT_TBC_RELEASING,
+	DECT_TBC_RELEASED,
+};
+
+/**
+ * enum dect_tbc_enc_state - DECT Traffic Bearer encryption state
+ *
+ * @DECT_TBC_ENC_DISABLED:	 Encryption is disabled
+ * @DECT_TBC_ENC_START_REQ_RCVD: Start request received (FP)
+ * @DECT_TBC_ENC_START_REQ_SENT: Start request sent (PP)
+ * @DECT_TBC_ENC_START_CFM_RCVD: Start confirm received (PP)
+ * @DECT_TBC_ENC_START_CFM_SENT: Start confirm sent (FP)
+ * @DECT_TBC_ENC_ENABLED:	 Encryption is enabled
+ */
+enum dect_tbc_enc_state {
+	DECT_TBC_ENC_DISABLED,
+	DECT_TBC_ENC_START_REQ_RCVD,
+	DECT_TBC_ENC_START_REQ_SENT,
+	DECT_TBC_ENC_START_CFM_RCVD,
+	DECT_TBC_ENC_START_CFM_SENT,
+	DECT_TBC_ENC_ENABLED,
+};
+
+/**
+ * enum dect_tbc_event - DECT Traffic Bearer events
+ *
+ * @DECT_TBC_ACK_RECEIVED:	Acknowledgement for C_S data received
+ * @DECT_TBC_CIPHER_ENABLED:	Ciphering enabled
+ * @DECT_TBC_CIPHER_DISABLED:	Ciphering disabled
+ */
+enum dect_tbc_event {
+	DECT_TBC_ACK_RECEIVED,
+	DECT_TBC_CIPHER_ENABLED,
+	DECT_TBC_CIPHER_DISABLED,
+};
+
+/**
+ * struct dect_tbc - DECT Traffic Bearer Control
+ *
+ * @list:		Cell TBC list node
+ * @cell:		DECT cell
+ * @id:			Traffic Bearer ID
+ * @txb:		TX bearer
+ * @rxb:		RX bearer
+ * @state:		Bearer establishment state
+ * @tx_timer:		Transmit activation timer
+ * @wd_timer:		Receive watchdog timer
+ * @release_timer:	Release timer for unacknowledged release procedure
+ * @release_reason:	release reason
+ * @normal_tx_timer:	Normal transmit timer for C-channel/I_N normal delay transmission
+ * @normal_rx_timer:	Normal receive timer for C-channel/I_N normal delay delivery
+ * @rx_timer:		Mimimum delay receive timer
+ * @tx_timer:		Minimum delay transmit timer
+ * @ck:			Cipher key
+ * @enc_timer:		Encryption TX timer
+ * @enc_state:		Encryption state
+ * @enc_msg_cnt:	Encryption message retransmit counter
+ * @c_rx_skb:		C_S segment for delivery to DLC
+ * @c_tx_skb:		C_S segment for transmission in next TDMA frame
+ * @c_tx_ok:		C_S segment was successfully transmitted
+ * @b_rx_skb:		B-field data segment for delivery to DLC
+ * @b_tx_skb:		B-field data segment for transmission in next TDMA frame
+ * @bc:			Broadcast Control
+ */
+struct dect_tbc {
+	struct list_head		list;
+	struct dect_cell		*cell;
+	struct dect_tbc_id		id;
+	enum dect_mac_connection_types	type;
+	enum dect_mac_service_types	service;
+	bool				handover;
+
+	struct dect_bearer		txb;
+	struct dect_bearer		rxb;
+
+	enum dect_tbc_state		state;
+	struct dect_timer		wait_timer;
+	struct dect_timer		wd_timer;
+
+	struct dect_timer		release_timer;
+	enum dect_release_reasons	release_reason;
+
+	/* PP handover trigger */
+	s8				handover_tokens;
+
+	/* Encryption */
+	u64				ck;
+	struct dect_timer		enc_timer;
+	enum dect_tbc_enc_state		enc_state:8;
+	u8				enc_msg_cnt;
+
+	/* C_S channel */
+	struct sk_buff			*cs_tx_skb;
+	bool				cs_tx_ok;
+
+	/* I channel */
+	struct sk_buff			*b_tx_skb;
+
+	struct dect_bc			bc;
+};
+
+#define DECT_TBC_RFPI_TIMEOUT		(5 * DECT_FRAMES_PER_SECOND)
+
+#define DECT_TBC_HO_TOKENS_INITIAL	16
+#define DECT_TBC_HO_TOKENS_OK		1 /* Correct slot adds one token */
+#define DECT_TBC_HO_TOKENS_ERROR	8 /* Error slot subtracts eight tokens */
+#define DECT_TBC_HO_TOKENS_MAX		32
+
+enum dect_scan_status {
+	DECT_SCAN_FAIL,
+	DECT_SCAN_TIMEOUT,
+	DECT_SCAN_COMPLETE,
+};
+
+/**
+ * struct dect_dmb - Monitor Bearer
+ *
+ * @list:	cell dmbs list node
+ * @cell:	DECT cell
+ * @rxb1:	receive bearer 1
+ * @rxb2:	receive bearer 2
+ */
+struct dect_dmb {
+	struct list_head		list;
+	struct dect_cell		*cell;
+
+	struct dect_timer		wd_timer;
+	struct dect_bearer		rxb1;
+	struct dect_bearer		rxb2;
+	struct dect_bc			bc;
+};
+
+/**
+ * struct dect_irc - Idle receiver control
+ *
+ * @cell:		DECT cell
+ * @trx:		DECT transceiver
+ * @ari:		ARI filter
+ * @ari_mask:		ARI filter mask
+ * @idi:		identities information
+ * @si:			system information
+ * @notify:		notification callback
+ * @rx_scn:		Scan carrier number (RX time base)
+ * @tx_scn:		Scan carrier number (TX time base)
+ * @rx_frame_timer:	rx_scn update timer
+ * @tx_frame_timer:	tx_scn update timer
+ */
+struct dect_irc {
+	struct dect_cell	*cell;
+	struct dect_transceiver	*trx;
+
+	struct dect_llme_req	lreq;
+
+	struct dect_ari		ari;
+	struct dect_ari		ari_mask;
+
+	u16			timeout;
+	u16			rssi;
+	struct dect_idi		idi;
+	struct dect_si		si;
+
+	void			(*notify)(struct dect_cell *,
+					  struct dect_transceiver *,
+					  enum dect_scan_status);
+
+	u8			rx_scn;
+	u8			tx_scn;
+	struct dect_timer	rx_frame_timer;
+	struct dect_timer	tx_frame_timer;
+	struct dect_bearer	scan_bearer;
+};
+
+#define DECT_IRC_SCN_OFF	3
+
+struct dect_scan_result {
+	struct dect_llme_req	lreq;
+	struct dect_idi		idi;
+	struct dect_si		si;
+	u16			rssi;
+};
+
+/**
+ * struct dect_csf_ops - Cell Site Function ops
+ *
+ * @set_mode:			set cell to PP/FP mode
+ * @scan:			initiate scan for pari/pari_mask
+ * @preload:			preload system information
+ * @enable:			enable cell
+ * @page_request:		deliver paging message
+ * @tbc_initiate:		initiate a new connection
+ * @tbc_confirm:		confirm an incoming connection
+ * @tbc_release:		release a TBC
+ * @tbc_enc_key_request:	set encryption key
+ * @tbc_enc_eks_request:	enable/disable encryption
+ *
+ * The CSF ops define the interface in the direction CCF -> CSF.
+ */
+struct dect_cell_handle;
+struct dect_csf_ops {
+	int	(*set_mode)(const struct dect_cell_handle *,
+			    enum dect_cluster_modes);
+	int	(*scan)(const struct dect_cell_handle *,
+			const struct dect_llme_req *lreq,
+			const struct dect_ari *, const struct dect_ari *);
+	int	(*preload)(const struct dect_cell_handle *,
+			   const struct dect_ari *, u8,
+			   const struct dect_si *);
+	int	(*enable)(const struct dect_cell_handle *);
+
+	void	(*page_req)(const struct dect_cell_handle *, struct sk_buff *);
+
+	int	(*tbc_establish_req)(const struct dect_cell_handle *,
+				     const struct dect_tbc_id *,
+				     const struct dect_channel_desc *,
+				     enum dect_mac_service_types, bool);
+	int	(*tbc_establish_res)(const struct dect_cell_handle *,
+				     const struct dect_tbc_id *);
+	void	(*tbc_dis_req)(const struct dect_cell_handle *,
+			       const struct dect_tbc_id *,
+			       enum dect_release_reasons);
+	int	(*tbc_enc_key_req)(const struct dect_cell_handle *,
+				   const struct dect_tbc_id *, u64 ck);
+	int	(*tbc_enc_eks_req)(const struct dect_cell_handle *,
+				   const struct dect_tbc_id *,
+				   enum dect_cipher_states status);
+	int	(*tbc_enc_req)(const struct dect_cell_handle *,
+			       const struct dect_tbc_id *, u64 ck);
+	void	(*tbc_data_req)(const struct dect_cell_handle *,
+				const struct dect_tbc_id *,
+				enum dect_data_channels chan,
+				struct sk_buff *);
+
+};
+
+/**
+ * struct dect_cell_handle - DECT cluster view of a cell
+ *
+ * @list:		cluster cell list node
+ * @clh:		bound cluster handle
+ * @ops:		cell site function ops
+ * @rpn:		assigned radio part number
+ * @portref:		cell control protocol port reference (remote cells)
+ */
+struct dect_cell_handle {
+	struct list_head		list;
+	struct dect_cluster_handle	*clh;
+	const struct dect_csf_ops	*ops;
+	u8				rpn;
+
+	u32				portref;
+};
+
+enum dect_cell_states {
+	DECT_CELL_ENABLED		= 1 << 0,
+};
+
+/**
+ * struct dect_cell - DECT cell: one radio system
+ *
+ * @list:		cell list node
+ * @name:		cells' name
+ * @index:		unique numeric cell identifier
+ * @flags:		operational and status flags
+ * @handle:		cell handle
+ * @lock:		lock
+ * @mode:		operational mode (FP/PP)
+ * @state:		bitmask of enum dect_cell_states
+ * @idi:		FP System Identity
+ * @fmid:		FMID (Fixed MAC IDentity)
+ * @si:			FP System Information
+ * @timer_sync_stamp:	Time (multiframe number) of last multiframe number sync
+ * @a_rcv_stamp:	Time (jiffies) of last received A-Field with correct CRC
+ * @nt_rcv_stamp:	Time (jiffies) of last received Nt-Tail containing the PARI
+ * @bcs:		Broadcast Controllers
+ * @cbc:		Connectionless Bearer Controller
+ * @dbcs:		Dummy Bearer Controllers
+ * @tbcs:		list of Traffic Bearer Controllers
+ * @tbc_num_est:	Number of TBCs in ESTABLISHED state
+ * @tbc_last_chd:	Channel description of last TBC leaving ESTABLISHED state
+ * @dmbs:		list of Monitor Bearers
+ * @chanlists:		list of channel lists for different channel types
+ * @timer_base:		RX/TX timer bases
+ * @trg:		DECT transceiver group
+ */
+struct dect_cell {
+	struct list_head		list;
+	char				name[DECTNAMSIZ];
+	u32				index;
+	u32				flags;
+
+	struct dect_cell_handle		handle;
+
+	spinlock_t			lock;
+	enum dect_cluster_modes		mode;
+	u32				state;
+
+	/* identities */
+	struct dect_idi			idi;
+	u16				fmid;
+
+	/* system information */
+	struct dect_si			si;
+	u32				blind_full_slots;
+
+	/* PP state maintenance */
+	u32				timer_sync_stamp;
+	unsigned long			a_rcv_stamp;
+	unsigned long			nt_rcv_stamp;
+
+	/* Broadcast controllers and related data */
+	struct dect_timer		page_timer;
+	struct sk_buff_head		page_queue;
+	struct sk_buff_head		page_fast_queue;
+
+	struct sk_buff			*page_sdu;
+	struct sk_buff_head		page_tx_queue;
+
+	struct list_head		bcs;
+	unsigned int			si_idx;
+	unsigned long			bfs_xmit_stamp;
+
+	struct dect_cbc			cbc;
+	struct list_head		dbcs;
+
+	u32				tbei_rover;
+	struct list_head		tbcs;
+	unsigned int			tbc_num_est;
+	struct dect_channel_desc	tbc_last_chd;
+
+	struct list_head		dmbs;
+
+	/* channel lists */
+	struct list_head		chl_pending;
+	struct list_head		chanlists;
+	struct dect_channel_list	*chl_next;
+	struct dect_channel_list	*chl;
+
+	/* raw transmission queue */
+	struct sk_buff_head		raw_tx_queue;
+
+	struct dect_timer_base		timer_base[DECT_TIMER_BASE_MAX + 1];
+	struct dect_transceiver_group	trg;
+	u32				trg_blind_full_slots;
+};
+
+#define DECT_CELL_TIMER_RESYNC_TIMEOUT	8		/* T216: 8 multiframes */
+#define DECT_CELL_A_RCV_TIMEOUT		(5 * HZ)	/* T207: 5 seconds */
+#define DECT_CELL_NT_RCV_TIMEOUT	(20 * HZ)	/* T208: 20 seconds */
+
+#define dect_foreach_transmit_slot(slot, end, cell) \
+	for ((slot) = dect_normal_transmit_base((cell)->mode), \
+	     (end) = (slot) + DECT_HALF_FRAME_SIZE; \
+	     (slot) < (end); (slot)++)
+
+#define dect_foreach_receive_slot(slot, end, cell) \
+	for ((slot) = dect_normal_receive_base((cell)->mode), \
+	     (end) = (slot) + DECT_HALF_FRAME_SIZE; \
+	     (slot) < (end); (slot)++)
+
+extern struct dect_cell *dect_cell_get_by_index(u32 index);
+
+extern int dect_cell_attach_transceiver(struct dect_cell *cell,
+					struct dect_transceiver *trx);
+extern void dect_cell_detach_transceiver(struct dect_cell *cell,
+					 struct dect_transceiver *trx);
+
+extern void dect_mac_rcv(struct dect_transceiver *trx,
+			 struct dect_transceiver_slot *ts,
+			 struct sk_buff *skb);
+extern void dect_mac_report_rssi(struct dect_transceiver *trx,
+				 struct dect_transceiver_slot *ts, u8 rssi);
+extern void dect_mac_rx_tick(struct dect_transceiver_group *grp, u8 slot);
+extern void dect_mac_tx_tick(struct dect_transceiver_group *grp, u8 slot);
+
+extern void dect_mac_irc_rcv(struct dect_transceiver *trx, struct sk_buff *skb);
+extern void dect_mac_irc_tick(struct dect_transceiver *trx);
+
+#endif /* _NET_DECT_MAC_CSF_H */
diff --git a/include/net/dect/transceiver.h b/include/net/dect/transceiver.h
new file mode 100644
index 0000000..508da7c
--- /dev/null
+++ b/include/net/dect/transceiver.h
@@ -0,0 +1,742 @@
+/*
+ * DECT Transceiver Layer
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ */
+
+#ifndef _NET_DECT_TRANSCEIVER_H
+#define _NET_DECT_TRANSCEIVER_H
+
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/dect.h>
+#include <linux/dect_netlink.h>
+
+#define DECT_RSSI_RANGE			255
+#define DECT_RSSI_DBM_LOW		-93
+#define DECT_RSSI_DBM_RANGE		60
+
+static inline u8 dect_dbm_to_rssi_rel(s8 dbm)
+{
+	return dbm * DECT_RSSI_RANGE / DECT_RSSI_DBM_RANGE;
+}
+
+static inline u8 dect_dbm_to_rssi(s8 dbm)
+{
+	return dect_dbm_to_rssi_rel(dbm - DECT_RSSI_DBM_LOW);
+}
+
+#define DECT_RSSI_AVG_SCALE		3
+
+static inline u16 dect_average_rssi(u16 cur, u16 sample)
+{
+	if (cur == 0)
+		cur = sample << DECT_RSSI_AVG_SCALE;
+	else {
+		cur -= cur >> DECT_RSSI_AVG_SCALE;
+		cur += sample;
+	}
+	return cur;
+}
+
+#define DECT_CARRIER_NUM		64
+
+static inline u8 dect_next_carrier(u64 rfcars, u8 carrier)
+{
+	u64 tmp;
+
+	if (WARN_ON(rfcars == 0))
+		return 0;
+	tmp = rfcars & ~((1ULL << (carrier + 1)) - 1);
+	if (tmp == 0)
+		tmp = rfcars;
+	return ffs(tmp) - 1;
+}
+
+static inline u8 dect_prev_carrier(u64 rfcars, u8 carrier)
+{
+	u64 tmp;
+
+	if (WARN_ON(rfcars == 0))
+		return 0;
+	tmp = rfcars & ((1ULL << carrier) - 1);
+	if (tmp == 0)
+		tmp = rfcars;
+	return fls(tmp) - 1;
+}
+
+static inline u8 dect_carrier_sub(u64 rfcars, u8 carrier, u8 n)
+{
+	while (n != 0) {
+		carrier = dect_prev_carrier(rfcars, carrier);
+		n--;
+	}
+	return carrier;
+}
+
+static inline u8 dect_carrier_distance(u64 rfcars, u8 from, u8 to)
+{
+	if (from >= to) {
+		/* clear bits between to and from */
+		rfcars &= ~(((1ULL << (from - to)) - 1) << to);
+	} else {
+		/* clear bits not between from and to */
+		rfcars &= ((1ULL << (to - from)) - 1) << from;
+	}
+	return hweight64(rfcars);
+}
+
+#define DECT_PHASE_OFFSET_EWMA_LOG	(DECT_PHASE_OFFSET_SCALE / 4)
+
+static inline s32 dect_average_phase_offset(s32 cur, s32 phaseoff)
+{
+	cur -= cur / DECT_PHASE_OFFSET_EWMA_LOG;
+	cur += phaseoff / DECT_PHASE_OFFSET_EWMA_LOG;
+	return cur;
+}
+
+#define DECT_BAND_NUM			32
+#define DECT_DEFAULT_BAND		0
+
+#define DECT_FREQUENCY_F0		1897344	/* kHz */
+#define DECT_CARRIER_WIDTH		1728	/* kHz */
+
+/**
+ * struct dect_band - DECT RF-band
+ *
+ * @band:		RF-band number
+ * @carriers:		number of defined carriers
+ * @frequency:		frequency of each carrier in kHz
+ */
+struct dect_band {
+	u8	band;
+	u8	carriers;
+	u32	frequency[];
+};
+
+#define DECT_FRAME_SIZE			24
+#define DECT_HALF_FRAME_SIZE		(DECT_FRAME_SIZE / 2)
+#define DECT_FRAMES_PER_SECOND		100
+
+#define DECT_SCAN_SLOT			0
+#define DECT_SLOT_MASK			0x00ffffff
+
+static inline u8 dect_next_slotnum(u8 slot)
+{
+	if (++slot == DECT_FRAME_SIZE)
+		slot = 0;
+	return slot;
+}
+
+static inline u8 dect_prev_slotnum(u8 slot)
+{
+	if (slot == 0)
+		slot = DECT_FRAME_SIZE;
+	return slot - 1;
+}
+
+static inline u8 dect_slot_add(u8 s1, u8 s2)
+{
+	return (s1 + s2) % DECT_FRAME_SIZE;
+}
+
+static inline u8 dect_slot_sub(u8 s1, u8 s2)
+{
+	return s1 >= s2 ? s1 - s2 : DECT_FRAME_SIZE + s1 - s2;
+}
+
+static inline u8 dect_slot_distance(u8 s1, u8 s2)
+{
+	return s2 >= s1 ? s2 - s1 : DECT_FRAME_SIZE + s2 - s1;
+}
+
+#define dect_foreach_slot(slot) \
+	for ((slot) = 0; (slot) < DECT_FRAME_SIZE; (slot)++)
+
+static inline u8 dect_normal_transmit_base(enum dect_cluster_modes mode)
+{
+	return mode == DECT_MODE_FP ? 0 : DECT_HALF_FRAME_SIZE;
+}
+
+static inline u8 dect_normal_receive_base(enum dect_cluster_modes mode)
+{
+	return mode == DECT_MODE_FP ? DECT_HALF_FRAME_SIZE : 0;
+}
+
+static inline u8 dect_normal_receive_end(enum dect_cluster_modes mode)
+{
+	return mode == DECT_MODE_FP ? DECT_FRAME_SIZE - 1 :
+				      DECT_HALF_FRAME_SIZE - 1;
+}
+
+static inline u8 dect_tdd_slot(u8 slot)
+{
+	return slot < DECT_HALF_FRAME_SIZE ? slot + DECT_HALF_FRAME_SIZE :
+					     slot - DECT_HALF_FRAME_SIZE;
+}
+
+/**
+ * enum dect_slot_types - DECT slot types
+ *
+ * @DECT_HALF_SLOT:	Half-slot format (240 bits)
+ * @DECT_FULL_SLOT:	Full-slot format (480 bits)
+ * @DECT_DOUBLE_SLOT:	Double-slot format (960 bits)
+ *
+ * The numeric values must match the MAC-layer attributes-T coding.
+ */
+enum dect_slot_types {
+	DECT_HALF_SLOT		= 0x0,
+	DECT_FULL_SLOT		= 0x1,
+	DECT_DOUBLE_SLOT	= 0x2,
+	DECT_LONG_SLOT_640	= 0x3,
+	DECT_LONG_SLOT_672	= 0x4,
+};
+
+/**
+ * enum dect_packet_types - DECT Physical Packet Types
+ *
+ * @DECT_PACKET_P00:	short physical packet P00, 96 bits, A-field only
+ * @DECT_PACKET_P08:	low capacity physical packet P08j, 180 bits
+ * @DECT_PACKET_P32:	basic physical packet P32, 420 bits
+ * @DECT_PACKET_P640j:	variable capacity packet P640j, 712 bits
+ * @DECT_PACKET_P80:	high capacity physical packet P80, 900 bits
+ */
+enum dect_packet_types {
+	DECT_PACKET_P00,
+	DECT_PACKET_P08,
+	DECT_PACKET_P32,
+	DECT_PACKET_P640j,
+	DECT_PACKET_P80,
+	__DECT_PACKET_MAX
+};
+#define DECT_PACKET_MAX		(__DECT_PACKET_MAX - 1)
+
+enum dect_packet_sizes {
+	DECT_P00_SIZE		= 12,
+	DECT_P08_SIZE		= 23,
+	DECT_P32_SIZE		= 53,
+	DECT_P640j_SIZE		= 89,
+	DECT_P80_SIZE		= 113,
+};
+
+#define DECT_PREAMBLE_SIZE	4
+
+/**
+ * enum dect_checksum - DECT hardware checksum results
+ *
+ * @DECT_CHECKSUM_A_CRC_OK:	A-field R-CRC OK
+ * @DECT_CHECKSUM_X_CRC_OK:	Unprotected B-field X-CRC OK
+ * @DECT_CHECKSUM_Z_CRC_OK:	Z-field OK
+ */
+enum dect_checksum {
+	DECT_CHECKSUM_A_CRC_OK	= 0x1,
+	DECT_CHECKSUM_X_CRC_OK	= 0x2,
+	DECT_CHECKSUM_Z_CRC_OK	= 0x4,
+};
+
+/**
+ * enum dect_b_formats - DECT B-Field formats
+ *
+ * @DECT_B_NONE:	No B-field
+ * @DECT_B_UNPROTECTED:	Unprotected B-field format
+ * @DECT_B_PROTECTED:	Protected B-field format
+ *
+ * The B-Field format can be used by a transceiver for offloading X-CRC
+ * calculation.
+ */
+enum dect_b_formats {
+	DECT_B_NONE,
+	DECT_B_UNPROTECTED,
+	DECT_B_PROTECTED,
+	__DECT_B_MAX
+};
+#define DECT_B_MAX		(__DECT_B_MAX - 1)
+
+/**
+ * struct dect_channel_desc - DECT physical channel description
+ *
+ * @pkt:	Packet type in use
+ * @b_fmt:	B-Field format for checksum offloading
+ * @slot:	Slot number
+ * @carrier:	RF-carrier number
+ */
+struct dect_channel_desc {
+	enum dect_packet_types		pkt:8;
+	enum dect_b_formats		b_fmt:8;
+	u8				slot;
+	u8				carrier;
+};
+
+enum dect_channel_priv_flags {
+	DECT_SLOT_RAW_TX		= 0x1,
+};
+
+/**
+ * struct dect_transceiver_slot - Transceiver TDMA slot
+ *
+ * @flags:		slot flags
+ * @priv_flags:		internally used flags
+ * @state:		current state
+ * @desc:		channel description
+ * @bearer:		associated bearer
+ * @ck:			cipher key
+ * @phaseoff:		measured phase offset
+ * @rssi:		averaged RSSI
+ * @rx_bytes:		RX byte count
+ * @rx_packets:		RX packet count
+ * @rx_a_crc_errors:	RX A-field CRC errors
+ * @tx_bytes:		TX byte count
+ * @tx_packets:		TX packet count
+ */
+struct dect_transceiver_slot {
+	u8				flags;
+	u8				priv_flags;
+	u8				blinded;
+	enum dect_slot_states		state:8;
+	struct dect_channel_desc	chd;
+	struct dect_bearer		*bearer;
+	u64				ck;
+
+	s32				phaseoff;
+	u16				rssi;
+	u32				rx_bytes;
+	u32				rx_packets;
+	u32				rx_a_crc_errors;
+	u32				rx_x_crc_errors;
+	u32				rx_z_crc_errors;
+	u32				tx_bytes;
+	u32				tx_packets;
+};
+
+/**
+ * struct dect_transceiver_event - one atomic unit of work for the MAC layer
+ *
+ * @trx:		transceiver
+ * @busy:		synchronizer
+ * @list:		transceiver group events list node
+ * @rx_queue:		received packets
+ * @rssi:		RSSI measurement in scanning slots
+ * @rssi_mask:		RSSI measurement positions
+ * @slotpos:		transceiver slot position in TDMA frame
+ *
+ * A transceiver operates asynchronously to the MAC layer, but the MAC layer's
+ * timing needs to be strictly synchronized to the receiver.
+ *
+ * This structure contains the packets from multiple consequitive slots received
+ * by the receiver in one unit (up to ops->eventrate frames). Slotpos specifies
+ * the transceivers current position in the TDMA frame (== the minimum current
+ * time) and is used for timing purposes and slot maintenance operations of the
+ * upcoming slots. A transceiver uses a fixed amount of these structure and
+ * synchronizes with BH processing through the busy marker. When BH processing
+ * is too slow, frames are dropped.
+ */
+struct dect_transceiver_event {
+	struct dect_transceiver	*trx;
+	atomic_t		busy;
+	struct list_head	list;
+	struct sk_buff_head	rx_queue;
+	u8			rssi[DECT_HALF_FRAME_SIZE / 2];
+	u8			rssi_mask;
+	u8			slotpos;
+};
+
+/**
+ * struct dect_skb_trx_cb - DECT Transceiver skb control block
+ *
+ * @trx:		transceiver
+ * @mfn:		multiframe number
+ * @frame:		frame number
+ * @slot:		slot number
+ * @lbn:		logical bearer number
+ * @csum:		checksum results
+ * @rssi:		RSSI measurement
+ */
+struct dect_skb_trx_cb {
+	struct dect_transceiver	*trx;
+	u32			mfn;
+	u8			frame;
+	u8			slot;
+	u8			lbn;
+	u8			csum;
+	u8			rssi;
+};
+
+static inline struct dect_skb_trx_cb *DECT_TRX_CB(const struct sk_buff *skb)
+{
+	BUILD_BUG_ON(sizeof(struct dect_skb_trx_cb) > sizeof(skb->cb));
+	return (struct dect_skb_trx_cb *)skb->cb;
+}
+
+/**
+ * struct dect_transceiver_ops - DECT transceiver operations
+ *
+ * @disable:		shut the transceiver down
+ * @enable:		bring the transceiver to operational state
+ * @confirm:		confirm a received signal in slave mode
+ * @unlock:		release a confirmed signal again
+ * @lock:		lock to a signal
+ * @set_mode:		set the mode (RX/TX/SCANNING) for a slot
+ * @set_carrier:	set the RF-carrier for a slot
+ * @set_band:		set the RF-band
+ * @destructor:		destructor
+ * @name		transceiver driver name
+ * @features:		transceiver features
+ * @eventrate:		rate at which slot events are generated, must be integral
+ * 			divisor of the number of slots per TDMA half frame
+ * @latency:		latency in slots until updates for a slot take effect
+ *
+ * A transceiver provides frame reception and transmission, signal strength
+ * measurement as well as a reference clock for the MAC layer. It can exist
+ * in two basic states:
+ *
+ * - master: doesn't need initial synchronization to a radio signal
+ * - slave: needs to synchronize timing with a signal
+ *
+ * Only the first transceiver of a FP is a master, PPs are always slaves to
+ * a FPs timing. Secondary and further transceivers of a FP also start as
+ * slaves until they have synchronized to one of the already running
+ * transceivers.
+ *
+ * Locking to a new signal works in multiple phases:
+ *
+ * 1) The ->enable() callback is invoked. The driver is expected to initiate a
+ *    scan for a signal, during which it will pass on any received frame to the
+ *    transceiver layer. As no framing has been established, all packets should
+ *    indicate a slot number of zero.
+ *
+ * 2) While scanning for a signal, the ->set_carrier() callback may be invoked
+ *    with a slot number of zero. The driver is expected to adjust the carrier
+ *    on which it is scanning for a signal.
+ *
+ * 3) When the MAC layer determines interest in a received signal, the ->confirm()
+ *    callback is invoked. The driver is expected to continue to pass frames from
+ *    this signal to the MAC layer to establish framing.
+ *
+ * 3a) When the MAC layer is only collecting information for a scan, it may call
+ *     the ->unlock callback to release a previously confirmed signal.
+ *
+ * 4) Once the MAC layer has determined framing relative to the slot timing, the
+ *    ->lock() callback is invoked. At this point, only a single physical channel
+ *    is received. The driver should synchronize the hardware to the framing to
+ *    make it interrupt at the appropriate times.
+ *
+ */
+struct dect_transceiver;
+struct dect_transceiver_ops {
+	void		(*disable)(const struct dect_transceiver *trx);
+	void		(*enable)(const struct dect_transceiver *trx);
+
+	void		(*confirm)(const struct dect_transceiver *trx);
+	void		(*unlock)(const struct dect_transceiver *trx);
+	void		(*lock)(const struct dect_transceiver *trx, u8 slot);
+
+	void		(*set_mode)(const struct dect_transceiver *trx,
+				    const struct dect_channel_desc *chd,
+				    enum dect_slot_states mode);
+	void		(*set_carrier)(const struct dect_transceiver *trx,
+				       u8 slot, u8 carrier);
+	void		(*tx)(const struct dect_transceiver *trx,
+			      struct sk_buff *skb);
+
+	u64		(*set_band)(const struct dect_transceiver *trx,
+				    const struct dect_band *band);
+	void		(*destructor)(struct dect_transceiver *trx);
+	const char	*name;
+
+	u32		features;
+	u8		eventrate;
+	u8		latency;
+};
+
+/**
+ * enum dect_transceiver_modes - Transceiver synchronization modes
+ *
+ * @DECT_TRANSCEIVER_MASTER:	Transceiver determines reference time (FP)
+ * @DECT_TRANSCEIVER_SLAVE:	Transceiver is slave to foreign reference timing
+ */
+enum dect_transceiver_modes {
+	DECT_TRANSCEIVER_MASTER,
+	DECT_TRANSCEIVER_SLAVE,
+};
+
+/**
+ * enum dect_transceiver_states - transceiver synchronization states
+ *
+ * @DECT_TRANSCEIVER_STOPPED:		transceiver is inactive
+ * @DECT_TRANSCEIVER_UNLOCKED:		transceiver is not synchronized to any RFP
+ * @DECT_TRANSCEIVER_LOCK_PENDING:	transceiver is receiving RFP transmissions,
+ * 					but has not obtained frame synchonization
+ * @DECT_TRANSCEIVER_LOCKED:		the transceiver has achieved frame and
+ * 					multiframe lock to an RFP
+ *
+ * These correspond to the ETS 300 175-3 Annex D PT MAC layer states, but are
+ * per transceiver as we also need to synchronize secondary transceivers.
+ */
+enum dect_transceiver_states {
+	DECT_TRANSCEIVER_STOPPED,
+	DECT_TRANSCEIVER_UNLOCKED,
+	DECT_TRANSCEIVER_LOCK_PENDING,
+	DECT_TRANSCEIVER_LOCKED,
+};
+
+/**
+ * struct dect_transceiver_stats - transceiver statistics
+ *
+ * @event_busy:		events lost due to MAC layer busy
+ * @event_late:		events lost due to transceiver late
+ */
+struct dect_transceiver_stats {
+	u32					event_busy;
+	u32					event_late;
+};
+
+/**
+ * struct dect_transceiver - DECT transceiver
+ *
+ * @list:		transceiver list node
+ * @ops:		transceiver ops
+ * @name:		transceiver identity
+ * @stats:		transceiver statistics
+ * @mode:		synchronization mode
+ * @state:		synchronization state
+ * @band:		current RF-band
+ * @carriers:		bitmask of supported carriers in the current band
+ * @slots:		transceiver slot state
+ * @index:		cell transceiver index
+ * @segno:		transceiver receive sequence number
+ * @cell:		cell the transceiver is assigned to
+ * @irc:		idle receiver control
+ * @event:		dynamic amount of transceiver event structures
+ *
+ * Following the event structures is the private driver data.
+ */
+struct dect_transceiver {
+	struct list_head			list;
+	const struct dect_transceiver_ops	*ops;
+	char					name[DECTNAMSIZ];
+
+	struct dect_transceiver_stats		stats;
+	enum dect_transceiver_modes		mode;
+	enum dect_transceiver_states		state;
+
+	const struct dect_band			*band;
+	u64					carriers;
+
+	struct dect_transceiver_slot		slots[DECT_FRAME_SIZE];
+	u32					blind_full_slots;
+
+	u8					index;
+	u32					seqno;
+	struct dect_cell			*cell;
+	struct dect_irc				*irc;
+	struct dect_transceiver_event		event[];
+};
+
+static inline void *dect_transceiver_priv(const struct dect_transceiver *trx)
+{
+	return (void *)&trx->event[DECT_HALF_FRAME_SIZE / trx->ops->eventrate];
+}
+
+extern struct dect_transceiver *dect_transceiver_alloc(const struct dect_transceiver_ops *ops,
+						       unsigned int priv);
+extern void dect_transceiver_free(struct dect_transceiver *trx);
+extern int dect_register_transceiver(struct dect_transceiver *trx);
+extern void dect_unregister_transceiver(struct dect_transceiver *trx);
+
+extern void dect_transceiver_enable(struct dect_transceiver *trx);
+extern void dect_transceiver_disable(struct dect_transceiver *trx);
+
+extern void dect_transceiver_confirm(struct dect_transceiver *trx);
+extern void dect_transceiver_unlock(struct dect_transceiver *trx);
+extern void dect_transceiver_lock(struct dect_transceiver *trx, u8 slot);
+
+extern int dect_transceiver_set_band(struct dect_transceiver *trx, u8 bandnum);
+
+static inline void dect_set_channel_mode(struct dect_transceiver *trx,
+					 const struct dect_channel_desc *chd,
+					 enum dect_slot_states mode)
+{
+	trx->ops->set_mode(trx, chd, mode);
+	trx->slots[chd->slot].state = mode;
+	trx->slots[chd->slot].chd.pkt = chd->pkt;
+	trx->slots[chd->slot].chd.b_fmt = chd->b_fmt;
+}
+
+static inline void dect_set_carrier(struct dect_transceiver *trx,
+				    u8 slot, u8 carrier)
+{
+	trx->slots[slot].chd.carrier = carrier;
+	trx->slots[slot].rssi	  = 0;
+	trx->slots[slot].phaseoff = 0;
+	trx->ops->set_carrier(trx, slot, carrier);
+}
+
+static inline void dect_set_flags(struct dect_transceiver *trx, u8 slot, u32 flags)
+{
+	trx->slots[slot].flags |= flags;
+	trx->ops->set_mode(trx, &trx->slots[slot].chd, trx->slots[slot].state);
+}
+
+static inline void dect_clear_flags(struct dect_transceiver *trx, u8 slot, u32 flags)
+{
+	trx->slots[slot].flags &= ~flags;
+	trx->ops->set_mode(trx, &trx->slots[slot].chd, trx->slots[slot].state);
+}
+
+static inline void dect_enable_cipher(struct dect_transceiver *trx,
+				      u8 slot, u64 ck)
+{
+	trx->slots[slot].ck = ck;
+	dect_set_flags(trx, slot, DECT_SLOT_CIPHER);
+}
+
+static inline void dect_disable_cipher(struct dect_transceiver *trx, u8 slot)
+{
+	dect_clear_flags(trx, slot, DECT_SLOT_CIPHER);
+	trx->slots[slot].ck = 0;
+}
+
+static inline void dect_transceiver_tx(struct dect_transceiver *trx,
+				       struct sk_buff *skb)
+{
+	u8 slot = DECT_TRX_CB(skb)->slot;
+
+	trx->slots[slot].tx_bytes += skb->len;
+	trx->slots[slot].tx_packets++;
+	trx->ops->tx(trx, skb);
+}
+
+extern struct sk_buff *dect_transceiver_alloc_skb(struct dect_transceiver *trx, u8 slot);
+
+static inline struct dect_transceiver_event *
+dect_transceiver_event(struct dect_transceiver *trx, u8 n, u8 slotpos)
+{
+	struct dect_transceiver_event *event;
+
+	event = &trx->event[n];
+	if (unlikely(!atomic_add_unless(&event->busy, 1, 1))) {
+		trx->stats.event_busy++;
+		return NULL;
+	}
+	event->slotpos = slotpos;
+	return event;
+}
+
+static inline void dect_transceiver_record_rssi(struct dect_transceiver_event *event,
+						u8 slot, u8 rssi)
+{
+	u8 idx;
+
+	idx = slot % event->trx->ops->eventrate;
+	event->rssi[idx] = rssi;
+	event->rssi_mask |= 1 << idx;
+}
+
+static inline void dect_release_transceiver_event(struct dect_transceiver_event *event)
+{
+	event->rssi_mask = 0;
+	smp_mb__before_atomic_dec();
+	atomic_dec(&event->busy);
+}
+
+enum dect_transceiver_events {
+	DECT_TRANSCEIVER_REGISTER,
+	DECT_TRANSCEIVER_UNREGISTER,
+};
+
+#define DECT_TRX_GROUP_MAX	16
+
+/**
+ * struct dect_transceiver_group
+ *
+ * @trx:		Transceiver array
+ * @trxmask:		Mask of present transceivers
+ * @latency:		Maximum latency of all transceivers
+ * @blind_full_slots:	combined blind full slots state of all transceivers
+ * @tasklet:		Event processing tasklet
+ * @lock:		Event list lock
+ * @events:		List of queued events
+ * @seqno:		Transceiver event loss detection
+ * @slot_low:		First unhandled slot
+ * @slot_high:		First slot after slot window
+ * @slots:		merged events for window slot_low - slot_high
+ */
+struct dect_transceiver_group {
+	struct dect_transceiver			*trx[DECT_TRX_GROUP_MAX];
+	u16					trxmask;
+	u8					latency;
+	u32					blind_full_slots;
+
+	struct tasklet_struct			tasklet;
+	spinlock_t				lock;
+	struct list_head			events;
+
+	u32					seqno;
+	u8					slot_low;
+	u8					slot_high;
+	struct {
+		struct sk_buff_head		queue;
+		u16				mask;
+		u8				rssi[DECT_TRX_GROUP_MAX];
+	} slots[DECT_HALF_FRAME_SIZE];
+};
+
+extern void dect_transceiver_group_init(struct dect_transceiver_group *trg);
+extern int dect_transceiver_group_add(struct dect_transceiver_group *trg,
+				      struct dect_transceiver *trx);
+extern void dect_transceiver_group_remove(struct dect_transceiver_group *trg,
+					  struct dect_transceiver *trx);
+
+extern bool dect_transceiver_channel_available(const struct dect_transceiver *trx,
+					       const struct dect_channel_desc *chd);
+extern bool dect_transceiver_reserve(struct dect_transceiver_group *trg,
+				     struct dect_transceiver *trx,
+				     const struct dect_channel_desc *chd);
+extern bool dect_transceiver_release(struct dect_transceiver_group *trg,
+				     struct dect_transceiver *trx,
+				     const struct dect_channel_desc *chd);
+
+extern void dect_transceiver_queue_event(struct dect_transceiver *trx,
+					 struct dect_transceiver_event *ev);
+
+#define dect_first_transceiver(trg)					\
+({									\
+	struct dect_transceiver_group *_trg = (void *)(trg);		\
+	u32 mask = _trg->trxmask;					\
+	mask ? (_trg)->trx[ffs(mask) - 1] : NULL; })
+
+#define dect_next_transceiver(trx, trg)					\
+({									\
+	struct dect_transceiver_group *_trg = (void *)(trg);		\
+	u32 mask = _trg->trxmask;					\
+	mask &= ~((1 << ((trx)->index + 1)) - 1);			\
+	mask ? (_trg)->trx[ffs(mask) - 1] : NULL; })
+
+#define dect_foreach_transceiver(trx, trg)				\
+	for ((trx) = dect_first_transceiver(trg);			\
+	     (trx) != NULL;						\
+	     (trx) = dect_next_transceiver(trx, trg))
+
+#define dect_last_transceiver(trg)					\
+({									\
+	struct dect_transceiver_group *_trg = (void *)(trg);		\
+	u32 mask = _trg->trxmask;					\
+	mask ? (_trg)->trx[fls(mask) - 1] : NULL; })
+
+#define dect_prev_transceiver(trx, trg)					\
+({									\
+	struct dect_transceiver_group *_trg = (void *)(trg);		\
+	u32 mask = _trg->trxmask;					\
+	mask &= (1 << (trx)->index) - 1;				\
+	mask ? (_trg)->trx[fls(mask) - 1] : NULL; })
+
+#define dect_foreach_transceiver_reverse(trx, trg)			\
+	for ((trx) = dect_last_transceiver(trg);			\
+	     (trx) != NULL;						\
+	     (trx) = dect_prev_transceiver(trx, trg))
+
+extern int dect_transceiver_module_init(void);
+extern void dect_transceiver_module_exit(void);
+
+#endif /* _NET_DECT_TRANSCEIVER_H */
diff --git a/net/Kconfig b/net/Kconfig
index 79cabf1..93a2763 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -285,6 +285,7 @@ source "net/ax25/Kconfig"
 source "net/can/Kconfig"
 source "net/irda/Kconfig"
 source "net/bluetooth/Kconfig"
+source "net/dect/Kconfig"
 source "net/rxrpc/Kconfig"
 
 config FIB_RULES
diff --git a/net/Makefile b/net/Makefile
index a51d946..72d60d8 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_AX25)		+= ax25/
 obj-$(CONFIG_CAN)		+= can/
 obj-$(CONFIG_IRDA)		+= irda/
 obj-$(CONFIG_BT)		+= bluetooth/
+obj-$(CONFIG_DECT)		+= dect/
 obj-$(CONFIG_SUNRPC)		+= sunrpc/
 obj-$(CONFIG_AF_RXRPC)		+= rxrpc/
 obj-$(CONFIG_ATM)		+= atm/
diff --git a/net/core/sock.c b/net/core/sock.c
index 6e81978..a5cb77a 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -158,7 +158,7 @@ static const char *const af_family_key_strings[AF_MAX+1] = {
   "sk_lock-AF_TIPC"  , "sk_lock-AF_BLUETOOTH", "sk_lock-IUCV"        ,
   "sk_lock-AF_RXRPC" , "sk_lock-AF_ISDN"     , "sk_lock-AF_PHONET"   ,
   "sk_lock-AF_IEEE802154", "sk_lock-AF_CAIF" , "sk_lock-AF_ALG"      ,
-  "sk_lock-AF_MAX"
+  "sk_lock-AF_DECT"  , "sk_lock-AF_MAX"
 };
 static const char *const af_family_slock_key_strings[AF_MAX+1] = {
   "slock-AF_UNSPEC", "slock-AF_UNIX"     , "slock-AF_INET"     ,
@@ -174,7 +174,7 @@ static const char *const af_family_slock_key_strings[AF_MAX+1] = {
   "slock-AF_TIPC"  , "slock-AF_BLUETOOTH", "slock-AF_IUCV"     ,
   "slock-AF_RXRPC" , "slock-AF_ISDN"     , "slock-AF_PHONET"   ,
   "slock-AF_IEEE802154", "slock-AF_CAIF" , "slock-AF_ALG"      ,
-  "slock-AF_MAX"
+  "slock-AF_DECT"  , "slock-AF_MAX"
 };
 static const char *const af_family_clock_key_strings[AF_MAX+1] = {
   "clock-AF_UNSPEC", "clock-AF_UNIX"     , "clock-AF_INET"     ,
@@ -190,7 +190,7 @@ static const char *const af_family_clock_key_strings[AF_MAX+1] = {
   "clock-AF_TIPC"  , "clock-AF_BLUETOOTH", "clock-AF_IUCV"     ,
   "clock-AF_RXRPC" , "clock-AF_ISDN"     , "clock-AF_PHONET"   ,
   "clock-AF_IEEE802154", "clock-AF_CAIF" , "clock-AF_ALG"      ,
-  "clock-AF_MAX"
+  "clock-AF_DECT"  , "clock-AF_MAX"
 };
 
 /*
diff --git a/net/dect/Kconfig b/net/dect/Kconfig
new file mode 100644
index 0000000..873500d
--- /dev/null
+++ b/net/dect/Kconfig
@@ -0,0 +1,66 @@
+menuconfig DECT
+	tristate "DECT protocol support"
+	help
+	  This option enables support for the DECT protocol.
+
+	  If unsure, say N.
+
+if DECT
+
+config DECT_DEBUG
+	bool "DECT debugging"
+	help
+	  This option enables support for debugging in the DECT modules.
+
+	  If unsure, say N.
+
+config DECT_CSF
+	tristate "DECT Cell Site Functions (CSF) support"
+	help
+	  This option enables support for DECT Cell Site Functions. A DECT
+	  cell is a radio endpoint containing one or more transceivers.
+
+	  If unsure, say N.
+
+config DECT_RAW
+	tristate "DECT raw sockets"
+	depends on DECT_CSF
+	help
+	  This option enables support for PF_DECT raw sockets. DECT raw
+	  sockets are used to receive raw frames from DECT devices.
+
+	  If unsure, say N.
+
+config DECT_CCF
+	tristate "DECT Cluster Control Functions (CCF) support"
+	help
+	  This option enables support for the DECT Cluster Control Functions.
+
+	  A DECT cluster is a Portable radio Termination (PT), containing a
+	  single cell, or a Fixed radio Termination (FT), containing up to
+	  8 cells.
+
+	  If unsure, say N.
+
+config DECT_LU1_SAP
+	tristate "DECT DLC LU1 SAP sockets"
+	select DECT_CCF
+	help
+	  This option enables support for PF_DECT DLC LU1 SAP sockets. DECT
+	  DLC LU1 SAP sockets are used for the TRUP (TRansparent UnProtected)
+	  service, most commonly used for audio.
+
+	  If unsure, say N.
+
+config DECT_CCP
+	bool "DECT Cell Control Protocol support"
+	depends on DECT_CSF || DECT_CCF
+	select TIPC
+	help
+	  This option enables support for the DECT Cell Control Protocol.
+	  This can be used to remotely control one or multiple DECT cells
+	  by the DECT cluster control functions.
+
+	  If unsure, say N.
+
+endif
diff --git a/net/dect/Makefile b/net/dect/Makefile
new file mode 100644
index 0000000..7d7bb14
--- /dev/null
+++ b/net/dect/Makefile
@@ -0,0 +1,17 @@
+dect-y				+= core.o identities.o dect_netlink.o af_dect.o
+
+dect_csf-y			+= mac_csf.o transceiver.o
+
+dect_ccf-y			+= mac_ccf.o dsc.o
+dect_ccf-y			+= dlc.o
+dect_ccf-y			+= dlc_cplane.o dlc_b_sap.o dlc_s_sap.o
+dect_ccf-y			+= dlc_uplane.o
+dect_ccf-$(CONFIG_DECT_CCP)	+= ccp.o
+
+dect_raw-y			+= raw.o
+
+obj-$(CONFIG_DECT)		+= dect.o
+obj-$(CONFIG_DECT_CSF)		+= dect_csf.o
+obj-$(CONFIG_DECT_RAW)		+= dect_raw.o
+obj-$(CONFIG_DECT_CCF)		+= dect_ccf.o
+obj-$(CONFIG_DECT_LU1_SAP)	+= dlc_lu1_sap.o
diff --git a/net/dect/af_dect.c b/net/dect/af_dect.c
new file mode 100644
index 0000000..452df7f
--- /dev/null
+++ b/net/dect/af_dect.c
@@ -0,0 +1,456 @@
+/*
+ * DECT sockets
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/poll.h>
+#include <linux/dect.h>
+#include <net/sock.h>
+#include <net/dect/dect.h>
+
+static struct dect_proto *dect_protos[DECT_PROTO_NUM];
+static DEFINE_SPINLOCK(dect_proto_lock);
+
+void (*dect_raw_rcv_hook)(struct sk_buff *skb);
+EXPORT_SYMBOL_GPL(dect_raw_rcv_hook);
+
+int dect_proto_register(struct dect_proto *proto)
+{
+	int err;
+
+	err = proto_register(&proto->proto, true);
+	if (err < 0)
+		return err;
+
+	spin_lock(&dect_proto_lock);
+	dect_protos[proto->protocol] = proto;
+	spin_unlock(&dect_proto_lock);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dect_proto_register);
+
+void dect_proto_unregister(struct dect_proto *proto)
+{
+	spin_lock(&dect_proto_lock);
+	dect_protos[proto->protocol] = NULL;
+	spin_unlock(&dect_proto_lock);
+	proto_unregister(&proto->proto);
+}
+EXPORT_SYMBOL_GPL(dect_proto_unregister);
+
+struct sk_buff *dect_alloc_notification(u32 type, const void *data,
+					unsigned int size)
+{
+	struct sk_buff *skb;
+
+	skb = alloc_skb(size, GFP_ATOMIC);
+	if (skb == NULL)
+		return NULL;
+	DECT_NOTIFY_CB(skb)->type = type;
+	memcpy(skb_put(skb, size), data, size);
+	return skb;
+}
+EXPORT_SYMBOL_GPL(dect_alloc_notification);
+
+static void dect_destruct(struct sock *sk)
+{
+	__skb_queue_purge(&sk->sk_receive_queue);
+	__skb_queue_purge(&sk->sk_error_queue);
+	__skb_queue_purge(&sk->sk_write_queue);
+}
+
+static int dect_release(struct socket *sock)
+{
+	struct sock *sk = sock->sk;
+	long timeout;
+
+	if (sk == NULL)
+		return 0;
+
+	timeout = 0;
+	if (sock_flag(sk, SOCK_LINGER) && !(current->flags & PF_EXITING))
+		timeout = sk->sk_lingertime;
+	sock->sk = NULL;
+	sk->sk_prot->close(sk, timeout);
+	return 0;
+}
+
+static int dect_bind(struct socket *sock, struct sockaddr *uaddr, int len)
+{
+	struct sock *sk = sock->sk;
+	int err;
+
+	err = 0;
+	if (sk->sk_prot->bind != NULL)
+		err = sk->sk_prot->bind(sk, uaddr, len);
+
+	return err;
+}
+
+static int dect_listen(struct socket *sock, int backlog)
+{
+	struct sock *sk = sock->sk;
+	int err;
+
+	lock_sock(sk);
+	err = -EINVAL;
+	if (sock->state != SS_UNCONNECTED ||
+	    (sock->type != SOCK_STREAM && sock->type != SOCK_SEQPACKET))
+		goto out;
+
+	if (sk->sk_state != DECT_SK_RELEASED && sk->sk_state != DECT_SK_LISTEN)
+		goto out;
+
+	if (sk->sk_state != DECT_SK_LISTEN)
+		sk->sk_prot->hash(sk);
+	sk->sk_max_ack_backlog = backlog;
+	err = 0;
+out:
+	release_sock(sk);
+	return err;
+}
+
+static int dect_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+	struct sock *sk = sock->sk, *newsk;
+	int err;
+
+	newsk = sk->sk_prot->accept(sk, flags, &err);
+	if (newsk == NULL)
+		return err;
+
+	lock_sock(newsk);
+	sock_graft(newsk, newsock);
+	newsock->state = SS_CONNECTED;
+	release_sock(newsk);
+	return 0;
+}
+
+static unsigned int dect_poll(struct file *file, struct socket *sock,
+			      struct poll_table_struct *wait)
+{
+	struct sock *sk = sock->sk;
+	unsigned int mask;
+
+	poll_wait(file, sk_sleep(sk), wait);
+	mask = 0;
+
+	if (sk->sk_state == DECT_SK_LISTEN) {
+		if (!hlist_empty(&dect_csk(sk)->accept_queue))
+			return POLLIN | POLLRDNORM;
+		return 0;
+	}
+
+	/* exceptional events? */
+	if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
+		mask |= POLLERR;
+	if (sk->sk_shutdown & RCV_SHUTDOWN)
+		mask |= POLLRDHUP;
+	if (sk->sk_shutdown == SHUTDOWN_MASK)
+		mask |= POLLHUP;
+
+	/* readable? */
+	if (!skb_queue_empty(&sk->sk_receive_queue) ||
+	    (sk->sk_shutdown & RCV_SHUTDOWN))
+		mask |= POLLIN | POLLRDNORM;
+
+	/* Connection-based need to check for termination and startup */
+	if (sk->sk_state == DECT_SK_RELEASED)
+		mask |= POLLHUP;
+	/* connection hasn't started yet? */
+	if (sk->sk_state == DECT_SK_ESTABLISH_PENDING)
+		return mask;
+
+	/* writable? */
+	if (sock_writeable(sk))
+		mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+	else
+		set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
+
+	return mask;
+}
+
+static int dect_shutdown(struct socket *sock, int how)
+{
+	struct sock *sk = sock->sk;
+	int err = 0;
+
+	how++;
+	if ((how & ~SHUTDOWN_MASK) || !how)
+		return -EINVAL;
+
+	lock_sock(sk);
+
+	if (sock->state == SS_CONNECTING &&
+	    sk->sk_state == DECT_SK_ESTABLISH_PENDING)
+		sock->state = SS_DISCONNECTING;
+
+	switch (sk->sk_state) {
+	case DECT_SK_RELEASED:
+		err = -ENOTCONN;
+		break;
+	case DECT_SK_LISTEN:
+		if (!(how & RCV_SHUTDOWN))
+			break;
+	default:
+		sk->sk_shutdown |= how;
+		if (sk->sk_prot->shutdown != NULL)
+			sk->sk_prot->shutdown(sk, how);
+	}
+
+	/* wake up processes sleeping in poll() */
+	sk->sk_state_change(sk);
+	release_sock(sk);
+	return err;
+}
+
+static int dect_connect(struct socket *sock, struct sockaddr *uaddr, int len,
+			int flags)
+{
+	struct sock *sk = sock->sk;
+	long timeo;
+	int err;
+
+	lock_sock(sk);
+	switch (sock->state) {
+	case SS_CONNECTED:
+		err = -EISCONN;
+		goto out;
+	case SS_CONNECTING:
+		err = -EALREADY;
+		goto out;
+	case SS_UNCONNECTED:
+		err = -EISCONN;
+		if (sk->sk_state != DECT_SK_RELEASED)
+			goto out;
+		err = sk->sk_prot->connect(sk, uaddr, len);
+		if (err < 0)
+			goto out;
+		sock->state = SS_CONNECTING;
+		err = -EINPROGRESS;
+		break;
+	default:
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (sk->sk_state == DECT_SK_ESTABLISH_PENDING) {
+		timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
+		err = sk_stream_wait_connect(sk, &timeo);
+		if (err < 0)
+			goto out;
+
+		err = sock_intr_errno(timeo);
+		if (signal_pending(current))
+			goto out;
+	}
+
+	/* Connection establishment was aborted or failed */
+	if (sk->sk_state == DECT_SK_RELEASED)
+		goto sock_error;
+
+	sock->state = SS_CONNECTED;
+	err = 0;
+out:
+	release_sock(sk);
+	return err;
+
+sock_error:
+	err = sock_error(sk) ? : -ECONNABORTED;
+	sock->state = SS_UNCONNECTED;
+	goto out;
+}
+
+static int dect_getname(struct socket *sock, struct sockaddr *uaddr, int *len,
+			int peer)
+{
+	const struct dect_proto *p;
+
+	/* AF_DECT uses different address formats for the different SAPs */
+	p = container_of(sock->sk->sk_prot, struct dect_proto, proto);
+	if (p->getname != NULL)
+		return p->getname(sock->sk, uaddr, len, peer);
+	*len = 0;
+	return 0;
+}
+
+static int dect_sendmsg(struct kiocb *iocb, struct socket *sock,
+			struct msghdr *msg, size_t size)
+{
+	struct sock *sk = sock->sk;
+
+	return sk->sk_prot->sendmsg(iocb, sk, msg, size);
+}
+
+static int dect_setsockopt(struct socket *sock, int level, int optname,
+			   char __user *optval, unsigned int optlen)
+{
+	struct sock *sk = sock->sk;
+	int err;
+
+	if (level != SOL_DECT)
+		return -ENOPROTOOPT;
+
+	switch (optname) {
+	default:
+		if (sk->sk_prot->setsockopt)
+			err = sk->sk_prot->setsockopt(sk, level, optname,
+						      optval, optlen);
+		else
+			err = -ENOPROTOOPT;
+	}
+	return err;
+}
+
+static int dect_getsockopt(struct socket *sock, int level, int optname,
+			   char __user *optval, int __user *optlen)
+{
+	struct sock *sk = sock->sk;
+	int err;
+
+	if (level != SOL_DECT)
+		return -ENOPROTOOPT;
+
+	switch (optname) {
+	default:
+		if (sk->sk_prot->getsockopt)
+			err = sk->sk_prot->getsockopt(sk, level, optname,
+						      optval, optlen);
+		else
+			err = -ENOPROTOOPT;
+	}
+	return err;
+}
+
+static int dect_create(struct net *net, struct socket *sock, int protocol,
+		       int kern)
+{
+	struct dect_proto *p;
+	struct sock *sk;
+	int err = 0;
+
+	if (protocol < 0 || protocol >= DECT_PROTO_NUM)
+		return -EPROTONOSUPPORT;
+#ifdef CONFIG_MODULES
+	if (dect_protos[protocol] == NULL) {
+		err = request_module("net-pf-%d-proto-%d", PF_DECT, protocol);
+		if (err < 0)
+			return err;
+	}
+#endif
+	spin_lock(&dect_proto_lock);
+	p = dect_protos[protocol];
+	if (p != NULL && !try_module_get(p->proto.owner))
+		p = NULL;
+	spin_unlock(&dect_proto_lock);
+
+	if (p == NULL)
+		return -EPROTONOSUPPORT;
+
+	if (p->type != sock->type) {
+		err = -EPROTONOSUPPORT;
+		goto err;
+	}
+
+	if (cap_valid(p->capability) && !capable(p->capability)) {
+		err = -EACCES;
+		goto err;
+	}
+
+	sock->state = SS_UNCONNECTED;
+	sock->ops = p->ops;
+
+	sk = sk_alloc(net, PF_DECT, GFP_KERNEL, &p->proto);
+	if (sk == NULL) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	sock_init_data(sock, sk);
+	sk->sk_protocol = protocol;
+	sk->sk_destruct = dect_destruct;
+
+	if (sk->sk_prot->init != NULL) {
+		err = sk->sk_prot->init(sk);
+		if (err < 0) {
+			sock_orphan(sk);
+			sock_put(sk);
+		}
+	}
+err:
+	module_put(p->proto.owner);
+	return err;
+}
+
+const struct proto_ops dect_stream_ops = {
+	.family		= PF_DECT,
+	.owner		= THIS_MODULE,
+	.release	= dect_release,
+	.bind		= dect_bind,
+	.connect	= dect_connect,
+	.socketpair	= sock_no_socketpair,
+	.getname	= dect_getname,
+	.poll		= dect_poll,
+	.ioctl		= sock_no_ioctl,
+	.listen		= dect_listen,
+	.accept		= dect_accept,
+	.shutdown	= dect_shutdown,
+	.setsockopt	= dect_setsockopt,
+	.getsockopt	= dect_getsockopt,
+	.sendmsg	= dect_sendmsg,
+	.recvmsg	= sock_common_recvmsg,
+	.mmap		= sock_no_mmap,
+	.sendpage	= sock_no_sendpage,
+};
+EXPORT_SYMBOL_GPL(dect_stream_ops);
+
+const struct proto_ops dect_dgram_ops = {
+	.family		= PF_DECT,
+	.owner		= THIS_MODULE,
+	.release	= dect_release,
+	.bind		= dect_bind,
+	.connect	= sock_no_connect,
+	.socketpair	= sock_no_socketpair,
+	.getname	= dect_getname,
+	.poll		= datagram_poll,
+	.ioctl		= sock_no_ioctl,
+	.listen		= sock_no_listen,
+	.accept		= sock_no_accept,
+	.shutdown	= sock_no_shutdown,
+	.setsockopt	= sock_no_setsockopt,
+	.getsockopt	= sock_no_getsockopt,
+	.sendmsg	= dect_sendmsg,
+	.recvmsg	= sock_common_recvmsg,
+	.mmap		= sock_no_mmap,
+	.sendpage	= sock_no_sendpage,
+};
+EXPORT_SYMBOL_GPL(dect_dgram_ops);
+
+static struct net_proto_family dect_family_ops = {
+	.family		= PF_DECT,
+	.create		= dect_create,
+	.owner		= THIS_MODULE,
+};
+
+int __init dect_af_module_init(void)
+{
+	return sock_register(&dect_family_ops);
+}
+
+void dect_af_module_exit(void)
+{
+	sock_unregister(PF_DECT);
+}
+
+MODULE_ALIAS_NETPROTO(PF_DECT);
diff --git a/net/dect/ccp.c b/net/dect/ccp.c
new file mode 100644
index 0000000..d423a96
--- /dev/null
+++ b/net/dect/ccp.c
@@ -0,0 +1,906 @@
+/*
+ * DECT Cell Control Protocol
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifdef CONFIG_DECT_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+#include <net/dect/mac_csf.h>
+#include <net/dect/mac_ccf.h>
+#include <net/dect/ccp.h>
+#include <net/tipc/tipc.h>
+
+static struct sk_buff *dect_ccp_msg_alloc(size_t size)
+{
+	struct sk_buff *skb;
+
+	size += sizeof(struct dect_ccp_msg_hdr) + 2 * LL_MAX_HEADER;
+	skb = alloc_skb(size, GFP_ATOMIC);
+	if (skb == NULL)
+		return NULL;
+	skb_reserve(skb, size);
+	return skb;
+}
+
+static void dect_ccp_build_msg(struct sk_buff *skb,
+			       enum dect_ccp_primitives prim)
+{
+	struct dect_ccp_msg_hdr *h;
+
+	h = (struct dect_ccp_msg_hdr *)skb_push(skb, sizeof(*h));
+	h->primitive = prim;
+}
+
+static int dect_ccp_send_to_cell(const struct dect_cell_handle *ch,
+				 struct sk_buff *skb,
+				 enum dect_ccp_primitives prim)
+{
+	int err;
+
+	dect_ccp_build_msg(skb, prim);
+	err = tipc_send_buf(ch->portref, skb, skb->len);
+	if (err < 0 && net_ratelimit())
+		printk("Failed to send DECT CCP message\n");
+	return err;
+}
+
+static int dect_ccp_send_to_cluster(const struct dect_cluster_handle *clh,
+				    struct sk_buff *skb,
+				    enum dect_ccp_primitives prim)
+{
+	int err;
+
+	dect_ccp_build_msg(skb, prim);
+	err = tipc_send_buf(clh->portref, skb, skb->len);
+	if (err < 0 && net_ratelimit())
+		printk("Failed to send DECT CCP message\n");
+	return err;
+}
+
+static void dect_ccp_build_tbc_msg(struct sk_buff *skb, const struct dect_tbc_id *id,
+				   u8 data)
+{
+	struct dect_ccp_tbc_msg *msg;
+
+	msg = (struct dect_ccp_tbc_msg *)__skb_push(skb, sizeof(*msg));
+	msg->tbei = cpu_to_be32(id->tbei);
+	msg->pmid = cpu_to_be32(dect_build_pmid(&id->pmid));
+	msg->ari  = cpu_to_be64(dect_build_ari(&id->ari));
+	msg->ecn  = id->ecn;
+	msg->data = data;
+}
+
+static bool dect_ccp_parse_tbc_msg(struct dect_tbc_id *id, u8 *data,
+				   struct sk_buff *skb)
+{
+	struct dect_ccp_tbc_msg *msg;
+
+	if (!pskb_may_pull(skb, sizeof(*msg)))
+		return false;
+	msg = (struct dect_ccp_tbc_msg *)skb->data;
+	__skb_pull(skb, sizeof(*msg));
+
+	id->tbei = be32_to_cpu(msg->tbei);
+	dect_parse_pmid(&id->pmid, be32_to_cpu(msg->pmid));
+	if (!dect_parse_ari(&id->ari, be64_to_cpu(msg->ari)))
+		return false;
+	id->ecn = msg->ecn;
+	if (data != NULL)
+		*data = msg->data;
+	return true;
+}
+
+static void dect_ccp_build_sysinfo(struct sk_buff *skb,
+				   const struct dect_ari *pari, u8 rpn,
+				   const struct dect_si *si)
+{
+	struct dect_ccp_sysinfo_msg *msg;
+	unsigned int i;
+
+	msg = (struct dect_ccp_sysinfo_msg *)__skb_push(skb, sizeof(*msg));
+	msg->pari = cpu_to_be64(dect_build_ari(pari));
+	for (i = 0; i < si->num_saris; i++)
+		msg->sari[i] = cpu_to_be64(dect_build_ari(&si->sari[i].ari));
+	msg->num_saris = i;
+	msg->fpc = cpu_to_be64(si->fpc.fpc);
+	msg->hlc = cpu_to_be64(si->fpc.hlc);
+	msg->mfn = cpu_to_be32(si->mfn.num);
+	msg->rpn = rpn;
+}
+
+static bool dect_ccp_parse_sysinfo(struct dect_ari *pari, u8 *rpn,
+				   struct dect_si *si, struct sk_buff *skb)
+{
+	struct dect_ccp_sysinfo_msg *msg;
+	unsigned int i;
+
+	if (!pskb_may_pull(skb, sizeof(*msg)))
+		return false;
+	msg = (struct dect_ccp_sysinfo_msg *)skb->data;
+	__skb_pull(skb, sizeof(*msg));
+
+	if (!dect_parse_ari(pari, be64_to_cpu(msg->pari)))
+		return false;
+	*rpn = msg->rpn;
+
+	if (msg->num_saris > ARRAY_SIZE(si->sari))
+		return false;
+	for (i = 0; i < msg->num_saris; i++) {
+		if (!dect_parse_ari(&si->sari[i].ari,
+				    be64_to_cpu(msg->sari[i])))
+			return false;
+	}
+	si->fpc.fpc = be64_to_cpu(msg->fpc);
+	si->fpc.hlc = be64_to_cpu(msg->hlc);
+	si->mfn.num = be32_to_cpu(msg->mfn);
+	return true;
+}
+
+static int dect_ccp_send_set_mode(const struct dect_cell_handle *ch,
+				  enum dect_cluster_modes mode)
+{
+	struct dect_ccp_mode_msg *msg;
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(*msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	msg = (struct dect_ccp_mode_msg *)__skb_push(skb, sizeof(*msg));
+	msg->mode = mode;
+
+	return dect_ccp_send_to_cell(ch, skb, DECT_CCP_SET_MODE);
+}
+
+static void dect_ccp_parse_set_mode(const struct dect_cell_handle *ch,
+				    struct sk_buff *skb)
+{
+	struct dect_ccp_mode_msg *msg;
+
+	if (!pskb_may_pull(skb, sizeof(*msg)))
+		return;
+	msg = (struct dect_ccp_mode_msg *)skb->data;
+
+	ch->ops->set_mode(ch, msg->mode);
+}
+
+static int dect_ccp_send_scan(const struct dect_cell_handle *ch,
+			      const struct dect_llme_req *lreq,
+			      const struct dect_ari *ari,
+			      const struct dect_ari *ari_mask)
+{
+	struct dect_ccp_scan_msg *msg;
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(*msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	msg = (struct dect_ccp_scan_msg *)__skb_push(skb, sizeof(*msg));
+	msg->ari = cpu_to_be64(dect_build_ari(ari));
+	msg->ari_mask = cpu_to_be64(dect_build_ari(ari_mask));
+
+	return dect_ccp_send_to_cell(ch, skb, DECT_CCP_SCAN);
+}
+
+static void dect_ccp_parse_scan(const struct dect_cell_handle *ch,
+				struct sk_buff *skb)
+{
+	struct dect_ccp_scan_msg *msg;
+	struct dect_ari ari, ari_mask;
+
+	if (!pskb_may_pull(skb, sizeof(*msg)))
+		return;
+	msg = (struct dect_ccp_scan_msg *)skb->data;
+
+	if (!dect_parse_ari(&ari, be64_to_cpu(msg->ari)))
+		return;
+	if (!dect_parse_ari(&ari_mask, be64_to_cpu(msg->ari_mask)))
+		return;
+	ch->ops->scan(ch, NULL, &ari, &ari_mask);
+}
+
+static int dect_ccp_send_preload(const struct dect_cell_handle *ch,
+				 const struct dect_ari *pari, u8 rpn,
+				 const struct dect_si *si)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_sysinfo_msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	dect_ccp_build_sysinfo(skb, pari, rpn, si);
+
+	return dect_ccp_send_to_cell(ch, skb, DECT_CCP_PRELOAD);
+}
+
+static void dect_ccp_parse_preload(const struct dect_cell_handle *ch,
+				   struct sk_buff *skb)
+{
+	struct dect_ari pari;
+	struct dect_si si;
+	u8 rpn;
+
+	if (!dect_ccp_parse_sysinfo(&pari, &rpn, &si, skb))
+		return;
+	ch->ops->preload(ch, &pari, rpn, &si);
+}
+
+static int dect_ccp_send_enable(const struct dect_cell_handle *ch)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(0);
+	if (skb == NULL)
+		return -ENOMEM;
+	return dect_ccp_send_to_cell(ch, skb, DECT_CCP_ENABLE);
+}
+
+static void dect_ccp_parse_enable(const struct dect_cell_handle *ch,
+				  struct sk_buff *skb)
+{
+	ch->ops->enable(ch);
+}
+
+static void dect_ccp_send_page_req(const struct dect_cell_handle *ch,
+				   struct sk_buff *skb)
+{
+	struct dect_ccp_page_msg *msg;
+
+	msg = (struct dect_ccp_page_msg *)__skb_push(skb, sizeof(*msg));
+	msg->fast_page = DECT_BMC_CB(skb)->fast_page;
+	msg->long_page = DECT_BMC_CB(skb)->long_page;
+
+	dect_ccp_send_to_cell(ch, skb, DECT_CCP_PAGE_REQ);
+}
+
+static void dect_ccp_parse_page_req(const struct dect_cell_handle *ch,
+				    struct sk_buff *skb)
+{
+	struct dect_ccp_page_msg *msg;
+
+	if (!pskb_may_pull(skb, sizeof(*msg)))
+		return;
+	msg = (struct dect_ccp_page_msg *)skb->data;
+	__pskb_pull(skb, sizeof(*msg));
+
+	DECT_BMC_CB(skb)->fast_page = msg->fast_page;
+	DECT_BMC_CB(skb)->long_page = msg->long_page;
+
+	ch->ops->page_req(ch, skb);
+}
+
+static int dect_ccp_send_tbc_establish_req(const struct dect_cell_handle *ch,
+					   const struct dect_tbc_id *id,
+					   const struct dect_channel_desc *chd,
+					   enum dect_mac_service_types service,
+					   bool handover)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	dect_ccp_build_tbc_msg(skb, id, 0);
+	return dect_ccp_send_to_cell(ch, skb, DECT_CCP_TBC_ESTABLISH_REQ);
+}
+
+static void dect_ccp_parse_tbc_establish_req(const struct dect_cell_handle *ch,
+					     struct sk_buff *skb)
+{
+	struct dect_tbc_id id;
+
+	if (!dect_ccp_parse_tbc_msg(&id, NULL, skb))
+		return;
+	ch->ops->tbc_establish_req(ch, &id, NULL, DECT_SERVICE_IN_MIN_DELAY, false);
+}
+
+static void dect_ccp_send_tbc_dis_req(const struct dect_cell_handle *ch,
+				      const struct dect_tbc_id *id,
+				      enum dect_release_reasons reason)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg));
+	if (skb == NULL)
+		return;
+	dect_ccp_build_tbc_msg(skb, id, reason);
+	dect_ccp_send_to_cell(ch, skb, DECT_CCP_TBC_DIS_REQ);
+}
+
+static void dect_ccp_parse_tbc_dis_req(const struct dect_cell_handle *ch,
+				       struct sk_buff *skb)
+{
+	struct dect_tbc_id id;
+	u8 reason;
+
+	if (!dect_ccp_parse_tbc_msg(&id, &reason, skb))
+		return;
+	ch->ops->tbc_dis_req(ch, &id, reason);
+}
+
+static int dect_ccp_send_tbc_establish_res(const struct dect_cell_handle *ch,
+					   const struct dect_tbc_id *id)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	dect_ccp_build_tbc_msg(skb, id, 0);
+	return dect_ccp_send_to_cell(ch, skb, DECT_CCP_TBC_ESTABLISH_RES);
+}
+
+static void dect_ccp_parse_tbc_establish_res(const struct dect_cell_handle *ch,
+					     struct sk_buff *skb)
+{
+	struct dect_tbc_id id;
+
+	if (!dect_ccp_parse_tbc_msg(&id, NULL, skb))
+		return;
+	ch->ops->tbc_establish_res(ch, &id);
+}
+
+static void dect_ccp_send_tbc_data_req(const struct dect_cell_handle *ch,
+				       const struct dect_tbc_id *id,
+				       enum dect_data_channels chan,
+				       struct sk_buff *skb)
+{
+	dect_ccp_build_tbc_msg(skb, id, chan);
+	dect_ccp_send_to_cell(ch, skb, DECT_CCP_TBC_DATA_REQ);
+}
+
+static int dect_ccp_send_tbc_enc_key_req(const struct dect_cell_handle *ch,
+					 const struct dect_tbc_id *id, u64 ck)
+{
+	struct dect_ccp_enc_key_msg *msg;
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg) + sizeof(*msg));
+	if (skb == NULL)
+		return -ENOMEM;
+
+	dect_ccp_build_tbc_msg(skb, id, 0);
+	msg = (struct dect_ccp_enc_key_msg *)skb_tail_pointer(skb);
+	msg->key = cpu_to_be64(ck);
+
+	return dect_ccp_send_to_cell(ch, skb, DECT_CCP_TBC_ENC_KEY_REQ);
+}
+
+static void dect_ccp_parse_tbc_enc_key_req(const struct dect_cell_handle *ch,
+					   struct sk_buff *skb)
+{
+	const struct dect_ccp_enc_key_msg *msg;
+	struct dect_tbc_id id;
+	u64 ck;
+
+	if (!dect_ccp_parse_tbc_msg(&id, NULL, skb))
+		return;
+
+	if (!pskb_may_pull(skb, sizeof(*msg)))
+		return;
+	msg = (struct dect_ccp_enc_key_msg *)skb->data;
+	ck = be64_to_cpu(msg->key);
+
+	ch->ops->tbc_enc_key_req(ch, &id, ck);
+}
+
+static int dect_ccp_send_tbc_enc_eks_req(const struct dect_cell_handle *ch,
+					 const struct dect_tbc_id *id,
+					 enum dect_cipher_states status)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	dect_ccp_build_tbc_msg(skb, id, status);
+	return dect_ccp_send_to_cell(ch, skb, DECT_CCP_TBC_ENC_EKS_REQ);
+}
+
+static void dect_ccp_parse_tbc_enc_eks_req(const struct dect_cell_handle *ch,
+					   struct sk_buff *skb)
+{
+	struct dect_tbc_id id;
+	u8 status;
+
+	if (!dect_ccp_parse_tbc_msg(&id, &status, skb))
+		return;
+
+	switch (status) {
+	case DECT_CIPHER_DISABLED:
+	case DECT_CIPHER_ENABLED:
+		break;
+	default:
+		return;
+	}
+
+	ch->ops->tbc_enc_eks_req(ch, &id, status);
+}
+
+static void dect_ccp_send_scan_report(const struct dect_cluster_handle *clh,
+				      const struct dect_scan_result *res)
+{
+}
+
+static void dect_ccp_send_mac_info_ind(const struct dect_cluster_handle *clh,
+				       const struct dect_idi *idi,
+				       const struct dect_si *si)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_sysinfo_msg));
+	if (skb == NULL)
+		return;
+
+	dect_ccp_build_sysinfo(skb, &idi->pari, idi->rpn, si);
+	dect_ccp_send_to_cluster(clh, skb, DECT_CCP_MAC_INFO_IND);
+}
+
+static void dect_ccp_parse_mac_info_ind(const struct dect_cell_handle *ch,
+					struct sk_buff *skb)
+{
+	const struct dect_cluster_handle *clh = ch->clh;
+	struct dect_idi idi;
+	struct dect_si si;
+
+	if (!dect_ccp_parse_sysinfo(&idi.pari, &idi.rpn, &si, skb))
+		return;
+	idi.e = si.num_saris ? true : false;
+
+	clh->ops->mac_info_ind(clh, &idi, &si);
+}
+
+static int dect_ccp_send_tbc_establish_ind(const struct dect_cluster_handle *clh,
+					   const struct dect_cell_handle *ch,
+					   const struct dect_tbc_id *id,
+					   enum dect_mac_service_types service,
+					   bool handover)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	dect_ccp_build_tbc_msg(skb, id, 0);
+
+	return dect_ccp_send_to_cluster(clh, skb, DECT_CCP_TBC_ESTABLISH_IND);
+}
+
+static void dect_ccp_parse_tbc_establish_ind(const struct dect_cell_handle *ch,
+					     struct sk_buff *skb)
+{
+	const struct dect_cluster_handle *clh = ch->clh;
+	struct dect_tbc_id id;
+
+	if (!dect_ccp_parse_tbc_msg(&id, NULL, skb))
+		return;
+	clh->ops->tbc_establish_ind(clh, ch, &id, DECT_SERVICE_IN_MIN_DELAY, false);
+}
+
+static int dect_ccp_send_tbc_establish_cfm(const struct dect_cluster_handle *clh,
+					   const struct dect_tbc_id *id,
+					   bool success, u8 rx_slot)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	dect_ccp_build_tbc_msg(skb, id, 0);
+
+	return dect_ccp_send_to_cluster(clh, skb, DECT_CCP_TBC_ESTABLISH_CFM);
+}
+
+static void dect_ccp_parse_tbc_establish_cfm(const struct dect_cell_handle *ch,
+					     struct sk_buff *skb)
+{
+	const struct dect_cluster_handle *clh = ch->clh;
+	struct dect_tbc_id id;
+
+	if (!dect_ccp_parse_tbc_msg(&id, NULL, skb))
+		return;
+	clh->ops->tbc_establish_cfm(clh, &id, true, 0);
+}
+
+static int dect_ccp_send_tbc_event_ind(const struct dect_cluster_handle *clh,
+				       const struct dect_tbc_id *id,
+				       enum dect_tbc_event event)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg));
+	if (skb == NULL)
+		return -ENOMEM;
+	dect_ccp_build_tbc_msg(skb, id, event);
+
+	return dect_ccp_send_to_cluster(clh, skb, DECT_CCP_TBC_EVENT_IND);
+}
+
+static void dect_ccp_parse_tbc_event_ind(const struct dect_cell_handle *ch,
+					 struct sk_buff *skb)
+{
+	const struct dect_cluster_handle *clh = ch->clh;
+	struct dect_tbc_id id;
+	u8 event;
+
+	if (!dect_ccp_parse_tbc_msg(&id, &event, skb))
+		return;
+	clh->ops->tbc_event_ind(clh, &id, event);
+}
+
+static void dect_ccp_send_tbc_data_ind(const struct dect_cluster_handle *clh,
+				       const struct dect_tbc_id *id,
+				       enum dect_data_channels chan,
+				       struct sk_buff *skb)
+{
+	dect_ccp_build_tbc_msg(skb, id, chan);
+	dect_ccp_send_to_cluster(clh, skb, DECT_CCP_TBC_DATA_IND);
+}
+
+static void dect_ccp_parse_tbc_data_ind(const struct dect_cell_handle *ch,
+					struct sk_buff *skb)
+{
+	const struct dect_cluster_handle *clh = ch->clh;
+	struct dect_tbc_id id;
+	u8 chan;
+
+	if (!dect_ccp_parse_tbc_msg(&id, &chan, skb))
+		return;
+	clh->ops->tbc_data_ind(clh, &id, chan, skb);
+}
+
+static void dect_ccp_send_tbc_dis_ind(const struct dect_cluster_handle *clh,
+				      const struct dect_tbc_id *id,
+				      enum dect_release_reasons reason)
+{
+	struct sk_buff *skb;
+
+	skb = dect_ccp_msg_alloc(sizeof(struct dect_ccp_tbc_msg));
+	if (skb == NULL)
+		return;// -ENOMEM;
+	dect_ccp_build_tbc_msg(skb, id, reason);
+
+	dect_ccp_send_to_cluster(clh, skb, DECT_CCP_TBC_DIS_IND);
+}
+
+static void dect_ccp_parse_tbc_dis_ind(const struct dect_cell_handle *ch,
+				       struct sk_buff *skb)
+{
+	const struct dect_cluster_handle *clh = ch->clh;
+	struct dect_tbc_id id;
+	u8 reason;
+
+	if (!dect_ccp_parse_tbc_msg(&id, &reason, skb))
+		return;
+	clh->ops->tbc_dis_ind(clh, &id, reason);
+}
+
+static void dect_ccp_rcv_cell_msg(void *handle, u32 portref,
+				  struct sk_buff **pskb,
+				  const u8 *data, u32 size)
+{
+	struct dect_cell_handle *ch = handle;
+	struct dect_ccp_msg_hdr *h;
+	struct sk_buff *skb = *pskb;
+
+	if (!pskb_may_pull(skb, sizeof(*h)))
+		return;
+	h = (struct dect_ccp_msg_hdr *)skb->data;
+	__skb_pull(skb, sizeof(*h));
+
+	switch (h->primitive) {
+	case DECT_CCP_MAC_INFO_IND:
+		return dect_ccp_parse_mac_info_ind(ch, skb);
+	case DECT_CCP_TBC_ESTABLISH_IND:
+		return dect_ccp_parse_tbc_establish_ind(ch, skb);
+	case DECT_CCP_TBC_ESTABLISH_CFM:
+		return dect_ccp_parse_tbc_establish_cfm(ch, skb);
+	case DECT_CCP_TBC_EVENT_IND:
+		return dect_ccp_parse_tbc_event_ind(ch, skb);
+	case DECT_CCP_TBC_DATA_IND:
+		return dect_ccp_parse_tbc_data_ind(ch, skb);
+	case DECT_CCP_TBC_DIS_IND:
+		return dect_ccp_parse_tbc_dis_ind(ch, skb);
+	}
+}
+
+static void dect_ccp_cl_disconnect(void *handle, u32 portref,
+				   struct sk_buff **pskb,
+				   const u8 *data, u32 size, int reason)
+{
+	struct dect_cell_handle *ch = handle;
+	struct dect_cluster_handle *clh = ch->clh;
+
+	pr_debug("cell disconnected\n");
+	clh->ops->unbind(clh, ch);
+	kfree(ch);
+}
+
+static const struct dect_csf_ops dect_ccp_csf_ops = {
+	.set_mode		= dect_ccp_send_set_mode,
+	.scan			= dect_ccp_send_scan,
+	.enable			= dect_ccp_send_enable,
+	.preload		= dect_ccp_send_preload,
+	.page_req		= dect_ccp_send_page_req,
+	.tbc_establish_req	= dect_ccp_send_tbc_establish_req,
+	.tbc_establish_res	= dect_ccp_send_tbc_establish_res,
+	.tbc_dis_req		= dect_ccp_send_tbc_dis_req,
+	.tbc_enc_key_req	= dect_ccp_send_tbc_enc_key_req,
+	.tbc_enc_eks_req	= dect_ccp_send_tbc_enc_eks_req,
+	.tbc_data_req		= dect_ccp_send_tbc_data_req,
+};
+
+static void dect_ccp_cl_named_msg(void *handle, u32 portref,
+				  struct sk_buff **pskb,
+				  const u8 *data, u32 size,
+				  u32 importance,
+				  const struct tipc_portid *source,
+				  const struct tipc_name_seq *dest)
+{
+	struct dect_cluster *cl = handle;
+	struct dect_cluster_handle *clh = &cl->handle;
+	struct dect_cell_handle *ch;
+	struct iovec ack = { NULL, 0};
+	int err;
+
+	ch = kzalloc(sizeof(*ch), GFP_ATOMIC);
+	if (ch == NULL)
+		goto err1;
+	ch->ops = &dect_ccp_csf_ops;
+
+	err = tipc_createport(cl->tipc_id, ch, TIPC_HIGH_IMPORTANCE,
+			      NULL, NULL, dect_ccp_cl_disconnect,
+			      NULL, NULL, dect_ccp_rcv_cell_msg, NULL,
+			      &ch->portref);
+	if (err < 0)
+		goto err2;
+
+	err = tipc_connect2port(ch->portref, source);
+	if (err < 0)
+		goto err3;
+
+	err = tipc_send(ch->portref, 1, &ack);
+	if (err < 0)
+		goto err3;
+
+	err = clh->ops->bind(clh, ch);
+	if (err < 0)
+		goto err4;
+	return;
+
+err4:
+	tipc_disconnect(ch->portref);
+err3:
+	tipc_deleteport(ch->portref);
+err2:
+	kfree(ch);
+err1:
+	return;
+}
+
+/**
+ * dect_ccp_cluster_init - Initialize a cluster control CCP instance
+ *
+ * @cl:		DECT cluster
+ */
+int dect_ccp_cluster_init(struct dect_cluster *cl)
+{
+	struct tipc_name_seq seq;
+	int err;
+
+	err = tipc_attach(&cl->tipc_id, NULL, NULL);
+	if (err < 0)
+		goto err1;
+
+	err = tipc_createport(cl->tipc_id, cl, TIPC_HIGH_IMPORTANCE,
+			      NULL, NULL, NULL, NULL, dect_ccp_cl_named_msg,
+			      NULL, NULL, &cl->tipc_portref);
+	if (err < 0)
+		goto err2;
+
+	seq.type  = DECT_CCP_TIPC_TYPE;
+	seq.lower = DECT_CCP_CLUSTER_PORT_BASE + cl->index;
+	seq.upper = DECT_CCP_CLUSTER_PORT_BASE + cl->index;
+	err = tipc_publish(cl->tipc_portref, TIPC_CLUSTER_SCOPE, &seq);
+	if (err < 0)
+		goto err3;
+	return 0;
+
+err3:
+	tipc_deleteport(cl->tipc_portref);
+err2:
+	tipc_detach(cl->tipc_id);
+err1:
+	return err;
+}
+
+void dect_ccp_cluster_shutdown(struct dect_cluster *cl)
+{
+	tipc_detach(cl->tipc_id);
+}
+
+static void dect_ccp_rcv_cluster_msg(void *handle, u32 portref,
+				     struct sk_buff **pskb,
+				     const u8 *data, u32 size)
+{
+	struct sk_buff *skb = *pskb;
+	struct dect_cell_handle *ch = handle;
+	struct dect_ccp_msg_hdr *h;
+
+	if (!pskb_may_pull(skb, sizeof(*h)))
+		return;
+	h = (struct dect_ccp_msg_hdr *)skb->data;
+	__skb_pull(skb, sizeof(*h));
+
+	switch (h->primitive) {
+	case DECT_CCP_SET_MODE:
+		return dect_ccp_parse_set_mode(ch, skb);
+	case DECT_CCP_SCAN:
+		return dect_ccp_parse_scan(ch, skb);
+	case DECT_CCP_ENABLE:
+		return dect_ccp_parse_enable(ch, skb);
+	case DECT_CCP_PRELOAD:
+		return dect_ccp_parse_preload(ch, skb);
+	case DECT_CCP_PAGE_REQ:
+		return dect_ccp_parse_page_req(ch, skb);
+	case DECT_CCP_TBC_ESTABLISH_REQ:
+		return dect_ccp_parse_tbc_establish_req(ch, skb);
+	case DECT_CCP_TBC_ESTABLISH_RES:
+		return dect_ccp_parse_tbc_establish_res(ch, skb);
+	case DECT_CCP_TBC_DIS_REQ:
+		return dect_ccp_parse_tbc_dis_req(ch, skb);
+	case DECT_CCP_TBC_ENC_KEY_REQ:
+		return dect_ccp_parse_tbc_enc_key_req(ch, skb);
+	case DECT_CCP_TBC_ENC_EKS_REQ:
+		return dect_ccp_parse_tbc_enc_eks_req(ch, skb);
+	}
+}
+
+static void dect_ccp_cluster_disconnect(void *handle, u32 portref,
+					struct sk_buff **pskb,
+					const u8 *data, u32 size, int reason)
+{
+	pr_debug("Cluster disconnected\n");
+#if 0
+	struct dect_cell_handle *clh = handle;
+
+	clh->ops->unbind(clh);
+#endif
+}
+
+static void dect_ccp_subscr_rcv(void *handle, u32 portref,
+				struct sk_buff **pskb,
+				const u8 *data, u32 size)
+{
+	struct dect_cell_handle *ch = handle;
+	struct dect_cluster_handle *clh = ch->clh;
+	struct sk_buff *skb = *pskb;
+	struct tipc_event *ev;
+	struct tipc_name name;
+	int err;
+
+	if (!pskb_may_pull(skb, sizeof(*ev)))
+		return;
+	ev = (struct tipc_event *)skb->data;
+
+	if (ev->event != TIPC_PUBLISHED)
+		return;
+
+	/* Connect to cluster */
+	err = tipc_createport(clh->tipc_id, ch, TIPC_HIGH_IMPORTANCE,
+			      NULL, NULL, dect_ccp_cluster_disconnect,
+			      NULL, NULL, dect_ccp_rcv_cluster_msg, NULL,
+			      &clh->portref);
+	if (err < 0)
+		goto err1;
+
+	name.type = DECT_CCP_TIPC_TYPE;
+	name.instance = DECT_CCP_CLUSTER_PORT_BASE + clh->index;
+	err = tipc_send2name(clh->portref, &name, 0, 0, NULL);
+	if (err < 0)
+		goto err2;
+	return;
+
+err2:
+	tipc_deleteport(clh->portref);
+err1:
+	return;
+}
+
+/**
+ * dect_ccp_cell_init - Initialize a cell CCP instance
+ *
+ * @cell:	DECT cell
+ */
+static int dect_ccp_bind_cell(struct dect_cluster_handle *clh,
+			      struct dect_cell_handle *ch)
+{
+	struct tipc_subscr subscr;
+	struct iovec iov = { &subscr, sizeof(subscr) };
+	struct tipc_name tname;
+	int err;
+
+	err = tipc_attach(&clh->tipc_id, NULL, NULL);
+	if (err < 0)
+		goto err1;
+	ch->clh = clh;
+
+	/* Connect to topology service and subscribe to cluster port */
+	err = tipc_createport(clh->tipc_id, ch, TIPC_CRITICAL_IMPORTANCE,
+			      NULL, NULL, NULL, NULL, NULL,
+			      dect_ccp_subscr_rcv, NULL, &clh->tportref);
+	if (err < 0)
+		goto err2;
+
+	subscr.seq.type = DECT_CCP_TIPC_TYPE;
+	subscr.seq.lower = DECT_CCP_CLUSTER_PORT_BASE + clh->index;
+	subscr.seq.upper = DECT_CCP_CLUSTER_PORT_BASE + clh->index;
+	subscr.timeout = TIPC_WAIT_FOREVER;
+	subscr.filter = TIPC_SUB_PORTS;
+	memset(&subscr.usr_handle, 0, sizeof(subscr.usr_handle));
+
+	tname.type = TIPC_TOP_SRV;
+	tname.instance = TIPC_TOP_SRV;
+
+	err = tipc_send2name(clh->tportref, &tname, 0, 1, &iov);
+	if (err < 0)
+		goto err3;
+	return 0;
+
+err3:
+	tipc_deleteport(clh->tportref);
+err2:
+	tipc_detach(clh->tipc_id);
+err1:
+	return err;
+
+}
+
+static void dect_ccp_unbind_cell(struct dect_cluster_handle *clh,
+				 struct dect_cell_handle *ch)
+{
+	tipc_detach(clh->tipc_id);
+}
+
+static void dect_ccp_send_bmc_page_ind(const struct dect_cluster_handle *clh,
+				       struct sk_buff *skb)
+{
+}
+
+static const struct dect_ccf_ops dect_ccp_ccf_ops = {
+	.bind			= dect_ccp_bind_cell,
+	.unbind			= dect_ccp_unbind_cell,
+	.scan_report		= dect_ccp_send_scan_report,
+	.mac_info_ind		= dect_ccp_send_mac_info_ind,
+	.tbc_establish_ind	= dect_ccp_send_tbc_establish_ind,
+	.tbc_establish_cfm	= dect_ccp_send_tbc_establish_cfm,
+	.tbc_event_ind		= dect_ccp_send_tbc_event_ind,
+	.tbc_dis_ind		= dect_ccp_send_tbc_dis_ind,
+	.tbc_data_ind		= dect_ccp_send_tbc_data_ind,
+	.bmc_page_ind		= dect_ccp_send_bmc_page_ind,
+};
+
+struct dect_cluster_handle *dect_ccp_cell_init(struct dect_cell *cell, u8 clindex)
+{
+	struct dect_cluster_handle *clh;
+
+	clh = kzalloc(sizeof(*clh), GFP_KERNEL);
+	if (clh == NULL)
+		return ERR_PTR(-ENOMEM);
+	clh->index = clindex;
+	clh->ops = &dect_ccp_ccf_ops;
+	return clh;
+}
diff --git a/net/dect/core.c b/net/dect/core.c
new file mode 100644
index 0000000..80a7dd8
--- /dev/null
+++ b/net/dect/core.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifdef CONFIG_DECT_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/notifier.h>
+#include <net/dect/dect.h>
+#include <net/dect/transceiver.h>
+
+static DEFINE_MUTEX(dect_cfg_mutex);
+
+void dect_lock(void)
+{
+	mutex_lock(&dect_cfg_mutex);
+}
+EXPORT_SYMBOL_GPL(dect_lock);
+
+void dect_unlock(void)
+{
+	mutex_unlock(&dect_cfg_mutex);
+}
+EXPORT_SYMBOL_GPL(dect_unlock);
+
+/*
+ * MAC layer timers
+ */
+
+#if 1
+#define timer_debug(name, base, fmt, args...) \
+	pr_debug("%s: %s %u.%.2u.%.2u: " fmt, name, \
+		 (base)->base == DECT_TIMER_TX ? "TX" : "RX", \
+		 base->mfn, base->framenum, base->slot, ## args)
+#else
+#define timer_debug(base, fmt, args...)
+#endif
+
+void __dect_run_timers(const char *name, struct dect_timer_base *base)
+{
+	struct dect_timer *t;
+
+	while (!list_empty(&base->timers)) {
+		t = list_first_entry(&base->timers, struct dect_timer, list);
+
+		if (dect_mfn_after(t->mfn, base->mfn) ||
+		    (t->mfn == base->mfn && t->frame > base->framenum) ||
+		    (t->mfn == base->mfn && t->frame == base->framenum &&
+		     t->slot > base->slot))
+			break;
+
+		timer_debug(name, base, "timer %p: %u.%u.%u\n",
+			    t, t->mfn, t->frame, t->slot);
+		list_del_init(&t->list);
+		t->cb.cb(t->obj, t->data);
+	}
+}
+EXPORT_SYMBOL_GPL(__dect_run_timers);
+
+/**
+ * dect_timer_add - (re)schedule a timer
+ *
+ * Frame numbers are relative to the current time, slot positions are absolute.
+ * A timer scheduled for (1, 2) will expire in slot 2 in the next frame.
+ *
+ * A frame number of zero will expire at the next occurence of the slot, which
+ * can be within the same frame in case the slot is not already in the past, or
+ * in the next frame in case it is.
+ */
+void __dect_timer_add(const char *name, struct dect_timer_base *base,
+		      struct dect_timer *timer, u32 frame, u8 slot)
+{
+	struct dect_timer *t;
+	u32 mfn;
+
+	if (frame == 0 && slot < base->slot)
+		frame++;
+	frame += base->framenum;
+	mfn = dect_mfn_add(base->mfn, frame / DECT_FRAMES_PER_MULTIFRAME);
+	frame %= DECT_FRAMES_PER_MULTIFRAME;
+
+	timer_debug(name, base, "timer %p: schedule for %u.%u.%u\n",
+		    timer, mfn, frame, slot);
+	if (!list_empty(&timer->list))
+		list_del(&timer->list);
+	list_for_each_entry(t, &base->timers, list) {
+		if (dect_mfn_after(t->mfn, mfn) ||
+		    (t->mfn == mfn && t->frame > frame) ||
+		    (t->mfn == mfn && t->frame == frame && t->slot > slot))
+			break;
+	}
+
+	timer->mfn   = mfn;
+	timer->frame = frame;
+	timer->slot  = slot;
+	list_add_tail(&timer->list, &t->list);
+}
+EXPORT_SYMBOL_GPL(__dect_timer_add);
+
+struct sk_buff *skb_append_frag(struct sk_buff *head, struct sk_buff *skb)
+{
+	struct sk_buff **pprev;
+
+	if (head == NULL)
+		return skb;
+
+	pprev = &skb_shinfo(head)->frag_list;
+	while (*pprev != NULL)
+		pprev = &(*pprev)->next;
+	*pprev = skb;
+
+	head->data_len += skb->len;
+	head->len += skb->len;
+	head->truesize += skb->truesize;
+	return head;
+}
+EXPORT_SYMBOL_GPL(skb_append_frag);
+
+unsigned int skb_queue_pull(struct sk_buff_head *list, unsigned int len)
+{
+	unsigned int pulled = 0;
+	unsigned long flags;
+	struct sk_buff *skb;
+
+	spin_lock_irqsave(&list->lock, flags);
+	while (len > pulled) {
+		skb = skb_peek(list);
+		if (skb == NULL)
+			break;
+		if (skb->len <= len) {
+			__skb_unlink(skb, list);
+			pulled += skb->len;
+			kfree_skb(skb);
+		} else {
+			__skb_pull(skb, len);
+			pulled += len;
+		}
+	}
+	spin_unlock_irqrestore(&list->lock, flags);
+	return pulled;
+}
+EXPORT_SYMBOL_GPL(skb_queue_pull);
+
+static int __init dect_module_init(void)
+{
+	int err;
+
+	err = dect_netlink_module_init();
+	if (err < 0)
+		goto err1;
+	err = dect_af_module_init();
+	if (err < 0)
+		goto err2;
+	return 0;
+
+err2:
+	dect_netlink_module_exit();
+err1:
+	return err;
+}
+
+static void __exit dect_module_exit(void)
+{
+	dect_af_module_exit();
+	dect_netlink_module_exit();
+}
+
+module_init(dect_module_init);
+module_exit(dect_module_exit);
+
+MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
+MODULE_DESCRIPTION("DECT protocol stack");
+MODULE_LICENSE("GPL");
diff --git a/net/dect/dect_netlink.c b/net/dect/dect_netlink.c
new file mode 100644
index 0000000..e1e0e69
--- /dev/null
+++ b/net/dect/dect_netlink.c
@@ -0,0 +1,144 @@
+/*
+ * DECT netlink control interface
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netlink.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/dect_netlink.h>
+#include <linux/dect.h>
+#include <net/netlink.h>
+#include <net/sock.h>
+#include <net/dect/dect.h>
+#include <net/dect/mac_csf.h>
+
+struct sock *dect_nlsk __read_mostly;
+EXPORT_SYMBOL_GPL(dect_nlsk);
+
+LIST_HEAD(dect_cluster_list);
+EXPORT_SYMBOL_GPL(dect_cluster_list);
+
+struct dect_cluster *dect_cluster_get_by_index(int index)
+{
+	struct dect_cluster *cl;
+
+	list_for_each_entry(cl, &dect_cluster_list, list) {
+		if (cl->index == index)
+			return cl;
+	}
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(dect_cluster_get_by_index);
+
+static const struct dect_netlink_handler *dect_dispatch[DECT_NR_MSGTYPES];
+
+void dect_netlink_register_handlers(const struct dect_netlink_handler *handler,
+				    unsigned int base, unsigned int n)
+{
+	unsigned int i;
+
+	dect_lock();
+	base -= DECT_MSG_BASE;
+	for (i = 0; i < n; i++)
+		dect_dispatch[base + i] = handler + i;
+	dect_unlock();
+}
+EXPORT_SYMBOL_GPL(dect_netlink_register_handlers);
+
+void dect_netlink_unregister_handlers(unsigned int base, unsigned int n)
+{
+	unsigned int i;
+
+	dect_lock();
+	base -= DECT_MSG_BASE;
+	for (i = 0; i < n; i++)
+		dect_dispatch[base + i] = NULL;
+	dect_unlock();
+}
+EXPORT_SYMBOL_GPL(dect_netlink_unregister_handlers);
+
+static int dect_netlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+	const struct dect_netlink_handler *link;
+	u16 type;
+	int err;
+
+	type = nlh->nlmsg_type;
+	if (type > DECT_MSG_MAX)
+		return -EINVAL;
+
+	link = dect_dispatch[type - DECT_MSG_BASE];
+	if (link == NULL) {
+#ifdef CONFIG_MODULES
+		dect_unlock();
+		switch (type) {
+		case DECT_NEW_TRANSCEIVER ... DECT_GET_CELL:
+			request_module("dect_csf");
+			break;
+		case DECT_NEW_CLUSTER ... DECT_LLME_MSG:
+			request_module("dect_ccf");
+			break;
+		}
+		dect_lock();
+
+		link = dect_dispatch[type - DECT_MSG_BASE];
+		if (link == NULL)
+#endif
+			return -EOPNOTSUPP;
+	}
+
+	/* dump and get requests don't require privileges */
+	if (link->dump == NULL && security_netlink_recv(skb, CAP_NET_ADMIN))
+		return -EPERM;
+
+	if (nlh->nlmsg_flags & NLM_F_DUMP) {
+		if (link->dump == NULL)
+			return -EOPNOTSUPP;
+		return netlink_dump_start(dect_nlsk, skb, nlh, link->dump,
+					  link->done);
+	} else {
+		struct nlattr *nla[link->maxtype + 1];
+
+		err = nlmsg_parse(nlh, sizeof(struct dectmsg), nla,
+				  link->maxtype, link->policy);
+		if (err < 0)
+			return err;
+		if (link->doit == NULL)
+			return -EOPNOTSUPP;
+		return link->doit(skb, nlh, (const struct nlattr **)nla);
+	}
+}
+
+static void dect_netlink_rcv(struct sk_buff *skb)
+{
+	dect_lock();
+	netlink_rcv_skb(skb, dect_netlink_rcv_msg);
+	dect_unlock();
+}
+
+int __init dect_netlink_module_init(void)
+{
+	struct sock *sk;
+
+	sk = netlink_kernel_create(&init_net, NETLINK_DECT, DECTNLGRP_MAX,
+				   dect_netlink_rcv, NULL, THIS_MODULE);
+	if (sk == NULL)
+		return -ENOMEM;
+	dect_nlsk = sk;
+	return 0;
+}
+
+void dect_netlink_module_exit(void)
+{
+	netlink_kernel_release(dect_nlsk);
+}
+
+MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_DECT);
diff --git a/net/dect/dlc.c b/net/dect/dlc.c
new file mode 100644
index 0000000..63d2235
--- /dev/null
+++ b/net/dect/dlc.c
@@ -0,0 +1,282 @@
+/*
+ * DECT DLC Layer
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifdef CONFIG_DECT_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+
+#define mc_debug(mc, fmt, args...) \
+	pr_debug("MC (MCEI %u/%s): " fmt, \
+		 (mc)->mcei, dect_mc_states[(mc)->state], ## args)
+
+static const char * const dect_mc_states[] = {
+	[DECT_MAC_CONN_CLOSED]		= "CLOSED",
+	[DECT_MAC_CONN_OPEN_PENDING]	= "OPEN_PENDING",
+	[DECT_MAC_CONN_OPEN]		= "OPEN",
+};
+
+static struct dect_mac_conn *
+dect_mac_conn_get_by_mcei(const struct dect_cluster *cl, u32 mcei)
+{
+	struct dect_mac_conn *mc;
+
+	list_for_each_entry(mc, &cl->mac_connections, list) {
+		if (mc->mcei == mcei)
+			return mc;
+	}
+	return NULL;
+}
+
+struct dect_mac_conn *
+dect_mac_conn_get_by_mci(const struct dect_cluster *cl, const struct dect_mci *mci)
+{
+	struct dect_mac_conn *mc;
+
+	list_for_each_entry(mc, &cl->mac_connections, list) {
+		if (!dect_ari_cmp(&mc->mci.ari, &mci->ari) &&
+		    !dect_pmid_cmp(&mc->mci.pmid, &mci->pmid) &&
+		    mc->mci.lcn == mci->lcn)
+			return mc;
+	}
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(dect_mac_conn_get_by_mci);
+
+void dect_dlc_mac_conn_destroy(struct dect_mac_conn *mc)
+{
+	mc_debug(mc, "destroy\n");
+	list_del(&mc->list);
+	kfree(mc);
+}
+
+void dect_dlc_mac_conn_bind(struct dect_mac_conn *mc)
+{
+	mc_debug(mc, "bind use %u\n", mc->use);
+	mc->use++;
+}
+EXPORT_SYMBOL_GPL(dect_dlc_mac_conn_bind);
+
+void dect_dlc_mac_conn_unbind(struct dect_mac_conn *mc)
+{
+	mc_debug(mc, "unbind use %u\n", mc->use);
+	if (--mc->use)
+		return;
+
+	if (mc->state == DECT_MAC_CONN_OPEN ||
+	    mc->state == DECT_MAC_CONN_OPEN_PENDING)
+		dect_mac_dis_req(mc->cl, mc->mcei);
+
+	dect_dlc_mac_conn_destroy(mc);
+}
+EXPORT_SYMBOL_GPL(dect_dlc_mac_conn_unbind);
+
+struct dect_mac_conn *dect_mac_conn_init(struct dect_cluster *cl,
+					 const struct dect_mci *mci,
+					 const struct dect_mbc_id *id)
+{
+	struct dect_mac_conn *mc;
+
+	mc = kzalloc(sizeof(*mc), GFP_ATOMIC);
+	if (mc == NULL)
+		return NULL;
+
+	mc->cl    = cl;
+	mc->mcei  = id != NULL ? id->mcei : dect_mbc_alloc_mcei(cl);
+	memcpy(&mc->mci, mci, sizeof(mc->mci));
+	mc->state = DECT_MAC_CONN_CLOSED;
+	mc_debug(mc, "init\n");
+
+	list_add_tail(&mc->list, &cl->mac_connections);
+	return mc;
+}
+
+static void dect_mac_conn_state_change(struct dect_mac_conn *mc,
+				       enum dect_mac_conn_states state)
+{
+	mc_debug(mc, "state change: %s (%u) -> %s (%u)\n",
+		 dect_mc_states[mc->state], mc->state,
+		 dect_mc_states[state], state);
+
+	mc->state = state;
+	dect_cplane_notify_state_change(mc);
+}
+
+int dect_dlc_mac_conn_establish(struct dect_mac_conn *mc)
+{
+	struct dect_mbc_id mid = {
+		.mcei		= mc->mcei,
+		.ari		= mc->mci.ari,
+		.pmid		= mc->mci.pmid,
+		.type		= DECT_MAC_CONN_BASIC,
+		.ecn		= mc->mci.lcn,
+	};
+	int err;
+
+	err = dect_mac_con_req(mc->cl, &mid);
+	if (err < 0)
+		return err;
+	dect_mac_conn_state_change(mc, DECT_MAC_CONN_OPEN_PENDING);
+	return 0;
+}
+
+int dect_mac_con_cfm(struct dect_cluster *cl, u32 mcei,
+		     enum dect_mac_service_types service)
+{
+	struct dect_mac_conn *mc;
+
+	mc = dect_mac_conn_get_by_mcei(cl, mcei);
+	if (WARN_ON(mc == NULL))
+		return -ENOENT;
+	mc->service = service;
+
+	mc_debug(mc, "MAC_CON-cfm\n");
+	dect_mac_conn_state_change(mc, DECT_MAC_CONN_OPEN);
+	return 0;
+}
+
+int dect_mac_con_ind(struct dect_cluster *cl, const struct dect_mbc_id *id,
+		     enum dect_mac_service_types service)
+{
+	struct dect_mac_conn *mc;
+	struct dect_mci mci = {
+		.ari		= id->ari,
+		.pmid		= id->pmid,
+		.lcn		= id->ecn & DECT_LCN_MASK,
+	};
+
+	mc = dect_mac_conn_init(cl, &mci, id);
+	if (mc == NULL)
+		return -ENOMEM;
+	mc->service = service;
+
+	mc_debug(mc, "MAC_CON-ind\n");
+	dect_mac_conn_state_change(mc, DECT_MAC_CONN_OPEN);
+	return 0;
+}
+
+int dect_dlc_mac_conn_enc_key_req(struct dect_mac_conn *mc, u64 ck)
+{
+	mc->ck = ck;
+	return dect_mac_enc_key_req(mc->cl, mc->mcei, ck);
+}
+
+int dect_dlc_mac_conn_enc_eks_req(struct dect_mac_conn *mc,
+				  enum dect_cipher_states status)
+{
+	return dect_mac_enc_eks_req(mc->cl, mc->mcei, status);
+}
+
+/* Encryption status change confirmation from CCF */
+void dect_mac_enc_eks_cfm(struct dect_cluster *cl, u32 mcei,
+			  enum dect_cipher_states status)
+
+{
+	struct dect_mac_conn *mc;
+
+	mc = dect_mac_conn_get_by_mcei(cl, mcei);
+	if (WARN_ON(mc == NULL))
+		return;
+	//dect_cplane_mac_enc_eks_ind(mc, status);
+}
+
+/* Encryption status change indication from CCF */
+void dect_mac_enc_eks_ind(struct dect_cluster *cl, u32 mcei,
+			  enum dect_cipher_states status)
+
+{
+	struct dect_mac_conn *mc;
+
+	mc = dect_mac_conn_get_by_mcei(cl, mcei);
+	if (WARN_ON(mc == NULL))
+		return;
+	mc_debug(mc, "MAC_ENC_EKS-ind: status: %u\n", status);
+	dect_cplane_mac_enc_eks_ind(mc, status);
+}
+
+/* Disconnection indication from CCF */
+int dect_mac_dis_ind(struct dect_cluster *cl, u32 mcei,
+		     enum dect_release_reasons reason)
+{
+	struct dect_mac_conn *mc;
+
+	mc = dect_mac_conn_get_by_mcei(cl, mcei);
+	if (WARN_ON(mc == NULL))
+		return -ENOENT;
+
+	mc_debug(mc, "MAC_DIS-ind: reason: %x\n", reason);
+	dect_mac_conn_state_change(mc, DECT_MAC_CONN_CLOSED);
+	/* If nothing is using the connection, release immediately */
+	if (mc->use == 0)
+		dect_dlc_mac_conn_destroy(mc);
+	else
+		dect_cplane_mac_dis_ind(mc, reason);
+	return 0;
+}
+
+/* Data indication from CCF */
+void dect_mac_co_data_ind(struct dect_cluster *cl, u32 mcei,
+			  enum dect_data_channels chan,
+			  struct sk_buff *skb)
+{
+	struct dect_mac_conn *mc;
+
+	mc = dect_mac_conn_get_by_mcei(cl, mcei);
+	if (WARN_ON(mc == NULL))
+		goto err;
+
+	mc_debug(mc, "MAC_CO_DATA-ind: chan: %u len: %u\n", chan, skb->len);
+	switch (chan) {
+	case DECT_MC_C_S:
+	case DECT_MC_C_F:
+		return dect_cplane_rcv(mc, chan, skb);
+	case DECT_MC_I_N:
+	case DECT_MC_I_P:
+		return dect_uplane_rcv(mc, chan, skb);
+	default:
+		goto err;
+	}
+err:
+	kfree_skb(skb);
+}
+
+/* Data-ready indication from CCF */
+struct sk_buff *dect_mac_co_dtr_ind(struct dect_cluster *cl, u32 mcei,
+				    enum dect_data_channels chan)
+{
+	struct dect_mac_conn *mc;
+
+	mc = dect_mac_conn_get_by_mcei(cl, mcei);
+	if (mc == NULL) {
+		if (net_ratelimit())
+			pr_debug("DLC: DTR no connection\n");
+		return NULL;
+	}
+
+	mc_debug(mc, "MAC_CO_DTR-ind: chan: %u\n", chan);
+	switch (chan) {
+	case DECT_MC_C_S:
+	case DECT_MC_C_F:
+		return dect_cplane_dtr(mc, chan);
+	case DECT_MC_I_N:
+	case DECT_MC_I_P:
+		return dect_uplane_dtr(mc, chan);
+	default:
+		return NULL;
+	}
+}
diff --git a/net/dect/dlc_b_sap.c b/net/dect/dlc_b_sap.c
new file mode 100644
index 0000000..aaabeae
--- /dev/null
+++ b/net/dect/dlc_b_sap.c
@@ -0,0 +1,278 @@
+/*
+ * DECT DLC B SAP sockets - DLC C-plane broadcast service access
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/dect.h>
+#include <net/sock.h>
+#include <net/dect/dect.h>
+
+static DEFINE_SPINLOCK(dect_bsap_lock);
+static HLIST_HEAD(dect_bsap_sockets);
+
+struct dect_bsap {
+	struct sock		sk;
+};
+
+static inline struct dect_bsap *dect_bsap(struct sock *sk)
+{
+	return (struct dect_bsap *)sk;
+}
+
+void dect_bsap_rcv(const struct dect_cluster *cl, struct sk_buff *skb)
+{
+	struct hlist_node *node;
+	struct sk_buff *skb2;
+	struct sock *sk, *prev = NULL;
+
+	spin_lock(&dect_bsap_lock);
+	sk_for_each(sk, node, &dect_bsap_sockets) {
+		if (sk->sk_bound_dev_if &&
+		    sk->sk_bound_dev_if != cl->index)
+			continue;
+
+		skb2 = skb_clone(skb, GFP_ATOMIC);
+		if (skb2 == NULL) {
+			sk->sk_err = -ENOMEM;
+			sk->sk_error_report(sk);
+			break;
+		}
+
+		if (prev != NULL) {
+			if (dect_sock_queue_rcv_skb(prev, skb2) < 0)
+				kfree_skb(skb2);
+		}
+		prev = sk;
+	}
+
+	if (prev == NULL || dect_sock_queue_rcv_skb(prev, skb) < 0)
+		kfree_skb(skb);
+
+	spin_unlock(&dect_bsap_lock);
+}
+
+static void dect_bsap_close(struct sock *sk, long timeout)
+{
+	sk_common_release(sk);
+}
+
+static int dect_bsap_bind(struct sock *sk, struct sockaddr *uaddr, int len)
+{
+	const struct sockaddr_dect *addr = (struct sockaddr_dect *)uaddr;
+	int err;
+
+	if (len < sizeof(*addr) || addr->dect_family != AF_DECT)
+		return -EINVAL;
+
+	if (addr->dect_index != 0 &&
+	    !dect_cluster_get_by_index(addr->dect_index))
+		return -ENODEV;
+
+	lock_sock(sk);
+	err = -EINVAL;
+	if (!sk_unhashed(sk))
+		goto out;
+
+	sk->sk_bound_dev_if = addr->dect_index;
+
+	spin_lock_bh(&dect_bsap_lock);
+	sk_add_node(sk, &dect_bsap_sockets);
+	spin_unlock_bh(&dect_bsap_lock);
+
+	err = 0;
+out:
+	release_sock(sk);
+	return err;
+}
+
+static void dect_bsap_unhash(struct sock *sk)
+{
+	if (sk_hashed(sk)) {
+		spin_lock_bh(&dect_bsap_lock);
+		sk_del_node_init(sk);
+		spin_unlock_bh(&dect_bsap_lock);
+	}
+}
+
+static int dect_bsap_getname(struct sock *sk, struct sockaddr *uaddr, int *len,
+			     int peer)
+{
+	struct sockaddr_dect *addr = (struct sockaddr_dect *)uaddr;
+
+	if (peer)
+		return -EOPNOTSUPP;
+
+	addr->dect_family = AF_DECT;
+	addr->dect_index  = sk->sk_bound_dev_if;
+	*len = sizeof(*addr);
+	return 0;
+}
+
+static int dect_bsap_recvmsg(struct kiocb *iocb, struct sock *sk,
+			     struct msghdr *msg, size_t len,
+			     int noblock, int flags, int *addrlen)
+{
+	struct sockaddr_dect *addr;
+	struct dect_bsap_auxdata aux;
+	struct sk_buff *skb;
+	size_t copied = 0;
+	int err;
+
+	if (flags & MSG_OOB)
+		return -EOPNOTSUPP;
+
+	skb = skb_recv_datagram(sk, flags, noblock, &err);
+	if (skb == NULL)
+		goto out;
+
+	//msg->msg_flags |= DECT_LB_CB(skb)->expedited ? MSG_OOB : 0;
+
+	copied = skb->len;
+	if (len < copied) {
+		msg->msg_flags |= MSG_TRUNC;
+		copied = len;
+	}
+
+	err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+	if (err < 0)
+		goto out_free;
+
+	if (msg->msg_name != NULL) {
+		addr = (struct sockaddr_dect *)msg->msg_name;
+		addr->dect_family = AF_DECT;
+		addr->dect_index = DECT_SK_CB(skb)->index;
+		msg->msg_namelen = sizeof(*addr);
+	}
+
+	sock_recv_timestamp(msg, sk, skb);
+
+	aux.long_page = DECT_BMC_CB(skb)->long_page;
+	put_cmsg(msg, SOL_DECT, DECT_BSAP_AUXDATA, sizeof(aux), &aux);
+
+	if (flags & MSG_TRUNC)
+		copied = skb->len;
+out_free:
+	skb_free_datagram(sk, skb);
+out:
+	return err ? : copied;
+}
+
+static int dect_bsap_sendmsg(struct kiocb *kiocb, struct sock *sk,
+			     struct msghdr *msg, size_t len)
+{
+	const struct sockaddr_dect *addr = msg->msg_name;
+	bool expedited = msg->msg_flags & MSG_OOB;
+	struct dect_cluster *cl;
+	struct sk_buff *skb;
+	struct cmsghdr *cmsg;
+	struct dect_bsap_auxdata *aux;
+	bool long_page = false;
+	int index;
+	int err;
+
+	if (msg->msg_namelen) {
+		if (addr->dect_family != AF_DECT)
+			return -EINVAL;
+		index = addr->dect_index;
+	} else
+		index = sk->sk_bound_dev_if;
+
+	/* Transmission is always in direction FP -> PP */
+	cl = dect_cluster_get_by_index(index);
+	if (cl == NULL)
+		return -ENODEV;
+	if (cl->mode != DECT_MODE_FP)
+		return -EOPNOTSUPP;
+
+	for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+		if (!CMSG_OK(msg, cmsg))
+			return -EINVAL;
+		if (cmsg->cmsg_level != SOL_DECT)
+			continue;
+
+		switch (cmsg->cmsg_type) {
+		case DECT_BSAP_AUXDATA:
+			if (cmsg->cmsg_len != CMSG_LEN(sizeof(*aux)))
+				return -EINVAL;
+			aux = (struct dect_bsap_auxdata *)CMSG_DATA(cmsg);
+			long_page = aux->long_page;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	/* Valid frame sizes are 3 bytes (short frame), 5 bytes (long frame)
+	 * or multiples of 5 bytes up to 30 bytes (extended frame). Extended
+	 * frames can not use expedited operation. */
+	if (len == DECT_LB_SHORT_FRAME_SIZE) {
+		if (long_page)
+			return -EINVAL;
+	} else if (len % DECT_LB_LONG_FRAME_SIZE == 0) {
+		if (len == 0 || len > DECT_LB_EXTENDED_FRAME_SIZE_MAX)
+			return -EMSGSIZE;
+		if (len > DECT_LB_LONG_FRAME_SIZE && !long_page)
+			return -EINVAL;
+		if (expedited)
+			return -EOPNOTSUPP;
+	} else
+		return -EINVAL;
+
+	skb = sock_alloc_send_skb(sk, len, msg->msg_flags & MSG_DONTWAIT, &err);
+	if (skb == NULL)
+		goto err1;
+	err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+	if (err < 0)
+		goto err2;
+	DECT_BMC_CB(skb)->long_page = long_page;
+	DECT_BMC_CB(skb)->fast_page = expedited;
+	dect_bmc_mac_page_req(cl, skb);
+	return len;
+
+err2:
+	kfree_skb(skb);
+err1:
+	return err;
+}
+
+static struct dect_proto dect_bsap_proto __read_mostly = {
+	.type		= SOCK_DGRAM,
+	.protocol	= DECT_B_SAP,
+	.capability	= CAP_NET_RAW,
+	.ops		= &dect_dgram_ops,
+	.proto.name	= "DECT_B_SAP",
+	.proto.owner	= THIS_MODULE,
+	.proto.obj_size	= sizeof(struct dect_bsap),
+	.proto.close	= dect_bsap_close,
+	.proto.bind	= dect_bsap_bind,
+	.proto.unhash	= dect_bsap_unhash,
+	.proto.recvmsg	= dect_bsap_recvmsg,
+	.proto.sendmsg	= dect_bsap_sendmsg,
+	.getname	= dect_bsap_getname,
+};
+
+int __init dect_bsap_module_init(void)
+{
+	return dect_proto_register(&dect_bsap_proto);
+}
+
+void dect_bsap_module_exit(void)
+{
+	dect_proto_unregister(&dect_bsap_proto);
+}
+
+MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
+MODULE_DESCRIPTION("DECT DLC B SAP sockets");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS_NET_PF_PROTO(PF_DECT, DECT_B_SAP);
diff --git a/net/dect/dlc_cplane.c b/net/dect/dlc_cplane.c
new file mode 100644
index 0000000..9365b9d
--- /dev/null
+++ b/net/dect/dlc_cplane.c
@@ -0,0 +1,981 @@
+/*
+ * DECT DLC C-plane
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifdef CONFIG_DECT_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+
+void dect_mac_page_ind(struct dect_cluster *cl, struct sk_buff *skb)
+{
+	dect_bsap_rcv(cl, skb);
+}
+
+static void dect_fa_parse_len(struct dect_fa_len *len, const struct sk_buff *skb)
+{
+	u8 l;
+
+	l = skb->data[DECT_FA_LI_OFF];
+	len->len  = (l & DECT_FA_LI_LENGTH_MASK) >> DECT_FA_LI_LENGTH_SHIFT;
+	len->more = (l & DECT_FA_LI_M_FLAG);
+}
+
+/*
+ * LAPC entity
+ */
+
+#define lapc_debug(lapc, fmt, args...) \
+	pr_debug("LAPC (MCEI: %u LLN: %u): " fmt, \
+		 (lapc)->lc->mc->mcei, (lapc)->dli.lln, ## args)
+
+static inline u8 lapc_seq_add(const struct dect_lapc *lapc, u8 s1, u8 s2)
+{
+	return (s1 + s2) & (lapc->mod - 1);
+}
+
+static inline bool dect_fa_seq_before(const struct dect_lapc *lapc, u8 s1, u8 s2)
+{
+	if (lapc->window == 1)
+		return s1 != s2;
+	else
+		return (s8)((s2 << 5) - (s1 << 5)) > 0;
+}
+
+static inline bool dect_fa_seq_after(const struct dect_lapc *lapc, u8 s1, u8 s2)
+{
+	return dect_fa_seq_before(lapc, s2, s1);
+}
+
+static void dect_lapc_transmit_skb(struct dect_lapc *lapc)
+{
+	struct sk_buff *skb = skb_peek(&lapc->retransmit_queue);
+	struct dect_fa_hdr *fh;
+
+	skb = skb_clone(skb, GFP_ATOMIC);
+	if (skb == NULL)
+		return;
+
+	fh = (struct dect_fa_hdr *)skb->data;
+	lapc_debug(lapc, "queue I-frame: v_a: %u v_r: %u v_s: %u "
+		   "len: %u addr: %02x ctrl: %02x\n", lapc->v_a, lapc->v_r,
+		   lapc->v_s, skb->len, fh->addr, fh->ctrl);
+	skb_queue_tail(&lapc->lc->txq, skb);
+}
+
+static void dect_lapc_error_report(struct dect_lapc *lapc, int err)
+{
+	struct sock *sk = lapc->sk;
+
+	lapc_debug(lapc, "socket error: %d\n", err);
+	sk->sk_err = err;
+	sk->sk_error_report(sk);
+}
+
+static void dect_lapc_state_change(struct dect_lapc *lapc, int state)
+{
+	struct sock *sk = lapc->sk;
+
+	lapc_debug(lapc, "socket state change: %d\n", state);
+	sk->sk_state = state;
+	sk->sk_state_change(sk);
+}
+
+/**
+ * dect_lapc_timeout - retransmission timer
+ *
+ * Handle missing acknowledgements:
+ *
+ * - If not already in timer recovery condition, enter it
+ * - otherwise add one to retransmission count
+ *
+ * If the retransmission count is below the maximum, restart the timer and
+ * send an "appropriate" S-frame acknowledgement or retransmit the last
+ * I-frame, in both cases with the poll bit set.
+ */
+static void dect_lapc_timeout(unsigned long data)
+{
+	struct dect_lapc *lapc = (struct dect_lapc *)data;
+
+	lapc_debug(lapc, "retransmission timer: cnt: %u\n", lapc->retransmit_cnt);
+	if (lapc->retransmit_cnt++ < DECT_LAPC_RETRANSMIT_MAX) {
+		dect_lapc_transmit_skb(lapc);
+		mod_timer(&lapc->timer, jiffies + DECT_LAPC_CLASS_A_ESTABLISH_TIMEOUT);
+	} else
+		dect_lapc_error_report(lapc, ETIMEDOUT);
+}
+
+static bool dect_lapc_done(const struct dect_lapc *lapc)
+{
+	return skb_queue_empty(&lapc->sk->sk_write_queue) &&
+	       skb_queue_empty(&lapc->retransmit_queue);
+}
+
+void dect_lapc_destroy(struct dect_lapc *lapc)
+{
+	lapc_debug(lapc, "destroy\n");
+
+	del_timer_sync(&lapc->timer);
+	skb_queue_purge(&lapc->retransmit_queue);
+	dect_lc_unbind(lapc->lc, lapc);
+	sock_put(lapc->sk);
+	kfree(lapc);
+}
+
+static void dect_lapc_reset(struct dect_lapc *lapc)
+{
+	lapc->nlf = true;
+	lapc->v_s = 0;
+	lapc->v_a = 0;
+	lapc->v_r = 0;
+}
+
+/**
+ * dect_lapc_init - initialize a new LAPC entity
+ */
+struct dect_lapc *dect_lapc_init(struct sock *sk, const struct dect_dli *dli,
+				 enum dect_sapis sapi, struct dect_lc *lc,
+				 gfp_t gfp)
+{
+	struct dect_lapc *lapc;
+
+	lapc = kzalloc(sizeof(*lapc), gfp);
+	if (lapc == NULL)
+		return NULL;
+
+	lapc->sk = sk;
+	sock_hold(sk);
+
+	memcpy(&lapc->dli, dli, sizeof(lapc->dli));
+	lapc->sapi = sapi;
+	lapc->state = DECT_LAPC_ULI;
+	skb_queue_head_init(&lapc->retransmit_queue);
+
+	lapc->lc = lc;
+	setup_timer(&lapc->timer, dect_lapc_timeout, (unsigned long)lapc);
+	lapc->cmd = (lc->mc->cl->mode == DECT_MODE_FP) ? true : false;
+
+	switch (lapc->dli.lln) {
+	case DECT_LLN_CLASS_U:
+		break;
+	case DECT_LLN_CLASS_A:
+		lapc->window = DECT_LAPC_CLASS_A_WINDOW;
+		lapc->mod = DECT_LAPC_CLASS_A_MOD;
+		break;
+	default:
+		lapc->window = DECT_LAPC_CLASS_B_INITIAL_WINDOW;
+		lapc->mod = DECT_LAPC_CLASS_B_MOD;
+		break;
+	}
+
+	dect_lapc_reset(lapc);
+
+	lapc_debug(lapc, "init\n");
+	return lapc;
+}
+
+#define DECT_FA_FRAME_RESERVE	16
+#define DECT_FA_FRAME_SPACE	16
+
+static struct sk_buff *dect_lapc_alloc_skb(struct dect_lapc *lapc)
+{
+	struct sk_buff *skb;
+
+	skb = alloc_skb(DECT_FA_FRAME_SPACE + DECT_FA_FRAME_RESERVE, GFP_ATOMIC);
+	if (skb == NULL)
+		return NULL;
+	skb_reset_mac_header(skb);
+	skb_reserve(skb, DECT_FA_FRAME_RESERVE);
+	skb_reserve(skb, DECT_FA_HDR_SIZE);
+	skb_reset_network_header(skb);
+	return skb;
+}
+
+static struct dect_fa_hdr *dect_prepare_fa_frame(const struct dect_lapc *lapc,
+						 bool command,
+						 struct sk_buff *skb)
+{
+	struct dect_fa_hdr *fh;
+	u8 ilen = skb->len;
+
+	fh = (struct dect_fa_hdr *)skb_push(skb, DECT_FA_HDR_SIZE);
+	fh->addr  = lapc->dli.lln << DECT_FA_ADDR_LLN_SHIFT;
+	fh->addr |= lapc->sapi << DECT_FA_ADDR_SAPI_SHIFT;
+	fh->addr |= DECT_FA_ADDR_RES_BIT;
+	fh->addr |= (command ? lapc->cmd : !lapc->cmd) ? DECT_FA_ADDR_CR_FLAG : 0;
+	fh->addr |= lapc->nlf ? DECT_FA_ADDR_NLF_FLAG : 0;
+	fh->ctrl  = 0;
+	fh->li    = ilen << DECT_FA_LI_LENGTH_SHIFT;
+	fh->li	 |= DECT_FA_LI_EXT_FLAG;
+	return fh;
+}
+
+static bool dect_lapc_send_iframe(struct dect_lapc *lapc, bool pf)
+{
+	struct dect_fa_hdr *fh;
+	struct sk_buff *skb;
+
+	/* Window size full? */
+	lapc_debug(lapc, "send I-frame: v_a: %u window: %u v_s: %u\n",
+		   lapc->v_a, lapc->window, lapc->v_s);
+	if (lapc_seq_add(lapc, lapc->v_a, lapc->window) == lapc->v_s)
+		return false;
+
+	/* Prepare a new I-frame */
+	skb = skb_dequeue(&lapc->sk->sk_write_queue);
+	if (skb == NULL)
+		return false;
+	fh = dect_prepare_fa_frame(lapc, true, skb);
+	fh->ctrl |= DECT_FA_CTRL_I_FMT_ID;
+	fh->ctrl |= lapc->v_r << DECT_FA_CTRL_I_NR_SHIFT;
+	fh->ctrl |= lapc->v_s << DECT_FA_CTRL_I_NS_SHIFT;
+	fh->ctrl |= pf ? DECT_FA_CTRL_I_P_FLAG : 0;
+
+	/* Append to retransmission queue and (re)start retransmission timer */
+	skb_queue_tail(&lapc->retransmit_queue, skb);
+	if (!timer_pending(&lapc->timer))
+		mod_timer(&lapc->timer, jiffies + DECT_LAPC_RETRANSMISSION_TIMEOUT);
+
+	lapc->v_s = lapc_seq_add(lapc, lapc->v_s, 1);
+
+	dect_lapc_transmit_skb(lapc);
+	return true;
+}
+
+/*
+ * Send a S-frame with the specified command. The command/response bit setting
+ * depends on the role of the LAPC, a PP uses 0 for commands and 1 for responses,
+ * a FT 1 for commands and 0 for responses.
+ */
+static bool dect_lapc_send_sframe(struct dect_lapc *lapc, u8 cr,
+				  bool command, bool pf)
+{
+	struct dect_fa_hdr *fh;
+	struct sk_buff *skb;
+
+	skb = dect_lapc_alloc_skb(lapc);
+	if (skb == NULL)
+		return false;
+
+	fh = dect_prepare_fa_frame(lapc, command, skb);
+	fh->ctrl |= DECT_FA_CTRL_S_FMT_ID;
+	fh->ctrl |= lapc->v_r << DECT_FA_CTRL_S_NR_SHIFT;
+	fh->ctrl |= cr;
+	fh->ctrl |= pf ? DECT_FA_CTRL_S_PF_FLAG : 0;
+
+	lapc_debug(lapc, "queue S-frame: v_r: %u len: %u addr: %02x ctrl: %02x\n",
+		   lapc->v_r, skb->len, fh->addr, fh->ctrl);
+	skb_queue_tail(&lapc->lc->txq, skb);
+
+	lapc->nlf = false;
+	return true;
+}
+
+/*
+ * Send an acknowledgement frame. Class B entities use RNR responses to indicate
+ * their status while busy. Otherwise an I-frame is used when data is available
+ * and a RR response frame otherwise.
+ */
+static void dect_lapc_send_ack(struct dect_lapc *lapc, bool pf)
+{
+	lapc_debug(lapc, "send ACK: I-frame present: %u\n",
+		   skb_peek(&lapc->sk->sk_write_queue) ? 1 : 0);
+	if (lapc->dli.lln != DECT_LLN_CLASS_A && lapc->busy)
+		dect_lapc_send_sframe(lapc, DECT_FA_CTRL_S_CR_RNR, false, false);
+	else if (!lapc->peer_busy && skb_peek(&lapc->sk->sk_write_queue))
+		dect_lapc_send_iframe(lapc, pf);
+	else
+		dect_lapc_send_sframe(lapc, DECT_FA_CTRL_S_CR_RR, false, pf);
+}
+
+static void dect_lapc_queue_data(struct dect_lapc *lapc, struct sk_buff *skb)
+{
+	struct dect_fa_hdr *fh = (struct dect_fa_hdr *)skb->data;
+
+	skb_pull(skb, DECT_FA_HDR_SIZE);
+	if (skb->len == 0) {
+		kfree_skb(skb);
+		return;
+	}
+	lapc_debug(lapc, "reassemble message: segment len: %u more: %u\n",
+		   skb->len, (fh->li & DECT_FA_LI_M_FLAG) ? 1 : 0);
+
+	lapc->rcv_head = skb_append_frag(lapc->rcv_head, skb);
+	if (!(fh->li & DECT_FA_LI_M_FLAG)) {
+		skb = lapc->rcv_head;
+		lapc->rcv_head = NULL;
+		lapc_debug(lapc, "reassembled message: len: %u\n", skb->len);
+		sock_queue_rcv_skb(lapc->sk, skb);
+	}
+}
+
+static bool dect_lapc_update_ack(struct dect_lapc *lapc, u8 seq)
+{
+	u8 v_a = lapc->v_a;
+
+	lapc_debug(lapc, "update ACK: v_a: %u v_s: %u seq: %u\n",
+		   lapc->v_a, lapc->v_s, seq);
+#if 0
+	lapc_debug(lapc, "seq %u after v_a %u: %u\n", seq, lapc->v_a,
+		   dect_fa_seq_after(lapc, seq, lapc->v_a));
+	lapc_debug(lapc, "v_s %u !after seq %u: %u\n", lapc->v_s, seq,
+		   !dect_fa_seq_after(lapc, lapc->v_s, seq));
+#endif
+
+	/* If all outstanding I-frames have been acknowledged, stop
+	 * retransmission timer, otherwise reset it.
+	 */
+	if (dect_fa_seq_after(lapc, seq, lapc->v_a) &&
+	    !dect_fa_seq_after(lapc, lapc->v_s, seq)) {
+		lapc->v_a = seq;
+		if (lapc->v_a == lapc->v_s) {
+			del_timer_sync(&lapc->timer);
+			lapc->retransmit_cnt = 0;
+		} else
+			mod_timer(&lapc->timer, jiffies + DECT_LAPC_RETRANSMISSION_TIMEOUT);
+	} else if (seq != lapc->v_a)
+		return false;
+
+	/* Purge acknowledged frames from transmit queue */
+	while (v_a != lapc->v_a) {
+		lapc_debug(lapc, "purge retransmit queue: seq: %u\n", v_a);
+		kfree_skb(skb_dequeue(&lapc->retransmit_queue));
+		v_a = lapc_seq_add(lapc, v_a, 1);
+	}
+
+	if (lapc->sk->sk_state == DECT_SK_RELEASE_PENDING &&
+	    dect_lapc_done(lapc)) {
+		dect_lapc_state_change(lapc, DECT_SK_RELEASED);
+		dect_lapc_destroy(lapc);
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Receive a Class A or Class B I-frame. Frames with valid sequence numbers
+ * are acknowledged and queued for segment reassembly. Invalid sequence
+ * numbers cause an ACK with the expected sequence number to be sent.
+ *
+ * Class B entities need to indicate their receiver busy status when busy or
+ * when explicitly polled.
+ */
+static void dect_lapc_rcv_iframe(struct dect_lapc *lapc, struct sk_buff *skb)
+{
+	struct dect_fa_hdr *fh = (struct dect_fa_hdr *)skb->data;
+	bool poll = false;
+	u8 n_s, n_r, res;
+
+	if (lapc->dli.lln == DECT_LLN_CLASS_U) {
+		kfree_skb(skb);
+		return;
+	}
+
+	if (fh->addr & DECT_FA_ADDR_NLF_FLAG)
+		dect_lapc_reset(lapc);
+
+	n_r = (fh->ctrl & DECT_FA_CTRL_I_NR_MASK) >> DECT_FA_CTRL_I_NR_SHIFT;
+	n_s = (fh->ctrl & DECT_FA_CTRL_I_NS_MASK) >> DECT_FA_CTRL_I_NS_SHIFT;
+	if (lapc->dli.lln != DECT_LLN_CLASS_A)
+		poll = fh->ctrl & DECT_FA_CTRL_I_P_FLAG;
+
+	lapc_debug(lapc, "receive I-frame: n_r: %u n_s: %u poll: %u\n",
+		   n_r, n_s, poll);
+	dect_lapc_update_ack(lapc, n_r);
+
+	/* While in receiver busy condition, all I-frames are dropped after
+	 * updating the acknowledgement number. In Class B mode receiver status
+	 * queries are still answered.
+	 */
+	if (lapc->busy) {
+		kfree_skb(skb);
+		if (poll)
+			goto poll;
+		return;
+	}
+
+	/* When the frame contains an invalid sequence number, send an
+	 * immediate ACK. */
+	if (n_s != lapc->v_r) {
+		lapc_debug(lapc, "invalid sequence number %u %u\n", n_s, lapc->v_r);
+		kfree_skb(skb);
+		goto ack;
+	}
+
+	lapc->v_r = lapc_seq_add(lapc, lapc->v_r, 1);
+	dect_lapc_queue_data(lapc, skb);
+	if (poll)
+		goto poll;
+ack:
+	return dect_lapc_send_ack(lapc, poll);
+
+poll:
+	res = lapc->busy ? DECT_FA_CTRL_S_CR_RNR : DECT_FA_CTRL_S_CR_RR;
+	dect_lapc_send_sframe(lapc, res, false, true);
+}
+
+static void dect_lapc_rcv_sframe(struct dect_lapc *lapc, struct sk_buff *skb)
+{
+	struct dect_fa_hdr *fh = (struct dect_fa_hdr *)skb->data;
+	bool pf;
+	u8 n_r;
+
+	n_r = (fh->ctrl & DECT_FA_CTRL_S_NR_MASK) >> DECT_FA_CTRL_S_NR_SHIFT;
+	pf  = (fh->ctrl & DECT_FA_CTRL_S_PF_FLAG);
+	lapc_debug(lapc, "receive S-frame: n_r: %u pf: %u\n", n_r, pf);
+
+	switch (fh->ctrl & DECT_FA_CTRL_S_CR_MASK) {
+	case DECT_FA_CTRL_S_CR_RR:
+		if (!dect_lapc_update_ack(lapc, n_r))
+			goto err;
+
+		if (lapc->lc->elapc == lapc) {
+			/* Connection establishment completed */
+			lapc_debug(lapc, "established\n");
+			lapc->lc->elapc = NULL;
+			del_timer_sync(&lapc->timer);
+			dect_lapc_state_change(lapc, DECT_SK_ESTABLISHED);
+		}
+
+		dect_lapc_send_iframe(lapc, pf);
+		break;
+	case DECT_FA_CTRL_S_CR_RNR:
+		/*
+		 * Note peer receiver busy condition. If it was a RNR command
+		 * with the P bit set to 1, send a RR response with the F bit
+		 * set to 1. If it was a RNR response with the F bit set to 1,
+		 * clear timer recovery condition and update V(S).
+		 */
+		lapc->peer_busy = true;
+
+		if (fh->addr & DECT_FA_ADDR_CR_FLAG && pf)
+			dect_lapc_send_sframe(lapc, DECT_FA_CTRL_S_CR_RR, true, true);
+		else if (!(fh->addr & DECT_FA_ADDR_CR_FLAG) && pf) {
+			del_timer_sync(&lapc->timer);
+			lapc->v_s = n_r;
+		}
+
+		dect_lapc_update_ack(lapc, n_r);
+		break;
+	case DECT_FA_CTRL_S_CR_REJ:
+		lapc->peer_busy = false;
+		lapc->v_s = n_r;
+		lapc->v_a = n_r;
+		del_timer_sync(&lapc->timer);
+		break;
+	default:
+		goto err;
+	}
+
+err:
+	kfree_skb(skb);
+}
+
+static void dect_lapc_rcv_uframe(struct dect_lapc *lapc, struct sk_buff *skb)
+{
+	struct dect_fa_hdr *fh = (struct dect_fa_hdr *)skb->data;
+	u8 pf, cr;
+
+	pf = (fh->ctrl & DECT_FA_CTRL_U_PF_FLAG);
+	cr = (fh->ctrl & DECT_FA_CTRL_U_U1_MASK) |
+	     (fh->ctrl & DECT_FA_CTRL_U_CR_MASK);
+
+	/* unnumbered information is only valid in class U mode */
+	if (cr == DECT_FA_CTRL_U_CR_UI) {
+		if (lapc->dli.lln != DECT_LLN_CLASS_U)
+			goto err;
+		lapc_debug(lapc, "queue UI message: len: %u\n", skb->len);
+		sock_queue_rcv_skb(lapc->sk, skb);
+		return;
+	}
+
+	/* the remaining commands/responses are only valid in class B mode */
+	if (lapc->dli.lln == DECT_LLN_CLASS_A)
+		goto err;
+
+	switch (cr) {
+	case DECT_FA_CTRL_U_CR_SABM:
+		break;
+	case DECT_FA_CTRL_U_CR_DM:
+		break;
+	case DECT_FA_CTRL_U_CR_DISC:
+		break;
+	case DECT_FA_CTRL_U_CR_UA:
+		break;
+	}
+
+err:
+	kfree_skb(skb);
+}
+
+static void dect_lapc_rcv(struct dect_lapc *lapc, struct sk_buff *skb)
+{
+	struct dect_fa_hdr *fh = (struct dect_fa_hdr *)skb->data;
+
+	if ((fh->ctrl & DECT_FA_CTRL_I_FMT_MASK) == DECT_FA_CTRL_I_FMT_ID)
+		return dect_lapc_rcv_iframe(lapc, skb);
+	else if ((fh->ctrl & DECT_FA_CTRL_S_FMT_MASK) == DECT_FA_CTRL_S_FMT_ID)
+		return dect_lapc_rcv_sframe(lapc, skb);
+	else if ((fh->ctrl & DECT_FA_CTRL_U_FMT_MASK) == DECT_FA_CTRL_U_FMT_ID)
+		return dect_lapc_rcv_uframe(lapc, skb);
+	else
+		kfree_skb(skb);
+}
+
+int dect_lapc_transmit(struct dect_lapc *lapc)
+{
+	dect_lapc_send_iframe(lapc, 0);
+	return 0;
+}
+
+int dect_lapc_establish(struct dect_lapc *lapc)
+{
+	struct sk_buff *skb;
+
+	lapc_debug(lapc, "establish\n");
+
+	/* Prepend zero-sized message to transmit queue to trigger connection
+	 * establishment.
+	 */
+	skb = dect_lapc_alloc_skb(lapc);
+	if (skb == NULL)
+		return -ENOMEM;
+	skb_queue_head(&lapc->sk->sk_write_queue, skb);
+
+	lapc->lc->elapc = lapc;
+	dect_lapc_send_iframe(lapc, lapc->dli.lln != DECT_LLN_CLASS_A);
+	lapc->nlf = false;
+
+	mod_timer(&lapc->timer, jiffies + DECT_LAPC_CLASS_A_ESTABLISH_TIMEOUT);
+	return 0;
+}
+
+/*
+ * Initiate link release.
+ */
+void dect_lapc_release(struct dect_lapc *lapc, bool normal)
+{
+	lapc_debug(lapc, "release: normal: %u\n", normal);
+	if (dect_lapc_done(lapc) || !normal) {
+		lapc->sk->sk_state = DECT_SK_RELEASED;
+		dect_lapc_destroy(lapc);
+	} else
+		dect_lapc_state_change(lapc, DECT_SK_RELEASE_PENDING);
+}
+
+/*
+ * Lc entity
+ *
+ * The Lc entity receives and transmits LAPC frames from/to the MAC layer.
+ *
+ * For transmission the frames are checksummed and fragmented into channel
+ * sized units. The channel is chosen before transmission of a new frame
+ * based on availability and demand. All fragments of one frame are
+ * transmitted in the chosen channel.
+ *
+ * Received fragments are resegmented and have their checksum validated,
+ * then routed to the LAPC entity associated with the logical link number.
+ */
+
+#define lc_debug(lc, fmt, args...) \
+	pr_debug("Lc (MCEI %u): " fmt, (lc)->mc->mcei, ## args)
+
+void dect_lc_destroy(struct dect_lc *lc)
+{
+	lc_debug(lc, "destroy\n");
+	dect_dlc_mac_conn_unbind(lc->mc);
+	kfree_skb(lc->rx_head);
+	kfree_skb(lc->tx_head);
+	__skb_queue_purge(&lc->txq);
+	kfree(lc);
+}
+
+static void dect_lc_put(struct dect_lc *lc)
+{
+	if (--lc->use > 0)
+		return;
+	dect_lc_destroy(lc);
+}
+
+static void dect_lc_hold(struct dect_lc *lc)
+{
+	lc->use++;
+}
+
+void dect_lc_unbind(struct dect_lc *lc, struct dect_lapc *lapc)
+{
+	lc_debug(lc, "unbind LLN: %u use: %u\n", lapc->dli.lln, lc->use);
+	if (WARN_ON(lc->lapcs[lapc->dli.lln] == NULL))
+		return;
+
+	lc->lapcs[lapc->dli.lln] = NULL;
+	dect_lc_put(lc);
+}
+
+void dect_lc_bind(struct dect_lc *lc, struct dect_lapc *lapc)
+{
+	lc_debug(lc, "bind LLN: %u use: %u\n", lapc->dli.lln, lc->use);
+
+	lc->lapcs[lapc->dli.lln] = lapc;
+	dect_lc_hold(lc);
+}
+
+struct dect_lc *dect_lc_init(struct dect_mac_conn *mc, gfp_t gfp)
+{
+	struct dect_lc *lc;
+
+	lc = kzalloc(sizeof(*lc), gfp);
+	if (lc == NULL)
+		return NULL;
+
+	lc->mc = mc;
+	dect_dlc_mac_conn_bind(mc);
+
+	lc_debug(lc, "init\n");
+	skb_queue_head_init(&lc->txq);
+	switch (mc->mci.pmid.type) {
+	case DECT_PMID_ASSIGNED:
+		lc->lsig = dect_build_pmid(&mc->mci.pmid);
+		break;
+	default:
+		lc->lsig = 0;
+		break;
+	}
+
+	return lc;
+}
+
+static void dect_fa_frame_csum(const struct dect_lc *lc, struct sk_buff *skb)
+{
+	u8 *data = skb->data;
+	unsigned int i;
+	u8 c0 = 0, c1 = 0;
+	u8 x, y;
+	u16 t;
+
+	data[skb->len - 2] = 0;
+	data[skb->len - 1] = 0;
+
+	for (i = 0; i < skb->len; i++) {
+		t = c0 + data[i];
+		c0 = (t & 0xffU) + ((t >> 8) & 0x1U);
+		t = c1 + c0;
+		c1 = (t & 0xffU) + ((t >> 8) & 0x1U);
+	}
+
+	t = c0 + (u8)~c1;
+	x = (t & 0xffU) + ((t >> 8) & 0x1U);
+
+	t = (u8)~c0 + (u8)~c0;
+	t = (t & 0xffU) + ((t >> 8) & 0x1U);
+	t += c1;
+	y = (t & 0xffU) + ((t >> 8) & 0x1U);
+
+	data[skb->len - 2] = x ^ (lc->lsig >> 8);
+	data[skb->len - 1] = y ^ (lc->lsig & 0xff);
+	lc_debug(lc, "checksum: lsig: %.4x x: %.2x y: %.2x\n",
+		 lc->lsig, x, y);
+}
+
+static bool dect_fa_frame_csum_verify(const struct dect_lc *lc,
+				      struct sk_buff *skb)
+{
+	u8 *data = skb->data;
+	unsigned int i;
+	u8 c0 = 0, c1 = 0;
+	u16 t;
+
+	data[skb->len - 2] ^= lc->lsig >> 8;
+	data[skb->len - 1] ^= lc->lsig & 0xff;
+
+	for (i = 0; i < skb->len; i++) {
+		t = c0 + data[i];
+		c0 = (t & 0xffU) + ((t >> 8) & 0x1U);
+		t = c1 + c0;
+		c1 = (t & 0xffU) + ((t >> 8) & 0x1U);
+	}
+
+	lc_debug(lc, "csum verify: lsig %.4x c0: %.2x c1: %.2x\n",
+		 lc->lsig, c0, c1);
+	return c0 == (u8)~0 && c1 == (u8)~0;
+}
+
+static const u8 channel_sdu_size[] = {
+	[DECT_MC_C_S]	= DECT_C_S_SDU_SIZE,
+	[DECT_MC_C_F]	= DECT_C_F_SDU_SIZE,
+};
+
+/*
+ * Prepare a DLC frame for transmission to the MAC layer. This involves
+ * checksumming the frame, selecting the logical channel for transmission
+ * and fragmenting it into units carried by the logical channel.
+ */
+static struct sk_buff *dect_lc_tx(struct dect_lc *lc)
+{
+	struct sk_buff *skb, *frag;
+	u8 *fill, fill_len;
+	u8 flen;
+
+	skb = lc->tx_head;
+	if (skb == NULL) {
+		skb = skb_dequeue(&lc->txq);
+		if (skb == NULL)
+			return NULL;
+		lc_debug(lc, "tx: begin new frame len: %u\n", skb->len);
+
+		flen = channel_sdu_size[DECT_MC_C_S];
+		fill_len = roundup(skb->len + DECT_FA_CSUM_SIZE, flen) -
+			   (skb->len + DECT_FA_CSUM_SIZE);
+		fill = skb_put(skb, fill_len);
+		memset(fill, DECT_FA_FILL_PATTERN, fill_len);
+
+		skb_put(skb, DECT_FA_CSUM_SIZE);
+		dect_fa_frame_csum(lc, skb);
+
+		lc->tx_head = skb;
+		lc->tx_len = flen;
+	}
+
+	/* Fragment into tx_len sized units */
+	if (skb->len > lc->tx_len) {
+		frag = skb_copy(skb, GFP_ATOMIC);
+		if (frag == NULL)
+			return NULL;
+		skb_trim(frag, lc->tx_len);
+		skb_pull(skb, lc->tx_len);
+	} else {
+		frag = lc->tx_head;
+		lc->tx_head = NULL;
+	}
+
+	lc_debug(lc, "tx: %sfragment len: %u\n",
+		 lc->tx_head ? "" : "last ", frag->len);
+	return frag;
+}
+
+static struct sk_buff *dect_lc_reassemble(struct dect_lc *lc,
+					  enum dect_data_channels chan,
+					  struct sk_buff *skb)
+{
+	struct dect_fa_len fl;
+	u8 flen, len;
+
+	if (lc->rx_head == NULL) {
+		dect_fa_parse_len(&fl, skb);
+		len = fl.len;
+		len += DECT_FA_HDR_SIZE + DECT_FA_CSUM_SIZE;
+
+		flen = channel_sdu_size[chan];
+		lc->rx_len = roundup(len, flen);
+		lc_debug(lc, "new SDU: len: %u flen: %u\n", len, flen);
+	}
+
+	lc->rx_head = skb_append_frag(lc->rx_head, skb);
+	skb = NULL;
+
+	if (lc->rx_head->len >= lc->rx_len) {
+		WARN_ON(lc->rx_head->len != lc->rx_len);
+		skb = lc->rx_head;
+		lc->rx_head = NULL;
+
+		if (skb_linearize(skb) < 0)
+			goto err;
+		if (!dect_fa_frame_csum_verify(lc, skb))
+			goto err;
+
+		/* Trim checksum and filling */
+		dect_fa_parse_len(&fl, skb);
+		skb_trim(skb, fl.len + DECT_FA_HDR_SIZE);
+		lc_debug(lc, "reassembled SDU: len: %u\n", skb->len);
+	}
+
+	return skb;
+
+err:
+	lc_debug(lc, "reassembly failed\n");
+	kfree_skb(skb);
+	return NULL;
+}
+
+static void dect_lc_rcv(struct dect_lc *lc, enum dect_data_channels chan,
+			struct sk_buff *skb)
+{
+	struct dect_fa_hdr *fh;
+	struct dect_lapc *lapc;
+	struct dect_dli dli;
+	enum dect_sapis sapi;
+
+	skb = dect_lc_reassemble(lc, chan, skb);
+	if (skb == NULL)
+		return;
+	fh = (struct dect_fa_hdr *)skb->data;
+
+	dli.lln = (fh->addr & DECT_FA_ADDR_LLN_MASK) >> DECT_FA_ADDR_LLN_SHIFT;
+	lc_debug(lc, "receive: LLN %u NLF %u SAPI %u\n",
+		 dli.lln, (fh->addr & DECT_FA_ADDR_NLF_FLAG) ? 1 : 0,
+		 (fh->addr & DECT_FA_ADDR_SAPI_MASK) >> DECT_FA_ADDR_SAPI_SHIFT);
+
+	if (lc->lapcs[dli.lln] != NULL)
+		return dect_lapc_rcv(lc->lapcs[dli.lln], skb);
+
+	/* Link establishment: new requests are only valid while no link
+	 * establishment is in progress.
+	 */
+	if (!(fh->addr & DECT_FA_ADDR_NLF_FLAG))
+		goto err;
+	if ((fh->ctrl & DECT_FA_CTRL_I_FMT_MASK) != DECT_FA_CTRL_I_FMT_ID)
+		goto err;
+	if (lc->elapc != NULL)
+		goto err;
+
+	sapi = (fh->addr & DECT_FA_ADDR_SAPI_MASK) >> DECT_FA_ADDR_SAPI_SHIFT;
+	if (sapi != DECT_SAPI_CO_SIGNALLING && sapi != DECT_SAPI_CL_SIGNALLING)
+		goto err;
+	memcpy(&dli.mci, &lc->mc->mci, sizeof(dli.mci));
+
+	lapc = dect_ssap_rcv_request(lc, &dli, sapi);
+	if (lapc == NULL)
+		goto err;
+	dect_lc_bind(lc, lapc);
+
+	return dect_lapc_rcv(lapc, skb);
+
+err:
+	lc_debug(lc, "packet ignored\n");
+	kfree_skb(skb);
+}
+
+void dect_cplane_rcv(struct dect_mac_conn *mc, enum dect_data_channels chan,
+		     struct sk_buff *skb)
+{
+	struct dect_lc *lc;
+
+	if (mc->lc == NULL) {
+		lc = dect_lc_init(mc, GFP_ATOMIC);
+		if (lc == NULL)
+			goto err;
+		mc->lc = lc;
+	}
+
+	lc_debug(mc->lc, "MAC_CO_DATA-ind: chan: %u len: %u\n", chan, skb->len);
+	return dect_lc_rcv(mc->lc, chan, skb);
+
+err:
+	kfree_skb(skb);
+}
+
+struct sk_buff *dect_cplane_dtr(struct dect_mac_conn *mc, enum dect_data_channels chan)
+{
+	struct dect_lc *lc;
+
+	lc = mc->lc;
+	if (lc == NULL)
+		return NULL;
+	lc_debug(lc, "MAC_CO_DTR-ind: chan: %u\n", chan);
+	return dect_lc_tx(lc);
+}
+
+void dect_cplane_notify_state_change(struct dect_mac_conn *mc)
+{
+	struct dect_lc *lc = mc->lc;
+	unsigned int i;
+
+	if (lc == NULL)
+		return;
+
+	lc_debug(lc, "mac conn state change: state: %u\n", mc->state);
+	switch (mc->state) {
+	// FIXME: this does not make sense for incoming connections
+	case DECT_MAC_CONN_OPEN_PENDING:
+		break;
+	case DECT_MAC_CONN_OPEN:
+		for (i = 0; i < ARRAY_SIZE(lc->lapcs); i++) {
+			if (lc->lapcs[i] == NULL)
+				continue;
+			dect_lapc_establish(lc->lapcs[i]);
+			break;
+		}
+		break;
+	case DECT_MAC_CONN_CLOSED:
+		break;
+	}
+}
+
+void dect_cplane_mac_dis_ind(const struct dect_mac_conn *mc,
+			     enum dect_release_reasons reason)
+{
+	struct dect_lc *lc = mc->lc;
+	unsigned int i;
+	int err;
+
+	if (lc == NULL)
+		return;
+
+	switch (reason) {
+	case DECT_REASON_BEARER_RELEASE:
+		err = 0;
+		break;
+	case DECT_REASON_BEARER_SETUP_OR_HANDOVER_FAILED:
+		err = EHOSTUNREACH;
+		break;
+	case DECT_REASON_TIMEOUT_LOST_HANDSHAKE:
+		err = ETIMEDOUT;
+		break;
+	default:
+		err = EIO;
+		break;
+	}
+
+	dect_lc_hold(lc);
+	for (i = 0; i < ARRAY_SIZE(lc->lapcs); i++) {
+		if (lc->lapcs[i] == NULL)
+			continue;
+		lc->lapcs[i]->sk->sk_state = DECT_SK_RELEASED;
+		dect_lapc_error_report(lc->lapcs[i], err);
+		dect_lapc_destroy(lc->lapcs[i]);
+	}
+	dect_lc_put(lc);
+}
+
+void dect_cplane_mac_enc_eks_ind(const struct dect_mac_conn *mc,
+				 enum dect_cipher_states status)
+{
+	struct dect_lc *lc = mc->lc;
+	struct dect_dl_encrypt enc;
+	struct sk_buff *skb, *nskb;
+	unsigned int i;
+
+	if (lc == NULL || lc->use == 0)
+		return;
+
+	enc.status = status;
+	skb = dect_alloc_notification(DECT_DL_ENCRYPT, &enc, sizeof(enc));
+
+	for (i = 0; i < ARRAY_SIZE(lc->lapcs); i++) {
+		if (lc->lapcs[i] == NULL)
+			continue;
+
+		nskb = skb ? skb_clone(skb, GFP_ATOMIC) : NULL;
+		if (nskb != NULL)
+			sock_queue_err_skb(lc->lapcs[i]->sk, nskb);
+		else
+			dect_lapc_error_report(lc->lapcs[i], ENOMEM);
+	}
+
+	kfree_skb(skb);
+}
diff --git a/net/dect/dlc_lu1_sap.c b/net/dect/dlc_lu1_sap.c
new file mode 100644
index 0000000..90f8392
--- /dev/null
+++ b/net/dect/dlc_lu1_sap.c
@@ -0,0 +1,474 @@
+/*
+ * DECT DLC LU1 SAP sockets
+ *
+ * Copyright (c) 2009-2010 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <net/sock.h>
+#include <net/dect/dect.h>
+#include <net/dect/transceiver.h>
+
+#define DECT_LU1_FRAME_NONE	255
+#define DECT_LU1_PREQUEUE_LEN	5
+
+#define lu1_debug(lu1, fmt, args...) \
+	pr_debug("LU1: rx_bytes: %u tx_bytes: %u " fmt, \
+		 (lu1)->qstats.rx_bytes, (lu1)->qstats.tx_bytes, \
+		 ## args)
+
+struct dect_lu1_sap {
+	struct sock			sk;
+	int				index;
+	struct dect_ulei		ulei;
+	struct dect_mac_conn		*mc;
+	u8				frame;
+	u8				slot;
+	struct sk_buff			*last;
+	struct dect_lux			lux;
+	struct dect_lu1_queue_stats	qstats;
+};
+
+/* Seamless handover slot offsets as per ETS 300 175-3 Annex F */
+static const u8 slot_offset_tbl[][DECT_HALF_FRAME_SIZE] = {
+	[DECT_FULL_SLOT] = {
+		[0]		= 0,
+		[1]		= 1,
+		[2]		= 3,
+		[3]		= 5,
+		[4]		= 6,
+		[5]		= 8,
+		[6]		= 10,
+		[7]		= 11,
+		[8]		= 13,
+		[9]		= 15,
+		[10]		= 16,
+		[11]		= 18,
+	},
+	[DECT_DOUBLE_SLOT] = {
+		[0]		= 0,
+		[2]		= 8,
+		[4]		= 16,
+		[6]		= 24,
+		[8]		= 32,
+		[10]		= 40,
+	},
+	[DECT_LONG_SLOT_640] = {
+		[0]		= 0,
+		[1]		= 3,
+		[2]		= 6,
+		[3]		= 10,
+		[4]		= 13,
+		[5]		= 16,
+		[6]		= 20,
+		[7]		= 23,
+		[8]		= 26,
+		[9]		= 30,
+		[10]		= 33,
+	},
+};
+
+static struct sk_buff *dect_lu1_dequeue(struct dect_lux *lux)
+{
+	struct dect_lu1_sap *lu1 = container_of(lux, struct dect_lu1_sap, lux);
+	struct dect_cluster *cl = lu1->mc->cl;
+	struct sock *sk = &lu1->sk;
+	struct sk_buff *skb, *clone, *head = NULL;
+	u8 frame, slot, off, last_off, need = 40;
+
+	/* Fill queue up to prequeue len before delivering the first frame */
+	if (lu1->frame == DECT_LU1_FRAME_NONE &&
+	    sk->sk_write_queue.qlen < DECT_LU1_PREQUEUE_LEN)
+		return NULL;
+
+	/* Calculate seamless handover data offset */
+	frame = __dect_framenum(&cl->timer_base[DECT_TIMER_TX]);
+	slot  = __dect_slotnum(&cl->timer_base[DECT_TIMER_TX]);
+	if (slot >= DECT_HALF_FRAME_SIZE)
+		slot -= DECT_HALF_FRAME_SIZE;
+
+	last_off = slot_offset_tbl[DECT_FULL_SLOT][lu1->slot];
+	off      = slot_offset_tbl[DECT_FULL_SLOT][slot];
+
+	if (off > last_off)
+		off -= last_off;
+	else
+		off += need - last_off;
+
+	/* Advance queue */
+	lu1_debug(lu1, "dequeue: slot: %u off: %u need: %u\n", slot, off, need);
+	if (lu1->frame != DECT_LU1_FRAME_NONE && lu1->frame != frame)
+		lu1->qstats.tx_bytes -= skb_queue_pull(&sk->sk_write_queue, off);
+
+	lu1->frame = frame;
+	lu1->slot  = slot;
+
+	/* Duplicate data from last frame on underflow */
+	if (lu1->qstats.tx_bytes < need && lu1->last) {
+		lu1->qstats.tx_underflow++;
+		skb = skb_clone(lu1->last, GFP_ATOMIC);
+		if (skb == NULL)
+			goto err;
+		skb_pull(skb, skb->len - (need - lu1->qstats.tx_bytes));
+
+		skb_queue_head(&sk->sk_write_queue, skb);
+		lu1->qstats.tx_bytes += skb->len;
+		lu1_debug(lu1, "fill: len: %u need: %u\n", skb->len, need);
+
+	}
+
+	skb = NULL;
+	while (need > 0) {
+		if (skb == NULL) {
+			skb = skb_peek(&sk->sk_write_queue);
+			if (skb == NULL)
+				goto underflow;
+			/* The head needs to be copied to avoid sharing the
+			 * frag list. */
+			clone = skb_copy(skb, GFP_ATOMIC);
+		} else {
+			if (skb_queue_is_last(&sk->sk_write_queue, skb))
+				goto underflow;
+			skb = skb->next;
+			clone = skb_clone(skb, GFP_ATOMIC);
+		}
+
+		if (clone == NULL)
+			goto err;
+
+		if (clone->len > need)
+			skb_trim(clone, need);
+		need -= clone->len;
+
+		head = skb_append_frag(head, clone);
+		lu1_debug(lu1, "dequeue: head: %u need: %u\n", head->len, need);
+	}
+
+	if (skb_linearize(head) < 0)
+		goto err;
+
+	kfree_skb(lu1->last);
+	lu1->last = skb_get(head);
+
+	lu1_debug(lu1, "dequeued: len: %u\n", head->len);
+	return head;
+
+underflow:
+	lu1->qstats.tx_underflow++;
+err:
+	kfree_skb(head);
+	lu1_debug(lu1, "dequeue: no frame available\n");
+	return NULL;
+}
+
+static void dect_lu1_enqueue(struct dect_lux *lux, struct sk_buff *skb)
+{
+	struct dect_lu1_sap *lu1 = container_of(lux, struct dect_lu1_sap, lux);
+	unsigned int len = skb->len;
+
+	if (sock_queue_rcv_skb(&lu1->sk, skb) < 0)
+		kfree_skb(skb);
+	else
+		lu1->qstats.rx_bytes += len;
+}
+
+static void dect_lu1_disconnect(struct dect_lux *lux)
+{
+	struct dect_lu1_sap *lu1 = container_of(lux, struct dect_lu1_sap, lux);
+	struct sock *sk = &lu1->sk;
+
+	sk->sk_state = DECT_SK_RELEASED;
+	sk->sk_err = ENETDOWN;
+	if (!sock_flag(sk, SOCK_DEAD))
+		sk->sk_error_report(sk);
+	lu1->mc->fbx = NULL;
+	dect_dlc_mac_conn_unbind(lu1->mc);
+	lu1->mc = NULL;
+}
+
+static const struct dect_lux_ops dect_lu1_ops = {
+	.dequeue	= dect_lu1_dequeue,
+	.enqueue	= dect_lu1_enqueue,
+	.disconnect	= dect_lu1_disconnect,
+};
+
+static inline struct dect_lu1_sap *dect_lu1_sap(struct sock *sk)
+{
+	return (struct dect_lu1_sap *)sk;
+}
+
+static int dect_parse_ulei(struct dect_ulei *ulei,
+			   const struct sockaddr_dect_lu *addr)
+{
+	if (dect_parse_ari(&ulei->mci.ari, (u64)addr->dect_ari << 24) == 0)
+		return -EINVAL;
+	dect_parse_pmid(&ulei->mci.pmid, addr->dect_pmid);
+	ulei->mci.lcn = addr->dect_lcn;
+	return 0;
+}
+
+static void dect_build_ulei(struct sockaddr_dect_lu *addr,
+			    const struct dect_ulei *ulei)
+{
+	addr->dect_family = AF_DECT;
+	addr->dect_pmid   = dect_build_pmid(&ulei->mci.pmid);
+	addr->dect_lcn    = ulei->mci.lcn;
+}
+
+static int dect_lu1_init(struct sock *sk)
+{
+	struct dect_lu1_sap *lu1 = dect_lu1_sap(sk);
+
+	sk->sk_state = DECT_SK_RELEASED;
+	lu1->frame   = DECT_LU1_FRAME_NONE;
+	return 0;
+}
+
+static void dect_lu1_close(struct sock *sk, long timeout)
+{
+	struct dect_lu1_sap *lu1 = dect_lu1_sap(sk);
+
+	if (sk->sk_state == DECT_SK_ESTABLISHED) {
+		lu1->mc->fbx = NULL;
+		dect_dlc_mac_conn_unbind(lu1->mc);
+		sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
+	}
+
+	__skb_queue_purge(&sk->sk_receive_queue);
+	__skb_queue_purge(&sk->sk_write_queue);
+	kfree_skb(lu1->last);
+
+	sock_orphan(sk);
+	sock_put(sk);
+}
+
+static int dect_lu1_getname(struct sock *sk, struct sockaddr *uaddr,
+			     int *len, int peer)
+{
+	struct sockaddr_dect_lu *addr = (struct sockaddr_dect_lu *)uaddr;
+	struct dect_lu1_sap *lu1 = dect_lu1_sap(sk);
+
+	if (peer)
+		return -EOPNOTSUPP;
+
+	addr->dect_index = lu1->index;
+	dect_build_ulei(addr, &lu1->ulei);
+	*len = sizeof(*addr);
+	return 0;
+}
+
+static int dect_lu1_connect(struct sock *sk, struct sockaddr *uaddr, int len)
+{
+	struct sockaddr_dect_lu *addr = (struct sockaddr_dect_lu *)uaddr;
+	struct dect_lu1_sap *lu1 = dect_lu1_sap(sk);
+	struct dect_cluster *cl;
+	struct dect_ulei ulei;
+	struct dect_mac_conn *mc;
+	int err;
+
+	err = dect_parse_ulei(&ulei, addr);
+	if (err < 0)
+		goto err1;
+
+	err = -ENODEV;
+	cl = dect_cluster_get_by_index(addr->dect_index);
+	if (cl == NULL)
+		goto err1;
+
+	err = -ENETDOWN;
+	mc = dect_mac_conn_get_by_mci(cl, &ulei.mci);
+	if (mc == NULL)
+		goto err1;
+	WARN_ON(mc->state == DECT_MAC_CONN_CLOSED);
+
+	err = -EBUSY;
+	if (mc->fbx != NULL)
+		goto err1;
+
+	memcpy(&lu1->ulei, &ulei, sizeof(lu1->ulei));
+
+	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
+	sk->sk_state = DECT_SK_ESTABLISHED;
+
+	lu1->lux.fbx.ops = &dect_fbn_ops;
+	lu1->lux.ops = &dect_lu1_ops;
+	lu1->mc = mc;
+	mc->fbx = &lu1->lux.fbx;
+	dect_dlc_mac_conn_bind(lu1->mc);
+	pr_debug("LU1: bound to MCEI %u\n", mc->mcei);
+	return 0;
+
+err1:
+	return err;
+}
+
+static int dect_lu1_getsockopt(struct sock *sk, int level, int optname,
+			       char __user *optval, int __user *optlen)
+{
+	struct dect_lu1_sap *lu1 = dect_lu1_sap(sk);
+	int len;
+
+	if (get_user(len, optlen))
+		return -EFAULT;
+	if (len < 0)
+		return -EINVAL;
+
+	switch (optname) {
+	case DECT_LU1_QUEUE_STATS:
+		if (len > sizeof(lu1->qstats))
+			len = sizeof(lu1->qstats);
+		if (put_user(len, optlen) ||
+		    copy_to_user(optval, &lu1->qstats, len))
+			return -EFAULT;
+		break;
+	default:
+		return -ENOPROTOOPT;
+	}
+
+	return 0;
+}
+
+static int dect_lu1_recvmsg(struct kiocb *iocb, struct sock *sk,
+			    struct msghdr *msg, size_t len,
+			    int noblock, int flags, int *addr_len)
+{
+	struct dect_lu1_sap *lu1 = dect_lu1_sap(sk);
+	struct sk_buff *skb;
+	size_t copied = 0, copy;
+	long timeo;
+	int err = 0;
+
+	if (flags & (MSG_OOB | MSG_TRUNC))
+		return -EOPNOTSUPP;
+
+	lock_sock(sk);
+
+	if (sk->sk_state != DECT_SK_ESTABLISHED) {
+		err = -ENOTCONN;
+		goto out;
+	}
+
+	timeo = sock_rcvtimeo(sk, noblock);
+
+	while (copied < len) {
+		skb = skb_peek(&sk->sk_receive_queue);
+		if (skb != NULL)
+			goto copy;
+
+		if (!timeo) {
+			err = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			err = sock_intr_errno(timeo);
+			break;
+		}
+
+		sk_wait_data(sk, &timeo);
+		continue;
+
+copy:
+		copy = len - copied;
+		if (copy > skb->len)
+			copy = skb->len;
+
+		err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copy);
+		if (err < 0)
+			break;
+		copied += copy;
+
+		if (copy < skb->len) {
+			__skb_pull(skb, copy);
+			break;
+		} else
+			sk_eat_skb(sk, skb, 0);
+	}
+
+out:
+	lu1->qstats.rx_bytes -= copied;
+	if (copied < len)
+		lu1->qstats.rx_underflow++;
+
+	release_sock(sk);
+	lu1_debug(lu1, "recvmsg: dequeued: %zu len: %zu\n", copied, len);
+	return copied ? : err;
+}
+
+static int dect_lu1_sendmsg(struct kiocb *kiocb, struct sock *sk,
+			    struct msghdr *msg, size_t len)
+{
+	struct dect_lu1_sap *lu1 = dect_lu1_sap(sk);
+	struct sk_buff *skb;
+	int err;
+
+	if (msg->msg_flags & MSG_OOB)
+		return -EOPNOTSUPP;
+
+	if (sk->sk_state != DECT_SK_ESTABLISHED)
+		return -ENOTCONN;
+
+	skb = sock_alloc_send_skb(sk, len, msg->msg_flags & MSG_DONTWAIT, &err);
+	if (skb == NULL)
+		goto err1;
+	err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+	if (err < 0)
+		goto err2;
+
+	skb_queue_tail(&sk->sk_write_queue, skb);
+	lu1->qstats.tx_bytes += len;
+	lu1_debug(lu1, "sendmsg: queued: %zu\n", len);
+	return len;
+
+err2:
+	kfree_skb(skb);
+err1:
+	return err;
+}
+
+static struct dect_proto dect_lu1_proto = {
+	.type			= SOCK_STREAM,
+	.protocol		= DECT_LU1_SAP,
+	.capability		= -1,
+	.ops			= &dect_stream_ops,
+	.proto.name		= "DECT_LU1_SAP",
+	.proto.owner		= THIS_MODULE,
+	.proto.obj_size		= sizeof(struct dect_lu1_sap),
+	.proto.init		= dect_lu1_init,
+	.proto.close		= dect_lu1_close,
+	.proto.connect		= dect_lu1_connect,
+	.proto.getsockopt	= dect_lu1_getsockopt,
+	.proto.recvmsg		= dect_lu1_recvmsg,
+	.proto.sendmsg		= dect_lu1_sendmsg,
+	.getname		= dect_lu1_getname,
+};
+
+int __init dect_lu1_sap_module_init(void)
+{
+	BUILD_BUG_ON(sizeof(struct sockaddr_dect_lu) >
+		     sizeof(struct sockaddr));
+	return dect_proto_register(&dect_lu1_proto);
+}
+
+void dect_lu1_sap_module_exit(void)
+{
+	dect_proto_unregister(&dect_lu1_proto);
+}
+
+module_init(dect_lu1_sap_module_init);
+module_exit(dect_lu1_sap_module_exit);
+
+MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
+MODULE_DESCRIPTION("DECT DLC LU1 SAP sockets");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS_NET_PF_PROTO(PF_DECT, DECT_LU1_SAP);
diff --git a/net/dect/dlc_s_sap.c b/net/dect/dlc_s_sap.c
new file mode 100644
index 0000000..b3481a2
--- /dev/null
+++ b/net/dect/dlc_s_sap.c
@@ -0,0 +1,659 @@
+/*
+ * DECT DLC S SAP sockets - DLC C-plane data link service access
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifdef CONFIG_DECT_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <asm/uaccess.h>
+#include <net/sock.h>
+#include <net/dect/dect.h>
+
+static DEFINE_SPINLOCK(dect_ssap_lock);
+static HLIST_HEAD(dect_ssap_sockets);
+static HLIST_HEAD(dect_ssap_listeners);
+
+struct dect_ssap {
+	struct dect_csk		csk;
+	int			index;
+	struct dect_dlei	dlei;
+	struct dect_lapc	*lapc;
+};
+
+static inline struct dect_ssap *dect_ssap(struct sock *sk)
+{
+	return (struct dect_ssap *)sk;
+}
+
+static int dect_parse_dlei(struct dect_dlei *dlei,
+			   const struct sockaddr_dect_ssap *addr)
+{
+	if (dect_parse_ari(&dlei->mci.ari, (u64)addr->dect_ari << 24) == 0)
+		return -EINVAL;
+	dect_parse_pmid(&dlei->mci.pmid, addr->dect_pmid);
+	dlei->mci.lcn = addr->dect_lcn;
+
+	dlei->lln = addr->dect_lln;
+	if (dlei->lln > DECT_LLN_UNASSIGNED &&
+	    dlei->lln != DECT_LLN_ANY)
+		return -EINVAL;
+
+	dlei->sapi = addr->dect_sapi;
+	switch (dlei->sapi) {
+	case DECT_SAPI_CO_SIGNALLING:
+	case DECT_SAPI_CL_SIGNALLING:
+	case DECT_SAPI_ANY:
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void dect_build_dlei(struct sockaddr_dect_ssap *addr,
+			    const struct dect_dlei *dlei)
+{
+	addr->dect_family = AF_DECT;
+	addr->dect_pmid = dect_build_pmid(&dlei->mci.pmid);
+	addr->dect_ari  = dect_build_ari(&dlei->mci.ari) >> 24;
+	addr->dect_lcn  = dlei->mci.lcn;
+	addr->dect_lln  = dlei->lln;
+	addr->dect_sapi = dlei->sapi;
+}
+
+static void dect_ssap_insert(struct sock *sk)
+{
+	sk_add_node(sk, &dect_ssap_sockets);
+	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
+}
+
+static void dect_ssap_unlink(struct sock *sk)
+{
+	if (sk_del_node_init(sk))
+		sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
+}
+
+static int dect_ssap_init(struct sock *sk)
+{
+	struct dect_ssap *ssap = dect_ssap(sk);
+
+	INIT_HLIST_HEAD(&ssap->csk.accept_queue);
+	return 0;
+}
+
+static struct sock *dect_ssap_acceptq_dequeue(struct dect_ssap *ssap)
+{
+	struct sock *sk;
+
+	if (hlist_empty(&ssap->csk.accept_queue))
+		return NULL;
+	sk = hlist_entry(ssap->csk.accept_queue.first, struct sock, sk_bind_node);
+	__sk_del_bind_node(sk);
+	sk_node_init(&sk->sk_bind_node);
+	sk_acceptq_removed(&ssap->csk.sk);
+	return sk;
+}
+
+static void dect_ssap_close(struct sock *sk, long timeout)
+{
+	struct dect_ssap *ssap = dect_ssap(sk);
+	struct sock *req;
+
+	pr_debug("close sock %p refcnt %u rmem %u wmem %u\n",
+		 sk, atomic_read(&sk->sk_refcnt),
+		 atomic_read(&sk->sk_rmem_alloc),
+		 atomic_read(&sk->sk_wmem_alloc));
+
+	spin_lock_bh(&dect_ssap_lock);
+	dect_ssap_unlink(sk);
+	spin_unlock_bh(&dect_ssap_lock);
+
+	if (sk->sk_state != DECT_SK_RELEASED && ssap->lapc != NULL)
+		dect_lapc_release(ssap->lapc, false);
+
+	if (!hlist_unhashed(&sk->sk_bind_node))
+		__sk_del_bind_node(sk);
+
+	while ((req = dect_ssap_acceptq_dequeue(ssap)) != NULL) {
+		spin_lock_bh(&dect_ssap_lock);
+		dect_ssap_unlink(req);
+		spin_unlock_bh(&dect_ssap_lock);
+
+		dect_lapc_release(dect_ssap(req)->lapc, false);
+	}
+
+	sk_common_release(sk);
+}
+
+static int dect_ssap_bind_conflict(int index, const struct dect_dlei *dlei)
+{
+	struct dect_ssap *ssap;
+	struct hlist_node *n;
+	struct sock *sk;
+
+	// FIXME: wildcards
+	sk_for_each(sk, n, &dect_ssap_sockets) {
+		ssap = dect_ssap(sk);
+		if (ssap->index == index &&
+		    !dect_ari_cmp(&ssap->dlei.mci.ari, &dlei->mci.ari) &&
+		    !dect_pmid_cmp(&ssap->dlei.mci.pmid, &dlei->mci.pmid) &&
+		    ssap->dlei.lln == dlei->lln &&
+		    ssap->dlei.sapi == dlei->sapi)
+			return -EADDRINUSE;
+	}
+	return 0;
+}
+
+static int dect_ssap_bind(struct sock *sk, struct sockaddr *uaddr, int len)
+{
+	struct sockaddr_dect_ssap *addr = (struct sockaddr_dect_ssap *)uaddr;
+	struct dect_ssap *ssap = dect_ssap(sk);
+	struct dect_dlei dlei;
+	int err;
+
+	if (len < sizeof(*addr) || addr->dect_family != AF_DECT)
+		return -EINVAL;
+
+	err = dect_parse_dlei(&dlei, addr);
+	if (err < 0)
+		return err;
+
+	lock_sock(sk);
+	spin_lock_bh(&dect_ssap_lock);
+
+	err = dect_ssap_bind_conflict(addr->dect_index, &dlei);
+	if (err < 0)
+		goto out;
+
+	ssap->index = addr->dect_index;
+	memcpy(&ssap->dlei, &dlei, sizeof(ssap->dlei));
+	dect_ssap_insert(sk);
+out:
+	spin_unlock_bh(&dect_ssap_lock);
+	release_sock(sk);
+	return err;
+}
+
+static struct dect_ssap *dect_ssap_lookup_listener(const struct dect_cluster *cl,
+						   const struct dect_dli *dli,
+						   enum dect_sapis sapi)
+{
+	struct dect_ssap *ssap;
+	struct hlist_node *n;
+	struct sock *sk;
+
+	pr_debug("lookup listener: lln %u sapi %u\n", dli->lln, sapi);
+	sk_for_each_bound(sk, n, &dect_ssap_listeners) {
+		ssap = dect_ssap(sk);
+		if (cl->index != ssap->index)
+			continue;
+#if 0
+		if (!dect_ari_cmp(&ssap->dlei.mci.ari, &dli->mci.ari))
+			continue;
+		if (!dect_pmid_cmp(&ssap->dlei.mci.pmid, &dli->mci.pmid))
+			continue;
+#endif
+		pr_debug("ssap: lln %u sapi %u\n", ssap->dlei.lln, ssap->dlei.sapi);
+		if (ssap->dlei.lln != DECT_LLN_ANY &&
+		    ssap->dlei.lln != dli->lln)
+			continue;
+		if (ssap->dlei.sapi != DECT_SAPI_ANY &&
+		    ssap->dlei.sapi != sapi)
+			continue;
+		return ssap;
+	}
+	return NULL;
+}
+
+struct dect_lapc *dect_ssap_rcv_request(struct dect_lc *lc,
+					const struct dect_dli *dli,
+					enum dect_sapis sapi)
+{
+	struct dect_ssap *ssap, *newssap;
+	struct sock *sk, *newsk;
+	struct dect_lapc *lapc = NULL;
+
+	spin_lock(&dect_ssap_lock);
+	ssap = dect_ssap_lookup_listener(lc->mc->cl, dli, sapi);
+	if (ssap == NULL)
+		goto out;
+
+	sk = &ssap->csk.sk;
+	if (sk_acceptq_is_full(sk))
+		goto out;
+
+	newsk = sk_alloc(&init_net, PF_DECT, GFP_ATOMIC, sk->sk_prot);
+	if (newsk == NULL)
+		goto out;
+
+	sock_init_data(NULL, newsk);
+	newsk->sk_type     = sk->sk_type;
+	newsk->sk_protocol = sk->sk_protocol;
+	newsk->sk_destruct = sk->sk_destruct;
+
+	lapc = dect_lapc_init(newsk, dli, sapi, lc, GFP_ATOMIC);
+	if (lapc == NULL)
+		goto err1;
+
+	newssap = dect_ssap(newsk);
+	newssap->index     = lc->mc->cl->index;
+	memcpy(&newssap->dlei.mci, &dli->mci, sizeof(newssap->dlei.mci));
+	newssap->dlei.lln  = dli->lln;
+	newssap->dlei.sapi = sapi;
+	newssap->lapc      = lapc;
+
+	newsk->sk_state = DECT_SK_ESTABLISHED;
+	dect_ssap_insert(newsk);
+	sk_add_bind_node(newsk, &ssap->csk.accept_queue);
+	sk_acceptq_added(sk);
+
+	sk->sk_state_change(sk);
+	sk->sk_data_ready(sk, 0);
+out:
+	spin_unlock(&dect_ssap_lock);
+	return lapc;
+
+err1:
+	sk_free(newsk);
+	goto out;
+}
+
+static void dect_ssap_hash(struct sock *sk)
+{
+	sk->sk_state = DECT_SK_LISTEN;
+
+	spin_lock_bh(&dect_ssap_lock);
+	sk_add_bind_node(sk, &dect_ssap_listeners);
+	spin_unlock_bh(&dect_ssap_lock);
+}
+
+static void dect_ssap_unhash(struct sock *sk)
+{
+	if (sk_hashed(sk)) {
+		spin_lock_bh(&dect_ssap_lock);
+		__sk_del_bind_node(sk);
+		spin_unlock_bh(&dect_ssap_lock);
+	}
+}
+
+static int dect_ssap_wait_req(struct sock *sk, int noblock)
+{
+	struct task_struct *tsk = current;
+	struct dect_ssap *ssap = dect_ssap(sk);
+	long timeo = sock_rcvtimeo(sk, noblock);
+
+	for (;;) {
+		DEFINE_WAIT(wait);
+
+		if (sk->sk_state != DECT_SK_LISTEN)
+			return -EINVAL;
+		if (!hlist_empty(&ssap->csk.accept_queue))
+			break;
+		if (!timeo)
+			return -EWOULDBLOCK;
+		if (signal_pending(tsk))
+			return sock_intr_errno(timeo);
+
+		prepare_to_wait_exclusive(sk_sleep(sk), &wait,
+					  TASK_INTERRUPTIBLE);
+		release_sock(sk);
+		timeo = schedule_timeout(timeo);
+		lock_sock(sk);
+		finish_wait(sk_sleep(sk), &wait);
+	}
+	return 0;
+}
+
+static struct sock *dect_ssap_accept(struct sock *sk, int flags, int *errp)
+{
+	struct dect_ssap *ssap = dect_ssap(sk);
+	struct sock *newsk;
+	int err;
+
+	lock_sock(sk);
+	err = dect_ssap_wait_req(sk, flags & O_NONBLOCK);
+	if (err < 0)
+		goto err;
+
+	newsk = dect_ssap_acceptq_dequeue(ssap);
+	release_sock(sk);
+
+	*errp = 0;
+	return newsk;
+
+err:
+	release_sock(sk);
+	*errp = err;
+	return NULL;
+}
+
+static int dect_ssap_connect(struct sock *sk, struct sockaddr *uaddr, int len)
+{
+	struct sockaddr_dect_ssap *addr = (struct sockaddr_dect_ssap *)uaddr;
+	struct dect_ssap *ssap = dect_ssap(sk);
+	struct dect_cluster *cl;
+	struct dect_dlei dlei;
+	struct dect_dli dli;
+	struct dect_lapc *lapc;
+	struct dect_lc *lc;
+	struct dect_mac_conn *mc;
+	bool new_mc = false, new_lc = false;
+	int err;
+
+	if (len < sizeof(*addr) || addr->dect_family != AF_DECT)
+		return -EINVAL;
+
+	err = dect_parse_dlei(&dlei, addr);
+	if (err < 0)
+		goto err1;
+
+	err = -ENODEV;
+	cl = dect_cluster_get_by_index(addr->dect_index);
+	if (cl == NULL)
+		goto err1;
+
+	/* The assignable class B LLNs may only be used for connections
+	 * originating from a PT. The unassigned LLN may be used by an FT
+	 * to request class B operation. Class A and U may be used by both.
+	 */
+	err = -EINVAL;
+	switch (dlei.lln) {
+	case DECT_LLN_ASSIGNABLE_MIN ... DECT_LLN_ASSIGNABLE_MAX:
+		if (cl->mode != DECT_MODE_PP)
+			goto err1;
+		break;
+	case DECT_LLN_UNASSIGNED:
+		if (cl->mode != DECT_MODE_FP)
+			goto err1;
+		break;
+	default:
+		break;
+	}
+
+	/* Lookup MAC connection and initiate new one if necessary */
+	err = -ENOMEM;
+	mc = dect_mac_conn_get_by_mci(cl, &dlei.mci);
+	if (mc == NULL) {
+		mc = dect_mac_conn_init(cl, &dlei.mci, NULL);
+		if (mc == NULL)
+			goto err1;
+		new_mc = true;
+		lc = NULL;
+	} else {
+		WARN_ON(mc->state == DECT_MAC_CONN_CLOSED);
+		lc = mc->lc;
+	}
+
+	/* Get Lc entity and verify LLN is available */
+	if (lc == NULL) {
+		lc = dect_lc_init(mc, GFP_KERNEL);
+		if (lc == NULL)
+			goto err2;
+		mc->lc = lc;
+		new_lc = true;
+	} else {
+		err = -EADDRINUSE;
+		if (lc->lapcs[dlei.lln] != NULL)
+			goto err2;
+	}
+
+	memcpy(&dli.mci, &dlei.mci, sizeof(dli.mci));
+	dli.lln = dlei.lln;
+
+	lapc = dect_lapc_init(sk, &dli, dlei.sapi, lc, GFP_KERNEL);
+	if (lapc == NULL)
+		goto err3;
+	ssap->lapc = lapc;
+
+	dect_lc_bind(lc, lapc);
+
+	if (new_mc)
+		err = dect_dlc_mac_conn_establish(mc);
+	else
+		err = dect_lapc_establish(lapc);
+
+	if (err < 0)
+		goto err4;
+
+	sk->sk_state = DECT_SK_ESTABLISH_PENDING;
+	return 0;
+
+err4:
+	dect_lapc_destroy(lapc);
+	/* Both will be release by dect_lapc_destroy() */
+	new_lc = false;
+	new_mc = false;
+err3:
+	if (new_lc)
+		dect_lc_destroy(lc);
+err2:
+	if (new_mc)
+		dect_dlc_mac_conn_destroy(mc);
+err1:
+	return err;
+}
+
+static int dect_ssap_getname(struct sock *sk, struct sockaddr *uaddr, int *len,
+			     int peer)
+{
+	struct sockaddr_dect_ssap *addr = (struct sockaddr_dect_ssap *)uaddr;
+	struct dect_ssap *ssap = dect_ssap(sk);
+
+#if 0
+	if (peer)
+		return -EOPNOTSUPP;
+#endif
+	addr->dect_index = ssap->index;
+	dect_build_dlei(addr, &ssap->dlei);
+	*len = sizeof(*addr);
+	return 0;
+}
+
+static void dect_ssap_shutdown(struct sock *sk, int how)
+{
+	struct dect_ssap *ssap = dect_ssap(sk);
+
+	if (!(how & SEND_SHUTDOWN))
+		return;
+
+	if (sk->sk_state == DECT_SK_ESTABLISHED)
+		dect_lapc_release(ssap->lapc, true);
+}
+
+static int dect_ssap_setsockopt(struct sock *sk, int level, int optname,
+				char __user *optval, unsigned int optlen)
+{
+	struct dect_ssap *ssap = dect_ssap(sk);
+	struct dect_dl_encrypt dle;
+	int err;
+	u64 ck;
+
+	switch (optname) {
+	case DECT_DL_ENC_KEY:
+		if (optlen != sizeof(ck))
+			return -EINVAL;
+		if (sk->sk_state != DECT_SK_ESTABLISH_PENDING &&
+		    sk->sk_state != DECT_SK_ESTABLISHED)
+			return -ENOTCONN;
+		if (copy_from_user(&ck, optval, sizeof(ck)))
+			return -EFAULT;
+		err = dect_dlc_mac_conn_enc_key_req(ssap->lapc->lc->mc, ck);
+		break;
+	case DECT_DL_ENCRYPT:
+		if (optlen != sizeof(dle))
+			return -EINVAL;
+		if (sk->sk_state != DECT_SK_ESTABLISHED)
+			return -ENOTCONN;
+		if (ssap->lapc->lc->mc->cl->mode != DECT_MODE_PP)
+			return -EOPNOTSUPP;
+		if (copy_from_user(&dle, optval, sizeof(dle)))
+			return -EFAULT;
+		err = dect_dlc_mac_conn_enc_eks_req(ssap->lapc->lc->mc,
+						    dle.status);
+		break;
+	default:
+		err = -ENOPROTOOPT;
+	}
+	return err;
+}
+
+static int dect_ssap_recvmsg(struct kiocb *iocb, struct sock *sk,
+			     struct msghdr *msg, size_t len,
+			     int noblock, int flags, int *addr_len)
+{
+	struct sockaddr_dect *addr;
+	struct sk_buff *skb, *eskb;
+	size_t copied = 0;
+	int err;
+
+	if (flags & MSG_OOB)
+		return -EOPNOTSUPP;
+
+	eskb = skb_dequeue(&sk->sk_error_queue);
+	skb = skb_recv_datagram(sk, flags, noblock, &err);
+	if (skb == NULL) {
+		if (eskb != NULL && err == -EAGAIN) {
+			err = 0;
+			goto out;
+		}
+		if (sk->sk_type == SOCK_SEQPACKET) {
+			lock_sock(sk);
+			if (sk->sk_state != DECT_SK_ESTABLISHED &&
+			    err == -EAGAIN)
+				err = -ENOTCONN;
+			release_sock(sk);
+		}
+		goto out;
+	}
+
+	copied = skb->len;
+	if (len < copied) {
+		msg->msg_flags |= MSG_TRUNC;
+		copied = len;
+	}
+
+	err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+	if (err < 0)
+		goto out_free;
+
+	if (msg->msg_name != NULL) {
+		addr = (struct sockaddr_dect *)msg->msg_name;
+		addr->dect_family = AF_DECT;
+		addr->dect_index = DECT_SK_CB(skb)->index;
+		msg->msg_namelen = sizeof(*addr);
+	}
+
+	sock_recv_timestamp(msg, sk, skb);
+
+	if (flags & MSG_TRUNC)
+		copied = skb->len;
+out_free:
+	skb_free_datagram(sk, skb);
+out:
+	if (eskb != NULL)
+		put_cmsg(msg, SOL_DECT, DECT_NOTIFY_CB(eskb)->type,
+			 eskb->len, eskb->data);
+	kfree_skb(eskb);
+
+	return err ? : copied;
+}
+
+static int dect_ssap_sendmsg(struct kiocb *kiocb, struct sock *sk,
+			     struct msghdr *msg, size_t len)
+{
+	struct dect_ssap *ssap = dect_ssap(sk);
+	struct sk_buff *skb;
+	long timeo;
+	int err;
+
+	if (msg->msg_flags & MSG_OOB)
+		return -EOPNOTSUPP;
+
+	if (len > DECT_FA_I_MAX)
+		return -EMSGSIZE;
+
+	lock_sock(sk);
+	if (sk->sk_type == SOCK_SEQPACKET) {
+		if (sk->sk_state != DECT_SK_ESTABLISHED) {
+			timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
+			err = sk_stream_wait_connect(sk, &timeo);
+			if (err < 0)
+				goto err1;
+		}
+	}
+
+	err = -EPIPE;
+	if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
+		goto err1;
+
+	skb = sock_alloc_send_skb(sk, len + 32, msg->msg_flags & MSG_DONTWAIT, &err);
+	if (skb == NULL)
+		goto err1;
+	skb_reset_mac_header(skb);
+	skb_reserve(skb, 16);
+	err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+	if (err < 0)
+		goto err2;
+
+	skb_queue_tail(&sk->sk_write_queue, skb);
+	release_sock(sk);
+
+	dect_lapc_transmit(ssap->lapc);
+	return len;
+
+err2:
+	kfree_skb(skb);
+err1:
+	err = sk_stream_error(sk, msg->msg_flags, err);
+	release_sock(sk);
+	return err;
+}
+
+static struct dect_proto dect_ssap_proto __read_mostly = {
+	.type			= SOCK_SEQPACKET,
+	.protocol		= DECT_S_SAP,
+	.capability		= CAP_NET_RAW,
+	.ops			= &dect_stream_ops,
+	.proto.name		= "DECT_S_SAP",
+	.proto.owner		= THIS_MODULE,
+	.proto.obj_size		= sizeof(struct dect_ssap),
+	.proto.init		= dect_ssap_init,
+	.proto.close		= dect_ssap_close,
+	.proto.bind		= dect_ssap_bind,
+	.proto.hash		= dect_ssap_hash,
+	.proto.unhash		= dect_ssap_unhash,
+	.proto.accept		= dect_ssap_accept,
+	.proto.connect		= dect_ssap_connect,
+	.proto.shutdown		= dect_ssap_shutdown,
+	.proto.setsockopt	= dect_ssap_setsockopt,
+	.proto.recvmsg		= dect_ssap_recvmsg,
+	.proto.sendmsg		= dect_ssap_sendmsg,
+	.getname		= dect_ssap_getname,
+};
+
+int __init dect_ssap_module_init(void)
+{
+	BUILD_BUG_ON(sizeof(struct sockaddr_dect_ssap) >
+		     sizeof(struct sockaddr));
+	return dect_proto_register(&dect_ssap_proto);
+}
+
+void dect_ssap_module_exit(void)
+{
+	dect_proto_unregister(&dect_ssap_proto);
+}
+
+MODULE_ALIAS_NET_PF_PROTO(PF_DECT, DECT_S_SAP);
diff --git a/net/dect/dlc_uplane.c b/net/dect/dlc_uplane.c
new file mode 100644
index 0000000..6d8d318
--- /dev/null
+++ b/net/dect/dlc_uplane.c
@@ -0,0 +1,86 @@
+/*
+ * DECT DLC U-plane
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifdef CONFIG_DECT_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+
+static struct sk_buff *dect_fbn_dequeue(struct dect_fbx *fbx)
+{
+	struct dect_lux *lux = container_of(fbx, struct dect_lux, fbx);
+
+	return lux->ops->dequeue(lux);
+}
+
+static void dect_fbn_enqueue(struct dect_fbx *fbx, struct sk_buff *skb)
+{
+	struct dect_lux *lux = container_of(fbx, struct dect_lux, fbx);
+
+	lux->ops->enqueue(lux, skb);
+}
+
+const struct dect_fbx_ops dect_fbn_ops = {
+	.dequeue	= dect_fbn_dequeue,
+	.enqueue	= dect_fbn_enqueue,
+};
+EXPORT_SYMBOL_GPL(dect_fbn_ops);
+
+struct sk_buff *dect_uplane_dtr(struct dect_mac_conn *mc, enum dect_data_channels chan)
+{
+	struct dect_fbx *fbx;
+
+	fbx = mc->fbx;
+	if (fbx == NULL)
+		return NULL;
+	return fbx->ops->dequeue(fbx);
+}
+
+void dect_uplane_rcv(struct dect_mac_conn *mc, enum dect_data_channels chan,
+		     struct sk_buff *skb)
+{
+	struct dect_fbx *fbx;
+
+	fbx = mc->fbx;
+	if (fbx == NULL)
+		goto err;
+	return fbx->ops->enqueue(fbx, skb);
+
+err:
+	kfree_skb(skb);
+}
+
+void dect_uplane_notify_state_change(struct dect_mac_conn *mc)
+{
+	struct dect_lux *lux;
+	struct dect_fbx *fbx;
+
+	fbx = mc->fbx;
+	if (fbx == NULL)
+		return;
+	lux = container_of(fbx, struct dect_lux, fbx);
+
+	switch (mc->state) {
+	case DECT_MAC_CONN_OPEN_PENDING:
+		break;
+	case DECT_MAC_CONN_OPEN:
+		break;
+	case DECT_MAC_CONN_CLOSED:
+		return lux->ops->disconnect(lux);
+	}
+}
diff --git a/net/dect/dsc.c b/net/dect/dsc.c
new file mode 100644
index 0000000..d3f597a0
--- /dev/null
+++ b/net/dect/dsc.c
@@ -0,0 +1,141 @@
+/*
+ * DECT Standard Cipher
+ *
+ * Copyright (c) 2010 Erik Tews <e_tews@cdc.informatik.tu-darmstadt.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <net/dect/dsc.h>
+
+#define R1_LEN			17
+#define R2_LEN			19
+#define R3_LEN			21
+#define R4_LEN			23
+
+#define MASK_R1			(65536 | 32)
+#define MASK_R2			(262144 | 4096 | 8 | 4)
+#define MASK_R3			(1048576 | 2)
+#define MASK_R4			(256 | 4194304)
+
+#define R1_CLOCKMASK		(1 << 8)
+#define R2_CLOCKMASK		(1 << 9)
+#define R3_CLOCKMASK		(1 << 10)
+
+#define R1_R4_CLOCKMASK		(1 << 0)
+#define R2_R4_CLOCKMASK		(1 << 1)
+#define R3_R4_CLOCKMASK		(1 << 2)
+
+static uint32_t clock(uint32_t lfsr, int length, uint32_t mask)
+{
+	return (lfsr >> 1) ^ (-(lfsr & 1) & mask);
+}
+
+static uint32_t combine(uint32_t comb, uint32_t r1, uint32_t r2, uint32_t r3)
+{
+	uint32_t c, x10, x11, x20, x21, x30, x31;
+
+	c = comb;
+	x10 = r1 & 1;
+	x11 = (r1 >> 1) & 1;
+	x20 = r2 & 1;
+	x21 = (r2 >> 1) & 1;
+	x30 = r3 & 1;
+	x31 = (r3 >> 1) & 1;
+
+	return (x11 & x10 & c) ^
+	       (x20 & x11 & x10) ^
+	       (x21 & x10 & c) ^
+	       (x21 & x20 & x10) ^
+	       (x30 & x10 & c) ^
+	       (x30 & x20 & x10) ^
+	       (x11 & c) ^
+	       (x11 & x10) ^
+	       (x20 & x11) ^
+	       (x30 & c) ^
+	       (x31 & c) ^
+	       (x31 & x10) ^
+	       (x21) ^
+	       (x31);
+}
+
+void dect_dsc_keystream(uint64_t iv, const uint8_t *key,
+			uint8_t *output, unsigned int len)
+{
+	uint8_t input[16];
+	uint32_t R1, R2, R3, R4, N1, N2, N3, COMB;
+	unsigned int i, keybit;
+
+	memset(output, 0, len);
+	input[0] = iv & 0xff;
+	input[1] = (iv >> 8) & 0xff;
+	input[2] = (iv >> 16) & 0xff;
+	input[3] = (iv >> 24) & 0xff;
+	input[4] = (iv >> 32) & 0xff;
+	for (i = 5; i < 8; i++)
+		input[i] = 0;
+	for (i = 0; i < 8; i++)
+		input[i + 8] = key[i];
+
+	R1 = R2 = R3 = R4 = COMB = 0;
+
+	/* load IV and KEY */
+	for (i = 0; i < 128; i++) {
+		keybit = (input[i / 8] >> ((i) & 7)) & 1;
+		R1 = clock(R1, R1_LEN, MASK_R1) ^ (keybit << (R1_LEN - 1));
+		R2 = clock(R2, R2_LEN, MASK_R2) ^ (keybit << (R2_LEN - 1));
+		R3 = clock(R3, R3_LEN, MASK_R3) ^ (keybit << (R3_LEN - 1));
+		R4 = clock(R4, R4_LEN, MASK_R4) ^ (keybit << (R4_LEN - 1));
+	}
+
+	for (i = 0; i < 40 + (len * 8); i++) {
+		N1 = R1;
+		N2 = R2;
+		N3 = R3;
+		COMB = combine(COMB, R1, R2, R3);
+		if (((R2 & R2_CLOCKMASK) != 0) ^
+		    ((R3 & R3_CLOCKMASK) != 0) ^
+		    ((R4 & R1_R4_CLOCKMASK) != 0))
+			N1 = clock(R1, R1_LEN, MASK_R1);
+		if (((R1 & R1_CLOCKMASK) != 0) ^
+		    ((R3 & R3_CLOCKMASK) != 0) ^
+		    ((R4 & R2_R4_CLOCKMASK) != 0))
+			N2 = clock(R2, R2_LEN, MASK_R2);
+		if (((R1 & R1_CLOCKMASK) != 0) ^
+		    ((R2 & R2_CLOCKMASK) != 0) ^
+		    ((R4 & R3_R4_CLOCKMASK) != 0))
+			N3 = clock(R3, R3_LEN, MASK_R3);
+
+		/* Check whether any registers are zero after 11 pre-ciphering
+		 * steps. If a register is all-zero after 11 steps, set input
+		 * bit to one (see U.S. patent 5608802)
+		 */
+		if (i == 11) {
+			if (!R1)
+				N1 ^= (1 << (R1_LEN - 1));
+			if (!R2)
+				N2 ^= (1 << (R2_LEN - 1));
+			if (!R3)
+				N3 ^= (1 << (R3_LEN - 1));
+			if (!R4)
+				R4 ^= (1 << (R4_LEN - 1));
+		}
+
+		N1 = clock(N1, R1_LEN, MASK_R1);
+		R1 = clock(N1, R1_LEN, MASK_R1);
+		N2 = clock(N2, R2_LEN, MASK_R2);
+		R2 = clock(N2, R2_LEN, MASK_R2);
+		N3 = clock(N3, R3_LEN, MASK_R3);
+		R3 = clock(N3, R3_LEN, MASK_R3);
+		R4 = clock(R4, R4_LEN, MASK_R4);
+		R4 = clock(R4, R4_LEN, MASK_R4);
+		R4 = clock(R4, R4_LEN, MASK_R4);
+
+		if (i >= 40)
+			output[(i - 40) / 8] |= ((COMB) << (7 - ((i - 40) & 7)));
+	}
+}
diff --git a/net/dect/identities.c b/net/dect/identities.c
new file mode 100644
index 0000000..64438ef
--- /dev/null
+++ b/net/dect/identities.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+
+bool dect_ari_masked_cmp(const struct dect_ari *a1, const struct dect_ari *a2,
+			 const struct dect_ari *m)
+{
+	/* An empty class mask implies a wildcard for everything */
+	if (!m->arc)
+		return false;
+	if (a1->arc != a2->arc)
+		return true;
+
+	if ((a1->fpn ^ a2->fpn) & m->fpn)
+		return true;
+
+	switch (a1->arc) {
+	case DECT_ARC_A:
+		return  ((a1->emc ^ a2->emc) & m->emc);
+	case DECT_ARC_B:
+		return (((a1->eic ^ a2->eic) & m->eic) |
+		        ((a1->fps ^ a2->fps) & m->fps));
+	case DECT_ARC_C:
+		return (((a1->poc ^ a2->poc) & m->poc) |
+		        ((a1->fps ^ a2->fps) & m->fps));
+	case DECT_ARC_D:
+		return  ((a1->gop ^ a2->gop) & m->gop);
+	case DECT_ARC_E:
+		return  ((a1->fil ^ a2->fil) & m->fil);
+	default:
+		return true;
+	}
+}
+EXPORT_SYMBOL_GPL(dect_ari_masked_cmp);;
+
+bool dect_ari_cmp(const struct dect_ari *a1, const struct dect_ari *a2)
+{
+	static const struct dect_ari mask = {
+		.arc = ~0,
+		.fpn = ~0,
+		.fps = ~0,
+		{ ~0 }
+	};
+	return dect_ari_masked_cmp(a1, a2, &mask);
+}
+EXPORT_SYMBOL_GPL(dect_ari_cmp);
+
+u8 dect_parse_ari(struct dect_ari *ari, u64 a)
+{
+	ari->arc = (a & DECT_ARI_ARC_MASK) >> DECT_ARI_ARC_SHIFT;
+	switch (ari->arc) {
+	case DECT_ARC_A:
+		ari->emc = (a & DECT_ARI_A_EMC_MASK) >> DECT_ARI_A_EMC_SHIFT;
+		ari->fpn = (a & DECT_ARI_A_FPN_MASK) >> DECT_ARI_A_FPN_SHIFT;
+		return DECT_ARC_A_LEN;
+	case DECT_ARC_B:
+		ari->eic = (a & DECT_ARI_B_EIC_MASK) >> DECT_ARI_B_EIC_SHIFT;
+		ari->fpn = (a & DECT_ARI_B_FPN_MASK) >> DECT_ARI_B_FPN_SHIFT;
+		ari->fps = (a & DECT_ARI_B_FPS_MASK) >> DECT_ARI_B_FPS_SHIFT;
+		return DECT_ARC_B_LEN;
+	case DECT_ARC_C:
+		ari->poc = (a & DECT_ARI_C_POC_MASK) >> DECT_ARI_C_POC_SHIFT;
+		ari->fpn = (a & DECT_ARI_C_FPN_MASK) >> DECT_ARI_C_FPN_SHIFT;
+		ari->fps = (a & DECT_ARI_C_FPS_MASK) >> DECT_ARI_C_FPS_SHIFT;
+		return DECT_ARC_C_LEN;
+	case DECT_ARC_D:
+		ari->gop = (a & DECT_ARI_D_GOP_MASK) >> DECT_ARI_D_GOP_SHIFT;
+		ari->fpn = (a & DECT_ARI_D_FPN_MASK) >> DECT_ARI_D_FPN_SHIFT;
+		return DECT_ARC_D_LEN;
+	case DECT_ARC_E:
+		ari->fil = (a & DECT_ARI_E_FIL_MASK) >> DECT_ARI_E_FIL_SHIFT;
+		ari->fpn = (a & DECT_ARI_E_FPN_MASK) >> DECT_ARI_E_FPN_SHIFT;
+		return DECT_ARC_E_LEN;
+	default:
+		return 0;
+	}
+}
+EXPORT_SYMBOL_GPL(dect_parse_ari);
+
+u64 dect_build_ari(const struct dect_ari *ari)
+{
+	u64 a = 0;
+
+	a |= (u64)ari->arc << DECT_ARI_ARC_SHIFT;
+	switch (ari->arc) {
+	case DECT_ARC_A:
+		a |= (u64)ari->emc << DECT_ARI_A_EMC_SHIFT;
+		a |= (u64)ari->fpn << DECT_ARI_A_FPN_SHIFT;
+		break;
+	case DECT_ARC_B:
+		a |= (u64)ari->eic << DECT_ARI_B_EIC_SHIFT;
+		a |= (u64)ari->fpn << DECT_ARI_B_FPN_SHIFT;
+		a |= (u64)ari->fps << DECT_ARI_B_FPS_SHIFT;
+		break;
+	case DECT_ARC_C:
+		a |= (u64)ari->poc << DECT_ARI_C_POC_SHIFT;
+		a |= (u64)ari->fpn << DECT_ARI_C_FPN_SHIFT;
+		a |= (u64)ari->fps << DECT_ARI_C_FPS_SHIFT;
+		break;
+	case DECT_ARC_D:
+		a |= (u64)ari->gop << DECT_ARI_D_GOP_SHIFT;
+		a |= (u64)ari->fpn << DECT_ARI_D_FPN_SHIFT;
+		break;
+	case DECT_ARC_E:
+		a |= (u64)ari->fil << DECT_ARI_E_FIL_SHIFT;
+		a |= (u64)ari->fpn << DECT_ARI_E_FPN_SHIFT;
+		break;
+	}
+	return a;
+}
+EXPORT_SYMBOL_GPL(dect_build_ari);
+
+u64 dect_build_rfpi(const struct dect_idi *idi)
+{
+	u64 t = 0;
+
+	t |= idi->e ? DECT_RFPI_E_FLAG : 0;
+	t |= dect_build_ari(&idi->pari) >> DECT_RFPI_ARI_SHIFT;
+	t |= (u64)idi->rpn << DECT_RFPI_RPN_SHIFT;
+	return t;
+}
+EXPORT_SYMBOL_GPL(dect_build_rfpi);
+
+bool dect_rfpi_cmp(const struct dect_idi *i1, const struct dect_idi *i2)
+{
+	return dect_ari_cmp(&i1->pari, &i2->pari) ||
+	       i1->rpn != i2->rpn ||
+	       i1->e   != i2->e;
+}
+EXPORT_SYMBOL_GPL(dect_rfpi_cmp);
+
+u16 dect_build_fmid(const struct dect_idi *idi)
+{
+	u64 rfpi;
+
+	rfpi = dect_build_rfpi(idi);
+	rfpi >>= (sizeof(rfpi) - DECT_NT_ID_RFPI_LEN - 1) * BITS_PER_BYTE;
+	return rfpi & DECT_FMID_MASK;
+}
+EXPORT_SYMBOL_GPL(dect_build_fmid);
+
+/*
+ * PMID (Portable MAC Identity)
+ */
+
+void dect_parse_pmid(struct dect_pmid *pmid, u32 p)
+{
+	if ((p & DECT_PMID_DEFAULT_ID_MASK) == DECT_PMID_DEFAULT_ID) {
+		pmid->type = DECT_PMID_DEFAULT;
+		pmid->num  = p & DECT_PMID_DEFAULT_NUM_MASK;
+	} else if ((p & DECT_PMID_EMERGENCY_ID_MASK) == DECT_PMID_EMERGENCY_ID) {
+		pmid->type = DECT_PMID_EMERGENCY;
+		pmid->tpui = p & DECT_PMID_EMERGENCY_TPUI_MASK;
+	} else {
+		pmid->type = DECT_PMID_ASSIGNED;
+		pmid->tpui = p & DECT_PMID_ASSIGNED_TPUI_MASK;
+	}
+}
+EXPORT_SYMBOL_GPL(dect_parse_pmid);
+
+u32 dect_build_pmid(const struct dect_pmid *pmid)
+{
+	u32 p = 0;
+
+	switch (pmid->type) {
+	case DECT_PMID_DEFAULT:
+		p |= DECT_PMID_DEFAULT_ID;
+		p |= pmid->tpui;
+		break;
+	case DECT_PMID_EMERGENCY:
+		p |= DECT_PMID_EMERGENCY_ID;
+		p |= pmid->tpui;
+		break;
+	case DECT_PMID_ASSIGNED:
+		p |= pmid->tpui;
+		break;
+	}
+	return p;
+}
+EXPORT_SYMBOL_GPL(dect_build_pmid);
+
+bool dect_pmid_cmp(const struct dect_pmid *p1, const struct dect_pmid *p2)
+{
+	return memcmp(p1, p2, sizeof(*p1));
+}
+EXPORT_SYMBOL(dect_pmid_cmp);
+
+/**
+ * dect_parse_mci - Extract the MCI elements from a packed MCI in a
+ * 		    struct sockaddr_dect_lu
+ *
+ * The packed MCI is build from ARI + PMID + LCN
+ */
+int dect_parse_mci(struct dect_mci *mci, u64 m)
+{
+	u32 p;
+	u8 len;
+
+	len = dect_parse_ari(&mci->ari, m);
+
+	len += DECT_PMID_SIZE;
+	p = (m >> (sizeof(m) * BITS_PER_BYTE - len)) & DECT_PMID_MASK;
+	dect_parse_pmid(&mci->pmid, p);
+
+	len += DECT_ECN_SIZE;
+	mci->lcn = (m >> (sizeof(m) * BITS_PER_BYTE - len)) & DECT_LCN_MASK;
+	return 0;
+}
+
+u64 dect_build_mci(const struct dect_mci *mci)
+{
+	return 0;
+}
diff --git a/net/dect/mac_ccf.c b/net/dect/mac_ccf.c
new file mode 100644
index 0000000..b94dc5d
--- /dev/null
+++ b/net/dect/mac_ccf.c
@@ -0,0 +1,1985 @@
+/*
+ * DECT MAC Cluster Control Functions
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifdef CONFIG_DECT_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+#include <net/dect/mac_ccf.h>
+#include <net/dect/mac_csf.h>
+#include <net/dect/ccp.h>
+
+static void dect_llme_scan_result_notify(const struct dect_cluster *cl,
+					 const struct dect_scan_result *res);
+static void dect_llme_mac_info_ind(const struct dect_cluster *cl,
+				   const struct dect_idi *idi,
+				   const struct dect_si *si);
+
+static struct dect_cluster *dect_cluster_get_by_name(const struct nlattr *nla)
+{
+	struct dect_cluster *cl;
+
+	list_for_each_entry(cl, &dect_cluster_list, list) {
+		if (!nla_strcmp(nla, cl->name))
+			return cl;
+	}
+	return NULL;
+}
+
+static struct dect_cluster *dect_cluster(const struct dect_cluster_handle *clh)
+{
+	return container_of(clh, struct dect_cluster, handle);
+}
+
+static struct dect_cell_handle *
+dect_cluster_get_cell_by_rpn(struct dect_cluster *cl, u8 rpn)
+{
+	struct dect_cell_handle *ch;
+
+	list_for_each_entry(ch, &cl->cells, list) {
+		if (ch->rpn == rpn)
+			return ch;
+	}
+	return NULL;
+}
+
+/*
+ * MAC CCF layer timers
+ */
+
+static u8 dect_framenum(const struct dect_cluster *cl, enum dect_timer_bases b)
+{
+	return __dect_framenum(&cl->timer_base[b]);
+}
+
+static void dect_run_timers(struct dect_cluster *cl, enum dect_timer_bases b)
+{
+	__dect_run_timers(cl->name, &cl->timer_base[b]);
+}
+
+static void dect_timer_base_update(struct dect_cluster *cl,
+				   enum dect_timer_bases base,
+				   u32 mfn, u8 framenum, u8 slot)
+{
+	cl->timer_base[base].mfn      = mfn;
+	cl->timer_base[base].framenum = framenum;
+	cl->timer_base[base].slot     = slot;
+}
+
+static void dect_timer_add(struct dect_cluster *cl, struct dect_timer *timer,
+			   enum dect_timer_bases b, u32 frame, u8 slot)
+{
+	timer->cluster = cl;
+	__dect_timer_add(cl->name, &cl->timer_base[b], timer, frame, slot);
+}
+
+static void dect_timer_setup(struct dect_timer *timer,
+			     void (*func)(struct dect_cluster *, void *),
+			     void *data)
+{
+	dect_timer_init(timer);
+	timer->cb.cluster = func;
+	timer->data       = data;
+}
+
+static void dect_ccf_time_ind(struct dect_cluster_handle *clh,
+			      enum dect_timer_bases base,
+			      u32 mfn, u8 framenum, u8 slot)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+
+	if (base == DECT_TIMER_TX) {
+		dect_timer_base_update(cl, base, mfn, framenum, slot);
+		dect_run_timers(cl, base);
+	} else {
+		dect_run_timers(cl, base);
+		dect_timer_base_update(cl, base, mfn, framenum, slot);
+	}
+}
+
+static void dect_scan_report(const struct dect_cluster_handle *clh,
+			     const struct dect_scan_result *res)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+
+	dect_llme_scan_result_notify(cl, res);
+}
+
+static void dect_mac_info_ind(const struct dect_cluster_handle *clh,
+			      const struct dect_idi *idi,
+			      const struct dect_si *si)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+
+	pr_debug("cl %p: MAC_INFO-ind: rpn: %u\n", cl, idi->rpn);
+	cl->si	= *si;
+	cl->rpn	= idi->rpn;
+
+	dect_llme_mac_info_ind(cl, idi, &cl->si);
+}
+
+/*
+ * Broadcast message control
+ */
+
+/**
+ * dect_bmc_mac_page_req - queue one segment of B_S channel data
+ *
+ * @cl:		DECT cluster
+ * @skb:	SDU
+ */
+void dect_bmc_mac_page_req(struct dect_cluster *cl, struct sk_buff *skb)
+{
+	const struct dect_cell_handle *ch, *prev = NULL;
+	struct sk_buff *clone;
+
+	BUG_ON(cl->mode != DECT_MODE_FP);
+
+	list_for_each_entry(ch, &cl->cells, list) {
+		if (prev != NULL) {
+			clone = skb_clone(skb, GFP_ATOMIC);
+			if (clone != NULL)
+				prev->ops->page_req(prev, clone);
+		}
+		prev = ch;
+	}
+	if (prev != NULL)
+		prev->ops->page_req(prev, skb);
+}
+
+static void dect_bmc_page_ind(const struct dect_cluster_handle *clh,
+			      struct sk_buff *skb)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+
+	return dect_mac_page_ind(cl, skb);
+}
+
+/*
+ * Multi-Bearer Control
+ */
+
+#define mbc_debug(mbc, fmt, args...) \
+	pr_debug("MBC (MCEI %u/%s): " fmt, \
+		 (mbc)->id.mcei, dect_mbc_states[(mbc)->state], ## args);
+
+static const char * const dect_mbc_states[] = {
+	[DECT_MBC_NONE]		= "NONE",
+	[DECT_MBC_INITIATED]	= "INITIATED",
+	[DECT_MBC_ESTABLISHED]	= "ESTABLISHED",
+	[DECT_MBC_RELEASED]	= "RELEASED",
+};
+
+static void dect_mbc_hold(struct dect_mbc *mbc)
+{
+	mbc->refcnt++;
+}
+
+static void dect_mbc_put(struct dect_mbc *mbc)
+{
+	if (--mbc->refcnt > 0)
+		return;
+	kfree(mbc);
+}
+
+static struct dect_tb *dect_mbc_tb_get_by_tbei(const struct dect_mbc *mbc,
+					       const struct dect_tbc_id *id)
+{
+	struct dect_tb *tb;
+
+	list_for_each_entry(tb, &mbc->tbs, list) {
+		if (tb->id.tbei == id->tbei)
+			return tb;
+	}
+	return NULL;
+}
+
+static struct dect_mbc *dect_mbc_get_by_tbc_id(const struct dect_cluster *cl,
+					       const struct dect_tbc_id *id)
+{
+	struct dect_mbc *mbc;
+
+	list_for_each_entry(mbc, &cl->mbcs, list) {
+		if (!memcmp(&mbc->id.ari, &id->ari, sizeof(id->ari)) &&
+		    !memcmp(&mbc->id.pmid, &id->pmid, sizeof(id->pmid)) &&
+		    mbc->id.ecn == id->ecn)
+			return mbc;
+	}
+	return NULL;
+}
+
+static struct dect_mbc *dect_mbc_get_by_mcei(const struct dect_cluster *cl, u32 mcei)
+{
+	struct dect_mbc *mbc;
+
+	list_for_each_entry(mbc, &cl->mbcs, list) {
+		if (mbc->id.mcei == mcei)
+			return mbc;
+	}
+	return NULL;
+}
+
+u32 dect_mbc_alloc_mcei(struct dect_cluster *cl)
+{
+	u32 mcei;
+
+	while (1) {
+		mcei = ++cl->mcei_rover;
+		if (mcei == 0)
+			continue;
+		if (dect_mbc_get_by_mcei(cl, mcei))
+			continue;
+		return mcei;
+	}
+}
+
+static bool dect_ct_tail_allowed(const struct dect_cluster *cl, u8 framenum)
+{
+	if (cl->mode == DECT_MODE_FP)
+		return (framenum & 0x1) == 0x1;
+	else
+		return (framenum & 0x1) == 0x0;
+}
+
+/*
+ * MBC normal receive half frame timer:
+ *
+ * Deliver received data segments to the DLC at half frame boundaries.
+ * Data is delivered for the following channels:
+ *
+ * - C_S after an ARQ window
+ * - I_N normal delay
+ *
+ * Additionally in half frames that end an ARQ window, acknowledgment
+ * of C_S segment reception of the preceeding transmit half frame is
+ * verified.
+ */
+static void dect_mbc_normal_rx_timer(struct dect_cluster *cl, void *data)
+{
+	struct dect_mbc *mbc = data;
+	struct dect_tb *tb;
+	struct sk_buff *skb;
+
+	mbc_debug(mbc, "Normal RX timer\n");
+	dect_mbc_hold(mbc);
+
+	if (mbc->cs_rx_skb != NULL) {
+		skb = mbc->cs_rx_skb;
+		mbc->cs_rx_skb = NULL;
+		dect_mac_co_data_ind(cl, mbc->id.mcei, DECT_MC_C_S, skb);
+
+		/* C-channel reception might trigger release of the MBC in case
+		 * it acknowledges the last outstanding LAPC I-frame. */
+		if (mbc->state == DECT_MBC_RELEASED)
+			goto out;
+	}
+
+	if (mbc->cs_tx_ok && mbc->cs_rx_ok) {
+		kfree_skb(mbc->cs_tx_skb);
+		mbc->cs_tx_skb = NULL;
+	}
+	mbc->cs_rx_ok = false;
+
+	list_for_each_entry(tb, &mbc->tbs, list) {
+		if (tb->b_rx_skb == NULL)
+			continue;
+		skb = tb->b_rx_skb;
+		tb->b_rx_skb = NULL;
+		dect_mac_co_data_ind(cl, mbc->id.mcei, DECT_MC_I_N, skb);
+	}
+
+	dect_timer_add(cl, &mbc->normal_rx_timer, DECT_TIMER_RX,
+		       1, dect_normal_receive_end(cl->mode));
+out:
+	dect_mbc_put(mbc);
+}
+
+/*
+ * MBC slot based receive timer:
+ *
+ * Deliver received I_N minimal delay B-field segments to the DLC.
+ */
+static void dect_mbc_slot_rx_timer(struct dect_cluster *cl, void *data)
+{
+	struct dect_tb *tb = data;
+	struct dect_mbc *mbc = tb->mbc;
+	struct sk_buff *skb;
+
+	mbc_debug(mbc, "Slot RX timer: TBEI: %u LBN: %u slot: %u\n",
+		  tb->id.tbei, tb->id.lbn, tb->rx_slot);
+
+	if (tb->b_rx_skb != NULL) {
+		skb = tb->b_rx_skb;
+		tb->b_rx_skb = NULL;
+		dect_mac_co_data_ind(cl, mbc->id.mcei, DECT_MC_I_N, skb);
+	}
+
+	dect_timer_add(cl, &tb->slot_rx_timer, DECT_TIMER_RX, 1, tb->rx_slot);
+}
+
+/*
+ * MBC normal transmit half frame timer:
+ *
+ * Request data from the DLC for the next frame. Data is requested for the
+ * following channels:
+ *
+ * - C_S before an ARQ window starts
+ * - I_N normal delay
+ */
+static void dect_mbc_normal_tx_timer(struct dect_cluster *cl, void *data)
+{
+	const struct dect_cell_handle *ch;
+	struct dect_mbc *mbc = data;
+	struct dect_tb *tb;
+	struct sk_buff *skb;
+
+	mbc_debug(mbc, "Normal TX timer\n");
+
+	if (dect_ct_tail_allowed(cl, dect_framenum(cl, DECT_TIMER_TX))) {
+		if (mbc->cs_tx_skb == NULL) {
+			skb = dect_mac_co_dtr_ind(cl, mbc->id.mcei, DECT_MC_C_S);
+			if (skb != NULL) {
+				DECT_CS_CB(skb)->seq = mbc->cs_tx_seq;
+				mbc->cs_tx_seq = !mbc->cs_tx_seq;
+				mbc->cs_tx_skb = skb;
+			}
+		}
+
+		if (mbc->cs_tx_skb != NULL) {
+			list_for_each_entry(tb, &mbc->tbs, list) {
+				skb = skb_clone(mbc->cs_tx_skb, GFP_ATOMIC);
+				if (skb == NULL)
+					continue;
+				ch = tb->ch;
+				ch->ops->tbc_data_req(ch, &tb->id, DECT_MC_C_S, skb);
+				mbc->cs_tx_ok = true;
+			}
+		}
+	}
+
+	if (mbc->service != DECT_SERVICE_IN_MIN_DELAY) {
+		list_for_each_entry(tb, &mbc->tbs, list) {
+			ch = tb->ch;
+			skb = dect_mac_co_dtr_ind(cl, mbc->id.mcei, DECT_MC_I_N);
+			if (skb != NULL)
+				ch->ops->tbc_data_req(ch, &tb->id, DECT_MC_I_N, skb);
+		}
+	}
+
+	dect_timer_add(cl, &mbc->normal_tx_timer, DECT_TIMER_TX,
+		       1, dect_normal_transmit_base(cl->mode));
+}
+
+/*
+ * MBC slot based transmit timer:
+ *
+ * Request data from the DLC for the I_N minimal delay channel.
+ */
+static void dect_mbc_slot_tx_timer(struct dect_cluster *cl, void *data)
+{
+	struct dect_tb *tb = data;
+	struct dect_mbc *mbc = tb->mbc;
+	const struct dect_cell_handle *ch = tb->ch;
+	struct sk_buff *skb;
+
+	mbc_debug(mbc, "Slot TX timer: TBEI: %u LBN: %u slot: %u\n",
+		  tb->id.tbei, tb->id.lbn, tb->tx_slot);
+
+	skb = dect_mac_co_dtr_ind(cl, mbc->id.mcei, DECT_MC_I_N);
+	if (skb != NULL)
+		ch->ops->tbc_data_req(ch, &tb->id, DECT_MC_I_N, skb);
+
+	dect_timer_add(cl, &tb->slot_tx_timer, DECT_TIMER_TX, 1, tb->tx_slot);
+}
+
+static int dect_mbc_complete_setup(struct dect_cluster *cl, struct dect_mbc *mbc)
+{
+	if (!del_timer(&mbc->timer))
+		return 0;
+
+	dect_timer_add(cl, &mbc->normal_rx_timer, DECT_TIMER_RX,
+		       0, dect_normal_receive_end(cl->mode));
+	dect_timer_add(cl, &mbc->normal_tx_timer, DECT_TIMER_TX,
+		       0, dect_normal_transmit_base(cl->mode));
+	mbc->state = DECT_MBC_ESTABLISHED;
+
+	return 1;
+}
+
+static void dect_mbc_tb_release(struct dect_tb *tb);
+
+static void dect_mbc_tb_handover_timer(struct dect_cluster *cl, void *data)
+{
+	struct dect_tb *tb = data, *tb1, *i;
+	struct dect_mbc *mbc = tb->mbc;
+
+	mbc_debug(mbc, "Handover timer: TBEI: %u LBN: %u\n",
+		  tb->id.tbei, tb->id.lbn);
+
+	tb1 = NULL;
+	list_for_each_entry(i, &mbc->tbs, list) {
+		if (i->id.lbn == tb->id.lbn) {
+			tb1 = i;
+			break;
+		}
+	}
+	if (tb1 == NULL)
+		return;
+
+	tb1->ch->ops->tbc_dis_req(tb1->ch, &tb1->id,
+				  DECT_REASON_BEARER_HANDOVER_COMPLETED);
+	list_del(&tb1->list);
+	dect_mbc_tb_release(tb1);
+}
+
+static void dect_mbc_tb_complete_setup(struct dect_cluster *cl, struct dect_tb *tb)
+{
+	if (cl->mode == DECT_MODE_FP && tb->handover)
+		dect_timer_add(cl, &tb->handover_timer, DECT_TIMER_RX,
+			       DECT_MBC_TB_HANDOVER_TIMEOUT, tb->rx_slot);
+
+	if (tb->mbc->service == DECT_SERVICE_IN_MIN_DELAY) {
+		dect_timer_add(cl, &tb->slot_rx_timer, DECT_TIMER_RX,
+			       0, tb->rx_slot);
+		dect_timer_add(cl, &tb->slot_tx_timer, DECT_TIMER_TX,
+			       0, tb->tx_slot);
+	}
+}
+
+static void dect_mbc_tb_release(struct dect_tb *tb)
+{
+	dect_timer_del(&tb->handover_timer);
+	dect_timer_del(&tb->slot_rx_timer);
+	dect_timer_del(&tb->slot_tx_timer);
+	kfree(tb);
+}
+
+static struct dect_tb *dect_mbc_tb_init(struct dect_mbc *mbc,
+					const struct dect_cell_handle *ch, u8 lbn)
+{
+	struct dect_tb *tb;
+
+	tb = kzalloc(sizeof(*tb), GFP_ATOMIC);
+	if (tb == NULL)
+		return NULL;
+
+	tb->mbc      = mbc;
+	tb->ch       = ch;
+	tb->id.ari   = mbc->id.ari;
+	tb->id.pmid  = mbc->id.pmid;
+	tb->id.ecn   = 0;
+	tb->id.lbn   = lbn;
+	tb->id.tbei  = 0;
+	tb->handover = false;
+	tb->rx_slot  = 0;
+	tb->tx_slot  = 0;
+
+	dect_timer_setup(&tb->handover_timer, dect_mbc_tb_handover_timer, tb);
+	dect_timer_setup(&tb->slot_rx_timer, dect_mbc_slot_rx_timer, tb);
+	dect_timer_setup(&tb->slot_tx_timer, dect_mbc_slot_tx_timer, tb);
+
+	return tb;
+}
+
+static void dect_mbc_release(struct dect_mbc *mbc)
+{
+	struct dect_tb *tb, *next;
+
+	mbc_debug(mbc, "release\n");
+	mbc->state = DECT_MBC_RELEASED;
+	del_timer(&mbc->timer);
+	list_del(&mbc->list);
+
+	dect_timer_del(&mbc->normal_rx_timer);
+	dect_timer_del(&mbc->normal_tx_timer);
+
+	list_for_each_entry_safe(tb, next, &mbc->tbs, list)
+		dect_mbc_tb_release(tb);
+
+	kfree_skb(mbc->cs_rx_skb);
+	kfree_skb(mbc->cs_tx_skb);
+	dect_mbc_put(mbc);
+}
+
+static void dect_mbc_timeout(unsigned long data)
+{
+	struct dect_mbc *mbc = (struct dect_mbc *)data;
+	struct dect_tb *tb;
+	enum dect_release_reasons reason;
+
+	mbc_debug(mbc, "timeout\n");
+	reason = DECT_REASON_BEARER_SETUP_OR_HANDOVER_FAILED;
+
+	list_for_each_entry(tb, &mbc->tbs, list)
+		tb->ch->ops->tbc_dis_req(tb->ch, &tb->id, reason);
+
+	if (mbc->state != DECT_MBC_NONE)
+		dect_mac_dis_ind(mbc->cl, mbc->id.mcei, reason);
+
+	dect_mbc_release(mbc);
+}
+
+static struct dect_mbc *dect_mbc_init(struct dect_cluster *cl,
+				      const struct dect_mbc_id *id)
+{
+	struct dect_mbc *mbc;
+
+	mbc = kzalloc(sizeof(*mbc), GFP_ATOMIC);
+	if (mbc == NULL)
+		return NULL;
+	mbc->refcnt   = 1;
+	mbc->cl       = cl;
+	mbc->id       = *id;
+	mbc->state    = DECT_MBC_NONE;
+	mbc->ho_stamp = jiffies - DECT_MBC_HANDOVER_TIMER;
+
+	INIT_LIST_HEAD(&mbc->tbs);
+	dect_timer_setup(&mbc->normal_rx_timer, dect_mbc_normal_rx_timer, mbc);
+	dect_timer_setup(&mbc->normal_tx_timer, dect_mbc_normal_tx_timer, mbc);
+
+	mbc->cs_tx_seq = 1;
+	mbc->cs_rx_seq = 1;
+
+	setup_timer(&mbc->timer, dect_mbc_timeout, (unsigned long)mbc);
+	list_add_tail(&mbc->list, &cl->mbcs);
+	return mbc;
+}
+
+static int dect_mbc_tb_setup(struct dect_mbc *mbc, struct dect_tb *tb)
+{
+	const struct dect_cell_handle *ch = tb->ch;
+	struct dect_channel_desc chd;
+	int err;
+
+	memset(&chd, 0, sizeof(chd));
+	chd.pkt   = DECT_PACKET_P32;
+	chd.b_fmt = DECT_B_UNPROTECTED;
+
+	err = ch->ops->tbc_establish_req(ch, &tb->id, &chd,
+					 DECT_SERVICE_IN_MIN_DELAY,
+					 tb->handover);
+	if (err < 0)
+		return err;
+
+	mbc->setup_cnt++;
+	return 0;
+}
+
+/**
+ * dect_mac_con_req - request a new MAC connection
+ *
+ * @cl:		DECT cluster
+ * @id:		MBC identifier
+ */
+int dect_mac_con_req(struct dect_cluster *cl, const struct dect_mbc_id *id)
+{
+	struct dect_cell_handle *ch;
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+	int err;
+
+	err = -EHOSTUNREACH;
+	ch = dect_cluster_get_cell_by_rpn(cl, 0);
+	if (ch == NULL)
+		goto err1;
+
+	err = -ENOMEM;
+	mbc = dect_mbc_init(cl, id);
+	if (mbc == NULL)
+		goto err1;
+	mbc->state = DECT_MBC_INITIATED;
+	mbc_debug(mbc, "MAC_CON-req\n");
+
+	tb = dect_mbc_tb_init(mbc, ch, 0xf);
+	if (tb == NULL)
+		goto err2;
+
+	err = dect_mbc_tb_setup(mbc, tb);
+	if (err < 0)
+		goto err3;
+
+	list_add_tail(&tb->list, &mbc->tbs);
+	mod_timer(&mbc->timer, jiffies + DECT_MBC_SETUP_TIMEOUT);
+	return 0;
+
+err3:
+	dect_mbc_tb_release(tb);
+err2:
+	dect_mbc_release(mbc);
+err1:
+	return err;
+}
+
+void dect_mac_dis_req(struct dect_cluster *cl, u32 mcei)
+{
+	const struct dect_cell_handle *ch;
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+
+	mbc = dect_mbc_get_by_mcei(cl, mcei);
+	if (mbc == NULL)
+		return;
+	mbc_debug(mbc, "MAC_DIS-req\n");
+
+	list_for_each_entry(tb, &mbc->tbs, list) {
+		ch = tb->ch;
+		ch->ops->tbc_dis_req(ch, &tb->id, DECT_REASON_CONNECTION_RELEASE);
+	}
+
+	dect_mbc_release(mbc);
+}
+
+/* TBC establishment indication from CSF */
+static int dect_tbc_establish_ind(const struct dect_cluster_handle *clh,
+				  const struct dect_cell_handle *ch,
+				  const struct dect_tbc_id *id,
+				  enum dect_mac_service_types service,
+				  bool handover)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+	struct dect_mbc_id mid;
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+	unsigned int cnt;
+	int err;
+
+	mbc = dect_mbc_get_by_tbc_id(cl, id);
+	if (mbc == NULL) {
+		if (handover)
+			return -ENOENT;
+
+		mid.mcei = dect_mbc_alloc_mcei(cl);
+		mid.type = 0;
+		mid.ari  = id->ari;
+		mid.pmid = id->pmid;
+		mid.ecn  = id->ecn;
+
+		err = -ENOMEM;
+		mbc = dect_mbc_init(cl, &mid);
+		if (mbc == NULL)
+			goto err1;
+		mbc->service = service;
+	} else {
+		if (!handover)
+			return -EEXIST;
+
+		cnt = 0;
+		list_for_each_entry(tb, &mbc->tbs, list) {
+			if (tb->id.lbn == id->lbn)
+				cnt++;
+		}
+		if (cnt > 1)
+			return -EEXIST;
+
+		if (mbc->cipher_state == DECT_CIPHER_ENABLED) {
+			err = ch->ops->tbc_enc_req(ch, id, mbc->ck);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	mbc_debug(mbc, "TBC_ESTABLISH-ind: TBEI: %u LBN: %u H/O: %u\n",
+		  id->tbei, id->lbn, handover);
+
+	err = -ENOMEM;
+	tb = dect_mbc_tb_init(mbc, ch, id->lbn);
+	if (tb == NULL)
+		goto err2;
+	tb->handover = handover;
+
+	err = ch->ops->tbc_establish_res(ch, id);
+	if (err < 0)
+		goto err3;
+
+	list_add_tail(&tb->list, &mbc->tbs);
+	if (!handover)
+		mod_timer(&mbc->timer, jiffies + DECT_MBC_SETUP_TIMEOUT);
+	return 0;
+
+err3:
+	dect_mbc_tb_release(tb);
+err2:
+	dect_mbc_release(mbc);
+err1:
+	return err;
+}
+
+static int dect_tbc_establish_cfm(const struct dect_cluster_handle *clh,
+				  const struct dect_tbc_id *id, bool success,
+				  u8 rx_slot)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+	const struct dect_cell_handle *ch;
+	struct dect_mbc *mbc;
+	struct dect_tb *tb, *i;
+
+	mbc = dect_mbc_get_by_tbc_id(cl, id);
+	if (mbc == NULL)
+		return -ENOENT;
+
+	mbc_debug(mbc, "TBC_ESTABLISH-cfm: TBEI: %u LBN: %u success: %d\n",
+		  id->tbei, id->lbn, success);
+
+	tb = NULL;
+	list_for_each_entry(i, &mbc->tbs, list) {
+		if (i->id.lbn  == id->lbn &&
+		    i->id.tbei == 0) {
+			tb = i;
+			break;
+		}
+	}
+	if (tb == NULL)
+		return -ENOENT;
+
+	if (success) {
+		tb->id.tbei = id->tbei;
+		tb->rx_slot = rx_slot;
+		tb->tx_slot = dect_tdd_slot(rx_slot);
+
+		switch (mbc->state) {
+		case DECT_MBC_NONE:
+			if (!dect_mbc_complete_setup(cl, mbc))
+				return 0;
+			dect_mbc_tb_complete_setup(cl, tb);
+
+			return dect_mac_con_ind(cl, &mbc->id, mbc->service);
+		case DECT_MBC_INITIATED:
+			if (!dect_mbc_complete_setup(cl, mbc))
+				return 0;
+			dect_mbc_tb_complete_setup(cl, tb);
+
+			return dect_mac_con_cfm(cl, mbc->id.mcei, mbc->service);
+		case DECT_MBC_ESTABLISHED:
+			ch = tb->ch;
+			if (mbc->cipher_state == DECT_CIPHER_ENABLED &&
+			    ch->ops->tbc_enc_req(ch, id, mbc->ck) < 0) {
+				ch->ops->tbc_dis_req(ch, id, DECT_REASON_UNKNOWN);
+				return -1;
+			}
+			dect_mbc_tb_complete_setup(cl, tb);
+			return 0;
+		default:
+			return WARN_ON(-1);
+		}
+	} else {
+		switch (mbc->state) {
+		case DECT_MBC_NONE:
+			dect_mbc_release(mbc);
+			return 0;
+		case DECT_MBC_INITIATED:
+			if (mbc->setup_cnt > DECT_MBC_SETUP_MAX_ATTEMPTS ||
+			    dect_mbc_tb_setup(mbc, tb) < 0) {
+				dect_mac_dis_ind(cl, mbc->id.mcei,
+					DECT_REASON_BEARER_SETUP_OR_HANDOVER_FAILED);
+				dect_mbc_release(mbc);
+			}
+			return 0;
+		case DECT_MBC_ESTABLISHED:
+			list_del(&tb->list);
+			dect_mbc_tb_release(tb);
+			return 0;
+		default:
+			return WARN_ON(-1);
+		}
+	}
+}
+
+static int dect_tbc_event_ind(const struct dect_cluster_handle *clh,
+			      const struct dect_tbc_id *id,
+			      enum dect_tbc_event event)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+
+	mbc = dect_mbc_get_by_tbc_id(cl, id);
+	if (mbc == NULL)
+		return -ENOENT;
+	mbc_debug(mbc, "TBC_EVENT-ind: TBEI: %u LBN: %u event: %u\n",
+		  id->tbei, id->lbn, event);
+
+	tb = dect_mbc_tb_get_by_tbei(mbc, id);
+	if (tb == NULL)
+		return -ENOENT;
+
+	switch (event) {
+	case DECT_TBC_ACK_RECEIVED:
+		mbc->cs_rx_ok = true;
+		return 0;
+	case DECT_TBC_CIPHER_ENABLED:
+		mbc->cipher_state = DECT_TBC_CIPHER_ENABLED;
+		dect_mac_enc_eks_ind(cl, mbc->id.mcei, DECT_CIPHER_ENABLED);
+		return 0;
+	case DECT_TBC_CIPHER_DISABLED:
+		mbc->cipher_state = DECT_TBC_CIPHER_DISABLED;
+		dect_mac_enc_eks_ind(cl, mbc->id.mcei, DECT_CIPHER_DISABLED);
+		return 0;
+	default:
+		return WARN_ON(-1);
+	}
+}
+
+static int dect_tbc_handover_req(const struct dect_cluster_handle *clh,
+				 const struct dect_tbc_id *id)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+	struct dect_cell_handle *ch;
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+	unsigned int cnt;
+	int err;
+
+	mbc = dect_mbc_get_by_tbc_id(cl, id);
+	if (mbc == NULL)
+		return -ENOENT;
+	mbc_debug(mbc, "TBC_HANDOVER-req: TBEI: %u LBN: %u\n",
+		  id->tbei, id->lbn);
+
+	/* Handover already in progress or two bearers active?? */
+	cnt = 0;
+	list_for_each_entry(tb, &mbc->tbs, list) {
+		if (tb->id.lbn  != id->lbn)
+			continue;
+		 if (tb->id.tbei == 0)
+			return 0;
+		 cnt++;
+	}
+	if (cnt > 1)
+		return 0;
+
+	/* Handover rate-limiting */
+	if (mbc->ho_tokens == 0) {
+		if (time_after_eq(jiffies, mbc->ho_stamp + DECT_MBC_HANDOVER_TIMER)) {
+			mbc->ho_tokens = DECT_MBC_HANDOVER_LIMIT;
+			mbc->ho_stamp  = jiffies;
+		}
+		mbc_debug(mbc, "handover: tokens: %u\n", mbc->ho_tokens);
+		if (mbc->ho_tokens == 0)
+			return 0;
+	}
+
+	ch = dect_cluster_get_cell_by_rpn(cl, 0);
+	if (ch == NULL)
+		return -EHOSTUNREACH;
+
+	tb = dect_mbc_tb_init(mbc, ch, id->lbn);
+	if (tb == NULL)
+		return -ENOMEM;
+	tb->handover = true;
+
+	err = dect_mbc_tb_setup(mbc, tb);
+	if (err < 0)
+		goto err1;
+
+	list_add_tail(&tb->list, &mbc->tbs);
+	mbc->ho_tokens--;
+	return 0;
+
+err1:
+	dect_mbc_tb_release(tb);
+	return err;
+}
+
+/* TBC release indication from CSF */
+static void dect_tbc_dis_ind(const struct dect_cluster_handle *clh,
+			     const struct dect_tbc_id *id,
+			     enum dect_release_reasons reason)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+
+	mbc = dect_mbc_get_by_tbc_id(cl, id);
+	if (mbc == NULL)
+		return;
+	mbc_debug(mbc, "TBC_DIS-ind: TBEI: %u LBN: %u reason: %u\n",
+		  id->tbei, id->lbn, reason);
+
+	tb = dect_mbc_tb_get_by_tbei(mbc, id);
+	if (tb == NULL)
+		return;
+
+	list_del(&tb->list);
+	dect_mbc_tb_release(tb);
+	if (!list_empty(&mbc->tbs))
+		return;
+
+	dect_mac_dis_ind(cl, mbc->id.mcei, reason);
+	dect_mbc_release(mbc);
+}
+
+/* Set Encryption key request from DLC */
+int dect_mac_enc_key_req(const struct dect_cluster *cl, u32 mcei, u64 ck)
+{
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+	int err;
+
+	mbc = dect_mbc_get_by_mcei(cl, mcei);
+	if (mbc == NULL)
+		return -ENOENT;
+	mbc_debug(mbc, "MAC_ENC_KEY-req: key: %016llx\n", (unsigned long long)ck);
+
+	mbc->ck = ck;
+	list_for_each_entry(tb, &mbc->tbs, list) {
+		err = tb->ch->ops->tbc_enc_key_req(tb->ch, &tb->id, ck);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/* Change encryption status requst from DLC */
+int dect_mac_enc_eks_req(const struct dect_cluster *cl, u32 mcei,
+			 enum dect_cipher_states status)
+{
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+	int err;
+
+	mbc = dect_mbc_get_by_mcei(cl, mcei);
+	if (mbc == NULL)
+		return -ENOENT;
+	mbc_debug(mbc, "MAC_ENC_EKS-req: status: %d\n", status);
+
+	if (mbc->cipher_state == status)
+		return 0;
+
+	list_for_each_entry(tb, &mbc->tbs, list) {
+		err = tb->ch->ops->tbc_enc_eks_req(tb->ch, &tb->id, status);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static void dect_tbc_data_ind(const struct dect_cluster_handle *clh,
+			      const struct dect_tbc_id *id,
+			      enum dect_data_channels chan,
+			      struct sk_buff *skb)
+{
+	const struct dect_cluster *cl = dect_cluster(clh);
+	struct dect_mbc *mbc;
+	struct dect_tb *tb;
+
+	mbc = dect_mbc_get_by_tbc_id(cl, id);
+	if (mbc == NULL)
+		goto err;
+	mbc_debug(mbc, "TBC_DATA-ind: TBEI: %u LBN: %u chan: %u len: %u\n",
+		  id->tbei, id->lbn, chan, skb->len);
+
+	switch (chan) {
+	case DECT_MC_C_S:
+		/* Drop duplicate segments */
+		if (DECT_CS_CB(skb)->seq != mbc->cs_rx_seq)
+			goto err;
+		if (mbc->cs_rx_skb != NULL)
+			goto err;
+		mbc->cs_rx_seq = !mbc->cs_rx_seq;
+		mbc->cs_rx_skb = skb;
+		return;
+	case DECT_MC_I_N:
+		tb = dect_mbc_tb_get_by_tbei(mbc, id);
+		if (tb == NULL)
+			goto err;
+		tb->b_rx_skb = skb;
+		return;
+	default:
+		break;
+	}
+err:
+	kfree_skb(skb);
+}
+
+static void dect_cluster_unbind_cell(struct dect_cluster_handle *clh,
+				     struct dect_cell_handle *ch)
+{
+	list_del(&ch->list);
+}
+
+static int dect_cluster_enable_cell(struct dect_cluster *cl,
+				    struct dect_cell_handle *ch)
+{
+	int err;
+
+	err = ch->ops->preload(ch, &cl->pari, ch->rpn, &cl->si);
+	if (err < 0)
+		return err;
+
+	err = ch->ops->enable(ch);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int dect_cluster_bind_cell(struct dect_cluster_handle *clh,
+				  struct dect_cell_handle *ch)
+{
+	struct dect_cluster *cl = dect_cluster(clh);
+	u8 rpn, max;
+	int err;
+
+	/* Allocate RPN for the cell */
+	max = 8;
+	for (rpn = 0; rpn < max; rpn++) {
+		if (!dect_cluster_get_cell_by_rpn(cl, rpn))
+			break;
+	}
+	if (rpn == max)
+		return -EMFILE;
+
+	ch->clh = clh;
+	ch->rpn = rpn;
+
+	err = ch->ops->set_mode(ch, cl->mode);
+	if (err < 0)
+		return err;
+
+	err = dect_cluster_enable_cell(cl, ch);
+	if (err < 0)
+		return err;
+
+	list_add_tail(&ch->list, &cl->cells);
+	return 0;
+}
+
+static const struct dect_ccf_ops dect_ccf_ops = {
+	.bind			= dect_cluster_bind_cell,
+	.unbind			= dect_cluster_unbind_cell,
+	.time_ind		= dect_ccf_time_ind,
+	.scan_report		= dect_scan_report,
+	.mac_info_ind		= dect_mac_info_ind,
+	.tbc_establish_ind	= dect_tbc_establish_ind,
+	.tbc_establish_cfm	= dect_tbc_establish_cfm,
+	.tbc_event_ind		= dect_tbc_event_ind,
+	.tbc_handover_req	= dect_tbc_handover_req,
+	.tbc_dis_ind		= dect_tbc_dis_ind,
+	.tbc_data_ind		= dect_tbc_data_ind,
+	.bmc_page_ind		= dect_bmc_page_ind,
+};
+
+static int dect_cluster_preload(struct dect_cluster *cl,
+				const struct dect_ari *pari,
+				const struct dect_si *si)
+{
+	const struct dect_cell_handle *ch;
+	int err = 0;
+
+	list_for_each_entry(ch, &cl->cells, list) {
+		err = ch->ops->preload(ch, pari, ch->rpn, si);
+		if (err < 0)
+			return err;
+	}
+
+	cl->pari = *pari;
+	cl->si   = *si;
+	return 0;
+}
+
+static int dect_cluster_scan(struct dect_cluster *cl,
+			     const struct dect_llme_req *lreq,
+			     const struct dect_ari *pari,
+			     const struct dect_ari *pari_mask)
+{
+	struct dect_cell_handle *ch;
+
+	ch = dect_cluster_get_cell_by_rpn(cl, 0);
+	if (ch == NULL)
+		return -ENOENT;
+	return ch->ops->scan(ch, lreq, pari, pari_mask);
+}
+
+static void dect_fp_init_si(struct dect_cluster *cl)
+{
+	struct dect_si *si = &cl->si;
+
+	/* Make phone not go into "call technician" mode :) */
+	si->fpc.fpc = DECT_FPC_FULL_SLOT |
+		      DECT_FPC_CO_SETUP_ON_DUMMY |
+   		      DECT_FPC_CL_UPLINK |
+   		      DECT_FPC_CL_DOWNLINK |
+		      DECT_FPC_BASIC_A_FIELD_SETUP |
+		      DECT_FPC_ADV_A_FIELD_SETUP |
+		      DECT_FPC_CF_MESSAGES |
+   		      DECT_FPC_IN_MIN_DELAY |
+   		      DECT_FPC_IN_NORM_DELAY |
+		      DECT_FPC_IP_ERROR_DETECTION |
+		      DECT_FPC_IP_ERROR_CORRECTION;
+	si->fpc.hlc = DECT_HLC_ADPCM_G721_VOICE |
+		      DECT_HLC_GAP_PAP_BASIC_SPEECH |
+		      DECT_HLC_CISS_SERVICE |
+		      DECT_HLC_CLMS_SERVICE |
+		      DECT_HLC_COMS_SERVICE |
+		      DECT_HLC_LOCATION_REGISTRATION |
+		      DECT_HLC_ACCESS_RIGHTS_REQUESTS |
+		      DECT_HLC_STANDARD_AUTHENTICATION |
+		      DECT_HLC_STANDARD_CIPHERING;
+}
+
+static int dect_cluster_init(struct dect_cluster *cl)
+{
+	spin_lock_init(&cl->lock);
+	INIT_LIST_HEAD(&cl->bmc.bcs);
+	INIT_LIST_HEAD(&cl->mbcs);
+	INIT_LIST_HEAD(&cl->cells);
+	INIT_LIST_HEAD(&cl->mac_connections);
+	dect_timer_base_init(cl->timer_base, DECT_TIMER_TX);
+	dect_timer_base_init(cl->timer_base, DECT_TIMER_RX);
+
+	if (cl->mode == DECT_MODE_FP)
+		dect_fp_init_si(cl);
+
+	cl->handle.ops = &dect_ccf_ops;
+	cl->handle.index = cl->index;
+
+	return dect_ccp_cluster_init(cl);
+}
+
+static void dect_cluster_shutdown(struct dect_cluster *cl)
+{
+	struct dect_cell_handle *ch, *ch_next;
+	struct dect_mbc *mbc, *mbc_next;
+
+	list_for_each_entry_safe(mbc, mbc_next, &cl->mbcs, list) {
+		dect_mac_dis_ind(cl, mbc->id.mcei, DECT_REASON_UNKNOWN);
+		dect_mbc_release(mbc);
+	}
+
+	list_for_each_entry_safe(ch, ch_next, &cl->cells, list)
+		dect_cluster_unbind_cell(&cl->handle, ch);
+
+	dect_ccp_cluster_shutdown(cl);
+}
+
+/*
+ * LLME netlink interface
+ */
+
+static struct sk_buff *dect_llme_fill(const struct dect_cluster *cl,
+				      const struct dect_llme_req *lreq,
+				      u8 op, u8 type,
+				      int (*fill)(const struct dect_cluster *,
+						  struct sk_buff *, const void *),
+				      const void *data);
+
+static void dect_llme_req_init(struct dect_llme_req *lreq,
+			       const struct nlmsghdr *nlh,
+			       const struct sk_buff *skb)
+{
+	memcpy(&lreq->nlh, nlh, sizeof(lreq->nlh));
+	lreq->nlpid = NETLINK_CB(skb).pid;
+}
+
+static int dect_fill_ari(struct sk_buff *skb, const struct dect_ari *ari, int attr)
+{
+	struct nlattr *nla;
+
+	nla = nla_nest_start(skb, attr);
+	if (nla == NULL)
+		goto nla_put_failure;
+
+	NLA_PUT_U8(skb, DECTA_ARI_CLASS, ari->arc);
+	NLA_PUT_U32(skb, DECTA_ARI_FPN, ari->fpn);
+
+	switch (ari->arc) {
+	case DECT_ARC_A:
+		NLA_PUT_U16(skb, DECTA_ARI_EMC, ari->emc);
+		break;
+	case DECT_ARC_B:
+		NLA_PUT_U16(skb, DECTA_ARI_EIC, ari->eic);
+		NLA_PUT_U32(skb, DECTA_ARI_FPS, ari->fps);
+		break;
+	case DECT_ARC_C:
+		NLA_PUT_U16(skb, DECTA_ARI_POC, ari->poc);
+		NLA_PUT_U32(skb, DECTA_ARI_FPS, ari->fps);
+		break;
+	case DECT_ARC_D:
+		NLA_PUT_U32(skb, DECTA_ARI_GOP, ari->gop);
+		break;
+	case DECT_ARC_E:
+		NLA_PUT_U16(skb, DECTA_ARI_FIL, ari->fil);
+		break;
+	}
+	nla_nest_end(skb, nla);
+	return 0;
+
+nla_put_failure:
+	return -1;
+}
+
+static const struct nla_policy dect_ari_policy[DECTA_ARI_MAX + 1] = {
+	[DECTA_ARI_CLASS]	= { .type = NLA_U8 },
+	[DECTA_ARI_FPN]		= { .type = NLA_U32 },
+	[DECTA_ARI_FPS]		= { .type = NLA_U32 },
+	[DECTA_ARI_EMC]		= { .type = NLA_U16 },
+	[DECTA_ARI_EIC]		= { .type = NLA_U16 },
+	[DECTA_ARI_POC]		= { .type = NLA_U16 },
+	[DECTA_ARI_GOP]		= { .type = NLA_U32 },
+	[DECTA_ARI_FIL]		= { .type = NLA_U32 },
+};
+
+static const u32 dect_ari_valid_attrs[] = {
+	[DECT_ARC_A]		= (1 << DECTA_ARI_EMC),
+	[DECT_ARC_B]		= (1 << DECTA_ARI_EIC) | (1 << DECTA_ARI_FPS),
+	[DECT_ARC_C]		= (1 << DECTA_ARI_POC) | (1 << DECTA_ARI_FPS),
+	[DECT_ARC_D]		= (1 << DECTA_ARI_GOP),
+	[DECT_ARC_E]		= (1 << DECTA_ARI_FIL),
+};
+
+static int dect_nla_parse_ari(struct dect_ari *ari, const struct nlattr *nla)
+{
+	struct nlattr *tb[DECTA_ARI_MAX + 1];
+	unsigned int attr;
+	int err;
+
+	err = nla_parse_nested(tb, DECTA_ARI_MAX, nla, dect_ari_policy);
+	if (err < 0)
+		return err;
+
+	if (tb[DECTA_ARI_CLASS] == NULL)
+		return -EINVAL;
+
+	memset(ari, 0, sizeof(ari));
+	ari->arc = nla_get_u8(tb[DECTA_ARI_CLASS]);
+	if (ari->arc > DECT_ARC_E)
+		return -EINVAL;
+
+	for (attr = DECTA_ARI_UNSPEC + 1; attr <= DECTA_ARI_MAX; attr++) {
+		if (tb[attr] == NULL)
+			continue;
+
+		switch (attr) {
+		case DECTA_ARI_CLASS:
+		case DECTA_ARI_FPN:
+			/* always valid */
+			break;
+		default:
+			if (!(dect_ari_valid_attrs[ari->arc] & (1 << attr)))
+				return -EINVAL;
+			break;
+		}
+	}
+
+	if (tb[DECTA_ARI_FPN] != NULL)
+		ari->fpn = nla_get_u32(tb[DECTA_ARI_FPN]);
+	if (tb[DECTA_ARI_FPS] != NULL)
+		ari->fps = nla_get_u32(tb[DECTA_ARI_FPS]);
+
+	switch (ari->arc) {
+	case DECT_ARC_A:
+		if (tb[DECTA_ARI_EMC] != NULL)
+			ari->emc = nla_get_u16(tb[DECTA_ARI_EMC]);
+		break;
+	case DECT_ARC_B:
+		if (tb[DECTA_ARI_EIC] != NULL)
+			ari->eic = nla_get_u16(tb[DECTA_ARI_EIC]);
+		break;
+	case DECT_ARC_C:
+		if (tb[DECTA_ARI_POC] != NULL)
+			ari->poc = nla_get_u16(tb[DECTA_ARI_POC]);
+		break;
+	case DECT_ARC_D:
+		if (tb[DECTA_ARI_GOP] != NULL)
+			ari->gop = nla_get_u32(tb[DECTA_ARI_GOP]);
+		break;
+	case DECT_ARC_E:
+		if (tb[DECTA_ARI_FIL] != NULL)
+			ari->fil = nla_get_u16(tb[DECTA_ARI_FIL]);
+		break;
+	}
+	return 0;
+}
+
+static int dect_fill_sari(struct sk_buff *skb, const struct dect_sari *sari,
+			  int attr)
+{
+	struct nlattr *nla;
+
+	nla = nla_nest_start(skb, attr);
+	if (nla == NULL)
+		goto nla_put_failure;
+	if (dect_fill_ari(skb, &sari->ari, DECTA_SARI_ARI) < 0)
+		goto nla_put_failure;
+	if (sari->black)
+		NLA_PUT_FLAG(skb, DECTA_SARI_BLACK);
+	if (sari->tari)
+		NLA_PUT_FLAG(skb, DECTA_SARI_TARI);
+	nla_nest_end(skb, nla);
+	return 0;
+
+nla_put_failure:
+	return -1;
+}
+
+static int dect_llme_fill_mac_info(const struct dect_cluster *cl,
+				   struct sk_buff *skb, const void *data)
+{
+	const struct dect_si *si = data;
+	struct nlattr *nla;
+	unsigned int i;
+
+	if (si->mask & (1 << DECT_TM_TYPE_SARI) && si->num_saris > 0) {
+		nla = nla_nest_start(skb, DECTA_MAC_INFO_SARI_LIST);
+		if (nla == NULL)
+			goto nla_put_failure;
+		for (i = 0; i < si->num_saris; i++) {
+			if (dect_fill_sari(skb, &si->sari[i],
+					   DECTA_LIST_ELEM) < 0)
+				goto nla_put_failure;
+		}
+		nla_nest_end(skb, nla);
+	}
+
+	NLA_PUT_U8(skb, DECTA_MAC_INFO_RPN, cl->rpn);
+
+	if (si->mask & (1 << DECT_TM_TYPE_FPC)) {
+		NLA_PUT_U32(skb, DECTA_MAC_INFO_FPC, si->fpc.fpc);
+		NLA_PUT_U16(skb, DECTA_MAC_INFO_HLC, si->fpc.hlc);
+	}
+
+	if (si->mask & (1 << DECT_TM_TYPE_EFPC)) {
+		NLA_PUT_U16(skb, DECTA_MAC_INFO_EFPC, si->efpc.fpc);
+		NLA_PUT_U32(skb, DECTA_MAC_INFO_EHLC, si->efpc.hlc);
+	}
+
+	if (si->mask & (1 << DECT_TM_TYPE_EFPC2)) {
+		NLA_PUT_U16(skb, DECTA_MAC_INFO_EFPC2, si->efpc2.fpc);
+		NLA_PUT_U32(skb, DECTA_MAC_INFO_EHLC2, si->efpc2.hlc);
+	}
+
+	return 0;
+
+nla_put_failure:
+	return -EMSGSIZE;
+}
+
+static int dect_llme_fill_scan_result(const struct dect_cluster *cl,
+				      struct sk_buff *skb, const void *data)
+{
+	const struct dect_scan_result *res = data;
+	const struct dect_idi *idi = &res->idi;
+	const struct dect_si *si = &res->si;
+
+	NLA_PUT_U8(skb, DECTA_MAC_INFO_RSSI, res->rssi >> DECT_RSSI_AVG_SCALE);
+
+	if (dect_fill_ari(skb, &idi->pari, DECTA_MAC_INFO_PARI) < 0)
+		goto nla_put_failure;
+	NLA_PUT_U8(skb, DECTA_MAC_INFO_RPN, idi->rpn);
+
+	dect_llme_fill_mac_info(cl, skb, si);
+	return 0;
+
+nla_put_failure:
+	return -EMSGSIZE;
+}
+
+static void dect_llme_scan_result_notify(const struct dect_cluster *cl,
+					 const struct dect_scan_result *res)
+{
+	struct sk_buff *skb;
+	u32 pid = res->lreq.nlpid;
+	int err = 0;
+
+	skb = dect_llme_fill(cl, &res->lreq,
+			     DECT_LLME_INDICATE, DECT_LLME_MAC_INFO,
+			     dect_llme_fill_scan_result, res);
+	if (IS_ERR(skb)) {
+		err = PTR_ERR(skb);
+		goto err;
+	}
+	nlmsg_notify(dect_nlsk, skb, pid, DECTNLGRP_LLME, 1, GFP_ATOMIC);
+err:
+	if (err < 0)
+		netlink_set_err(dect_nlsk, pid, DECTNLGRP_LLME, err);
+}
+
+static void dect_llme_mac_info_ind(const struct dect_cluster *cl,
+				   const struct dect_idi *idi,
+				   const struct dect_si *si)
+{
+	struct sk_buff *skb;
+	int err = 0;
+
+	skb = dect_llme_fill(cl, NULL,
+			     DECT_LLME_INDICATE, DECT_LLME_MAC_INFO,
+			     dect_llme_fill_mac_info, si);
+	if (IS_ERR(skb)) {
+		err = PTR_ERR(skb);
+		goto err;
+	}
+	nlmsg_notify(dect_nlsk, skb, 0, DECTNLGRP_LLME, 0, GFP_ATOMIC);
+err:
+	if (err < 0)
+		netlink_set_err(dect_nlsk, 0, DECTNLGRP_LLME, err);
+}
+
+static int dect_llme_mac_info_req(struct dect_cluster *cl,
+				  const struct sk_buff *skb_in,
+				  const struct nlmsghdr *nlh,
+				  const struct nlattr *tb[DECTA_MAC_INFO_MAX + 1])
+{
+	struct dect_llme_req lreq;
+	struct sk_buff *skb;
+
+	dect_llme_req_init(&lreq, nlh, skb_in);
+	skb = dect_llme_fill(cl, &lreq,
+			     DECT_LLME_INDICATE, DECT_LLME_MAC_INFO,
+			     dect_llme_fill_mac_info, &cl->si);
+	if (IS_ERR(skb))
+		return PTR_ERR(skb);
+
+	return nlmsg_unicast(dect_nlsk, skb, lreq.nlpid);
+}
+
+static int dect_llme_mac_info_res(struct dect_cluster *cl,
+				  const struct sk_buff *skb_in,
+				  const struct nlmsghdr *nlh,
+				  const struct nlattr *tb[DECTA_MAC_INFO_MAX + 1])
+{
+	struct dect_cell_handle *ch;
+	struct dect_ari pari;
+	int err;
+
+	if (cl->mode != DECT_MODE_PP)
+		return -EOPNOTSUPP;
+
+	if (tb[DECTA_MAC_INFO_PARI] != NULL) {
+		err = dect_nla_parse_ari(&pari, tb[DECTA_MAC_INFO_PARI]);
+		if (err < 0)
+			return err;
+	} else
+		return -EINVAL;
+
+	ch = dect_cluster_get_cell_by_rpn(cl, 0);
+	if (ch == NULL)
+		return -EHOSTUNREACH;
+
+	cl->pari = pari;
+	memset(&cl->si, 0, sizeof(cl->si));
+
+	return dect_cluster_enable_cell(cl, ch);
+}
+
+static const struct nla_policy dect_llme_mac_info_policy[DECTA_MAC_INFO_MAX + 1] =  {
+	[DECTA_MAC_INFO_PARI]		= { .type = NLA_NESTED },
+	[DECTA_MAC_INFO_RPN]		= { .type = NLA_U8 },
+	[DECTA_MAC_INFO_RSSI]		= { .type = NLA_U8 },
+	[DECTA_MAC_INFO_SARI_LIST]	= { .type = NLA_NESTED },
+	[DECTA_MAC_INFO_FPC]		= { .type = NLA_U32 },
+	[DECTA_MAC_INFO_HLC]		= { .type = NLA_U16 },
+	[DECTA_MAC_INFO_EFPC]		= { .type = NLA_U16 },
+	[DECTA_MAC_INFO_EHLC]		= { .type = NLA_U32 },
+	[DECTA_MAC_INFO_EFPC2]		= { .type = NLA_U16 },
+	[DECTA_MAC_INFO_EHLC2]		= { .type = NLA_U32 },
+};
+
+static int dect_llme_scan_request(struct dect_cluster *cl,
+				  const struct sk_buff *skb,
+				  const struct nlmsghdr *nlh,
+				  const struct nlattr *tb[DECTA_MAC_INFO_MAX + 1])
+{
+	struct dect_llme_req lreq;
+	struct dect_ari pari, pari_mask;
+	int err;
+
+	if (tb[DECTA_MAC_INFO_PARI] != NULL) {
+		err = dect_nla_parse_ari(&pari, tb[DECTA_MAC_INFO_PARI]);
+		if (err < 0)
+			return err;
+	} else
+		memset(&pari, 0, sizeof(pari));
+	memset(&pari_mask, 0, sizeof(pari_mask));
+
+	dect_llme_req_init(&lreq, nlh, skb);
+	return dect_cluster_scan(cl, &lreq, &pari, &pari_mask);
+}
+
+static int dect_llme_mac_rfp_preload(struct dect_cluster *cl,
+				     const struct sk_buff *skb,
+				     const struct nlmsghdr *nlh,
+				     const struct nlattr *tb[DECTA_MAC_INFO_MAX + 1])
+{
+	struct dect_ari pari;
+	struct dect_si si;
+	int err = 0;
+	u32 num;
+
+	if (cl->mode != DECT_MODE_FP)
+		return -EINVAL;
+
+	if (tb[DECTA_MAC_INFO_PARI] != NULL) {
+		err = dect_nla_parse_ari(&pari, tb[DECTA_MAC_INFO_PARI]);
+		if (err < 0)
+			return err;
+	} else
+		pari = cl->pari;
+
+	si = cl->si;
+	if (tb[DECTA_MAC_INFO_HLC])
+		si.fpc.hlc = nla_get_u16(tb[DECTA_MAC_INFO_HLC]);
+	if (tb[DECTA_MAC_INFO_EHLC])
+		si.efpc.hlc = nla_get_u32(tb[DECTA_MAC_INFO_EHLC]);
+	if (tb[DECTA_MAC_INFO_EHLC2])
+		si.efpc2.hlc = nla_get_u32(tb[DECTA_MAC_INFO_EHLC2]);
+
+	if (si.efpc2.fpc || si.efpc2.hlc)
+		si.efpc.fpc |= DECT_EFPC_EXTENDED_FP_INFO2;
+	else
+		si.efpc.fpc &= ~DECT_EFPC_EXTENDED_FP_INFO2;
+
+	if (si.efpc.fpc || si.efpc.hlc)
+		si.fpc.fpc |= DECT_FPC_EXTENDED_FP_INFO;
+	else
+		si.fpc.fpc &= ~DECT_FPC_EXTENDED_FP_INFO;
+
+        if (tb[DECTA_MAC_INFO_MFN]) {
+                num = nla_get_u32(tb[DECTA_MAC_INFO_MFN]);
+                if (num >= (1 << 24))
+                        return -EINVAL;
+                si.mfn.num = num;
+        }
+
+	return dect_cluster_preload(cl, &pari, &si);
+}
+
+static struct sk_buff *dect_llme_fill(const struct dect_cluster *cl,
+				      const struct dect_llme_req *lreq,
+				      u8 op, u8 type,
+				      int (*fill)(const struct dect_cluster *,
+						  struct sk_buff *, const void *),
+				      const void *data)
+{
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	struct dectmsg *dm;
+	struct nlattr *nest;
+	u32 seq = lreq ? lreq->nlh.nlmsg_seq : 0;
+	int err = -ENOBUFS;
+
+	skb = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+	if (skb == NULL)
+		goto err1;
+
+	nlh = nlmsg_put(skb, 0, seq, DECT_LLME_MSG, sizeof(*dm), NLMSG_DONE);
+	if (nlh == NULL) {
+		err = -EMSGSIZE;
+		goto err2;
+	}
+
+	dm = nlmsg_data(nlh);
+	dm->dm_index = cl->index;
+
+	NLA_PUT_U8(skb, DECTA_LLME_OP, op);
+	NLA_PUT_U8(skb, DECTA_LLME_TYPE, type);
+	nest = nla_nest_start(skb, DECTA_LLME_DATA);
+	if (nest == NULL)
+		goto nla_put_failure;
+	if (fill(cl, skb, data) < 0)
+		goto nla_put_failure;
+	nla_nest_end(skb, nest);
+
+	nlmsg_end(skb, nlh);
+	return skb;
+
+nla_put_failure:
+err2:
+	kfree_skb(skb);
+err1:
+	return ERR_PTR(err);
+}
+
+static const struct dect_llme_link {
+	struct {
+		int (*doit)(struct dect_cluster *cl, const struct sk_buff *,
+			    const struct nlmsghdr *, const struct nlattr *[]);
+	} ops[DECT_LLME_MAX + 1];
+	const struct nla_policy *policy;
+	unsigned int maxtype;
+} dect_llme_dispatch[DECT_LLME_MAX + 1] = {
+	[DECT_LLME_SCAN]	= {
+		.policy		= dect_llme_mac_info_policy,
+		.maxtype	= DECTA_MAC_INFO_MAX,
+		.ops		= {
+			[DECT_LLME_REQUEST].doit = dect_llme_scan_request,
+		},
+	},
+	[DECT_LLME_MAC_INFO] = {
+		.policy		= dect_llme_mac_info_policy,
+		.maxtype	= DECTA_MAC_INFO_MAX,
+		.ops		= {
+			[DECT_LLME_REQUEST].doit = dect_llme_mac_info_req,
+			[DECT_LLME_RESPONSE].doit = dect_llme_mac_info_res,
+		},
+	},
+	[DECT_LLME_MAC_RFP_PRELOAD] = {
+		.policy		= dect_llme_mac_info_policy,
+		.maxtype	= DECTA_MAC_INFO_MAX,
+		.ops		= {
+			[DECT_LLME_REQUEST].doit = dect_llme_mac_rfp_preload,
+		},
+	},
+};
+
+static const struct nla_policy dect_llme_policy[DECTA_LLME_MAX + 1] = {
+	[DECTA_LLME_OP]		= { .type = NLA_U8 },
+	[DECTA_LLME_TYPE]	= { .type = NLA_U8 },
+	[DECTA_LLME_DATA]	= { .type = NLA_NESTED },
+};
+
+static int dect_llme_msg(const struct sk_buff *skb,
+			 const struct nlmsghdr *nlh,
+			 const struct nlattr *tb[DECTA_LLME_MAX + 1])
+{
+	const struct dect_llme_link *link;
+	struct dect_cluster *cl;
+	struct dectmsg *dm;
+	enum dect_llme_msg_types type;
+	enum dect_llme_ops op;
+	int err;
+
+	if (tb[DECTA_LLME_OP] == NULL ||
+	    tb[DECTA_LLME_TYPE] == NULL ||
+	    tb[DECTA_LLME_DATA] == NULL)
+		return -EINVAL;
+
+	dm = nlmsg_data(nlh);
+	cl = dect_cluster_get_by_index(dm->dm_index);
+	if (cl == NULL)
+		return -ENODEV;
+
+	type = nla_get_u8(tb[DECTA_LLME_TYPE]);
+	if (type > DECT_LLME_MAX)
+		return -EINVAL;
+	link = &dect_llme_dispatch[type];
+
+	op = nla_get_u8(tb[DECTA_LLME_OP]);
+	switch (op) {
+	case DECT_LLME_REQUEST:
+	case DECT_LLME_INDICATE:
+	case DECT_LLME_RESPONSE:
+	case DECT_LLME_CONFIRM:
+		if (link->ops[op].doit == NULL)
+			return -EOPNOTSUPP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (1) {
+		struct nlattr *nla[link->maxtype + 1];
+
+		err = nla_parse_nested(nla, link->maxtype, tb[DECTA_LLME_DATA],
+				       link->policy);
+		if (err < 0)
+			return err;
+		return link->ops[op].doit(cl, skb, nlh,
+					  (const struct nlattr **)nla);
+	}
+}
+
+/*
+ * Cluster netlink interface
+ */
+
+static int dect_cluster_alloc_index(void)
+{
+	static int index;
+
+	for (;;) {
+		if (++index <= 0)
+			index = 1;
+		if (!dect_cluster_get_by_index(index))
+			return index;
+	}
+}
+
+static int dect_fill_cluster(struct sk_buff *skb,
+			     const struct dect_cluster *cl,
+			     u16 type, u32 pid, u32 seq, u16 flags)
+{
+	struct nlmsghdr *nlh;
+	struct dectmsg *dm;
+	struct nlattr *nest;
+	struct dect_cell_handle *ch;
+
+	nlh = nlmsg_put(skb, pid, seq, type, sizeof(*dm), flags);
+	if (nlh == NULL)
+		return -EMSGSIZE;
+
+	dm = nlmsg_data(nlh);
+	dm->dm_index = cl->index;
+	NLA_PUT_STRING(skb, DECTA_CLUSTER_NAME, cl->name);
+	NLA_PUT_U8(skb, DECTA_CLUSTER_MODE, cl->mode);
+	if (dect_fill_ari(skb, &cl->pari, DECTA_CLUSTER_PARI) < 0)
+		goto nla_put_failure;
+
+	if (!list_empty(&cl->cells)) {
+		nest = nla_nest_start(skb, DECTA_CLUSTER_CELLS);
+		if (nest == NULL)
+			goto nla_put_failure;
+		list_for_each_entry(ch, &cl->cells, list)
+			NLA_PUT_U8(skb, DECTA_LIST_ELEM, ch->rpn);
+		nla_nest_end(skb, nest);
+	}
+
+	return nlmsg_end(skb, nlh);
+
+nla_put_failure:
+	nlmsg_cancel(skb, nlh);
+	return -EMSGSIZE;
+}
+
+static int dect_dump_cluster(struct sk_buff *skb,
+			     struct netlink_callback *cb)
+{
+	const struct dect_cluster *cl;
+	unsigned int idx, s_idx;
+
+	s_idx = cb->args[0];
+	idx = 0;
+	list_for_each_entry(cl, &dect_cluster_list, list) {
+		if (idx < s_idx)
+			goto cont;
+		if (dect_fill_cluster(skb, cl, DECT_NEW_CLUSTER,
+				      NETLINK_CB(cb->skb).pid,
+				      cb->nlh->nlmsg_seq, NLM_F_MULTI) <= 0)
+			break;
+cont:
+		idx++;
+	}
+	cb->args[0] = idx;
+
+	return skb->len;
+}
+
+static void dect_notify_cluster(u16 event, const struct dect_cluster *cl,
+				const struct nlmsghdr *nlh, u32 pid)
+{
+	struct sk_buff *skb;
+	bool report = nlh ? nlmsg_report(nlh) : 0;
+	u32 seq = nlh ? nlh->nlmsg_seq : 0;
+	int err = -ENOBUFS;
+
+	skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		goto err;
+
+	err = dect_fill_cluster(skb, cl, event, pid, seq, NLMSG_DONE);
+	if (err < 0) {
+		WARN_ON(err == -EMSGSIZE);
+		kfree_skb(skb);
+		goto err;
+	}
+	nlmsg_notify(dect_nlsk, skb, pid, DECTNLGRP_CLUSTER, report, GFP_KERNEL);
+err:
+	if (err < 0)
+		netlink_set_err(dect_nlsk, pid, DECTNLGRP_CLUSTER, err);
+}
+
+static const struct nla_policy dect_cluster_policy[DECTA_CLUSTER_MAX + 1] = {
+	[DECTA_CLUSTER_NAME]		= { .type = NLA_STRING, .len = DECTNAMSIZ },
+	[DECTA_CLUSTER_MODE]		= { .type = NLA_U8 },
+	[DECTA_CLUSTER_PARI]		= { .len  = NLA_NESTED },
+};
+
+static int dect_new_cluster(const struct sk_buff *skb,
+			    const struct nlmsghdr *nlh,
+			    const struct nlattr *tb[DECTA_CLUSTER_MAX + 1])
+{
+	struct dect_cluster *cl;
+	struct dect_ari pari;
+	enum dect_cluster_modes uninitialized_var(mode);
+	int err;
+
+	if (tb[DECTA_CLUSTER_NAME] == NULL)
+		return -EINVAL;
+
+	if (tb[DECTA_CLUSTER_MODE] != NULL) {
+		mode = nla_get_u8(tb[DECTA_CLUSTER_MODE]);
+		switch (mode) {
+		case DECT_MODE_FP:
+		case DECT_MODE_PP:
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (tb[DECTA_CLUSTER_PARI] != NULL) {
+		err = dect_nla_parse_ari(&pari, tb[DECTA_CLUSTER_PARI]);
+		if (err < 0)
+			return err;
+	}
+
+	cl = dect_cluster_get_by_name(tb[DECTA_CLUSTER_NAME]);
+	if (cl != NULL) {
+		if (nlh->nlmsg_flags & NLM_F_EXCL)
+			return -EEXIST;
+
+		return 0;
+	}
+
+	if (!(nlh->nlmsg_flags & NLM_F_CREATE))
+		return -ENOENT;
+
+	if (tb[DECTA_CLUSTER_MODE] == NULL)
+		return -EINVAL;
+
+	cl = kzalloc(sizeof(*cl), GFP_KERNEL);
+	if (cl == NULL)
+		return -ENOMEM;
+	nla_strlcpy(cl->name, tb[DECTA_CLUSTER_NAME], sizeof(cl->name));
+
+	memcpy(&cl->pari, &pari, sizeof(cl->pari));
+	cl->index = dect_cluster_alloc_index();
+	cl->mode  = mode;
+
+	err = dect_cluster_init(cl);
+	if (err < 0)
+		goto err1;
+
+	list_add_tail(&cl->list, &dect_cluster_list);
+	dect_notify_cluster(DECT_NEW_CLUSTER, cl, nlh, NETLINK_CB(skb).pid);
+	return 0;
+
+err1:
+	kfree(cl);
+	return err;
+}
+
+static int dect_del_cluster(const struct sk_buff *skb,
+			    const struct nlmsghdr *nlh,
+			    const struct nlattr *tb[DECTA_CLUSTER_MAX + 1])
+{
+	struct dect_cluster *cl;
+	struct dectmsg *dm;
+
+	dm = nlmsg_data(nlh);
+	if (dm->dm_index != 0)
+		cl = dect_cluster_get_by_index(dm->dm_index);
+	else if (tb[DECTA_CLUSTER_NAME] != NULL)
+		cl = dect_cluster_get_by_name(tb[DECTA_CLUSTER_NAME]);
+	else
+		return -EINVAL;
+	if (cl == NULL)
+		return -ENODEV;
+
+	dect_cluster_shutdown(cl);
+	list_del(&cl->list);
+
+	dect_notify_cluster(DECT_DEL_CLUSTER, cl, nlh, NETLINK_CB(skb).pid);
+	kfree(cl);
+	return 0;
+}
+
+static int dect_get_cluster(const struct sk_buff *in_skb,
+			    const struct nlmsghdr *nlh,
+			    const struct nlattr *tb[DECTA_CLUSTER_MAX + 1])
+{
+	u32 pid = NETLINK_CB(in_skb).pid;
+	const struct dect_cluster *cl;
+	struct dectmsg *dm;
+	struct sk_buff *skb;
+	int err;
+
+	dm = nlmsg_data(nlh);
+	if (dm->dm_index != 0)
+		cl = dect_cluster_get_by_index(dm->dm_index);
+	else if (tb[DECTA_CLUSTER_NAME] != NULL)
+		cl = dect_cluster_get_by_name(tb[DECTA_CLUSTER_NAME]);
+	else
+		return -EINVAL;
+	if (cl == NULL)
+		return -ENODEV;
+
+	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		return -ENOMEM;
+	err = dect_fill_cluster(skb, cl, DECT_NEW_CLUSTER, pid,
+			        nlh->nlmsg_seq, NLMSG_DONE);
+	if (err < 0)
+		goto err1;
+	return nlmsg_unicast(dect_nlsk, skb, pid);
+
+err1:
+	kfree_skb(skb);
+	return err;
+}
+
+static const struct dect_netlink_handler dect_cluster_handlers[] = {
+	{
+		/* DECT_NEW_CLUSTER */
+		.policy		= dect_cluster_policy,
+		.maxtype	= DECTA_CLUSTER_MAX,
+		.doit		= dect_new_cluster,
+	},
+	{
+		/* DECT_DEL_CLUSTER */
+		.policy		= dect_cluster_policy,
+		.maxtype	= DECTA_CLUSTER_MAX,
+		.doit		= dect_del_cluster,
+	},
+	{
+		/* DECT_GET_CLUSTER */
+		.policy		= dect_cluster_policy,
+		.maxtype	= DECTA_CLUSTER_MAX,
+		.doit		= dect_get_cluster,
+		.dump		= dect_dump_cluster,
+	},
+	{
+		/* DECT_LLME_MSG */
+		.policy		= dect_llme_policy,
+		.maxtype	= DECTA_LLME_MAX,
+		.doit		= dect_llme_msg,
+	},
+};
+
+static int __init dect_ccf_module_init(void)
+{
+	int err;
+
+	err = dect_bsap_module_init();
+	if (err < 0)
+		goto err1;
+
+	err = dect_ssap_module_init();
+	if (err < 0)
+		goto err2;
+
+	dect_netlink_register_handlers(dect_cluster_handlers, DECT_NEW_CLUSTER,
+				       ARRAY_SIZE(dect_cluster_handlers));
+
+	return 0;
+
+err2:
+	dect_bsap_module_exit();
+err1:
+	return err;
+}
+
+static void __exit dect_ccf_module_exit(void)
+{
+	dect_netlink_unregister_handlers(DECT_NEW_CLUSTER,
+				         ARRAY_SIZE(dect_cluster_handlers));
+	dect_bsap_module_exit();
+	dect_ssap_module_exit();
+}
+
+module_init(dect_ccf_module_init);
+module_exit(dect_ccf_module_exit);
+MODULE_LICENSE("GPL");
diff --git a/net/dect/mac_csf.c b/net/dect/mac_csf.c
new file mode 100644
index 0000000..82302fe
--- /dev/null
+++ b/net/dect/mac_csf.c
@@ -0,0 +1,5143 @@
+/*
+ * DECT MAC Cell Site Functions
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifdef CONFIG_DECT_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <net/dect/dect.h>
+#include <net/dect/mac_csf.h>
+#include <net/dect/ccp.h>
+
+/* avoid <KERN_DEBUG> for continuation lines */
+#undef KERN_DEBUG
+#define KERN_DEBUG
+
+static void dect_notify_cell(u16 event, const struct dect_cell *cell,
+			     const struct nlmsghdr *nlh, u32 pid);
+static void dect_cell_schedule_page(struct dect_cell *cell, u32 mask);
+
+static const u8 dect_fp_preamble[]	= { 0x55, 0x55, 0xe9, 0x8a};
+static const u8 dect_pp_preamble[]	= { 0xaa, 0xaa, 0x16, 0x75};
+
+static const u8 dect_b_field_sizes[] = {
+	[DECT_PACKET_P00]	= 0,
+	[DECT_PACKET_P32]	= 40,
+	[DECT_PACKET_P640j]	= 80,
+	[DECT_PACKET_P80]	= 100,
+};
+
+static u8 dect_b_field_size(const struct dect_channel_desc *chd)
+{
+	return dect_b_field_sizes[chd->pkt];
+}
+
+#define mac_debug(cell, base, fmt, args...) \
+	pr_debug("%s %u.%.2u.%.2u: " fmt, \
+		 (base) == DECT_TIMER_TX ? "TX" : "RX", \
+		 cell->timer_base[(base)].mfn, cell->timer_base[(base)].framenum, \
+		 cell->timer_base[(base)].slot, ## args)
+
+#define rx_debug(cell, fmt, args...) \
+	mac_debug(cell, DECT_TIMER_RX, fmt, ## args)
+#define tx_debug(cell, fmt, args...) \
+	mac_debug(cell, DECT_TIMER_TX, fmt, ## args)
+
+static LIST_HEAD(dect_cell_list);
+
+struct dect_cell *dect_cell_get_by_index(u32 index)
+{
+	struct dect_cell *cell;
+
+	list_for_each_entry(cell, &dect_cell_list, list) {
+		if (cell->index == index)
+			return cell;
+	}
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(dect_cell_get_by_index);
+
+static struct dect_cell *dect_cell_get_by_name(const struct nlattr *nla)
+{
+	struct dect_cell *cell;
+
+	list_for_each_entry(cell, &dect_cell_list, list) {
+		if (!nla_strcmp(nla, cell->name))
+			return cell;
+	}
+	return NULL;
+}
+
+static struct dect_cell *dect_cell(const struct dect_cell_handle *ch)
+{
+	return container_of(ch, struct dect_cell, handle);
+}
+
+/*
+ * MAC CSF layer timers
+ */
+
+#if 0
+#define timer_debug(cell, base, fmt, args...) \
+	mac_debug(cell, base, fmt, ## args)
+#else
+#define timer_debug(cell, base, fmt, args...)
+#endif
+
+static u8 dect_slotnum(const struct dect_cell *cell, enum dect_timer_bases b)
+{
+	return __dect_slotnum(&cell->timer_base[b]);
+}
+
+static u8 dect_framenum(const struct dect_cell *cell, enum dect_timer_bases b)
+{
+	return __dect_framenum(&cell->timer_base[b]);
+}
+
+static u32 dect_mfn(const struct dect_cell *cell, enum dect_timer_bases b)
+{
+	return __dect_mfn(&cell->timer_base[b]);
+}
+
+/* Return whether the TX time is in the next frame relative to the RX time */
+static bool dect_tx_time_wrapped(const struct dect_cell *cell)
+{
+	return dect_slotnum(cell, DECT_TIMER_TX) <
+	       dect_slotnum(cell, DECT_TIMER_RX);
+}
+
+/**
+ * dect_timer_synchronize_framenum
+ *
+ * Synchronize the current frame number based on Q-channel reception.
+ *
+ * Q-channel information is transmitted only in frame 8 and serves as an
+ * indirect indication. The TX frame number update needs to take the clock
+ * difference into account.
+ */
+static void dect_timer_synchronize_framenum(struct dect_cell *cell, u8 framenum)
+{
+	cell->timer_base[DECT_TIMER_RX].framenum = framenum;
+	if (dect_tx_time_wrapped(cell))
+		framenum++;
+	cell->timer_base[DECT_TIMER_TX].framenum = framenum;
+}
+
+static void dect_timer_synchronize_mfn(struct dect_cell *cell, u32 mfn)
+{
+	cell->timer_base[DECT_TIMER_RX].mfn = mfn;
+	cell->timer_base[DECT_TIMER_TX].mfn = mfn;
+	cell->timer_sync_stamp = mfn;
+}
+
+static void dect_run_timers(struct dect_cell *cell, enum dect_timer_bases b)
+{
+	__dect_run_timers(cell->name, &cell->timer_base[b]);
+}
+
+static void dect_timer_base_update(struct dect_cell *cell,
+				   enum dect_timer_bases b, u8 slot)
+{
+	struct dect_timer_base *base = &cell->timer_base[b];
+
+	base->slot = slot;
+	if (base->slot == 0) {
+		base->framenum = dect_next_framenum(base->framenum);
+		if (base->framenum == 0)
+			base->mfn = dect_next_mfn(base->mfn);
+	}
+	timer_debug(cell, base, "update time base\n");
+}
+
+/**
+ * dect_timer_add - (re)schedule a timer
+ *
+ * Frame numbers are relative to the current time, slot positions are absolute.
+ * A timer scheduled for (1, 2) will expire in slot 2 in the next frame.
+ *
+ * A frame number of zero will expire at the next occurence of the slot, which
+ * can be within the same frame in case the slot is not already in the past, or
+ * in the next frame in case it is.
+ */
+static void dect_timer_add(struct dect_cell *cell, struct dect_timer *timer,
+			   enum dect_timer_bases base, u32 frame, u8 slot)
+{
+	timer->cell = cell;
+	__dect_timer_add(cell->name, &cell->timer_base[base], timer, frame, slot);
+}
+
+static void dect_timer_setup(struct dect_timer *timer,
+			     void (*func)(struct dect_cell *, void *),
+			     void *data)
+{
+	dect_timer_init(timer);
+	timer->cb.cell = func;
+	timer->data    = data;
+}
+
+/*
+ * Basic Channel lists
+ *
+ * A channel list contains channel descriptions of all physical channels
+ * able to carry the packet type, sorted into multiple bins based on the
+ * maximum RSSI value of the TDD slot pair.
+ *
+ * At any time, only a single incomplete channel list exists that is updated
+ * based on the RSSI measurements gathered by the individual IRC instances.
+ * Once a list is complete, it is added to the list of active channel lists,
+ * replacing the previous one for the same packet type, if any.
+ */
+
+#if 1
+#define chl_debug(cell, chl, fmt, args...) \
+	rx_debug(cell, "channel-list %s (%u): " fmt, \
+		 (chl)->pkt == DECT_PACKET_P00 ? "P00" : \
+		 (chl)->pkt == DECT_PACKET_P08 ? "P08" : \
+		 (chl)->pkt == DECT_PACKET_P32 ? "P32" : "?", \
+		 (chl)->available, ## args)
+#else
+#define chl_debug(cell, chl, fmt, args...)
+#endif
+
+static int dect_chl_schedule_update(struct dect_cell *cell,
+				    enum dect_packet_types pkt);
+
+static struct dect_channel_list *dect_chl_lookup(const struct dect_cell *cell,
+						 enum dect_packet_types pkt)
+{
+	struct dect_channel_list *chl;
+
+	list_for_each_entry(chl, &cell->chanlists, list) {
+		if (chl->pkt == pkt)
+			return chl;
+	}
+	return NULL;
+}
+
+static void dect_chl_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_channel_list *chl = data;
+
+	if (dect_chl_schedule_update(cell, chl->pkt) < 0)
+		dect_timer_add(cell, &chl->timer, DECT_TIMER_RX, 1, 0);
+}
+
+static void dect_chl_release(struct dect_channel_list *chl)
+{
+	dect_timer_del(&chl->timer);
+	kfree(chl);
+}
+
+static struct dect_channel_list *dect_chl_init(struct dect_cell *cell,
+					       enum dect_packet_types pkt)
+{
+	struct dect_channel_list *chl;
+	unsigned int entries, i;
+
+	entries = DECT_CARRIER_NUM * DECT_HALF_FRAME_SIZE;
+	chl = kzalloc(sizeof(*chl) + entries * sizeof(chl->entries[0]), GFP_ATOMIC);
+	if (chl == NULL)
+		return NULL;
+	chl->pkt = pkt;
+	dect_timer_setup(&chl->timer, dect_chl_timer, chl);
+	for (i = 0; i < ARRAY_SIZE(chl->bins); i++)
+		INIT_LIST_HEAD(&chl->bins[i]);
+	for (i = 0; i < entries; i++)
+		INIT_LIST_HEAD(&chl->entries[i].list);
+	return chl;
+}
+
+static int dect_chl_schedule_update(struct dect_cell *cell,
+				    enum dect_packet_types pkt)
+{
+	struct dect_channel_list *chl;
+
+	list_for_each_entry(chl, &cell->chl_pending, list) {
+		if (chl->pkt == pkt)
+			return 0;
+	}
+
+	chl = dect_chl_init(cell, pkt);
+	if (chl == NULL)
+		return -ENOMEM;
+	chl_debug(cell, chl, "schedule update\n");
+	list_add_tail(&chl->list, &cell->chl_pending);
+	return 0;
+}
+
+static struct dect_channel_list *dect_chl_get_pending(struct dect_cell *cell)
+{
+	struct dect_channel_list *chl;
+
+	if (list_empty(&cell->chl_pending))
+		return NULL;
+	chl = list_first_entry(&cell->chl_pending,
+			       struct dect_channel_list,
+			       list);
+	list_del(&chl->list);
+	return chl;
+}
+
+static void dect_chl_update(struct dect_cell *cell,
+			    struct dect_channel_list *chl,
+			    const struct dect_channel_desc *chd, u8 rssi)
+{
+	struct dect_channel_list_entry *e;
+	u8 slot, bin;
+
+	if (rssi > dect_dbm_to_rssi(DECT_CHANNEL_LIST_MAX_DBM)) {
+		chl_debug(cell, chl, "carrier %u slot %u: too much noise: RSSI %u\n",
+			  chd->carrier, chd->slot, rssi);
+		return;
+	}
+
+	slot = chd->slot < 12 ? chd->slot : chd->slot - 12;
+	chl_debug(cell, chl, "update carrier %u slot %u pos %u RSSI %u\n",
+		  chd->carrier, chd->slot, slot, rssi);
+
+	e = &chl->entries[chd->carrier * DECT_HALF_FRAME_SIZE + slot];
+	if (!list_empty(&e->list))
+		return;
+
+	if (chd->slot < DECT_HALF_FRAME_SIZE) {
+		e->slot    = slot;
+		e->carrier = chd->carrier;
+		e->rssi    = rssi;
+	} else if (e->rssi != 0) {
+		e->rssi = max(e->rssi, rssi);
+		bin = rssi * ARRAY_SIZE(chl->bins) / (DECT_RSSI_RANGE + 1);
+
+		list_add_tail(&e->list, &chl->bins[bin]);
+		chl->available++;
+	}
+}
+
+static void dect_chl_update_carrier(struct dect_cell *cell, u8 carrier)
+{
+	struct dect_channel_list *chl, *old;
+
+	chl = cell->chl;
+	chl_debug(cell, chl, "update status %llx rfcars %x carrier %u\n",
+		  (unsigned long long)chl->status, cell->si.ssi.rfcars, carrier);
+
+	chl->status |= 1ULL << carrier;
+	if (chl->status != cell->si.ssi.rfcars)
+		return;
+	cell->chl = NULL;
+
+	chl_debug(cell, chl, "complete %u entries\n", chl->available);
+	old = dect_chl_lookup(cell, chl->pkt);
+	if (old != NULL) {
+		list_del(&old->list);
+		dect_chl_release(old);
+	}
+
+	dect_timer_add(cell, &chl->timer, DECT_TIMER_RX,
+		       DECT_CHANNEL_LIST_MAX_AGE * 2 / 3 *
+		       DECT_FRAMES_PER_SECOND, 0);
+	list_add_tail(&chl->list, &cell->chanlists);
+}
+
+/**
+ * dect_channel_delay - calculate delay in frames until a channel is accessible
+ *
+ * Calculate the delay in frames until one of the remote sides' scans is on the
+ * specified carrier.
+ *
+ * A FP maintains one to three scans, which lag behind each other by three
+ * carriers, a PP maintains zero or one (fast-setup) scan. The PP fast-
+ * setup scan leads the FP primary scan by one carrier.
+ *
+ * Setup needs at least one full frame, therefore a scan reaching a carrier
+ * earlier than that must be treated as reachable one cycle later.
+ */
+static u8 dect_channel_delay(const struct dect_cell *cell,
+			     const struct dect_channel_desc *chd)
+{
+	u64 rfcars = cell->si.ssi.rfcars;
+	u8 i, txs, scn, frames;
+	s8 d;
+
+	if (cell->mode == DECT_MODE_FP) {
+		/* PP fast-setup scan */
+		scn = dect_next_carrier(rfcars, cell->si.ssi.pscn);
+		txs = 1;
+	} else {
+		/* FP primary scan */
+		scn = dect_prev_carrier(rfcars, cell->si.ssi.pscn);
+		txs = min(cell->si.ssi.txs + 1, 3);
+	}
+
+	frames = ~0;
+	for (i = 0; i < txs; i++) {
+		d = dect_carrier_distance(rfcars, scn, chd->carrier);
+#if 0
+		if (dect_slotnum(cell, DECT_TIMER_TX) >= chd->slot)
+			d--;
+#endif
+		/* More than two frames in the future? */
+		if (d <= DECT_CHANNEL_MIN_DELAY)
+			d += hweight64(rfcars);
+
+		frames = min_t(u8, frames, d);
+		pr_debug("rfcars %llx distance %u->%u slot %u: %u frames %u\n",
+			 (unsigned long long)rfcars, scn, chd->carrier,
+			 chd->slot, d, frames);
+
+		scn = dect_carrier_sub(rfcars, scn, 3);
+	}
+
+	return frames;
+}
+
+static void dect_update_blind_full_slots(struct dect_cell *cell)
+{
+	u16 bfs;
+
+	bfs = ~(cell->trg.blind_full_slots | (cell->trg.blind_full_slots >> 12));
+	cell->trg_blind_full_slots = bfs & ((1 << DECT_HALF_FRAME_SIZE) - 1);
+
+	dect_cell_schedule_page(cell, 1 << DECT_TM_TYPE_BFS);
+}
+
+/**
+ * dect_channel_reserve - reserve a channel on a transceiver
+ *
+ * Reserve the specified transceiver and schedule a blind-full-slots
+ * page message if the visibility state changed.
+ */
+static void dect_channel_reserve(struct dect_cell *cell,
+				 struct dect_transceiver *trx,
+				 const struct dect_channel_desc *chd)
+{
+	if (!dect_transceiver_reserve(&cell->trg, trx, chd))
+		return;
+
+	dect_update_blind_full_slots(cell);
+}
+
+/**
+ * dect_channel_release - release a channel on a transceiver
+ *
+ * Release the specified transceiver and schedule a blind-full-slots
+ * page message if the visibility state changed.
+ */
+static void dect_channel_release(struct dect_cell *cell,
+				 struct dect_transceiver *trx,
+				 const struct dect_channel_desc *chd)
+{
+	if (!dect_transceiver_release(&cell->trg, trx, chd))
+		return;
+
+	dect_update_blind_full_slots(cell);
+}
+
+/**
+ * dect_select_transceiver - select a transceiver for placing a bearer
+ *
+ * Select the lowest order transceiver that is able to operate on a physical
+ * channel.
+ */
+static struct dect_transceiver *
+dect_select_transceiver(const struct dect_cell *cell,
+			const struct dect_channel_desc *chd)
+{
+	struct dect_transceiver *trx;
+
+	dect_foreach_transceiver_reverse(trx, &cell->trg) {
+		if (trx->state != DECT_TRANSCEIVER_LOCKED)
+			continue;
+		if (!dect_transceiver_channel_available(trx, chd))
+			continue;
+		return trx;
+	}
+	return NULL;
+}
+
+/**
+ * dect_select_channel - select a physical channel for bearer setup
+ *
+ * @cell:	DECT cell
+ * @trx:	selected transceiver
+ * @chd:	channel description
+ * @rssi:	last measure RSSI value of selected channel
+ * @quick:	prefer quickly accessible channel
+ *
+ * This performs the common steps of channel selection based on channel lists.
+ * In "quick" mode, the selected channel is the first channel accessible within
+ * three TDMA frames from the lowest three available bands. When not in quick
+ * mode or when no channel is accessible within three frames, the first
+ * available channel from the lowest available band is selected.
+ *
+ * "quick" mode is used for setting up pilot bearers and for bearer handover.
+ *
+ * The returned channel description is within the normal transmit half
+ * of the cell's mode.
+ */
+static int dect_select_channel(struct dect_cell *cell,
+			       struct dect_transceiver **trxp,
+			       struct dect_channel_desc *chd, u8 *rssi,
+			       bool quick)
+{
+	struct dect_channel_list_entry *e, *sel;
+	struct dect_channel_list *chl;
+	struct dect_transceiver *trx, *uninitialized_var(tsel);
+	u8 bin, first, last;
+
+	chl = dect_chl_lookup(cell, chd->pkt);
+	if (chl == NULL)
+		return -ENOENT;
+
+	/* Find first non-empty bin */
+	for (first = 0; first < ARRAY_SIZE(chl->bins); first++) {
+		if (!list_empty(&chl->bins[first]))
+			break;
+	}
+	if (first == ARRAY_SIZE(chl->bins))
+		return -ENOSPC;
+
+	sel = NULL;
+retry:
+	last = max_t(u8, first + quick ? 3 : 1, ARRAY_SIZE(chl->bins));
+	for (bin = first; sel == NULL && bin < last; bin++) {
+		list_for_each_entry(e, &chl->bins[bin], list) {
+			u8 n = DECT_HALF_FRAME_SIZE - 1 - e->slot;
+
+			if (!(cell->trg_blind_full_slots & (1 << n)))
+				continue;
+
+			if (cell->mode == DECT_MODE_PP &&
+			    !(cell->blind_full_slots & (1 << n)))
+				continue;
+
+			chd->carrier = e->carrier;
+			chd->slot = dect_normal_transmit_base(cell->mode) + e->slot;
+			if (quick && dect_channel_delay(cell, chd) > 3)
+				continue;
+
+			trx = dect_select_transceiver(cell, chd);
+			if (trx == NULL)
+				continue;
+			if (sel != NULL) {
+				if (trx->index < tsel->index)
+					continue;
+				if (sel->rssi < e->rssi)
+					continue;
+			}
+
+			sel  = e;
+			tsel = trx;
+
+			/* Stop searching if this is the best possible choice */
+			if (tsel->index == hweight16(cell->trg.trxmask))
+				break;
+		}
+	}
+
+	if (sel == NULL) {
+		/* Check the first band again without considering delay when
+		 * no quickly accessible channel is available within the first
+		 * three bands. */
+		if (quick) {
+			quick = false;
+			goto retry;
+		}
+		return -ENOSPC;
+	}
+
+	list_del_init(&sel->list);
+	chl->available--;
+	if (chl->available < DECT_CHANNEL_LIST_LOW_WATERMARK)
+		dect_chl_schedule_update(cell, chl->pkt);
+
+	chd->carrier = sel->carrier;
+	chd->slot = dect_normal_transmit_base(cell->mode) + sel->slot;
+	chl_debug(cell, chl, "select channel: carrier %u slot %u RSSI %u\n",
+		  chd->carrier,	chd->slot, sel->rssi);
+
+	*rssi = sel->rssi;
+	*trxp = tsel;
+	return 0;
+}
+
+static struct dect_dbc *dect_dbc_get(const struct dect_cell *cell)
+{
+	if (list_empty(&cell->dbcs))
+		return NULL;
+	return list_first_entry(&cell->dbcs, struct dect_dbc, list);
+}
+
+
+/*
+ * Tail message parsing/construction
+ */
+
+static enum dect_tail_identifications dect_parse_tail(const struct sk_buff *skb)
+{
+	return skb->data[DECT_HDR_TA_OFF] & DECT_HDR_TA_MASK;
+}
+
+static enum dect_b_identifications dect_parse_b_id(const struct sk_buff *skb)
+{
+	return skb->data[DECT_HDR_TA_OFF] & DECT_HDR_BA_MASK;
+}
+
+static int dect_parse_identities_information(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_idi *idi = &tm->idi;
+	u8 ari_len, rpn_len;
+
+	ari_len = dect_parse_ari(&idi->pari, t << DECT_RFPI_ARI_SHIFT);
+	if (ari_len == 0)
+		return -1;
+	rpn_len = BITS_PER_BYTE * DECT_NT_ID_RFPI_LEN - 1 - ari_len;
+
+	idi->e   = (t & DECT_RFPI_E_FLAG);
+	idi->rpn = (t >> DECT_RFPI_RPN_SHIFT) & ((1 << rpn_len) - 1);
+	tm->type = DECT_TM_TYPE_ID;
+
+	pr_debug("identities information: e: %u class: %u emc: %.4x "
+		 "fpn: %.5x rpn: %x\n", idi->e, idi->pari.arc,
+		 idi->pari.emc, idi->pari.fpn, idi->rpn);
+	return 0;
+}
+
+static u64 dect_build_identities_information(const struct dect_idi *idi)
+{
+	return dect_build_rfpi(idi);
+}
+
+static int dect_parse_static_system_information(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_ssi *ssi = &tm->ssi;
+
+	ssi->nr	    = (t & DECT_QT_SSI_NR_FLAG);
+	ssi->sn     = (t & DECT_QT_SSI_SN_MASK) >> DECT_QT_SSI_SN_SHIFT;
+	ssi->sp     = (t & DECT_QT_SSI_SP_MASK) >> DECT_QT_SSI_SP_SHIFT;
+	ssi->txs    = (t & DECT_QT_SSI_TXS_MASK) >> DECT_QT_SSI_TXS_SHIFT;
+	ssi->mc     = (t & DECT_QT_SSI_MC_FLAG);
+	ssi->rfcars = (t & DECT_QT_SSI_RFCARS_MASK) >> DECT_QT_SSI_RFCARS_SHIFT;
+	ssi->cn     = (t & DECT_QT_SSI_CN_MASK) >> DECT_QT_SSI_CN_SHIFT;
+	ssi->pscn   = (t & DECT_QT_SSI_PSCN_MASK) >> DECT_QT_SSI_PSCN_SHIFT;
+
+	if (ssi->sn > 11 || ssi->cn > 9 || ssi->pscn > 9 || ssi->rfcars == 0)
+		return -1;
+	tm->type = DECT_TM_TYPE_SSI;
+
+	pr_debug("static system information: nr: %u sn: %u cn: %u pscn: %u\n",
+		 ssi->nr, ssi->sn, ssi->cn, ssi->pscn);
+	return 0;
+}
+
+static u64 dect_build_static_system_information(const struct dect_ssi *ssi)
+{
+	u64 t = 0;
+
+	t |= ssi->nr ? DECT_QT_SSI_NR_FLAG : 0;
+	t |= (u64)ssi->sn << DECT_QT_SSI_SN_SHIFT;
+	t |= (u64)ssi->sp << DECT_QT_SSI_SP_SHIFT;
+	t |= (u64)ssi->txs << DECT_QT_SSI_TXS_SHIFT;
+	t |= (u64)ssi->cn << DECT_QT_SSI_CN_SHIFT;
+	t |= ssi->mc ? DECT_QT_SSI_MC_FLAG : 0;
+	t |= (u64)ssi->rfcars << DECT_QT_SSI_RFCARS_SHIFT;
+	t |= (u64)ssi->pscn << DECT_QT_SSI_PSCN_SHIFT;
+	t |= DECT_QT_SI_SSI;
+	return t;
+}
+
+static int dect_parse_extended_rf_carrier_information(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_erfc *erfc = &tm->erfc;
+
+	erfc->rfcars	 = (t & DECT_QT_ERFC_RFCARS_MASK) >>
+			   DECT_QT_ERFC_RFCARS_SHIFT;
+	erfc->band	 = (t & DECT_QT_ERFC_RFBAND_MASK) >>
+			   DECT_QT_ERFC_RFBAND_SHIFT;
+	erfc->num_rfcars = (t & DECT_QT_ERFC_NUM_RFCARS_MASK) >
+			   DECT_QT_ERFC_NUM_RFCARS_SHIFT;
+	tm->type = DECT_TM_TYPE_ERFC;
+
+	pr_debug("extended rf carrier information: rfcars %.6x band %u num %u\n",
+		 erfc->rfcars, erfc->band, erfc->num_rfcars);
+	return 0;
+}
+
+static u64 dect_build_extended_rf_carrier_information(const struct dect_erfc *erfc)
+{
+	u64 t = 0;
+
+	t |= (u64)erfc->rfcars << DECT_QT_ERFC_RFCARS_SHIFT;
+	t |= (u64)erfc->band << DECT_QT_ERFC_RFBAND_SHIFT;
+	t |= (u64)erfc->num_rfcars << DECT_QT_ERFC_NUM_RFCARS_SHIFT;
+	t |= DECT_QT_SI_ERFC;
+	return t;
+}
+
+static int dect_parse_fixed_part_capabilities(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_fpc *fpc = &tm->fpc;
+
+	fpc->fpc = (t & DECT_QT_FPC_CAPABILITY_MASK) >>
+		   DECT_QT_FPC_CAPABILITY_SHIFT;
+	fpc->hlc = (t & DECT_QT_FPC_HLC_MASK) >> DECT_QT_FPC_HLC_SHIFT;
+	tm->type = DECT_TM_TYPE_FPC;
+
+	pr_debug("fixed part capabilities: fpc: %.5x hlc: %.4x\n",
+		 fpc->fpc, fpc->hlc);
+	return 0;
+}
+
+static u64 dect_build_fixed_part_capabilities(const struct dect_fpc *fpc)
+{
+	u64 t = 0;
+
+	t |= (u64)fpc->fpc << DECT_QT_FPC_CAPABILITY_SHIFT;
+	t |= (u64)fpc->hlc << DECT_QT_FPC_HLC_SHIFT;
+	t |= DECT_QT_SI_FPC;
+	return t;
+}
+
+static int dect_parse_extended_fixed_part_capabilities(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_efpc *efpc = &tm->efpc;
+
+	efpc->fpc = (t & DECT_QT_EFPC_EFPC_MASK) >> DECT_QT_EFPC_EFPC_SHIFT;
+	efpc->hlc = (t & DECT_QT_EFPC_EHLC_MASK) >> DECT_QT_EFPC_EHLC_SHIFT;
+	tm->type  = DECT_TM_TYPE_EFPC;
+
+	pr_debug("extended fixed part capabilities: fpc: %.5x hlc: %.6x\n",
+		 efpc->fpc, efpc->hlc);
+	return 0;
+}
+
+static u64 dect_build_extended_fixed_part_capabilities(const struct dect_efpc *efpc)
+{
+	u64 t = 0;
+
+	t |= (u64)efpc->fpc << DECT_QT_EFPC_EFPC_SHIFT;
+	t |= (u64)efpc->hlc << DECT_QT_EFPC_EHLC_SHIFT;
+	t |= DECT_QT_SI_EFPC;
+	return t;
+}
+
+static int dect_parse_extended_fixed_part_capabilities2(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_efpc2 *efpc2 = &tm->efpc2;
+
+	efpc2->fpc = (t & DECT_QT_EFPC2_FPC_MASK) >> DECT_QT_EFPC2_FPC_SHIFT;
+	efpc2->hlc = (t & DECT_QT_EFPC2_HLC_MASK) >> DECT_QT_EFPC2_HLC_SHIFT;
+	tm->type   = DECT_TM_TYPE_EFPC2;
+
+	pr_debug("extended fixed part capabilities2: fpc: %x hlc: %x\n",
+		 efpc2->fpc, efpc2->hlc);
+	return 0;
+}
+
+static u64 dect_build_extended_fixed_part_capabilities2(const struct dect_efpc2 *efpc2)
+{
+	u64 t = 0;
+
+	t |= (u64)efpc2->fpc << DECT_QT_EFPC2_FPC_SHIFT;
+	t |= (u64)efpc2->hlc << DECT_QT_EFPC2_HLC_SHIFT;
+	t |= DECT_QT_SI_EFPC2;
+	return t;
+}
+
+static int dect_parse_sari(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_sari *sari = &tm->sari;
+
+	sari->list_cycle = (((t & DECT_QT_SARI_LIST_CYCLE_MASK) >>
+			     DECT_QT_SARI_LIST_CYCLE_SHIFT) + 1) * 2;
+	sari->tari  = (t & DECT_QT_SARI_TARI_FLAG);
+	sari->black = (t & DECT_QT_SARI_BLACK_FLAG);
+	dect_parse_ari(&sari->ari, t << DECT_QT_SARI_ARI_SHIFT);
+	tm->type = DECT_TM_TYPE_SARI;
+
+	pr_debug("sari: cycle %u tari: %u black: %u\n",
+		 sari->list_cycle, sari->tari, sari->black);
+	return 0;
+}
+
+static u64 dect_build_sari(const struct dect_sari *sari)
+{
+	u64 t = 0;
+
+	t |= sari->tari ? DECT_QT_SARI_TARI_FLAG : 0;
+	t |= sari->black ? DECT_QT_SARI_BLACK_FLAG : 0;
+	t |= dect_build_ari(&sari->ari) >> DECT_QT_SARI_ARI_SHIFT;
+	t |= DECT_QT_SI_SARI;
+	return t;
+}
+
+static int dect_parse_multiframe_number(struct dect_tail_msg *tm, u64 t)
+{
+	tm->mfn.num = (t & DECT_QT_MFN_MASK) >> DECT_QT_MFN_SHIFT;
+	tm->type = DECT_TM_TYPE_MFN;
+
+	pr_debug("multi-frame number: %u\n", tm->mfn.num);
+	return 0;
+}
+
+static u64 dect_build_multiframe_number(const struct dect_mfn *mfn)
+{
+	u64 t = 0;
+
+	t |= (u64)mfn->num << DECT_QT_MFN_SHIFT;
+	t |= DECT_QT_SI_MFN;
+	return t;
+}
+
+static int dect_parse_system_information(struct dect_tail_msg *tm, u64 t)
+{
+	/* clear of memcmp */
+	memset(((void *)tm) + offsetof(struct dect_tail_msg, ssi), 0, 
+	       sizeof(*tm) - offsetof(struct dect_tail_msg, ssi));
+
+	switch (t & DECT_QT_H_MASK) {
+	case DECT_QT_SI_SSI:
+	case DECT_QT_SI_SSI2:
+		return dect_parse_static_system_information(tm, t);
+	case DECT_QT_SI_ERFC:
+		return dect_parse_extended_rf_carrier_information(tm, t);
+	case DECT_QT_SI_FPC:
+		return dect_parse_fixed_part_capabilities(tm, t);
+	case DECT_QT_SI_EFPC:
+		return dect_parse_extended_fixed_part_capabilities(tm, t);
+	case DECT_QT_SI_EFPC2:
+		return dect_parse_extended_fixed_part_capabilities2(tm, t);
+	case DECT_QT_SI_SARI:
+		return dect_parse_sari(tm, t);
+	case DECT_QT_SI_MFN:
+		return dect_parse_multiframe_number(tm, t);
+	default:
+		pr_debug("unknown system information type %llx\n",
+			 (unsigned long long)t & DECT_QT_H_MASK);
+		return -1;
+	}
+}
+
+static int dect_parse_blind_full_slots(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_bfs *bfs = &tm->bfs;
+
+	bfs->mask = (t & DECT_PT_BFS_MASK) >> DECT_PT_BFS_SHIFT;
+	tm->type = DECT_TM_TYPE_BFS;
+
+	pr_debug("page: RFPI: %.3x blind full slots: %.3x\n",
+		 tm->page.rfpi, bfs->mask);
+	return 0;
+}
+
+static u64 dect_build_blind_full_slots(const struct dect_bfs *bfs)
+{
+	u64 t = 0;
+
+	t |= (u64)bfs->mask << DECT_PT_BFS_SHIFT;
+	t |= DECT_PT_IT_BLIND_FULL_SLOT;
+	return t;
+}
+
+static int dect_parse_bearer_description(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_bearer_desc *bd = &tm->bd;
+
+	bd->bt = (t & DECT_PT_INFO_TYPE_MASK);
+	bd->sn = (t & DECT_PT_BEARER_SN_MASK) >> DECT_PT_BEARER_SN_SHIFT;
+	bd->sp = (t & DECT_PT_BEARER_SP_MASK) >> DECT_PT_BEARER_SP_SHIFT;
+	bd->cn = (t & DECT_PT_BEARER_CN_MASK) >> DECT_PT_BEARER_CN_SHIFT;
+	if (bd->sn >= DECT_HALF_FRAME_SIZE)
+		return -1;
+	tm->type = DECT_TM_TYPE_BD;
+
+	pr_debug("page: RFPI: %.3x bearer description: bt: %llx sn: %u sp: %u cn: %u\n",
+		 tm->page.rfpi, (unsigned long long)bd->bt, bd->sn, bd->sp, bd->cn);
+	return 0;
+}
+
+static u64 dect_build_bearer_description(const struct dect_bearer_desc *bd)
+{
+	u64 t = 0;
+
+	t |= (u64)bd->sn << DECT_PT_BEARER_SN_SHIFT;
+	t |= (u64)bd->sp << DECT_PT_BEARER_SP_SHIFT;
+	t |= (u64)bd->cn << DECT_PT_BEARER_CN_SHIFT;
+	t |= bd->bt;
+	return t;
+}
+
+static int dect_parse_rfp_identity(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_rfp_id *id = &tm->rfp_id;
+
+	id->id = (t & DECT_PT_RFP_ID_MASK) >> DECT_PT_RFP_ID_SHIFT;
+	tm->type = DECT_TM_TYPE_RFP_ID;
+
+	pr_debug("page: RFPI: %.3x RFP identity: %.3x\n",
+		 tm->page.rfpi, id->id);
+	return 0;
+}
+
+static u64 dect_build_rfp_identity(const struct dect_rfp_id *id)
+{
+	u64 t = 0;
+
+	t |= (u64)id->id << DECT_PT_RFP_ID_SHIFT;
+	t |= DECT_PT_IT_RFP_IDENTITY;
+	return t;
+}
+
+static int dect_parse_rfp_status(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_rfp_status *st = &tm->rfp_status;
+
+	st->rfp_busy = t & DECT_PT_RFPS_RFP_BUSY_FLAG;
+	st->sys_busy = t & DECT_PT_RFPS_SYS_BUSY_FLAG;
+	tm->type = DECT_TM_TYPE_RFP_STATUS;
+
+	pr_debug("page: RFPI: %.3x RFP status: rfp_busy: %d sys_busy: %d\n",
+		 tm->page.rfpi, st->rfp_busy, st->sys_busy);
+	return 0;
+}
+
+static u64 dect_build_rfp_status(const struct dect_rfp_status *st)
+{
+	u64 t = 0;
+
+	t |= st->rfp_busy ? DECT_PT_RFPS_RFP_BUSY_FLAG : 0;
+	t |= st->sys_busy ? DECT_PT_RFPS_SYS_BUSY_FLAG : 0;
+	t |= DECT_PT_IT_RFP_STATUS;
+	return t;
+}
+
+static int dect_parse_active_carriers(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_active_carriers *ac = &tm->active_carriers;
+
+	ac->active = (t & DECT_PT_ACTIVE_CARRIERS_MASK) >>
+		     DECT_PT_ACTIVE_CARRIERS_SHIFT;
+	tm->type = DECT_TM_TYPE_ACTIVE_CARRIERS;
+
+	pr_debug("page: RFPI: %.3x active carriers: %.3x\n",
+		 tm->page.rfpi, ac->active);
+	return 0;
+}
+
+static u64 dect_build_active_carriers(const struct dect_active_carriers *ac)
+{
+	u64 t = 0;
+
+	t |= (u64)ac->active << DECT_PT_ACTIVE_CARRIERS_SHIFT;
+	t |= DECT_PT_IT_ACTIVE_CARRIERS;
+	return t;
+}
+
+static int dect_parse_paging_info(struct dect_tail_msg *tm, u64 t)
+{
+	switch (t & DECT_PT_INFO_TYPE_MASK) {
+	case DECT_PT_IT_BLIND_FULL_SLOT:
+		return dect_parse_blind_full_slots(tm, t);
+	case DECT_PT_IT_OTHER_BEARER:
+	case DECT_PT_IT_RECOMMENDED_OTHER_BEARER:
+	case DECT_PT_IT_GOOD_RFP_BEARER:
+	case DECT_PT_IT_DUMMY_OR_CL_BEARER_POSITION:
+	case DECT_PT_IT_CL_BEARER_POSITION:
+		return dect_parse_bearer_description(tm, t);
+	case DECT_PT_IT_RFP_IDENTITY:
+		return dect_parse_rfp_identity(tm, t);
+	case DECT_PT_IT_DUMMY_OR_CL_BEARER_MARKER:
+		pr_debug("dummy or cl bearer marker\n");
+		return 0;
+	case DECT_PT_IT_RFP_STATUS:
+		return dect_parse_rfp_status(tm, t);
+	case DECT_PT_IT_ACTIVE_CARRIERS:
+		return dect_parse_active_carriers(tm, t);
+	default:
+		pr_debug("unknown MAC page info %llx\n",
+			 (unsigned long long)t & DECT_PT_INFO_TYPE_MASK);
+		return -1;
+	}
+}
+
+static int dect_parse_paging_msg(struct dect_tail_msg *tm, u64 t)
+{
+	tm->page.extend = t & DECT_PT_HDR_EXTEND_FLAG;
+	tm->page.length = t & DECT_PT_HDR_LENGTH_MASK;
+
+	switch (tm->page.length) {
+	case DECT_PT_ZERO_PAGE:
+		tm->page.rfpi = (t & DECT_PT_ZP_RFPI_MASK) >>
+				DECT_PT_ZP_RFPI_SHIFT;
+
+		return dect_parse_paging_info(tm, t);
+	case DECT_PT_SHORT_PAGE:
+		tm->page.rfpi = 0;
+		return dect_parse_paging_info(tm, t);
+	case DECT_PT_FULL_PAGE:
+	case DECT_PT_LONG_PAGE:
+	case DECT_PT_LONG_PAGE_FIRST:
+	case DECT_PT_LONG_PAGE_LAST:
+	case DECT_PT_LONG_PAGE_ALL:
+		tm->type = DECT_TM_TYPE_PAGE;
+		pr_debug("full/long page: extend: %u length: %llx\n",
+			 tm->page.extend, (unsigned long long)tm->page.length);
+		return 0;
+	default:
+		pr_debug("invalid page length %llx\n",
+			 (unsigned long long)tm->page.length);
+		return -1;
+	}
+}
+
+static int dect_parse_cctrl_common(struct dect_cctrl *cctl, u64 t)
+{
+	cctl->fmid = (t & DECT_CCTRL_FMID_MASK) >> DECT_CCTRL_FMID_SHIFT;
+	cctl->pmid = (t & DECT_CCTRL_PMID_MASK) >> DECT_CCTRL_PMID_SHIFT;
+
+	pr_debug("cctrl: cmd: %llx fmid: %.3x pmid: %.5x\n",
+		 (unsigned long long)cctl->cmd, cctl->fmid, cctl->pmid);
+	return 0;
+}
+
+static u64 dect_build_cctrl_common(const struct dect_cctrl *cctl)
+{
+	u64 t = 0;
+
+	t |= cctl->cmd;
+	t |= (u64)cctl->fmid << DECT_CCTRL_FMID_SHIFT;
+	t |= (u64)cctl->pmid << DECT_CCTRL_PMID_SHIFT;
+	return t;
+}
+
+static int dect_parse_cctrl_attr(struct dect_cctrl *cctl, u64 t)
+{
+	cctl->ecn        = (t & DECT_CCTRL_ATTR_ECN_MASK) >> DECT_CCTRL_ATTR_ECN_SHIFT;
+	cctl->lbn        = (t & DECT_CCTRL_ATTR_LBN_MASK) >> DECT_CCTRL_ATTR_LBN_SHIFT;
+	cctl->type       = (t & DECT_CCTRL_ATTR_TYPE_MASK) >> DECT_CCTRL_ATTR_TYPE_SHIFT;
+	cctl->service    = (t & DECT_CCTRL_ATTR_SERVICE_MASK) >> DECT_CCTRL_ATTR_SERVICE_SHIFT;
+	cctl->slot       = (t & DECT_CCTRL_ATTR_SLOT_MASK) >> DECT_CCTRL_ATTR_SLOT_SHIFT;
+	cctl->cf         = (t & DECT_CCTRL_ATTR_CF_FLAG);
+	cctl->a_mod      = (t & DECT_CCTRL_ATTR_A_MOD_MASK) >> DECT_CCTRL_ATTR_A_MOD_SHIFT;
+	cctl->bz_mod     = (t & DECT_CCTRL_ATTR_BZ_MOD_MASK) >> DECT_CCTRL_ATTR_BZ_MOD_SHIFT;
+	cctl->bz_ext_mod = (t & DECT_CCTRL_ATTR_BZ_EXT_MOD_MASK) >> DECT_CCTRL_ATTR_BZ_EXT_MOD_SHIFT;
+	cctl->acr        = (t & DECT_CCTRL_ATTR_ACR_MASK) >> DECT_CCTRL_ATTR_ACR_SHIFT;
+
+	pr_debug("cctrl: cmd: %llx ecn: %x lbn: %x type: %x "
+		 "service: %x slot: %x cf %d a_mod %x bz_mod %x bz_ext_mod %x acr %x\n",
+		 (unsigned long long)cctl->cmd, cctl->ecn, cctl->lbn,
+		 cctl->type, cctl->service, cctl->slot, cctl->cf,
+		 cctl->a_mod, cctl->bz_mod, cctl->bz_ext_mod, cctl->acr);
+	return 0;
+}
+
+static u64 dect_build_cctrl_attr(const struct dect_cctrl *cctl)
+{
+	u64 t = 0;
+
+	t |= cctl->cmd;
+	t |= (u64)cctl->ecn << DECT_CCTRL_ATTR_ECN_SHIFT;
+	t |= (u64)cctl->lbn << DECT_CCTRL_ATTR_LBN_SHIFT;
+	t |= (u64)cctl->type << DECT_CCTRL_ATTR_TYPE_SHIFT;
+	t |= (u64)cctl->service << DECT_CCTRL_ATTR_SERVICE_SHIFT;
+	t |= (u64)cctl->slot << DECT_CCTRL_ATTR_SLOT_SHIFT;
+	t |= cctl->cf ? DECT_CCTRL_ATTR_CF_FLAG : 0;
+	t |= (u64)cctl->a_mod << DECT_CCTRL_ATTR_A_MOD_SHIFT;
+	t |= (u64)cctl->bz_mod << DECT_CCTRL_ATTR_BZ_MOD_SHIFT;
+	t |= (u64)cctl->bz_ext_mod << DECT_CCTRL_ATTR_BZ_EXT_MOD_SHIFT;
+	t |= (u64)cctl->acr << DECT_CCTRL_ATTR_ACR_SHIFT;
+	return t;
+}
+
+static int dect_parse_cctrl_release(struct dect_cctrl *cctl, u64 t)
+{
+	cctl->lbn    = (t & DECT_CCTRL_RELEASE_LBN_MASK) >>
+		       DECT_CCTRL_RELEASE_LBN_SHIFT;
+	cctl->reason = (t & DECT_CCTRL_RELEASE_REASON_MASK) >>
+		       DECT_CCTRL_RELEASE_REASON_SHIFT;
+	cctl->pmid   = (t & DECT_CCTRL_RELEASE_PMID_MASK) >>
+		       DECT_CCTRL_RELEASE_PMID_SHIFT;
+
+	pr_debug("cctrl: release: pmid: %.5x lbn: %x reason: %x\n",
+		 cctl->pmid, cctl->lbn, cctl->reason);
+	return 0;
+}
+
+static u64 dect_build_cctrl_release(const struct dect_cctrl *cctl)
+{
+	u64 t = 0;
+
+	t |= cctl->cmd;
+	t |= (u64)cctl->lbn << DECT_CCTRL_RELEASE_LBN_SHIFT;
+	t |= (u64)cctl->reason << DECT_CCTRL_RELEASE_REASON_SHIFT;
+	t |= (u64)cctl->pmid << DECT_CCTRL_RELEASE_PMID_SHIFT;
+	return t;
+}
+
+static int dect_parse_basic_cctrl(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_cctrl *cctl = &tm->cctl;
+
+	cctl->cmd = t & DECT_MT_CMD_MASK;
+	switch (cctl->cmd) {
+	case DECT_CCTRL_ACCESS_REQ:
+	case DECT_CCTRL_BEARER_HANDOVER_REQ:
+	case DECT_CCTRL_CONNECTION_HANDOVER_REQ:
+	case DECT_CCTRL_UNCONFIRMED_ACCESS_REQ:
+	case DECT_CCTRL_BEARER_CONFIRM:
+	case DECT_CCTRL_WAIT:
+		return dect_parse_cctrl_common(cctl, t);
+	case DECT_CCTRL_ATTRIBUTES_T_REQUEST:
+	case DECT_CCTRL_ATTRIBUTES_T_CONFIRM:
+		return dect_parse_cctrl_attr(cctl, t);
+	case DECT_CCTRL_RELEASE:
+		return dect_parse_cctrl_release(cctl, t);
+	default:
+		pr_debug("unknown cctrl command: %llx\n",
+			 (unsigned long long)cctl->cmd);
+		return -1;
+	}
+}
+
+static int dect_parse_advanced_cctrl(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_cctrl *cctl = &tm->cctl;
+
+	cctl->cmd = t & DECT_MT_CMD_MASK;
+	switch (cctl->cmd) {
+	case DECT_CCTRL_UNCONFIRMED_DUMMY:
+	case DECT_CCTRL_UNCONFIRMED_HANDOVER:
+		return dect_parse_cctrl_common(cctl, t);
+	case DECT_CCTRL_BANDWIDTH_T_REQUEST:
+	case DECT_CCTRL_BANDWIDTH_T_CONFIRM:
+		return -1;
+	default:
+		return dect_parse_basic_cctrl(tm, t);
+	}
+}
+
+static int dect_parse_encryption_ctrl(struct dect_tail_msg *tm, u64 t)
+{
+	struct dect_encctrl *ectl = &tm->encctl;
+
+	ectl->cmd  = (t & DECT_ENCCTRL_CMD_MASK) >> DECT_ENCCTRL_CMD_SHIFT;
+	ectl->fmid = (t & DECT_ENCCTRL_FMID_MASK) >> DECT_ENCCTRL_FMID_SHIFT;
+	ectl->pmid = (t & DECT_ENCCTRL_PMID_MASK) >> DECT_ENCCTRL_PMID_SHIFT;
+	pr_debug("encctrl: cmd: %x fmid: %.4x pmid: %.5x\n",
+		 ectl->cmd, ectl->fmid, ectl->pmid);
+	return 0;
+}
+
+static u64 dect_build_encryption_ctrl(const struct dect_encctrl *ectl)
+{
+	u64 t = 0;
+
+	t |= (u64)DECT_ENCCTRL_FILL_MASK;
+	t |= (u64)ectl->cmd << DECT_ENCCTRL_CMD_SHIFT;
+	t |= (u64)ectl->fmid << DECT_ENCCTRL_FMID_SHIFT;
+	t |= (u64)ectl->pmid << DECT_ENCCTRL_PMID_SHIFT;
+	return t;
+}
+
+static int dect_parse_mac_ctrl(struct dect_tail_msg *tm, u64 t)
+{
+	switch (t & DECT_MT_HDR_MASK) {
+	case DECT_MT_BASIC_CCTRL:
+		if (dect_parse_basic_cctrl(tm, t) < 0)
+			return -1;
+		tm->type = DECT_TM_TYPE_BCCTRL;
+		return 0;
+	case DECT_MT_ADV_CCTRL:
+		if (dect_parse_advanced_cctrl(tm, t) < 0)
+			return -1;
+		tm->type = DECT_TM_TYPE_ACCTRL;
+		return 0;
+	case DECT_MT_ENC_CTRL:
+		if (dect_parse_encryption_ctrl(tm, t) < 0)
+			return -1;
+		tm->type = DECT_TM_TYPE_ENCCTRL;
+		return 0;
+	default:
+		pr_debug("Unknown MAC control %llx\n",
+			 (unsigned long long)t & DECT_MT_HDR_MASK);
+		return -1;
+	}
+}
+
+static u64 dect_build_cctrl(const struct dect_cctrl *cctl)
+{
+	switch (cctl->cmd) {
+	case DECT_CCTRL_ACCESS_REQ:
+	case DECT_CCTRL_BEARER_HANDOVER_REQ:
+	case DECT_CCTRL_CONNECTION_HANDOVER_REQ:
+	case DECT_CCTRL_UNCONFIRMED_ACCESS_REQ:
+	case DECT_CCTRL_BEARER_CONFIRM:
+	case DECT_CCTRL_WAIT:
+	case DECT_CCTRL_UNCONFIRMED_DUMMY:
+	case DECT_CCTRL_UNCONFIRMED_HANDOVER:
+		return dect_build_cctrl_common(cctl);
+	case DECT_CCTRL_ATTRIBUTES_T_REQUEST:
+	case DECT_CCTRL_ATTRIBUTES_T_CONFIRM:
+		return dect_build_cctrl_attr(cctl);
+	case DECT_CCTRL_BANDWIDTH_T_REQUEST:
+	case DECT_CCTRL_BANDWIDTH_T_CONFIRM:
+	case DECT_CCTRL_CHANNEL_LIST:
+		return 0;
+	case DECT_CCTRL_RELEASE:
+		return dect_build_cctrl_release(cctl);
+	default:
+		return 0;
+	}
+}
+
+static int dect_parse_ct_data(struct dect_tail_msg *tm, u64 t, u8 seq)
+{
+	struct dect_ct_data *ctd = &tm->ctd;
+
+	ctd->seq = seq;
+	tm->type = DECT_TM_TYPE_CT;
+	pr_debug("C_S tail sequence number %u\n", seq);
+	return 0;
+}
+
+static int dect_parse_tail_msg(struct dect_tail_msg *tm,
+			       const struct sk_buff *skb)
+{
+	u64 t;
+
+	tm->type = DECT_TM_TYPE_INVALID;
+	WARN_ON_ONCE(!IS_ALIGNED((unsigned long)skb->data, __alignof__(u64)));
+	t = be64_to_cpu(*(__be64 *)skb->data);
+
+	switch (dect_parse_tail(skb)) {
+	case DECT_TI_CT_PKT_0:
+		return dect_parse_ct_data(tm, t, 0);
+	case DECT_TI_CT_PKT_1:
+		return dect_parse_ct_data(tm, t, 1);
+	case DECT_TI_NT_CL:
+		pr_debug("connectionless: ");
+	case DECT_TI_NT:
+		return dect_parse_identities_information(tm, t);
+	case DECT_TI_QT:
+		return dect_parse_system_information(tm, t);
+	case DECT_TI_PT:
+		/* Paging tail in direction FP->PP, MAC control otherwise */
+		if (DECT_TRX_CB(skb)->slot < 12)
+			return dect_parse_paging_msg(tm, t);
+	case DECT_TI_MT:
+		return dect_parse_mac_ctrl(tm, t);
+	default:
+		pr_debug("unknown tail %x\n", dect_parse_tail(skb));
+		return -1;
+	}
+}
+
+static struct sk_buff *dect_build_tail_msg(struct sk_buff *skb,
+					   enum dect_tail_msg_types type,
+					   const void *data)
+{
+	enum dect_tail_identifications ti;
+	unsigned int i;
+	u64 t;
+
+	switch (type) {
+	case DECT_TM_TYPE_ID:
+		t = dect_build_identities_information(data);
+		ti = DECT_TI_NT;
+		break;
+	case DECT_TM_TYPE_SSI:
+		t = dect_build_static_system_information(data);
+		ti = DECT_TI_QT;
+		break;
+	case DECT_TM_TYPE_ERFC:
+		t = dect_build_extended_rf_carrier_information(data);
+		ti = DECT_TI_QT;
+		break;
+	case DECT_TM_TYPE_FPC:
+		t = dect_build_fixed_part_capabilities(data);
+		ti = DECT_TI_QT;
+		break;
+	case DECT_TM_TYPE_EFPC:
+		t = dect_build_extended_fixed_part_capabilities(data);
+		ti = DECT_TI_QT;
+		break;
+	case DECT_TM_TYPE_EFPC2:
+		t = dect_build_extended_fixed_part_capabilities2(data);
+		ti = DECT_TI_QT;
+		break;
+	case DECT_TM_TYPE_SARI:
+		t = dect_build_sari(data);
+		ti = DECT_TI_QT;
+		break;
+	case DECT_TM_TYPE_MFN:
+		t = dect_build_multiframe_number(data);
+		ti = DECT_TI_QT;
+		break;
+	case DECT_TM_TYPE_BCCTRL:
+		t = dect_build_cctrl(data) | DECT_MT_BASIC_CCTRL;
+		ti = DECT_TI_MT;
+		break;
+	case DECT_TM_TYPE_ACCTRL:
+		t = dect_build_cctrl(data) | DECT_MT_ADV_CCTRL;
+		ti = DECT_TI_MT;
+		break;
+	case DECT_TM_TYPE_ENCCTRL:
+		t = dect_build_encryption_ctrl(data);
+		ti = DECT_TI_MT;
+		break;
+	default:
+		BUG();
+	}
+
+	skb_put(skb, DECT_T_FIELD_SIZE);
+	for (i = 0; i < DECT_T_FIELD_SIZE; i++)
+		skb->data[i] = t >> ((sizeof(t) - i - 2) * BITS_PER_BYTE);
+
+	DECT_A_CB(skb)->id = ti;
+	return skb;
+}
+
+/**
+ * dect_t_skb_alloc - allocate a socket buffer for the T-Field
+ *
+ */
+static struct sk_buff *dect_t_skb_alloc(void)
+{
+	struct sk_buff *skb;
+
+	skb = alloc_skb(DECT_PREAMBLE_SIZE + DECT_A_FIELD_SIZE, GFP_ATOMIC);
+	if (skb == NULL)
+		return NULL;
+
+	/* Reserve space for preamble */
+	skb_reset_mac_header(skb);
+	skb_reserve(skb, DECT_PREAMBLE_SIZE);
+
+	skb_reset_network_header(skb);
+
+	/* Reserve space for Header Field */
+	skb_reserve(skb, DECT_HDR_FIELD_SIZE);
+	return skb;
+}
+
+/*
+ * MAC Bearers
+ */
+
+static void dect_bearer_enable(struct dect_bearer *bearer)
+{
+	switch (bearer->mode) {
+	case DECT_BEARER_RX:
+		dect_set_channel_mode(bearer->trx, &bearer->chd, DECT_SLOT_RX);
+		break;
+	case DECT_BEARER_TX:
+		dect_set_channel_mode(bearer->trx, &bearer->chd, DECT_SLOT_TX);
+		break;
+	};
+	dect_set_carrier(bearer->trx, bearer->chd.slot, bearer->chd.carrier);
+	bearer->state = DECT_BEARER_ENABLED;
+}
+
+static void dect_bearer_disable(struct dect_bearer *bearer)
+{
+	dect_set_channel_mode(bearer->trx, &bearer->chd, DECT_SLOT_IDLE);
+	bearer->trx->slots[bearer->chd.slot].bearer = NULL;
+}
+
+static void dect_bearer_timer_add(struct dect_cell *cell,
+				  struct dect_bearer *bearer,
+				  struct dect_timer *timer,
+				  unsigned int frames)
+{
+	u8 slot = bearer->chd.slot;
+
+	switch (bearer->mode) {
+	case DECT_BEARER_RX:
+		dect_timer_add(cell, timer, DECT_TIMER_RX, frames, slot);
+		break;
+	case DECT_BEARER_TX:
+		dect_timer_add(cell, timer, DECT_TIMER_TX, frames, slot);
+		break;
+	}
+}
+
+/**
+ * dect_bearer_release - release a MAC bearer
+ *
+ * Release a MAC bearer that is no longer used. The unused slot position is
+ * given to IRC and converted to a scan bearer.
+ */
+static void dect_scan_bearer_enable(struct dect_transceiver *trx,
+				    const struct dect_channel_desc *chd);
+
+static void dect_bearer_release(struct dect_cell *cell,
+				struct dect_bearer *bearer)
+{
+	struct dect_transceiver *trx = bearer->trx;
+	u8 slot = bearer->chd.slot;
+
+	__skb_queue_purge(&bearer->m_tx_queue);
+	dect_timer_del(&bearer->tx_timer);
+	dect_bearer_disable(bearer);
+	dect_disable_cipher(trx, bearer->chd.slot);
+
+	if (trx->index < 3 &&
+	    ((slot >= dect_normal_receive_base(cell->mode) &&
+	      slot <= dect_normal_receive_end(cell->mode)) ||
+	     (cell->flags & DECT_CELL_MONITOR)))
+		dect_scan_bearer_enable(trx, &bearer->chd);
+}
+
+static void dect_bearer_init(struct dect_cell *cell, struct dect_bearer *bearer,
+			     const struct dect_bearer_ops *ops,
+			     struct dect_transceiver *trx,
+			     const struct dect_channel_desc *chd,
+			     enum dect_bearer_modes mode, void *data)
+{
+	pr_debug("init bearer: mode: %s slot: %u carrier: %u\n",
+		 mode == DECT_BEARER_RX ? "RX" : "TX" , chd->slot, chd->carrier);
+
+	bearer->ops   = ops;
+	bearer->trx   = trx;
+	bearer->chd   = *chd;
+	bearer->mode  = mode;
+	bearer->state = DECT_BEARER_INACTIVE;
+	dect_timer_setup(&bearer->tx_timer, NULL, NULL);
+	skb_queue_head_init(&bearer->m_tx_queue);
+	bearer->data  = data;
+
+	trx->slots[chd->slot].bearer = bearer;
+	dect_set_channel_mode(bearer->trx, &bearer->chd, DECT_SLOT_IDLE);
+}
+
+/*
+ * TX bearer activation:
+ *
+ * The first transmission of an outgoing traffic or connectionless bearer is
+ * scheduled for the frame in which the remote sides' scan is on the desired
+ * carrier.
+ *
+ * The noise value of the physical channel must be confirmed not to be more
+ * than 12dBm stronger than the RSSI measurement obtained from the channel
+ * list when selecting the channel within two frames before the first
+ * transmission.
+ *
+ * Dummy bearers are activated immediately after confirming the RSSI.
+ */
+
+static void dect_tx_bearer_report_rssi(struct dect_cell *cell,
+				       struct dect_bearer *bearer,
+				       u8 rssi)
+{
+	rx_debug(cell, "RSSI confirm: last: %u new: %u\n", bearer->rssi, rssi);
+	if (rssi > bearer->rssi + dect_dbm_to_rssi_rel(12))
+		pr_debug("RSSI: too much noise\n");
+	bearer->state = DECT_BEARER_RSSI_CONFIRMED;
+}
+
+static void dect_tx_bearer_enable_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_bearer *bearer = data;
+
+	switch ((int)bearer->state) {
+	case DECT_BEARER_SCHEDULED:
+		tx_debug(cell, "confirm RSSI carrier %u\n", bearer->chd.carrier);
+		dect_set_channel_mode(bearer->trx, &bearer->chd, DECT_SLOT_RX);
+		dect_set_carrier(bearer->trx, bearer->chd.slot, bearer->chd.carrier);
+		dect_bearer_timer_add(cell, bearer, &bearer->tx_timer, 2);
+		bearer->state = DECT_BEARER_RSSI_CONFIRM;
+		break;
+	case DECT_BEARER_RSSI_CONFIRMED:
+		tx_debug(cell, "enable bearer\n");
+		if (bearer->ops->enable != NULL)
+			bearer->ops->enable(cell, bearer);
+		else
+			dect_bearer_enable(bearer);
+		break;
+	}
+}
+
+static void dect_tx_bearer_schedule(struct dect_cell *cell,
+				    struct dect_bearer *bearer, u8 rssi)
+{
+	u8 delay = 0;
+
+	dect_timer_setup(&bearer->tx_timer, dect_tx_bearer_enable_timer, bearer);
+	if (bearer->ops->state != DECT_DUMMY_BEARER)
+		delay = dect_channel_delay(cell, &bearer->chd) - 2;
+
+	bearer->state = DECT_BEARER_SCHEDULED;
+	bearer->rssi  = rssi;
+
+	if (delay == 0)
+		dect_tx_bearer_enable_timer(cell, bearer);
+	else {
+		dect_bearer_timer_add(cell, bearer, &bearer->tx_timer, delay);
+		tx_debug(cell, "scheduled bearer: delay %u carrier %u pscn %u\n",
+			 delay, bearer->chd.carrier, cell->si.ssi.pscn);
+	}
+}
+
+/*
+ * Broadcast Message Control - decentralized components
+ */
+
+/* Paging:
+ *
+ * The following rules apply to page message transmission:
+ *
+ * - Fast pages may be transmitted in any frame and have priority over normal
+ *   pages.
+ *
+ * - Normal short and full pages, as well as the first segment of a normal long
+ *   page, may only be transmitted in frame 0, or a frame up to 12 if the page
+ *   transmitted in the previously allowed frame had the extend bit bit set.
+ *
+ * - Normal pages must be repeated three times in the frames following their
+ *   first transmission for page detection in low duty idle mode.
+ *
+ * - Fast pages may be repeated up to three times following their first
+ *   transmission. New page message have priority over repetitions.
+ *
+ * FIXME: fast pages should not interrupt repetitions
+ */
+
+static void dect_page_timer_schedule(struct dect_cell *cell)
+{
+	u8 framenum = dect_framenum(cell, DECT_TIMER_TX);
+	u8 frames;
+
+	if ((framenum & 0x1) == 1)
+		frames = 1;
+	 else
+		frames = 2;
+	framenum = dect_framenum_add(framenum, frames);
+
+	if (framenum == 8 || framenum == 14)
+		frames += 2;
+
+	tx_debug(cell, "page timer: schedule in %u frames\n", frames);
+	dect_timer_add(cell, &cell->page_timer, DECT_TIMER_TX, frames, 0);
+}
+
+/**
+ * dect_queue_page - Add a paging message to the appropriate queue
+ *
+ * The first transmission of a page is added to the end of the normal or
+ * fast queue. The first three repetitions of normal pages have priority
+ * over first transmissions.
+ */
+static void dect_queue_page(struct dect_cell *cell, struct sk_buff *skb)
+{
+	u8 repetitions = DECT_BMC_CB(skb)->repetitions;
+	bool fast = DECT_BMC_CB(skb)->fast_page;
+	struct sk_buff_head *page_queue;
+
+	page_queue = fast ? &cell->page_fast_queue : &cell->page_queue;
+	if (!fast && repetitions > 0)
+		skb_queue_head(page_queue, skb);
+	else
+		skb_queue_tail(page_queue, skb);
+
+	dect_page_timer_schedule(cell);
+}
+
+/**
+ * dect_queue_page_segments - perform segmentation and queue the page segments
+ *
+ * Segment a page message into B_S channel sized segments and add them
+ * to the TX queue.
+ */
+static void dect_queue_page_segments(struct sk_buff_head *list,
+				     struct sk_buff *skb)
+{
+	unsigned int len = skb->len;
+	struct sk_buff *seg;
+	u64 t;
+
+	while (skb->len > DECT_PT_LFP_BS_DATA_SIZE) {
+		seg = skb_clone(skb, GFP_ATOMIC);
+		if (seg == NULL)
+			goto err;
+		skb_trim(seg, DECT_PT_LFP_BS_DATA_SIZE);
+
+		if (skb_queue_empty(list))
+			t = DECT_PT_LONG_PAGE_FIRST;
+		else
+			t = DECT_PT_LONG_PAGE;
+
+		seg->data[0] &= 0x0f;
+		seg->data[0] |= t >> 48;
+		pr_debug("queue page segment len %u hdr %x\n",
+			 seg->len, seg->data[0] & 0xf0);
+		__skb_queue_tail(list, seg);
+
+		skb_pull(skb, DECT_PT_LFP_BS_DATA_SIZE);
+	}
+
+	/* Short and full pages have the extend bit set in order to reduce
+	 * the delay for new pages arriving while a page is already active.
+	 */
+	if (skb->len == DECT_PT_SP_BS_DATA_SIZE)
+		t = DECT_PT_SHORT_PAGE | DECT_PT_HDR_EXTEND_FLAG;
+	else if (!DECT_BMC_CB(skb)->long_page)
+		t = DECT_PT_FULL_PAGE | DECT_PT_HDR_EXTEND_FLAG;
+	else if (len == DECT_PT_LFP_BS_DATA_SIZE)
+		t = DECT_PT_LONG_PAGE_ALL;
+	else
+		t = DECT_PT_LONG_PAGE_LAST;
+
+	skb->data[0] &= 0x0f;
+	skb->data[0] |= t >> 48;
+	pr_debug("queue page segment len %u hdr %x\n",
+		 skb->len, skb->data[0] & 0xf0);
+	__skb_queue_tail(list, skb);
+	return;
+
+err:
+	__skb_queue_purge(list);
+	kfree_skb(skb);
+}
+
+/**
+ * dect_page_timer - page message transmission timer
+ *
+ * This timer performs maintenance of the page transmit queue. While the queue
+ * is active, it is advanced by one segment per frame. When a page message has
+ * been fully transmitted, the next message is selected for transmission,
+ * segmented into appropriate units and queued to the transmit queue.
+ */
+static void dect_page_tx_timer(struct dect_cell *cell, void *data)
+{
+	u32 timeout, mfn = dect_mfn(cell, DECT_TIMER_TX);
+	u8 framenum = dect_framenum(cell, DECT_TIMER_TX);
+	struct sk_buff *skb, *last;
+
+	tx_debug(cell, "page timer\n");
+
+	/* Advance the transmit queue by one segment per allowed tail. */
+	if (!skb_queue_empty(&cell->page_tx_queue)) {
+		tx_debug(cell, "advance queue\n");
+		kfree_skb(__skb_dequeue(&cell->page_tx_queue));
+		if (!skb_queue_empty(&cell->page_tx_queue)) {
+			dect_page_timer_schedule(cell);
+			return;
+		}
+	}
+
+	/* Add the last page back to the queue unless its lifetime expired. */
+	last = cell->page_sdu;
+	if (last != NULL) {
+		cell->page_sdu = NULL;
+
+		DECT_BMC_CB(last)->repetitions++;
+		timeout = dect_mfn_add(DECT_BMC_CB(last)->stamp, DECT_PAGE_LIFETIME);
+		if (dect_mfn_before(mfn, timeout))
+			dect_queue_page(cell, last);
+		else
+			kfree_skb(last);
+	}
+
+	/* Get the next page message */
+	while (1) {
+		skb = skb_dequeue(&cell->page_fast_queue);
+		tx_debug(cell, "fast page: %p\n", skb);
+		if (skb == NULL && !skb_queue_empty(&cell->page_queue)) {
+			if (framenum == 0 || (last != NULL && framenum <= 12))
+				skb = skb_dequeue(&cell->page_queue);
+			tx_debug(cell, "normal page: %p\n", skb);
+		}
+		if (skb == NULL)
+			goto out;
+
+		timeout = dect_mfn_add(DECT_BMC_CB(skb)->stamp, DECT_PAGE_LIFETIME);
+		if (dect_mfn_before(mfn, timeout))
+			break;
+		else
+			kfree_skb(skb);
+	}
+
+	/* Save a copy of short and full pages for repetitions. */
+	if (!DECT_BMC_CB(skb)->long_page &&
+	    DECT_BMC_CB(skb)->repetitions < 3)
+		cell->page_sdu = skb_clone(skb, GFP_ATOMIC);
+
+	/* Segment page message and queue segments to tx queue */
+	dect_queue_page_segments(&cell->page_tx_queue, skb);
+out:
+	if (skb != NULL || !skb_queue_empty(&cell->page_queue))
+		dect_page_timer_schedule(cell);
+}
+
+static void dect_cell_schedule_page(struct dect_cell *cell, u32 mask)
+{
+	struct dect_bc *bc;
+
+	list_for_each_entry(bc, &cell->bcs, list)
+		bc->p_tx_mask |= mask;
+}
+
+static void dect_cell_bmc_init(struct dect_cell *cell)
+{
+	skb_queue_head_init(&cell->page_queue);
+	skb_queue_head_init(&cell->page_fast_queue);
+	skb_queue_head_init(&cell->page_tx_queue);
+	dect_timer_setup(&cell->page_timer, dect_page_tx_timer, NULL);
+}
+
+static void dect_cell_bmc_disable(struct dect_cell *cell)
+{
+	dect_timer_del(&cell->page_timer);
+	__skb_queue_purge(&cell->page_tx_queue);
+	__skb_queue_purge(&cell->page_fast_queue);
+	__skb_queue_purge(&cell->page_queue);
+}
+
+/*
+ * Broadcast Control
+ */
+
+static void dect_cell_mac_info_ind(struct dect_cell *cell)
+{
+	const struct dect_cluster_handle *clh = cell->handle.clh;
+
+	clh->ops->mac_info_ind(clh, &cell->idi, &cell->si);
+}
+
+static u32 dect_build_page_rfpi(const struct dect_cell *cell)
+{
+	return (dect_build_rfpi(&cell->idi) >> 24) & ((1 << 20) - 1);
+}
+
+static void dect_bc_release(struct dect_bc *bc)
+{
+	kfree_skb(bc->p_rx_skb);
+	list_del(&bc->list);
+}
+
+static void dect_bc_init(struct dect_cell *cell, struct dect_bc *bc)
+{
+	INIT_LIST_HEAD(&bc->list);
+	bc->p_rx_skb = NULL;
+	list_add_tail(&bc->list, &cell->bcs);
+}
+
+static const enum dect_mac_system_information_types dect_bc_q_cycle[] = {
+	DECT_QT_SI_SSI,
+	DECT_QT_SI_ERFC,
+	DECT_QT_SI_SARI,
+	DECT_QT_SI_FPC,
+	DECT_QT_SI_EFPC,
+	DECT_QT_SI_EFPC2,
+	DECT_QT_SI_MFN,
+};
+
+static struct sk_buff *dect_bc_q_dequeue(struct dect_cell *cell,
+					 struct dect_bearer *bearer)
+{
+	const struct dect_si *si = &cell->si;
+	struct dect_ssi ssi;
+	struct dect_mfn mfn;
+	struct sk_buff *skb;
+	unsigned int index;
+
+	skb = dect_t_skb_alloc();
+	if (skb == NULL)
+		return NULL;
+
+	while (1) {
+		index = cell->si_idx++;
+		if (cell->si_idx == ARRAY_SIZE(dect_bc_q_cycle))
+			cell->si_idx = 0;
+
+		switch (dect_bc_q_cycle[index]) {
+		case DECT_QT_SI_SSI:
+			memcpy(&ssi, &si->ssi, sizeof(ssi));
+			ssi.sn = bearer->chd.slot;
+			ssi.cn = bearer->chd.carrier;
+			ssi.sp = 0;
+			ssi.pscn = dect_next_carrier(ssi.rfcars, ssi.pscn);
+
+			return dect_build_tail_msg(skb, DECT_TM_TYPE_SSI, &ssi);
+		case DECT_QT_SI_ERFC:
+			if (!si->ssi.mc)
+				continue;
+			return dect_build_tail_msg(skb, DECT_TM_TYPE_ERFC,
+						   &si->erfc);
+		case DECT_QT_SI_SARI:
+			break;
+		case DECT_QT_SI_FPC:
+			return dect_build_tail_msg(skb, DECT_TM_TYPE_FPC,
+						   &si->fpc);
+		case DECT_QT_SI_EFPC:
+			if (!(si->fpc.fpc & DECT_FPC_EXTENDED_FP_INFO))
+				continue;
+			return dect_build_tail_msg(skb, DECT_TM_TYPE_EFPC,
+						   &si->efpc);
+		case DECT_QT_SI_EFPC2:
+			if (!(si->efpc.fpc & DECT_EFPC_EXTENDED_FP_INFO2))
+				continue;
+			return dect_build_tail_msg(skb, DECT_TM_TYPE_EFPC2,
+						   &si->efpc2);
+		case DECT_QT_SI_MFN:
+			mfn.num = dect_mfn(cell, DECT_TIMER_TX);
+			return dect_build_tail_msg(skb, DECT_TM_TYPE_MFN, &mfn);
+		default:
+			BUG();
+		}
+	}
+}
+
+static void dect_page_add_mac_info(struct dect_cell *cell, struct dect_bc *bc,
+				   struct sk_buff *skb)
+{
+	struct dect_tail_msg tm;
+	struct dect_dbc *dbc;
+	u64 t;
+	u8 *it;
+
+	memset(&tm, 0, sizeof(tm));
+	if (bc->p_tx_mask & (1 << DECT_TM_TYPE_BFS))
+		tm.type = DECT_TM_TYPE_BFS;
+	else if (bc->p_tx_mask & (1 << DECT_TM_TYPE_BD))
+		tm.type = DECT_TM_TYPE_BD;
+	else
+		tm.type = DECT_TM_TYPE_ACTIVE_CARRIERS;
+
+	switch (tm.type) {
+	case DECT_TM_TYPE_BFS:
+		tm.bfs.mask = cell->trg_blind_full_slots;
+		t = dect_build_blind_full_slots(&tm.bfs);
+		break;
+	case DECT_TM_TYPE_BD:
+		dbc = dect_dbc_get(cell);
+		if (dbc == NULL)
+			goto out;
+		tm.bd.bt = DECT_PT_IT_DUMMY_OR_CL_BEARER_POSITION;
+		tm.bd.sn = dbc->bearer.chd.slot;
+		tm.bd.sp = 0;
+		tm.bd.cn = dbc->bearer.chd.carrier;
+		t = dect_build_bearer_description(&tm.bd);
+		break;
+	case DECT_TM_TYPE_RFP_ID:
+		t = dect_build_rfp_identity(&tm.rfp_id);
+		break;
+	case DECT_TM_TYPE_RFP_STATUS:
+		t = dect_build_rfp_status(&tm.rfp_status);
+		break;
+	case DECT_TM_TYPE_ACTIVE_CARRIERS:
+	default:
+		t = dect_build_active_carriers(&tm.active_carriers);
+		break;
+	}
+
+	it = skb_put(skb, DECT_PT_INFO_TYPE_SIZE);
+	it[0] = t >> 24;
+	it[1] = t >> 16;
+out:
+	bc->p_tx_mask &= ~(1 << tm.type);
+}
+
+static struct sk_buff *dect_bc_p_dequeue(struct dect_cell *cell,
+					 struct dect_bearer *bearer,
+					 struct dect_bc *bc)
+{
+	unsigned int headroom, tailroom = 0;
+	struct sk_buff *skb;
+	u8 *hdr;
+	u64 t;
+
+	/* Send higher layer page messages if present */
+	skb = skb_peek(&cell->page_tx_queue);
+	if (skb != NULL) {
+		/* The frame needs headroom for the preamble and hdr-field.
+		 * Short pages need additional tailroom for the MAC Layer
+		 * Information. */
+		headroom = DECT_PREAMBLE_SIZE + DECT_HDR_FIELD_SIZE;
+		if (skb->len == DECT_PT_SP_BS_DATA_SIZE)
+			tailroom = DECT_PT_INFO_TYPE_SIZE;
+
+		skb = skb_copy_expand(skb, headroom, tailroom, GFP_ATOMIC);
+		if (skb == NULL)
+			return NULL;
+		/* Reserve space for preamble */
+		skb_set_mac_header(skb, -headroom);
+	} else {
+		/* Send zero-length page if required */
+		if (dect_framenum(cell, DECT_TIMER_TX) == 0 ||
+		    bc->p_tx_mask == 0)
+			return NULL;
+
+		skb = dect_t_skb_alloc();
+		if (skb == NULL)
+			return NULL;
+
+		t  = DECT_PT_ZERO_PAGE | DECT_PT_HDR_EXTEND_FLAG;
+		t |= (u64)dect_build_page_rfpi(cell) << DECT_PT_ZP_RFPI_SHIFT;
+
+		hdr = skb_put(skb, 3);
+		hdr[0] = t >> 48;
+		hdr[1] = t >> 40;
+		hdr[2] = t >> 32;
+
+		tailroom = DECT_PT_INFO_TYPE_SIZE;
+	}
+
+	DECT_A_CB(skb)->id = DECT_TI_PT;
+	if (tailroom > 0)
+		dect_page_add_mac_info(cell, bc, skb);
+
+	return skb;
+}
+
+static struct sk_buff *dect_bc_dequeue(struct dect_cell *cell,
+				       struct dect_bearer *bearer,
+				       struct dect_bc *bc,
+				       enum dect_mac_channels chan)
+{
+	struct sk_buff *skb;
+
+	switch (chan) {
+	case DECT_MC_P:
+		return dect_bc_p_dequeue(cell, bearer, bc);
+	case DECT_MC_Q:
+		return dect_bc_q_dequeue(cell, bearer);
+	case DECT_MC_N:
+		skb = dect_t_skb_alloc();
+		if (skb == NULL)
+			return NULL;
+		return dect_build_tail_msg(skb, DECT_TM_TYPE_ID, &cell->idi);
+	default:
+		BUG();
+	}
+}
+
+/**
+ * dect_bc_queue_bs_data - queue a page message to the broadcast controller for
+ * 			   reassembly and delivery to broadcast message control.
+ *
+ * @cell:	DECT cell
+ * @bc:		broadcast controller
+ * @skb_in:	DECT frame
+ * @page:	page message
+ */
+static void dect_bc_queue_bs_data(struct dect_cell *cell, struct dect_bc *bc,
+				  struct sk_buff *skb_in, const struct dect_page *page)
+{
+	const struct dect_cluster_handle *clh = cell->handle.clh;
+	struct sk_buff *skb, *head;
+
+	if (page->length == DECT_PT_ZERO_PAGE)
+		return;
+
+	skb = skb_clone(skb_in, GFP_ATOMIC);
+	if (skb == NULL)
+		return;
+	skb_pull(skb, DECT_T_FIELD_OFF);
+	DECT_BMC_CB(skb)->long_page = false;
+
+	head = bc->p_rx_skb;
+	switch (page->length) {
+	case DECT_PT_SHORT_PAGE:
+		skb_trim(skb, DECT_PT_SP_BS_DATA_SIZE);
+		break;
+	case DECT_PT_LONG_PAGE_ALL:
+		DECT_BMC_CB(skb)->long_page = true;
+		/* fall through */
+	case DECT_PT_FULL_PAGE:
+		skb_trim(skb, DECT_PT_LFP_BS_DATA_SIZE);
+		break;
+	case DECT_PT_LONG_PAGE_FIRST:
+		if (head != NULL)
+			goto err_free;
+		DECT_BMC_CB(skb)->long_page = true;
+		skb_trim(skb, DECT_PT_LFP_BS_DATA_SIZE);
+		bc->p_rx_skb = skb;
+		return;
+	case DECT_PT_LONG_PAGE:
+		if (head == NULL)
+			goto err_free;
+		skb_trim(skb, DECT_PT_LFP_BS_DATA_SIZE);
+		skb_append_frag(head, skb);
+		if (head->len >= 30)
+			goto err;
+		return;
+	case DECT_PT_LONG_PAGE_LAST:
+		if (head == NULL)
+			goto err_free;
+		skb_trim(skb, DECT_PT_LFP_BS_DATA_SIZE);
+		skb = skb_append_frag(head, skb);
+		bc->p_rx_skb = NULL;
+		break;
+	default:
+		BUG();
+	}
+
+	return clh->ops->bmc_page_ind(clh, skb);
+
+err_free:
+	kfree_skb(skb);
+err:
+	kfree_skb(bc->p_rx_skb);
+	bc->p_rx_skb = NULL;
+}
+
+static bool dect_bc_update_si(struct dect_si *si,
+			      const struct dect_tail_msg *tm)
+{
+	bool notify = false;
+	unsigned int i;
+
+	switch (tm->type) {
+	case DECT_TM_TYPE_SSI:
+		if (memcmp(&si->ssi, &tm->ssi, sizeof(si->ssi)))
+			memcpy(&si->ssi, &tm->ssi, sizeof(si->ssi));
+		break;
+	case DECT_TM_TYPE_ERFC:
+		if (memcmp(&si->erfc, &tm->erfc, sizeof(si->erfc)))
+			memcpy(&si->erfc, &tm->erfc, sizeof(si->erfc));
+		break;
+	case DECT_TM_TYPE_FPC:
+		if (memcmp(&si->fpc, &tm->fpc, sizeof(si->fpc))) {
+			memcpy(&si->fpc, &tm->fpc, sizeof(si->fpc));
+			notify = true;
+		}
+		break;
+	case DECT_TM_TYPE_EFPC:
+		if (memcmp(&si->efpc, &tm->efpc, sizeof(si->efpc))) {
+			memcpy(&si->efpc, &tm->efpc, sizeof(si->efpc));
+			notify = true;
+		}
+		break;
+	case DECT_TM_TYPE_EFPC2:
+		if (memcmp(&si->efpc2, &tm->efpc2, sizeof(si->efpc2))) {
+			memcpy(&si->efpc2, &tm->efpc2, sizeof(si->efpc2));
+			notify = true;
+		}
+		break;
+	case DECT_TM_TYPE_SARI:
+		if (si->num_saris == ARRAY_SIZE(si->sari))
+			break;
+
+		for (i = 0; i < si->num_saris; i++) {
+			if (!dect_ari_cmp(&tm->sari.ari, &si->sari[i].ari))
+				break;
+		}
+		if (i < si->num_saris)
+			break;
+
+		memcpy(&si->sari[si->num_saris++], &tm->sari,
+		       sizeof(si->sari[i]));
+		notify = true;
+		break;
+	case DECT_TM_TYPE_MFN:
+		memcpy(&si->mfn, &tm->mfn, sizeof(si->mfn));
+		break;
+	default:
+		return false;
+	}
+
+	si->mask |= 1 << tm->type;
+	return notify;
+}
+
+static bool dect_bc_si_cycle_complete(struct dect_idi *idi,
+				      const struct dect_si *si)
+{
+	if (!(si->mask & (1 << DECT_TM_TYPE_SSI))) {
+		pr_debug("incomplete: SSI\n");
+		return false;
+	}
+	if (si->ssi.mc &&
+	    !(si->mask & (1 << DECT_TM_TYPE_ERFC))) {
+		pr_debug("incomplete: ERFC\n");
+		return false;
+	}
+
+	if (!(si->mask & (1 << DECT_TM_TYPE_FPC))) {
+		pr_debug("incomplete: FPC\n");
+		return false;
+	}
+	if (si->fpc.fpc & DECT_FPC_EXTENDED_FP_INFO &&
+	    !(si->mask & (1 << DECT_TM_TYPE_EFPC))) {
+		pr_debug("incomplete: EFPC\n");
+		return false;
+	}
+
+	if (si->mask & (1 << DECT_TM_TYPE_EFPC) &&
+	    si->efpc.fpc & DECT_EFPC_EXTENDED_FP_INFO2 &&
+	    !(si->mask & (1 << DECT_TM_TYPE_EFPC2))) {
+		pr_debug("incomplete: EFPC2\n");
+		return false;
+	}
+
+	if (idi->e &&
+	    (!(si->mask & (1 << DECT_TM_TYPE_SARI)) ||
+	     si->num_saris != si->sari[0].list_cycle)) {
+		pr_debug("incomplete: SARI\n");
+		return false;
+	}
+
+	pr_debug("complete\n");
+	return true;
+}
+
+static void dect_bc_rcv(struct dect_cell *cell, struct dect_bc *bc,
+			struct sk_buff *skb, const struct dect_tail_msg *tm)
+{
+	enum dect_tail_identifications ti;
+	bool notify;
+
+	if (cell->mode != DECT_MODE_PP)
+		return;
+
+	ti = dect_parse_tail(skb);
+	if (ti == DECT_TI_QT) {
+		/* Q-channel information is broadcast in frame 8 */
+		dect_timer_synchronize_framenum(cell, DECT_Q_CHANNEL_FRAME);
+		if (tm->type == DECT_TM_TYPE_MFN)
+			dect_timer_synchronize_mfn(cell, tm->mfn.num);
+
+		notify = dect_bc_update_si(&cell->si, tm);
+		if (dect_bc_si_cycle_complete(&cell->idi, &cell->si) && notify)
+			dect_cell_mac_info_ind(cell);
+	} else if (ti == DECT_TI_PT) {
+		if (tm->page.length == DECT_PT_ZERO_PAGE &&
+		    tm->page.rfpi != dect_build_page_rfpi(cell))
+			pr_debug("RFPI mismatch %.3x %.3x\n",
+				 tm->page.rfpi, dect_build_page_rfpi(cell));
+	}
+
+	switch (tm->type) {
+	case DECT_TM_TYPE_BFS:
+		cell->blind_full_slots = tm->bfs.mask;
+	case DECT_TM_TYPE_BD:
+	case DECT_TM_TYPE_RFP_ID:
+	case DECT_TM_TYPE_RFP_STATUS:
+	case DECT_TM_TYPE_ACTIVE_CARRIERS:
+	case DECT_TM_TYPE_PAGE:
+		dect_bc_queue_bs_data(cell, bc, skb, &tm->page);
+		break;
+	default:
+		break;
+	}
+}
+
+/*
+ * Traffic Bearer Control (TBC)
+ */
+
+#define tbc_debug(tbc, fmt, args...) \
+	pr_debug("TBC (TBEI %u/%s): PMID: %s %x FMID: %.3x: " fmt, \
+		 (tbc)->id.tbei, tbc_states[(tbc)->state], \
+		 (tbc)->id.pmid.type == DECT_PMID_DEFAULT ? "default" : \
+		 (tbc)->id.pmid.type == DECT_PMID_ASSIGNED ? "assigned" : \
+		 (tbc)->id.pmid.type == DECT_PMID_EMERGENCY ? "emergency" : "?", \
+		 (tbc)->id.pmid.tpui, (tbc)->cell->fmid, ## args);
+
+static const char *tbc_states[] = {
+	[DECT_TBC_NONE]			= "NONE",
+	[DECT_TBC_REQ_SENT]		= "REQ_SENT",
+	[DECT_TBC_WAIT_RCVD]		= "WAIT_RCVD",
+	[DECT_TBC_REQ_RCVD]		= "REQ_RCVD",
+	[DECT_TBC_RESPONSE_SENT]	= "RESPONSE_SENT",
+	[DECT_TBC_OTHER_WAIT]		= "OTHER_WAIT",
+	[DECT_TBC_ESTABLISHED]		= "ESTABLISHED",
+	[DECT_TBC_RELEASING]		= "RELEASING",
+	[DECT_TBC_RELEASED]		= "RELEASED",
+};
+
+static struct dect_tbc *dect_tbc_get_by_tbei(const struct dect_cell *cell, u32 tbei)
+{
+	struct dect_tbc *tbc;
+
+	list_for_each_entry(tbc, &cell->tbcs, list) {
+		if (tbc->id.tbei == tbei)
+			return tbc;
+	}
+	return NULL;
+}
+
+static u32 dect_tbc_alloc_tbei(struct dect_cell *cell)
+{
+	u32 tbei;
+
+	while (1) {
+		tbei = ++cell->tbei_rover;
+		if (tbei == 0)
+			continue;
+		if (dect_tbc_get_by_tbei(cell, tbei))
+			continue;
+		return tbei;
+	}
+}
+
+static void dect_tbc_queue_mac_control(struct dect_tbc *tbc, struct sk_buff *skb)
+{
+	skb_queue_tail(&tbc->txb.m_tx_queue, skb);
+}
+
+static struct sk_buff *dect_tbc_build_cctrl(const struct dect_tbc *tbc,
+					    enum dect_cctrl_cmds cmd)
+{
+	struct dect_cctrl cctl;
+	struct sk_buff *skb;
+
+	skb = dect_t_skb_alloc();
+	if (skb == NULL)
+		return NULL;
+
+	cctl.fmid = tbc->cell->fmid;
+	cctl.pmid = dect_build_pmid(&tbc->id.pmid);
+	cctl.cmd  = cmd;
+
+	if (tbc->type == DECT_MAC_CONN_BASIC)
+		return dect_build_tail_msg(skb, DECT_TM_TYPE_BCCTRL, &cctl);
+	else
+		return dect_build_tail_msg(skb, DECT_TM_TYPE_ACCTRL, &cctl);
+}
+
+static struct sk_buff *dect_tbc_build_encctrl(const struct dect_tbc *tbc,
+					      enum dect_encctrl_cmds cmd)
+{
+	struct dect_encctrl ectl;
+	struct sk_buff *skb;
+
+	skb = dect_t_skb_alloc();
+	if (skb == NULL)
+		return NULL;
+
+	ectl.fmid = tbc->cell->fmid;
+	ectl.pmid = dect_build_pmid(&tbc->id.pmid);
+	ectl.cmd  = cmd;
+
+	return dect_build_tail_msg(skb, DECT_TM_TYPE_ENCCTRL, &ectl);
+}
+
+static int dect_tbc_send_confirm(struct dect_tbc *tbc)
+{
+	struct sk_buff *skb;
+
+	tbc_debug(tbc, "TX CONFIRM\n");
+	skb = dect_tbc_build_cctrl(tbc, DECT_CCTRL_BEARER_CONFIRM);
+	if (skb == NULL)
+		return -ENOMEM;
+
+	/* The first response is permitted in any frame */
+	if (tbc->state == DECT_TBC_REQ_RCVD)
+		skb->priority = DECT_MT_HIGH_PRIORITY;
+	dect_tbc_queue_mac_control(tbc, skb);
+	return 0;
+}
+
+static int dect_tbc_send_attributes_confirm(struct dect_tbc *tbc)
+{
+	struct dect_cctrl cctl;
+	struct sk_buff *skb;
+
+	tbc_debug(tbc, "TX ATTRIBUTES_T_CONFIRM\n");
+	skb = dect_t_skb_alloc();
+	if (skb == NULL)
+		return -ENOMEM;
+
+	cctl.cmd     = DECT_CCTRL_ATTRIBUTES_T_CONFIRM;
+	cctl.ecn     = tbc->id.ecn;
+	cctl.lbn     = tbc->id.lbn;
+	cctl.type    = DECT_CCTRL_TYPE_SYMETRIC_BEARER;
+	cctl.service = tbc->service;
+	cctl.cf      = false;
+
+	cctl.slot   = DECT_FULL_SLOT;
+	cctl.a_mod  = DECT_MODULATION_2_LEVEL;
+	cctl.bz_mod = DECT_MODULATION_2_LEVEL;
+	cctl.bz_ext_mod = 7;
+	cctl.acr    = 0;
+
+	if (tbc->type == DECT_MAC_CONN_BASIC)
+		dect_build_tail_msg(skb, DECT_TM_TYPE_BCCTRL, &cctl);
+	else
+		dect_build_tail_msg(skb, DECT_TM_TYPE_ACCTRL, &cctl);
+
+	dect_tbc_queue_mac_control(tbc, skb);
+	return 0;
+}
+
+static int dect_tbc_send_release(struct dect_tbc *tbc,
+				 enum dect_release_reasons reason)
+{
+	struct dect_cctrl cctl;
+	struct sk_buff *skb;
+
+	tbc_debug(tbc, "TX RELEASE: reason: %x\n", reason);
+	skb = dect_t_skb_alloc();
+	if (skb == NULL)
+		return -ENOMEM;
+
+	cctl.cmd     = DECT_CCTRL_RELEASE;
+	cctl.pmid    = dect_build_pmid(&tbc->id.pmid);
+	cctl.reason  = reason;
+
+	if (tbc->type == DECT_MAC_CONN_BASIC)
+		dect_build_tail_msg(skb, DECT_TM_TYPE_BCCTRL, &cctl);
+	else
+		dect_build_tail_msg(skb, DECT_TM_TYPE_ACCTRL, &cctl);
+
+	/* RELEASE messages may appear in any frame */
+	skb->priority = DECT_MT_HIGH_PRIORITY;
+	dect_tbc_queue_mac_control(tbc, skb);
+	return 0;
+}
+
+static void dect_tbc_state_change(struct dect_tbc *tbc, enum dect_tbc_state state)
+{
+	struct dect_cell *cell = tbc->cell;
+
+	tbc_debug(tbc, "state change: %s (%u) -> %s (%u)\n",
+		  tbc_states[tbc->state], tbc->state, tbc_states[state], state);
+
+	if (tbc->state == DECT_TBC_ESTABLISHED) {
+		cell->tbc_num_est--;
+		cell->tbc_last_chd = tbc->rxb.chd;
+	} else if (state == DECT_TBC_ESTABLISHED)
+		cell->tbc_num_est++;
+
+	tbc->state = state;
+}
+
+static int dect_tbc_event(const struct dect_tbc *tbc, enum dect_tbc_event event)
+{
+	const struct dect_cluster_handle *clh = tbc->cell->handle.clh;
+
+	return clh->ops->tbc_event_ind(clh, &tbc->id, event);
+}
+
+static int dect_tbc_establish_cfm(const struct dect_tbc *tbc, bool success)
+{
+	const struct dect_cluster_handle *clh = tbc->cell->handle.clh;
+
+	return clh->ops->tbc_establish_cfm(clh, &tbc->id, success,
+					   tbc->rxb.chd.slot);
+}
+
+static void dect_tbc_release_notify(const struct dect_tbc *tbc,
+				    enum dect_release_reasons reason)
+{
+	const struct dect_cluster_handle *clh = tbc->cell->handle.clh;
+
+	clh->ops->tbc_dis_ind(clh, &tbc->id, reason);
+}
+
+static void dect_tdd_channel_desc(struct dect_channel_desc *dst,
+				  const struct dect_channel_desc *chd)
+{
+	dst->pkt     = chd->pkt;
+	dst->b_fmt   = chd->b_fmt;
+	dst->carrier = chd->carrier;
+	dst->slot    = dect_tdd_slot(chd->slot);
+}
+
+static void dect_tbc_destroy(struct dect_cell *cell, struct dect_tbc *tbc)
+{
+	tbc_debug(tbc, "destroy\n");
+	dect_tbc_state_change(tbc, DECT_TBC_NONE);
+
+	dect_timer_del(&tbc->wd_timer);
+	dect_timer_del(&tbc->wait_timer);
+	dect_timer_del(&tbc->release_timer);
+	dect_timer_del(&tbc->enc_timer);
+	dect_bc_release(&tbc->bc);
+
+	dect_channel_release(cell, tbc->txb.trx, &tbc->txb.chd);
+	dect_bearer_release(cell, &tbc->txb);
+
+	dect_channel_release(cell, tbc->rxb.trx, &tbc->rxb.chd);
+	dect_bearer_release(cell, &tbc->rxb);
+
+	list_del(&tbc->list);
+	kfree_skb(tbc->cs_tx_skb);
+	kfree(tbc);
+}
+
+static void dect_tbc_release_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_tbc *tbc = data;
+
+	switch (tbc->state) {
+	default:
+		dect_tbc_state_change(tbc, DECT_TBC_RELEASING);
+		break;
+	case DECT_TBC_RELEASING:
+		dect_tbc_state_change(tbc, DECT_TBC_RELEASED);
+		break;
+	case DECT_TBC_NONE:
+	case DECT_TBC_REQ_SENT:
+	case DECT_TBC_RELEASED:
+		return dect_tbc_destroy(cell, tbc);
+	}
+
+	dect_tbc_send_release(tbc, tbc->release_reason);
+	dect_bearer_timer_add(tbc->cell, &tbc->txb, &tbc->release_timer,
+			      DECT_MT_FRAME_RATE);
+}
+
+static void dect_tbc_begin_release(struct dect_cell *cell, struct dect_tbc *tbc,
+				   enum dect_release_reasons reason)
+{
+	tbc->release_reason = reason;
+	dect_tbc_release_timer(cell, tbc);
+}
+
+static void dect_tbc_dis_req(const struct dect_cell_handle *ch,
+			     const struct dect_tbc_id *id,
+			     enum dect_release_reasons reason)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_tbc *tbc;
+
+	tbc = dect_tbc_get_by_tbei(cell, id->tbei);
+	if (tbc == NULL)
+		return;
+	tbc_debug(tbc, "TBC_DIS-req: reason: %u\n", reason);
+	dect_tbc_begin_release(cell, tbc, reason);
+}
+
+static int dect_tbc_establish(struct dect_cell *cell, struct dect_tbc *tbc)
+{
+	dect_tbc_state_change(tbc, DECT_TBC_ESTABLISHED);
+	if (dect_tbc_establish_cfm(tbc, true) < 0)
+		return -1;
+	return 0;
+}
+
+/**
+ * dect_watchdog_timer - connection watchdog timer
+ *
+ * The watchdog timer is forwarded when an expected event occurs, on expiry
+ * it will release the TBC. The relevant event depends on the TBC's state:
+ *
+ * Until ESTABLISHED state, P_T tails must be sent in every allowed frame.
+ * The timer is forwarded when receiving a P_T tail in an allowed frame.
+ *
+ * In ESTABLISHED state, an RFPI handshake must be received at least
+ * every T201 (5) seconds. The timer is forwarded when receiving an N_T
+ * tail containing a matching RFPI.
+ */
+static void dect_tbc_watchdog_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_tbc *tbc = data;
+
+	tbc_debug(tbc, "watchdog expire\n");
+	if (tbc->state != DECT_TBC_ESTABLISHED) {
+		dect_tbc_establish_cfm(tbc, false);
+		dect_tbc_begin_release(cell, tbc, DECT_REASON_BEARER_SETUP_OR_HANDOVER_FAILED);
+	} else {
+		dect_tbc_release_notify(tbc, DECT_REASON_TIMEOUT_LOST_HANDSHAKE);
+		dect_tbc_begin_release(cell, tbc, DECT_REASON_TIMEOUT_LOST_HANDSHAKE);
+	}
+}
+
+static void dect_tbc_watchdog_reschedule(struct dect_cell *cell,
+					 struct dect_tbc *tbc)
+{
+	u16 timeout;
+
+	if (tbc->state == DECT_TBC_ESTABLISHED)
+		timeout = DECT_TBC_RFPI_TIMEOUT;
+	else
+		timeout = DECT_MT_FRAME_RATE;
+
+	tbc_debug(tbc, "watchdog reschedule timeout: %u\n", timeout);
+	dect_bearer_timer_add(cell, &tbc->rxb, &tbc->wd_timer, timeout);
+}
+
+static int dect_tbc_check_attributes(struct dect_cell *cell, struct dect_tbc *tbc,
+				     const struct dect_cctrl *cctl)
+{
+	const struct dect_cluster_handle *clh = cell->handle.clh;
+
+	tbc_debug(tbc, "RX ATTRIBUTES_T_REQUEST\n");
+	tbc->id.ecn  = cctl->ecn;
+	tbc->id.lbn  = cctl->lbn;
+	tbc->service = cctl->service;
+
+	if (clh->ops->tbc_establish_ind(clh, &cell->handle, &tbc->id,
+					tbc->service, tbc->handover) < 0)
+		return -1;
+	return 0;
+}
+
+/**
+ * dect_tbc_state_process - connection setup and maintenance state proccesing
+ *
+ * Process all messages before ESTABLISHED state, as well as all connection
+ * control messages in ESTABLISHED state.
+ */
+static int dect_tbc_state_process(struct dect_cell *cell, struct dect_tbc *tbc,
+				  const struct dect_tail_msg *tm)
+{
+	const struct dect_cctrl *cctl = &tm->cctl;
+	struct sk_buff *m_skb;
+	u8 framenum;
+
+	if (tbc->state == DECT_TBC_OTHER_WAIT) {
+		tbc_debug(tbc, "RX in OTHER_WAIT\n");
+		/* Any message except RELEASE switches the bearer to
+		 * ESTABLISHED state.
+		 */
+		if ((tm->type == DECT_TM_TYPE_BCCTRL ||
+		     tm->type == DECT_TM_TYPE_ACCTRL) &&
+		    (cctl->fmid != cell->fmid ||
+		     cctl->pmid != dect_build_pmid(&tbc->id.pmid) ||
+		     cctl->cmd == DECT_CCTRL_RELEASE))
+			goto release;
+
+		if (dect_tbc_establish(cell, tbc) < 0)
+			goto release;
+		goto out;
+	}
+
+	/* Before OTHER_WAIT state, M_T tails must be received in every allowed
+	 * frame. FPs may send M_T tails in uneven frames, PTs in even frames.
+	 * Additionally FPs may transmit responses to BEARER_REQUEST messages in
+	 * every frame.
+	 */
+	framenum = dect_framenum(cell, DECT_TIMER_RX);
+	if (cell->mode == DECT_MODE_FP) {
+		if ((framenum & 0x1) == 1)
+			return 1;
+	} else {
+		if ((framenum & 0x1) == 0 && tbc->state != DECT_TBC_REQ_SENT)
+			return 1;
+	}
+
+	if (tm->type != DECT_TM_TYPE_BCCTRL && tm->type != DECT_TM_TYPE_ACCTRL)
+		goto release;
+
+	switch (cctl->cmd) {
+	case DECT_CCTRL_ATTRIBUTES_T_REQUEST:
+	case DECT_CCTRL_ATTRIBUTES_T_CONFIRM:
+	case DECT_CCTRL_BANDWIDTH_T_REQUEST:
+	case DECT_CCTRL_BANDWIDTH_T_CONFIRM:
+	case DECT_CCTRL_CHANNEL_LIST:
+		break;
+	default:
+		if (cctl->fmid != cell->fmid)
+			goto release;
+		/* fall through */
+	case DECT_CCTRL_RELEASE:
+		if (cctl->pmid != dect_build_pmid(&tbc->id.pmid))
+			goto release;
+	}
+
+	switch ((int)tbc->state) {
+	case DECT_TBC_NONE:
+		/*
+		 * Receiving side, initial request.
+		 */
+		dect_tbc_state_change(tbc, DECT_TBC_REQ_RCVD);
+		break;
+
+	case DECT_TBC_REQ_RCVD:
+	case DECT_TBC_RESPONSE_SENT:
+		/*
+		 * Receiving side: waiting for LLME to create MBC. Only "WAIT"
+		 * messages are valid in both directions.
+		 */
+		tbc_debug(tbc, "RX in REQ_RCVD: %llx\n",
+			  (unsigned long long)cctl->cmd);
+
+		if (tbc->type == DECT_MAC_CONN_ADVANCED &&
+		    cctl->cmd == DECT_CCTRL_ATTRIBUTES_T_REQUEST)
+			dect_tbc_check_attributes(cell, tbc, cctl);
+		else if (cctl->cmd != DECT_CCTRL_WAIT)
+			goto release;
+
+		m_skb = dect_tbc_build_cctrl(tbc, DECT_CCTRL_WAIT);
+		if (m_skb == NULL)
+			goto release;
+		dect_tbc_queue_mac_control(tbc, m_skb);
+		break;
+
+	case DECT_TBC_REQ_SENT:
+	case DECT_TBC_WAIT_RCVD:
+		/*
+		 * Initiator: request was sent, waiting for confirm. "WAIT"
+		 * messages must be responded to with another "WAIT" message.
+		 */
+		tbc_debug(tbc, "Reply in REQ_SENT %u\n", tm->type);
+		if (cctl->cmd != DECT_CCTRL_BEARER_CONFIRM) {
+			if (cctl->cmd != DECT_CCTRL_WAIT)
+				goto release;
+
+			m_skb = dect_tbc_build_cctrl(tbc, DECT_CCTRL_WAIT);
+			if (m_skb == NULL)
+				goto release;
+			dect_tbc_queue_mac_control(tbc, m_skb);
+
+			dect_tbc_state_change(tbc, DECT_TBC_WAIT_RCVD);
+		} else {
+			tbc_debug(tbc, "Confirmed\n");
+			m_skb = dect_tbc_build_cctrl(tbc, DECT_CCTRL_WAIT);
+			if (m_skb == NULL)
+				goto release;
+			dect_tbc_queue_mac_control(tbc, m_skb);
+
+			dect_tbc_state_change(tbc, DECT_TBC_OTHER_WAIT);
+		}
+		break;
+
+	case DECT_TBC_ESTABLISHED:
+		if (cctl->cmd != DECT_CCTRL_RELEASE)
+			break;
+		/* Immediate release */
+		dect_tbc_release_notify(tbc, DECT_REASON_BEARER_RELEASE);
+		dect_tbc_destroy(cell, tbc);
+		return 0;
+
+	case DECT_TBC_RELEASING:
+		/*
+		 * Unacknowledged release procedure in progress, ignore the
+		 * packet unless its a release message, in which case the
+		 * bearer can be destroyed immediately (crossed bearer release
+		 * procedure).
+		 */
+		if (cctl->cmd == DECT_CCTRL_RELEASE)
+			dect_tbc_destroy(cell, tbc);
+
+	case DECT_TBC_RELEASED:
+		return 0;
+	}
+
+out:
+	dect_tbc_watchdog_reschedule(cell, tbc);
+	return 1;
+
+release:
+	dect_tbc_establish_cfm(tbc, false);
+	dect_tbc_begin_release(cell, tbc, DECT_REASON_UNKNOWN);
+	return 0;
+}
+
+static void dect_tbc_enc_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_tbc *tbc = data;
+	enum dect_encctrl_cmds cmd;
+	struct sk_buff *skb;
+
+	tbc_debug(tbc, "encryption timer: state: %u cnt: %u\n",
+		  tbc->enc_state, tbc->enc_msg_cnt);
+
+	if (++tbc->enc_msg_cnt > 5) {
+		dect_tbc_release_notify(tbc, DECT_REASON_BEARER_RELEASE);
+		return dect_tbc_begin_release(cell, tbc, DECT_REASON_BEARER_RELEASE);
+	}
+
+	dect_bearer_timer_add(cell, &tbc->txb, &tbc->enc_timer, 2);
+
+	switch (tbc->enc_state) {
+	case DECT_TBC_ENC_START_REQ_RCVD:
+		tbc_debug(tbc, "TX encryption enabled\n");
+		dect_enable_cipher(tbc->txb.trx, tbc->txb.chd.slot, tbc->ck);
+		/* fall through */
+	case DECT_TBC_ENC_START_CFM_SENT:
+		tbc->enc_state = DECT_TBC_ENC_START_CFM_SENT;
+		cmd = DECT_ENCCTRL_START_CONFIRM;
+		break;
+	case DECT_TBC_ENC_START_REQ_SENT:
+		cmd = DECT_ENCCTRL_START_REQUEST;
+		break;
+	default:
+		return;
+	}
+
+	skb = dect_tbc_build_encctrl(tbc, cmd);
+	if (skb != NULL)
+		dect_tbc_queue_mac_control(tbc, skb);
+}
+
+static int dect_tbc_enc_state_process(struct dect_cell *cell,
+				      struct dect_tbc *tbc,
+				      const struct dect_tail_msg *tm)
+{
+	const struct dect_encctrl *ectl = &tm->encctl;
+	struct sk_buff *skb;
+
+	if (ectl->fmid != cell->fmid ||
+	    ectl->pmid != dect_build_pmid(&tbc->id.pmid))
+		return 0;
+
+	switch (ectl->cmd) {
+	case DECT_ENCCTRL_START_REQUEST:
+		if (tbc->enc_state != DECT_TBC_ENC_DISABLED ||
+		    cell->mode != DECT_MODE_FP)
+			break;
+		tbc->enc_state = DECT_TBC_ENC_START_REQ_RCVD;
+		tbc->enc_msg_cnt = 0;
+
+		dect_bearer_timer_add(cell, &tbc->txb, &tbc->enc_timer, 0);
+		break;
+	case DECT_ENCCTRL_START_CONFIRM:
+		if (tbc->enc_state == DECT_TBC_ENC_START_REQ_SENT) {
+			tbc->enc_state = DECT_TBC_ENC_START_CFM_RCVD;
+			tbc->enc_msg_cnt = 0;
+
+			dect_timer_del(&tbc->enc_timer);
+			tbc_debug(tbc, "TX encryption enabled\n");
+			dect_enable_cipher(tbc->txb.trx, tbc->txb.chd.slot,
+					   tbc->ck);
+		}
+		if (tbc->enc_state == DECT_TBC_ENC_START_CFM_RCVD) {
+			skb = dect_tbc_build_encctrl(tbc, DECT_ENCCTRL_START_GRANT);
+			if (skb != NULL)
+				dect_tbc_queue_mac_control(tbc, skb);
+		}
+		break;
+	case DECT_ENCCTRL_START_GRANT:
+		if (tbc->enc_state != DECT_TBC_ENC_START_CFM_SENT)
+			break;
+		tbc->enc_state = DECT_TBC_ENC_ENABLED;
+
+		dect_timer_del(&tbc->enc_timer);
+		tbc_debug(tbc, "RX encryption enabled\n");
+		dect_enable_cipher(tbc->rxb.trx, tbc->rxb.chd.slot, tbc->ck);
+		dect_tbc_event(tbc, DECT_TBC_CIPHER_ENABLED);
+		break;
+	case DECT_ENCCTRL_STOP_REQUEST:
+		break;
+	case DECT_ENCCTRL_STOP_CONFIRM:
+		break;
+	case DECT_ENCCTRL_STOP_GRANT:
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void dect_tbc_queue_cs_data(struct dect_cell *cell, struct dect_tbc *tbc,
+				   struct sk_buff *skb,
+				   const struct dect_tail_msg *tm)
+{
+	const struct dect_cluster_handle *clh = cell->handle.clh;
+
+	skb = skb_clone(skb, GFP_ATOMIC);
+	if (skb == NULL)
+		return;
+	skb_pull(skb, DECT_T_FIELD_OFF);
+	skb_trim(skb, DECT_C_S_SDU_SIZE);
+
+	DECT_CS_CB(skb)->seq = tm->ctd.seq;
+	clh->ops->tbc_data_ind(clh, &tbc->id, DECT_MC_C_S, skb);
+}
+
+static void dect_tbc_update_handover_state(struct dect_tbc *tbc, bool ok)
+{
+	const struct dect_cluster_handle *clh = tbc->cell->handle.clh;
+
+	if (ok) {
+		tbc->handover_tokens += DECT_TBC_HO_TOKENS_OK;
+		if (tbc->handover_tokens > DECT_TBC_HO_TOKENS_MAX)
+			tbc->handover_tokens = DECT_TBC_HO_TOKENS_MAX;
+	} else {
+		tbc->handover_tokens -= DECT_TBC_HO_TOKENS_ERROR;
+		if (tbc->handover_tokens < 0)
+			tbc->handover_tokens = 0;
+	}
+
+	tbc_debug(tbc, "handover: ok: %u tokens: %d\n", ok, tbc->handover_tokens);
+	if (tbc->handover_tokens == 0)
+		clh->ops->tbc_handover_req(clh, &tbc->id);
+}
+
+static void dect_tbc_report_rssi(struct dect_cell *cell,
+				 struct dect_bearer *bearer,
+				 u8 slot, u8 rssi)
+{
+	struct dect_tbc *tbc = bearer->tbc;
+
+	/* A RSSI report implies an In-Sync error */
+	if (cell->mode == DECT_MODE_PP)
+		dect_tbc_update_handover_state(tbc, false);
+}
+
+#define DECT_CHECKSUM_ALL \
+	(DECT_CHECKSUM_A_CRC_OK | DECT_CHECKSUM_X_CRC_OK | DECT_CHECKSUM_Z_CRC_OK)
+
+static bool dect_tbc_checksum_ok(const struct sk_buff *skb)
+{
+	return (DECT_TRX_CB(skb)->csum & DECT_CHECKSUM_ALL) == DECT_CHECKSUM_ALL;
+}
+
+static void dect_tbc_rcv(struct dect_cell *cell, struct dect_bearer *bearer,
+			 struct sk_buff *skb)
+{
+	const struct dect_cluster_handle *clh = cell->handle.clh;
+	struct dect_tbc *tbc = bearer->tbc;
+	struct dect_tail_msg _tm, *tm = &_tm;
+	bool a_crc_ok, collision;
+	bool q1, q2;
+
+	dect_raw_rcv(skb);
+
+	if (cell->mode == DECT_MODE_PP)
+		dect_tbc_update_handover_state(tbc, dect_tbc_checksum_ok(skb));
+
+	/* Verify A-field checksum. Sucessful reception of the A-field is
+	 * indicated by transmitting the Q2 bit in the reverse direction
+	 * or the Q1 bit in the direction FP->PP when Q1 is set to 0.
+	 */
+	if (DECT_TRX_CB(skb)->csum & DECT_CHECKSUM_A_CRC_OK)
+		tbc->txb.q = DECT_HDR_Q2_FLAG;
+	else
+		goto rcv_b_field;
+
+	/* Q1 and Q2 bit settings for MAC service IN as per section 10.8.1.3.1 */
+	q1 = skb->data[DECT_HDR_Q1_OFF] & DECT_HDR_Q1_FLAG;
+	q2 = skb->data[DECT_HDR_Q2_OFF] & DECT_HDR_Q2_FLAG;
+
+	if (cell->mode == DECT_MODE_FP)
+		a_crc_ok = q2;
+	else {
+		if (q2) {
+			a_crc_ok  = true;
+			collision = q1;
+			/* ignore collision indications for now as the
+			 * transceiver firmware seems to improperly transmit
+			 * the Z-Field.
+			 */
+			collision = false;
+		} else {
+			a_crc_ok  = q1;
+			collision = false;
+		}
+
+		dect_tbc_update_handover_state(tbc, a_crc_ok && !collision);
+	}
+
+	if (tbc->cs_tx_ok) {
+		if (a_crc_ok) {
+			tbc_debug(tbc, "ARQ acknowledgement\n");
+			dect_tbc_event(tbc, DECT_TBC_ACK_RECEIVED);
+		} else
+			tbc_debug(tbc, "C-channel data lost\n");
+	}
+
+	if (dect_parse_tail_msg(tm, skb) < 0)
+		goto err;
+
+	if (tbc->state != DECT_TBC_ESTABLISHED ||
+	    tm->type == DECT_TM_TYPE_BCCTRL ||
+	    tm->type == DECT_TM_TYPE_ACCTRL) {
+		if (!dect_tbc_state_process(cell, tbc, tm))
+			goto err;
+	}
+
+	tbc_debug(tbc, "receive\n");
+
+	/* Reschedule watchdog on successful RFPI handshake. */
+	if (tm->type == DECT_TM_TYPE_ID && !dect_rfpi_cmp(&tm->idi, &cell->idi))
+		dect_tbc_watchdog_reschedule(cell, tbc);
+
+	if (tm->type == DECT_TM_TYPE_ENCCTRL) {
+		if (!dect_tbc_enc_state_process(cell, tbc, tm))
+			goto err;
+	}
+
+	dect_bc_rcv(cell, &tbc->bc, skb, tm);
+
+	switch (tbc->enc_state) {
+	case DECT_TBC_ENC_START_REQ_SENT:
+	case DECT_TBC_ENC_START_CFM_SENT:
+		goto err;
+	default:
+		break;
+	}
+
+	if (tbc->state != DECT_TBC_REQ_RCVD &&
+	    tbc->state != DECT_TBC_RESPONSE_SENT) {
+		if (tm->type == DECT_TM_TYPE_CT)
+			dect_tbc_queue_cs_data(cell, tbc, skb, tm);
+	}
+
+rcv_b_field:
+	switch (dect_parse_b_id(skb)) {
+	case DECT_BI_UTYPE_0:
+	case DECT_BI_UTYPE_1:
+		break;
+	default:
+		goto err;
+	}
+
+	skb_pull(skb, DECT_A_FIELD_SIZE);
+	skb_trim(skb, dect_b_field_size(&bearer->chd));
+	clh->ops->tbc_data_ind(clh, &tbc->id, DECT_MC_I_N, skb);
+	return;
+
+err:
+	kfree_skb(skb);
+}
+
+static void dect_tbc_data_req(const struct dect_cell_handle *ch,
+			      const struct dect_tbc_id *id,
+			      enum dect_data_channels chan,
+			      struct sk_buff *skb)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_tbc *tbc;
+
+	tbc = dect_tbc_get_by_tbei(cell, id->tbei);
+	if (tbc == NULL)
+		goto err;
+	tbc_debug(tbc, "TBC_DATA-req: chan: %u len: %u\n", chan, skb->len);
+
+	switch (chan) {
+	case DECT_MC_C_S:
+		DECT_A_CB(skb)->id = DECT_CS_CB(skb)->seq ? DECT_TI_CT_PKT_1 :
+							    DECT_TI_CT_PKT_0;
+		tbc->cs_tx_skb = skb;
+		break;
+	case DECT_MC_I_N:
+		tbc->b_tx_skb = skb;
+		break;
+	default:
+		goto err;
+	}
+	return;
+
+err:
+	kfree_skb(skb);
+}
+
+static int dect_tbc_enc_req(const struct dect_cell_handle *ch,
+			    const struct dect_tbc_id *id, u64 ck)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_tbc *tbc;
+
+	tbc = dect_tbc_get_by_tbei(cell, id->tbei);
+	if (tbc == NULL)
+		return -ENOENT;
+
+	tbc_debug(tbc, "RX/TX encryption enabled: ck: %.16llx\n",
+		  (unsigned long long)ck);
+
+	tbc->ck = ck;
+	dect_enable_cipher(tbc->rxb.trx, tbc->rxb.chd.slot, tbc->ck);
+	dect_enable_cipher(tbc->txb.trx, tbc->txb.chd.slot, tbc->ck);
+	return 0;
+}
+
+static int dect_tbc_enc_eks_req(const struct dect_cell_handle *ch,
+				const struct dect_tbc_id *id,
+				enum dect_cipher_states status)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_tbc *tbc;
+	struct sk_buff *skb;
+
+	tbc = dect_tbc_get_by_tbei(cell, id->tbei);
+	if (tbc == NULL)
+		return -ENOENT;
+
+	tbc_debug(tbc, "TBC_ENC_EKS-req: status: %u\n", status);
+	skb = dect_tbc_build_encctrl(tbc, DECT_ENCCTRL_START_REQUEST);
+	if (skb != NULL)
+		dect_tbc_queue_mac_control(tbc, skb);
+
+	tbc->enc_state = DECT_TBC_ENC_START_REQ_SENT;
+	tbc->enc_msg_cnt = 0;
+	dect_bearer_timer_add(cell, &tbc->txb, &tbc->enc_timer, 0);
+
+	tbc_debug(tbc, "RX encryption enabled\n");
+	dect_enable_cipher(tbc->rxb.trx, tbc->rxb.chd.slot, tbc->ck);
+	return 0;
+}
+
+static int dect_tbc_enc_key_req(const struct dect_cell_handle *ch,
+				const struct dect_tbc_id *id, u64 ck)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_tbc *tbc;
+
+	tbc = dect_tbc_get_by_tbei(cell, id->tbei);
+	if (tbc == NULL)
+		return -ENOENT;
+
+	tbc_debug(tbc, "TBC_ENC_KEY-req: key: %.16llx\n", (unsigned long long)ck);
+	tbc->ck = ck;
+	return 0;
+}
+
+static void dect_tbc_enable(struct dect_cell *cell, struct dect_tbc *tbc)
+{
+	dect_bearer_enable(&tbc->rxb);
+	dect_bearer_enable(&tbc->txb);
+	dect_bc_init(cell, &tbc->bc);
+}
+
+/*
+ * Activation timer: enable the bearer once the TX channel is accessible,
+ * which is defined by the receivers scanning sequence.
+ */
+static void dect_tbc_enable_timer(struct dect_cell *cell,
+				  struct dect_bearer *bearer)
+{
+	struct dect_tbc *tbc = bearer->tbc;
+	struct sk_buff *skb;
+	enum dect_cctrl_cmds cmd;
+
+	tbc_debug(tbc, "TX ACCESS_REQUEST\n");
+	if (tbc->handover)
+		cmd = DECT_CCTRL_BEARER_HANDOVER_REQ;
+	else
+		cmd = DECT_CCTRL_ACCESS_REQ;
+
+	skb = dect_tbc_build_cctrl(tbc, cmd);
+	if (skb == NULL)
+		return;
+
+	/* The packet overrides the T-MUX rules. PPs use a special tail
+	 * coding for the first transmission. */
+	skb->priority = DECT_MT_HIGH_PRIORITY;
+	if (cell->mode == DECT_MODE_FP)
+		DECT_A_CB(skb)->id = DECT_TI_MT;
+	else
+		DECT_A_CB(skb)->id = DECT_TI_MT_PKT_0;
+
+	dect_tbc_enable(cell, tbc);
+	dect_tbc_queue_mac_control(tbc, skb);
+	dect_tbc_state_change(tbc, DECT_TBC_REQ_SENT);
+
+	/* Start watchdog */
+	dect_bearer_timer_add(cell, &tbc->rxb, &tbc->wd_timer, 1);
+}
+
+static const struct dect_bearer_ops dect_tbc_ops = {
+	.state		= DECT_TRAFFIC_BEARER,
+	.enable		= dect_tbc_enable_timer,
+	.rcv		= dect_tbc_rcv,
+	.report_rssi	= dect_tbc_report_rssi,
+};
+
+/**
+ * dect_tbc_init - initialise a traffic bearer control instance
+ *
+ * @cell:	DECT cell
+ * @id:		MBC ID
+ * @rchd:	RX channel description
+ * @tchd:	TX channel description
+ */
+static struct dect_tbc *dect_tbc_init(struct dect_cell *cell,
+				      const struct dect_tbc_id *id,
+				      struct dect_transceiver *rtrx,
+				      struct dect_transceiver *ttrx,
+				      const struct dect_channel_desc *rchd,
+				      const struct dect_channel_desc *tchd)
+
+{
+	struct dect_tbc *tbc;
+
+	tbc = kzalloc(sizeof(*tbc), GFP_ATOMIC);
+	if (tbc == NULL)
+		return NULL;
+
+	tbc->cell	     = cell;
+	tbc->id		     = *id;
+	tbc->handover_tokens = DECT_TBC_HO_TOKENS_INITIAL;
+
+	INIT_LIST_HEAD(&tbc->bc.list);
+	dect_timer_init(&tbc->wait_timer);
+	dect_timer_setup(&tbc->wd_timer, dect_tbc_watchdog_timer, tbc);
+	dect_timer_setup(&tbc->release_timer, dect_tbc_release_timer, tbc);
+	dect_timer_setup(&tbc->enc_timer, dect_tbc_enc_timer, tbc);
+
+	dect_bearer_init(cell, &tbc->rxb, &dect_tbc_ops,
+			 rtrx, rchd, DECT_BEARER_RX, tbc);
+	dect_bearer_init(cell, &tbc->txb, &dect_tbc_ops,
+			 ttrx, tchd, DECT_BEARER_TX, tbc);
+
+	list_add_tail(&tbc->list, &cell->tbcs);
+	return tbc;
+}
+
+static int dect_tbc_establish_req(const struct dect_cell_handle *ch,
+				  const struct dect_tbc_id *id,
+				  const struct dect_channel_desc *chd,
+				  enum dect_mac_service_types service,
+				  bool handover)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_transceiver *ttrx, *rtrx;
+	struct dect_channel_desc tchd, rchd;
+	struct dect_tbc *tbc;
+	u8 rssi;
+	int err;
+
+	/* Select TDD slot pair and reserve transceiver resources */
+	tchd.pkt   = chd->pkt;
+	tchd.b_fmt = chd->b_fmt;
+	err = dect_select_channel(cell, &ttrx, &tchd, &rssi, true);
+	if (err < 0)
+		goto err1;
+	dect_channel_reserve(cell, ttrx, &tchd);
+
+	err = -ENOSPC;
+	dect_tdd_channel_desc(&rchd, &tchd);
+	rtrx = dect_select_transceiver(cell, &rchd);
+	if (rtrx == NULL)
+		goto err2;
+	dect_channel_reserve(cell, rtrx, &rchd);
+
+	err = -ENOMEM;
+	tbc = dect_tbc_init(cell, id, rtrx, ttrx, &rchd, &tchd);
+	if (tbc == NULL)
+		goto err3;
+	tbc->id.tbei  = dect_tbc_alloc_tbei(cell);
+	tbc->service  = service;
+	tbc->handover = handover;
+
+	tbc_debug(tbc, "TBC_ESTABLISH-req: handover: %d\n", handover);
+	dect_tx_bearer_schedule(cell, &tbc->txb, rssi);
+	return 0;
+
+err3:
+	dect_channel_release(cell, rtrx, &rchd);
+err2:
+	dect_channel_release(cell, ttrx, &tchd);
+err1:
+	return err;
+}
+
+/* TBC establishment confirmation from CCF */
+static int dect_tbc_establish_res(const struct dect_cell_handle *ch,
+				  const struct dect_tbc_id *id)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_tbc *tbc;
+	int err;
+
+	tbc = dect_tbc_get_by_tbei(cell, id->tbei);
+	if (tbc == NULL)
+		return -ENOENT;
+	tbc_debug(tbc, "TBC_ESTABLISH-res\n");
+	WARN_ON(tbc->state != DECT_TBC_REQ_RCVD);
+
+	/* Stop wait timer and send CONFIRM */
+	dect_timer_del(&tbc->wait_timer);
+	if (tbc->type == DECT_MAC_CONN_BASIC)
+		err = dect_tbc_send_confirm(tbc);
+	else
+		err = dect_tbc_send_attributes_confirm(tbc);
+	if (err < 0)
+		return err;
+
+	dect_tbc_state_change(tbc, DECT_TBC_OTHER_WAIT);
+	return 0;
+}
+
+static void dect_tbc_wait_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_tbc *tbc = data;
+	struct sk_buff *skb;
+
+	tbc_debug(tbc, "wait timer\n");
+	skb = dect_tbc_build_cctrl(tbc, DECT_CCTRL_WAIT);
+	if (skb == NULL)
+		return;
+
+	/* The first response is permitted in any frame */
+	if (tbc->state == DECT_TBC_REQ_RCVD)
+		skb->priority = DECT_MT_HIGH_PRIORITY;
+	dect_tbc_queue_mac_control(tbc, skb);
+
+	dect_tbc_state_change(tbc, DECT_TBC_RESPONSE_SENT);
+}
+
+/**
+ * dect_tbc_rcv_request - handle incoming connection setup attempts
+ *
+ *
+ */
+static void dect_tbc_rcv_request(struct dect_cell *cell,
+				 const struct dect_transceiver_slot *ts,
+				 const struct dect_tail_msg *tm,
+				 struct sk_buff *skb)
+{
+	const struct dect_cluster_handle *clh = cell->handle.clh;
+	struct dect_transceiver *rtrx, *ttrx;
+	struct dect_channel_desc rchd, tchd;
+	struct dect_tbc_id id;
+	struct dect_tbc *tbc;
+	bool handover = false;
+
+	if (tm->cctl.fmid != cell->fmid)
+		goto err1;
+	dect_raw_rcv(skb);
+
+	switch (tm->cctl.cmd) {
+	case DECT_CCTRL_ACCESS_REQ:
+		break;
+	case DECT_CCTRL_BEARER_HANDOVER_REQ:
+	case DECT_CCTRL_CONNECTION_HANDOVER_REQ:
+		/* Handover can only be initiated by the PP */
+		if (cell->mode == DECT_MODE_FP) {
+			handover = true;
+			break;
+		}
+	default:
+		rx_debug(cell, "unhandled TBC request: %llu\n",
+			 (unsigned long long)tm->cctl.cmd);
+		goto err1;
+	}
+
+	/* Select transceivers for RX/TX and reserve resources */
+	memcpy(&rchd, &ts->chd, sizeof(rchd));
+	rchd.pkt   = DECT_PACKET_P32;
+	rchd.b_fmt = DECT_B_UNPROTECTED;
+	rtrx = dect_select_transceiver(cell, &rchd);
+	if (rtrx == NULL)
+		goto err1;
+	dect_channel_reserve(cell, rtrx, &rchd);
+
+	dect_tdd_channel_desc(&tchd, &rchd);
+	ttrx = dect_select_transceiver(cell, &tchd);
+	if (ttrx == NULL)
+		goto err2;
+	dect_channel_reserve(cell, ttrx, &tchd);
+
+	memset(&id, 0, sizeof(id));
+	memcpy(&id.ari, &cell->idi.pari, sizeof(id.ari));
+	dect_parse_pmid(&id.pmid, tm->cctl.pmid);
+	id.lbn  = 0xf;
+	id.ecn  = 0;
+	id.tbei = dect_tbc_alloc_tbei(cell);
+
+	/* Initialize TBC */
+	tbc = dect_tbc_init(cell, &id, rtrx, ttrx, &rchd, &tchd);
+	if (tbc == NULL)
+		goto err3;
+
+	tbc->handover = handover;
+
+	if (tm->type == DECT_TM_TYPE_BCCTRL) {
+		/* Basic MAC connections only support the I_N_minimal_delay service */
+		tbc->type    = DECT_MAC_CONN_BASIC;
+		tbc->service = DECT_SERVICE_IN_MIN_DELAY;
+	} else {
+		/* Service is unknown at this time */
+		tbc->type    = DECT_MAC_CONN_ADVANCED;
+		tbc->service = DECT_SERVICE_UNKNOWN;
+	}
+
+	dect_tbc_state_change(tbc, DECT_TBC_REQ_RCVD);
+	tbc_debug(tbc, "RCV ACCESS_REQUEST\n");
+
+	/* Set Q2 bit on first response */
+	tbc->txb.q = DECT_HDR_Q2_FLAG;
+
+	/* Start the WAIT transmit timer */
+	dect_timer_setup(&tbc->wait_timer, dect_tbc_wait_timer, tbc);
+	dect_bearer_timer_add(cell, &tbc->txb, &tbc->wait_timer, 1);
+
+	/* Start watchdog timer: until ESTABLISHED state, the remote side
+	 * must transmit a M-tail in every allowed frame. */
+	dect_tbc_watchdog_reschedule(cell, tbc);
+	dect_tbc_enable(cell, tbc);
+
+	if (tbc->type == DECT_MAC_CONN_BASIC) {
+		if (clh->ops->tbc_establish_ind(clh, &cell->handle, &tbc->id,
+						tbc->service, tbc->handover) < 0)
+			goto err4;
+	} else {
+		if (dect_tbc_send_confirm(tbc) < 0)
+			goto err4;
+		dect_tbc_state_change(tbc, DECT_TBC_RESPONSE_SENT);
+	}
+
+	kfree_skb(skb);
+	return;
+
+err4:
+	dect_tbc_destroy(cell, tbc);
+err3:
+	dect_channel_release(cell, ttrx, &tchd);
+err2:
+	dect_channel_release(cell, rtrx, &rchd);
+err1:
+	kfree_skb(skb);
+}
+
+#if 0
+/*
+ * Connectionless Bearer Control (CBC)
+ */
+
+static void dect_cbc_rcv(struct dect_cell *cell, struct dect_bearer *bearer,
+			 struct sk_buff *skb)
+{
+	struct dect_cbc *cbc = bearer->cbc;
+	struct dect_tail_msg tm;
+
+	dect_parse_tail_msg(&tm, skb);
+	dect_bc_rcv(cell, &cbc->bc, skb, &tm);
+	kfree_skb(skb);
+}
+
+static const struct dect_bearer_ops dect_cbc_ops = {
+	.state		= DECT_CL_BEARER,
+	.rcv		= dect_cbc_rcv,
+};
+
+/**
+ * dect_cbc_init - Initialise a connectionless bearer control
+ *
+ * @cell:	DECT cell
+ * @chd:	channel description
+ */
+static struct dect_cbc *dect_cbc_init(struct dect_cell *cell,
+				      struct dect_channel_desc *chd)
+{
+	struct dect_bearer *bearer;
+	enum dect_slot_states mode;
+	struct dect_cbc *cbc = NULL;
+
+	bearer = dect_bearer_init(cell, &dect_cbc_ops, DECT_SIMPLEX_BEARER,
+				  NULL, chd, mode, cbc);
+	if (bearer == NULL)
+		return NULL;
+	cbc->dl_bearer = bearer;
+
+	dect_bc_init(cell, &cbc->bc);
+	return cbc;
+}
+#endif
+
+/*
+ * Dummy Bearer Control (DBC)
+ */
+
+#define dbc_debug(dbc, fmt, args...) \
+	pr_debug("DBC slot %u carrier %u: " fmt, \
+		 (dbc)->bearer.chd.slot, (dbc)->bearer.chd.carrier, ## args)
+
+static void dect_dbc_rcv(struct dect_cell *cell, struct dect_bearer *bearer,
+			 struct sk_buff *skb)
+{
+	struct dect_dbc *dbc = bearer->dbc;
+	struct dect_tail_msg tm;
+
+	/* Update A-field receive time stamp (A-field CRC is always correct) */
+	if (dect_framenum(cell, DECT_TIMER_RX) == 0)
+		cell->a_rcv_stamp = jiffies;
+
+	dect_raw_rcv(skb);
+
+	if (dect_parse_tail_msg(&tm, skb) < 0)
+		goto err;
+
+	/* Update Nt receive stamp if PARI matches */
+	if (tm.type == DECT_TM_TYPE_ID && !dect_rfpi_cmp(&tm.idi, &cell->idi))
+		cell->nt_rcv_stamp = jiffies;
+
+	dect_bc_rcv(cell, &dbc->bc, skb, &tm);
+err:
+	kfree_skb(skb);
+}
+
+static void dect_dbc_report_rssi(struct dect_cell *cell,
+				 struct dect_bearer *bearer,
+				 u8 slot, u8 rssi)
+{
+	dbc_debug(bearer->dbc, "RSSI: selection: %u now: %u\n", bearer->rssi, rssi);
+}
+
+static void dect_dbc_quality_control_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_dbc *dbc = data;
+	struct dect_bearer *bearer = &dbc->bearer;
+
+	switch (dbc->qctrl) {
+	case DECT_BEARER_QCTRL_WAIT:
+		dbc_debug(dbc, "quality control: confirm quality\n");
+		dect_set_channel_mode(bearer->trx, &bearer->chd, DECT_SLOT_RX);
+		dect_set_carrier(bearer->trx, bearer->chd.slot, bearer->chd.carrier);
+		dbc->qctrl = DECT_BEARER_QCTRL_CONFIRM;
+		dect_timer_add(cell, &dbc->qctrl_timer, DECT_TIMER_TX,
+			       1, bearer->chd.slot);
+		break;
+	case DECT_BEARER_QCTRL_CONFIRM:
+		dbc_debug(dbc, "quality control: wait\n");
+		dect_set_channel_mode(bearer->trx, &bearer->chd, DECT_SLOT_TX);
+		dect_set_carrier(bearer->trx, bearer->chd.slot, bearer->chd.carrier);
+		dbc->qctrl = DECT_BEARER_QCTRL_WAIT;
+		dect_timer_add(cell, &dbc->qctrl_timer, DECT_TIMER_TX,
+			       DECT_BEARER_QCTRL_PERIOD - 1, bearer->chd.slot);
+		break;
+	}
+}
+
+static void dect_dbc_enable(struct dect_cell *cell, struct dect_bearer *bearer)
+{
+	struct dect_dbc *dbc = bearer->dbc;
+	u8 framenum = dect_framenum(cell, DECT_TIMER_TX);
+	u8 extra;
+
+	extra = DECT_BEARER_QCTRL_FRAMENUM - framenum;
+	dbc->qctrl = DECT_BEARER_QCTRL_WAIT;
+	dect_timer_add(cell, &dbc->qctrl_timer, DECT_TIMER_TX,
+		       DECT_BEARER_QCTRL_PERIOD + extra, bearer->chd.slot);
+
+	dect_bearer_enable(bearer);
+}
+
+static const struct dect_bearer_ops dect_dbc_ops = {
+	.state		= DECT_DUMMY_BEARER,
+	.enable		= dect_dbc_enable,
+	.report_rssi	= dect_dbc_report_rssi,
+	.rcv		= dect_dbc_rcv,
+};
+
+static void dect_dbc_release(struct dect_dbc *dbc)
+{
+	struct dect_cell *cell = dbc->cell;
+
+	dect_channel_release(cell, dbc->bearer.trx, &dbc->bearer.chd);
+	dect_bearer_release(cell, &dbc->bearer);
+
+	dect_timer_del(&dbc->qctrl_timer);
+	dect_bc_release(&dbc->bc);
+	list_del(&dbc->list);
+	kfree(dbc);
+}
+
+/**
+ * dect_dbc_init - initialise dummy bearer control
+ *
+ * @cell:	DECT cell
+ * @chd:	channel description (PP only)
+ */
+static struct dect_dbc *dect_dbc_init(struct dect_cell *cell,
+				      const struct dect_channel_desc *chd)
+{
+	struct dect_channel_desc tchd;
+	struct dect_transceiver *trx;
+	enum dect_bearer_modes mode;
+	struct dect_dbc *dbc;
+	u8 uninitialized_var(rssi);
+
+	/* Transmission is always in direction FP -> PP */
+	if (cell->mode == DECT_MODE_FP) {
+		tchd.pkt   = DECT_PACKET_P00;
+		tchd.b_fmt = DECT_B_NONE;
+		if (dect_select_channel(cell, &trx, &tchd, &rssi, false) < 0)
+			goto err1;
+		chd = &tchd;
+
+		mode = DECT_BEARER_TX;
+	} else {
+		trx = dect_select_transceiver(cell, chd);
+		if (trx == NULL)
+			goto err1;
+		mode = DECT_BEARER_RX;
+	}
+
+	dect_channel_reserve(cell, trx, chd);
+
+	dbc = kzalloc(sizeof(*dbc), GFP_ATOMIC);
+	if (dbc == NULL)
+		goto err2;
+	dbc->cell = cell;
+	dect_timer_setup(&dbc->qctrl_timer, dect_dbc_quality_control_timer, dbc);
+	dect_bc_init(cell, &dbc->bc);
+
+	dect_bearer_init(cell, &dbc->bearer, &dect_dbc_ops, trx, chd, mode, dbc);
+
+	if (cell->mode == DECT_MODE_FP)
+		dect_tx_bearer_schedule(cell, &dbc->bearer, rssi);
+	else {
+		dect_bearer_enable(&dbc->bearer);
+
+		cell->a_rcv_stamp  = jiffies;
+		cell->nt_rcv_stamp = jiffies;
+	}
+
+	list_add_tail(&dbc->list, &cell->dbcs);
+	return dbc;
+
+err2:
+	dect_channel_release(cell, trx, chd);
+err1:
+	return NULL;
+}
+
+/*
+ * Monitor Bearer
+ */
+
+static void dect_dmb_release(struct dect_cell *cell, struct dect_dmb *dmb)
+{
+	cell->tbc_last_chd = dmb->rxb2.chd;
+
+	dect_timer_del(&dmb->wd_timer);
+
+	dect_transceiver_release(&cell->trg, dmb->rxb1.trx, &dmb->rxb1.chd);
+	dect_bearer_release(dmb->cell, &dmb->rxb1);
+
+	dect_transceiver_release(&cell->trg, dmb->rxb2.trx, &dmb->rxb2.chd);
+	dect_bearer_release(dmb->cell, &dmb->rxb2);
+
+	dect_bc_release(&dmb->bc);
+	list_del(&dmb->list);
+	kfree(dmb);
+}
+
+static void dect_dmb_watchdog_timer(struct dect_cell *cell, void *data)
+{
+	dect_dmb_release(cell, data);
+}
+
+static void dect_dmb_watchdog_reschedule(struct dect_cell *cell,
+					 struct dect_dmb *dmb)
+{
+	dect_bearer_timer_add(cell, &dmb->rxb1, &dmb->wd_timer,
+			      DECT_TBC_RFPI_TIMEOUT);
+}
+
+static void dect_dmb_rcv(struct dect_cell *cell, struct dect_bearer *bearer,
+			 struct sk_buff *skb)
+{
+	struct dect_dmb *dmb = bearer->dmb;
+	struct dect_tail_msg tm;
+
+	dect_raw_rcv(skb);
+
+	if (dect_parse_tail_msg(&tm, skb) < 0)
+		goto err;
+
+	/* Reschedule watchdog on successful RFPI handshake. */
+	if (tm.type == DECT_TM_TYPE_ID && !dect_rfpi_cmp(&tm.idi, &cell->idi))
+		dect_dmb_watchdog_reschedule(cell, dmb);
+
+	dect_bc_rcv(cell, &dmb->bc, skb, &tm);
+
+	switch (tm.type) {
+	case DECT_TM_TYPE_BCCTRL:
+	case DECT_TM_TYPE_ACCTRL:
+		if (tm.cctl.cmd == DECT_CCTRL_RELEASE)
+			return dect_dmb_release(cell, dmb);
+		break;
+	default:
+		break;
+	}
+err:
+	kfree_skb(skb);
+}
+
+static const struct dect_bearer_ops dect_dmb_ops = {
+	.state		= DECT_MONITOR_BEARER,
+	.rcv		= dect_dmb_rcv,
+};
+
+static struct dect_dmb *dect_dmb_init(struct dect_cell *cell,
+				      struct dect_transceiver *trx1,
+				      struct dect_transceiver *trx2,
+				      const struct dect_channel_desc *chd1,
+				      const struct dect_channel_desc *chd2)
+{
+	struct dect_dmb *dmb;
+
+	dmb = kzalloc(sizeof(*dmb), GFP_ATOMIC);
+	if (dmb == NULL)
+		return NULL;
+	dmb->cell = cell;
+
+	dect_timer_setup(&dmb->wd_timer, dect_dmb_watchdog_timer, dmb);
+	dect_bearer_init(cell, &dmb->rxb1, &dect_dmb_ops,
+			 trx1, chd1, DECT_BEARER_RX, dmb);
+	dect_bearer_init(cell, &dmb->rxb2, &dect_dmb_ops,
+			 trx2, chd2, DECT_BEARER_RX, dmb);
+	dect_bc_init(cell, &dmb->bc);
+
+	list_add_tail(&dmb->list, &cell->dmbs);
+	return dmb;
+}
+
+static void dect_dmb_rcv_request(struct dect_cell *cell,
+				 const struct dect_transceiver_slot *ts,
+				 const struct dect_tail_msg *tm,
+				 struct sk_buff *skb)
+{
+	struct dect_transceiver *trx1, *trx2;
+	struct dect_channel_desc chd1, chd2;
+	struct dect_dmb *dmb;
+
+	if (tm->cctl.fmid != cell->fmid)
+		goto err1;
+	dect_raw_rcv(skb);
+
+	switch (tm->cctl.cmd) {
+	case DECT_CCTRL_ACCESS_REQ:
+	case DECT_CCTRL_BEARER_HANDOVER_REQ:
+	case DECT_CCTRL_CONNECTION_HANDOVER_REQ:
+		break;
+	default:
+		rx_debug(cell, "unhandled DMB request: %llu\n",
+			 (unsigned long long)tm->cctl.cmd);
+		goto err1;
+	}
+
+	rx_debug(cell, "DMB: RCV ACCESS_REQUEST\n");
+
+	/* Select transceivers for RX/TX and reserve resources */
+	memcpy(&chd1, &ts->chd, sizeof(chd1));
+	chd1.pkt   = DECT_PACKET_P32;
+	chd1.b_fmt = DECT_B_UNPROTECTED;
+	trx1 = dect_select_transceiver(cell, &chd1);
+	if (trx1 == NULL)
+		goto err1;
+	dect_transceiver_reserve(&cell->trg, trx1, &chd1);
+
+	dect_tdd_channel_desc(&chd2, &chd1);
+	trx2 = dect_select_transceiver(cell, &chd2);
+	if (trx2 == NULL)
+		goto err2;
+	dect_transceiver_reserve(&cell->trg, trx2, &chd2);
+
+	dmb = dect_dmb_init(cell, trx1, trx2, &chd1, &chd2);
+	if (dmb == NULL)
+		goto err3;
+
+	dect_bearer_enable(&dmb->rxb1);
+	dect_bearer_enable(&dmb->rxb2);
+
+	dect_bearer_timer_add(cell, &dmb->rxb1, &dmb->wd_timer,
+			      DECT_TBC_RFPI_TIMEOUT);
+
+	kfree_skb(skb);
+	return;
+
+err3:
+	dect_transceiver_release(&cell->trg, trx2, &chd2);
+err2:
+	dect_transceiver_release(&cell->trg, trx1, &chd1);
+err1:
+	kfree_skb(skb);
+}
+
+/*
+ * Idle Receiver Control
+ */
+
+static void dect_initiate_scan(struct dect_transceiver *trx,
+			       const struct dect_ari *ari,
+			       const struct dect_ari *ari_mask,
+			       void (*notify)(struct dect_cell *,
+				       	      struct dect_transceiver *,
+					      enum dect_scan_status))
+{
+	struct dect_irc *irc = trx->irc;
+
+	if (ari != NULL) {
+		memcpy(&irc->ari, ari, sizeof(irc->ari));
+		if (ari_mask != NULL)
+			memcpy(&irc->ari_mask, ari_mask, sizeof(irc->ari_mask));
+		else
+			memset(&irc->ari_mask, 0xff, sizeof(irc->ari_mask));
+	}
+
+	memset(&irc->si, 0, sizeof(irc->si));
+	irc->notify = notify;
+
+	dect_transceiver_enable(trx);
+}
+
+static void dect_restart_scan(struct dect_cell *cell,
+			      struct dect_transceiver *trx)
+{
+	struct dect_irc *irc = trx->irc;
+
+	memset(&irc->si, 0, sizeof(irc->si));
+	dect_transceiver_unlock(trx);
+	dect_set_channel_mode(trx, &trx->slots[DECT_SCAN_SLOT].chd, DECT_SLOT_SCANNING);
+}
+
+/* This function controls the transceiver while scanning. It collects the
+ * information requested in struct dect_scan_ctrl and invokes the completion
+ * handler once all information is available.
+ */
+void dect_mac_irc_rcv(struct dect_transceiver *trx, struct sk_buff *skb)
+{
+	struct dect_cell *cell = trx->cell;
+	struct dect_irc *irc = trx->irc;
+	struct dect_tail_msg tm;
+
+	if (dect_parse_tail_msg(&tm, skb) < 0)
+		goto err;
+
+	switch (trx->state) {
+	case DECT_TRANSCEIVER_UNLOCKED:
+		if (tm.type != DECT_TM_TYPE_ID)
+			break;
+		if (dect_ari_masked_cmp(&tm.idi.pari, &irc->ari, &irc->ari_mask))
+			break;
+		memcpy(&irc->idi, &tm.idi, sizeof(irc->idi));
+
+		irc->timeout = 16 * DECT_FRAMES_PER_MULTIFRAME;
+		irc->rssi    = dect_average_rssi(0, DECT_TRX_CB(skb)->rssi);
+		dect_transceiver_confirm(trx);
+		break;
+	case DECT_TRANSCEIVER_LOCK_PENDING:
+		irc->rssi = dect_average_rssi(irc->rssi, DECT_TRX_CB(skb)->rssi);
+		if (dect_parse_tail(skb) == DECT_TI_QT) {
+			dect_bc_update_si(&irc->si, &tm);
+			if (dect_bc_si_cycle_complete(&irc->idi, &irc->si) &&
+			    tm.type == DECT_TM_TYPE_MFN)
+				irc->notify(cell, trx, DECT_SCAN_COMPLETE);
+		}
+		break;
+	default:
+		break;
+	}
+err:
+	kfree_skb(skb);
+}
+
+void dect_mac_irc_tick(struct dect_transceiver *trx)
+{
+	struct dect_cell *cell = trx->cell;
+	struct dect_irc *irc = trx->irc;
+
+	switch (trx->state) {
+	case DECT_TRANSCEIVER_UNLOCKED:
+		/* maintain scan until clock is running */
+		irc->rx_scn = dect_next_carrier(0x3ff, irc->rx_scn);
+		dect_set_carrier(trx, DECT_SCAN_SLOT, irc->rx_scn);
+		break;
+	case DECT_TRANSCEIVER_LOCK_PENDING:
+		irc->si.ssi.pscn = dect_next_carrier(0x3ff, irc->si.ssi.pscn);
+		if (--irc->timeout == 0)
+			irc->notify(cell, trx, DECT_SCAN_TIMEOUT);
+		break;
+	default:
+		break;
+	}
+}
+
+static void dect_scan_bearer_rcv(struct dect_cell *cell,
+				 struct dect_bearer *bearer,
+				 struct sk_buff *skb)
+{
+	struct dect_transceiver *trx = bearer->trx;
+	struct dect_transceiver_slot *ts;
+	enum dect_tail_identifications ti;
+	struct dect_tail_msg tm;
+	bool monitor = false;
+
+	ti = dect_parse_tail(skb);
+	/* A PP uses a special encoding for the first transmission */
+	if (cell->mode == DECT_MODE_FP && ti != DECT_TI_MT_PKT_0)
+		goto out;
+	if (cell->mode == DECT_MODE_PP) {
+		if (cell->flags & DECT_CELL_MONITOR && ti == DECT_TI_MT_PKT_0)
+			monitor = true;
+		else if (ti != DECT_TI_MT)
+			goto out;
+	}
+
+	if (dect_parse_tail_msg(&tm, skb) < 0)
+		goto out;
+
+	ts = &trx->slots[DECT_TRX_CB(skb)->slot];
+	switch (tm.type) {
+	case DECT_TM_TYPE_BCCTRL:
+	case DECT_TM_TYPE_ACCTRL:
+		if (monitor)
+			return dect_dmb_rcv_request(cell, ts, &tm, skb);
+		else
+			return dect_tbc_rcv_request(cell, ts, &tm, skb);
+	default:
+		break;
+	}
+out:
+	kfree_skb(skb);
+}
+
+static void dect_scan_bearer_report_rssi(struct dect_cell *cell,
+					 struct dect_bearer *bearer,
+					 u8 slot, u8 rssi)
+{
+	if (cell->chl == NULL)
+		return;
+	dect_chl_update(cell, cell->chl, &bearer->trx->slots[slot].chd, rssi);
+}
+
+static const struct dect_bearer_ops dect_scan_ops = {
+	.report_rssi	= dect_scan_bearer_report_rssi,
+	.rcv		= dect_scan_bearer_rcv,
+};
+
+static void dect_scan_channel_desc(struct dect_channel_desc *chd)
+{
+	memset(chd, 0, sizeof(*chd));
+	chd->pkt   = DECT_PACKET_P32;
+	chd->b_fmt = DECT_B_UNPROTECTED;
+}
+
+static void dect_chl_scan_channel_desc(struct dect_channel_desc *chd,
+				       const struct dect_channel_list *chl)
+{
+	memset(chd, 0, sizeof(*chd));
+	chd->pkt = chl->pkt;
+	if (chl->pkt == DECT_PACKET_P00)
+		chd->b_fmt = DECT_B_NONE;
+	else
+		chd->b_fmt = DECT_B_UNPROTECTED;
+}
+
+static void dect_scan_bearer_enable(struct dect_transceiver *trx,
+				    const struct dect_channel_desc *chd)
+{
+	trx->slots[chd->slot].bearer = &trx->irc->scan_bearer;
+	dect_set_channel_mode(trx, chd, DECT_SLOT_SCANNING);
+}
+
+static void dect_scan_bearer_disable(struct dect_transceiver *trx,
+				     const struct dect_channel_desc *chd)
+{
+	dect_set_channel_mode(trx, chd, DECT_SLOT_IDLE);
+	trx->slots[chd->slot].bearer = NULL;
+}
+
+static void dect_irc_tx_frame_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_irc *irc = data;
+	struct dect_transceiver *trx = irc->trx;
+	struct dect_channel_desc chd;
+	u8 end;
+
+	irc->tx_scn = dect_next_carrier(cell->si.ssi.rfcars, irc->tx_scn);
+
+	/* Begin a pending channel list update:
+	 *
+	 * The IRC of the first transceiver that reaches a new frame queues the
+	 * channel list. All IRCs then switch the idle normal transmit slots
+	 * to scanning mode and switch all scanning slots to the lists physical
+	 * channel type. The actual update will begin once the receive side
+	 * reaches the same frame.
+	 */
+	if (cell->chl == NULL && cell->chl_next == NULL)
+		cell->chl_next = dect_chl_get_pending(cell);
+
+	if (cell->chl_next != NULL) {
+		dect_chl_scan_channel_desc(&chd, cell->chl_next);
+		dect_foreach_receive_slot(chd.slot, end, cell) {
+			if (trx->slots[chd.slot].state != DECT_SLOT_IDLE &&
+			    trx->slots[chd.slot].state != DECT_SLOT_SCANNING)
+				continue;
+			if (!dect_transceiver_channel_available(trx, &chd))
+				continue;
+
+			dect_scan_bearer_enable(trx, &chd);
+		}
+		dect_foreach_transmit_slot(chd.slot, end, cell) {
+			if (trx->slots[chd.slot].state != DECT_SLOT_IDLE)
+				continue;
+			if (!dect_transceiver_channel_available(trx, &chd))
+				continue;
+			dect_scan_bearer_enable(trx, &chd);
+		}
+	} else if (cell->chl == NULL) {
+		/* Switch back primary, secondary and tertiary scan to proper
+		 * packet format and disable scan on remaining transceivers
+		 * after the channel list update is complete.
+		 */
+		dect_scan_channel_desc(&chd);
+		dect_foreach_receive_slot(chd.slot, end, cell) {
+			if (trx->slots[chd.slot].state != DECT_SLOT_SCANNING)
+				continue;
+
+			if (trx->index < 3)
+				dect_scan_bearer_enable(trx, &chd);
+			else
+				dect_scan_bearer_disable(trx, &chd);
+		}
+
+		/* In monitor mode, transmit slots keep scanning for FP setup
+		 * attempts.
+		 */
+		if (!(cell->flags & DECT_CELL_MONITOR)) {
+			dect_foreach_transmit_slot(chd.slot, end, cell) {
+				if (trx->slots[chd.slot].state != DECT_SLOT_SCANNING)
+					continue;
+				dect_scan_bearer_disable(trx, &chd);
+			}
+		}
+	}
+
+	dect_timer_add(cell, &irc->tx_frame_timer, DECT_TIMER_TX, 1, 0);
+}
+
+static void dect_irc_rx_frame_timer(struct dect_cell *cell, void *data)
+{
+	struct dect_irc *irc = data;
+
+	/* Update the list status at the end of a frame in case of an
+	 * active update or activate an update before a new frame begins.
+	 */
+	if (cell->chl != NULL)
+		dect_chl_update_carrier(cell, irc->rx_scn);
+	else if (cell->chl_next != NULL) {
+		cell->chl = cell->chl_next;
+		cell->chl_next = NULL;
+		chl_debug(cell, cell->chl, "begin update\n");
+	}
+
+	irc->rx_scn = dect_next_carrier(cell->si.ssi.rfcars, irc->rx_scn);
+	dect_timer_add(cell, &irc->rx_frame_timer, DECT_TIMER_RX, 1, 23);
+}
+
+/* Primary, secondary and tertiary scan: the secondary scan lags behind the
+ * primary scan by 6 TDMA frames, the tertiary scan by 3 TDMA frames.
+ *
+ * Additional transceivers don't scan for setup attempts, however they each
+ * cover a different carrier in order to speed up channel list construction.
+ */
+static const u8 scn_off_tbl[10] = {
+	[0]	= 0,
+	[1]	= 6,
+	[2]	= 3,
+	[3]	= 8,
+	[4]	= 1,
+	[5]	= 4,
+	[6]	= 9,
+	[7]	= 2,
+	[8]	= 5,
+	[9]	= 7,
+};
+
+static void dect_irc_enable(struct dect_cell *cell, struct dect_irc *irc)
+{
+	struct dect_transceiver *trx = irc->trx;
+	struct dect_channel_desc chd;
+	u8 end, scn_off, scn;
+
+	if (trx->index >= 10)
+		return;
+
+	scn_off = scn_off_tbl[trx->index];
+	scn = dect_carrier_sub(cell->si.ssi.rfcars, cell->si.ssi.pscn, scn_off);
+	irc->rx_scn = scn;
+	irc->tx_scn = scn;
+
+	if (trx->index < 3) {
+		/* Set all idle slots to scanning */
+		dect_scan_channel_desc(&chd);
+		dect_foreach_receive_slot(chd.slot, end, cell) {
+			if (trx->slots[chd.slot].state != DECT_SLOT_IDLE)
+				continue;
+			if (!dect_transceiver_channel_available(trx, &chd))
+				continue;
+			dect_scan_bearer_enable(trx, &chd);
+		}
+
+		if (cell->flags & DECT_CELL_MONITOR) {
+			dect_foreach_transmit_slot(chd.slot, end, cell) {
+				if (!dect_transceiver_channel_available(trx, &chd))
+					continue;
+				dect_scan_bearer_enable(trx, &chd);
+			}
+		}
+	}
+
+	/* Start frame timers */
+	dect_timer_add(cell, &irc->tx_frame_timer, DECT_TIMER_TX, 1, 0);
+	dect_timer_add(cell, &irc->rx_frame_timer, DECT_TIMER_RX, 0, 23);
+}
+
+static void dect_irc_disable(struct dect_cell *cell, struct dect_irc *irc)
+{
+	struct dect_transceiver *trx = irc->trx;
+	u8 slot;
+
+	dect_timer_del(&irc->rx_frame_timer);
+	dect_timer_del(&irc->tx_frame_timer);
+
+	dect_foreach_slot(slot) {
+		if (trx->slots[slot].state != DECT_SLOT_SCANNING)
+			continue;
+		dect_scan_bearer_disable(trx, &trx->slots[slot].chd);
+	}
+}
+
+static struct dect_irc *dect_irc_init(struct dect_cell *cell,
+				      struct dect_transceiver *trx)
+{
+	struct dect_irc *irc;
+
+	irc = kzalloc(sizeof(*irc), GFP_KERNEL);
+	if (irc == NULL)
+		return NULL;
+
+	irc->cell = cell;
+	dect_timer_setup(&irc->rx_frame_timer, dect_irc_rx_frame_timer, irc);
+	dect_timer_setup(&irc->tx_frame_timer, dect_irc_tx_frame_timer, irc);
+	irc->scan_bearer.ops   = &dect_scan_ops;
+	irc->scan_bearer.irc   = irc;
+	irc->scan_bearer.trx   = trx;
+	irc->scan_bearer.mode  = DECT_BEARER_RX;
+	irc->scan_bearer.state = DECT_BEARER_ENABLED;
+	irc->trx = trx;
+	trx->irc = irc;
+	return irc;
+}
+
+static void dect_lock_fp(struct dect_cell *cell, struct dect_transceiver *trx,
+			 enum dect_scan_status status)
+{
+	const struct dect_cluster_handle *clh = cell->handle.clh;
+	struct dect_irc *irc = trx->irc;
+	struct dect_si *si = &irc->si;
+	struct dect_channel_desc chd;
+	struct dect_dbc *dbc;
+
+	switch (status) {
+	case DECT_SCAN_FAIL:
+	case DECT_SCAN_TIMEOUT:
+		return dect_restart_scan(cell, trx);
+	case DECT_SCAN_COMPLETE:
+		break;
+	}
+
+	dect_set_channel_mode(trx, &trx->slots[DECT_SCAN_SLOT].chd, DECT_SLOT_IDLE);
+
+	chd.slot    = si->ssi.sn + (si->ssi.nr ? DECT_HALF_FRAME_SIZE : 0);
+	chd.carrier = si->ssi.cn;
+	chd.pkt     = DECT_PACKET_P00;
+	chd.b_fmt   = DECT_B_NONE;
+
+	if (!dect_transceiver_channel_available(trx, &chd))
+		return dect_restart_scan(cell, trx);
+
+	if (cell->mode != DECT_MODE_FP) {
+		memcpy(&cell->idi, &irc->idi, sizeof(cell->idi));
+		cell->fmid = dect_build_fmid(&cell->idi);
+		memcpy(&cell->si, si, sizeof(cell->si));
+
+		/* Q-channel information is broadcast in frame 8 */
+		dect_timer_synchronize_framenum(cell, DECT_Q_CHANNEL_FRAME);
+		dect_timer_synchronize_mfn(cell, si->mfn.num);
+
+		/* Lock framing based on slot position and create DBC */
+		dect_transceiver_lock(trx, chd.slot);
+		dect_dbc_init(cell, &chd);
+
+		clh->ops->mac_info_ind(clh, &cell->idi, &cell->si);
+	} else {
+		/* secondary transceiver */
+		dbc = dect_dbc_get(cell);
+		if (!(cell->flags & DECT_CELL_SLAVE) &&
+		    (dbc == NULL ||
+		     dbc->bearer.chd.slot    != chd.slot ||
+		     dbc->bearer.chd.carrier != chd.carrier))
+			return dect_restart_scan(cell, trx);
+
+		dect_transceiver_lock(trx, chd.slot);
+
+		/* Lock to the primary dummy bearer to keep the radio synchronized */
+		/* FIXME: do this cleanly */
+		dect_set_channel_mode(trx, &chd, DECT_SLOT_RX);
+		dect_set_flags(trx, chd.slot, DECT_SLOT_SYNC);
+		dect_set_carrier(trx, chd.slot, chd.carrier);
+	}
+
+	/* Enable IRC */
+	dect_irc_enable(cell, irc);
+}
+
+static void dect_attempt_lock(struct dect_cell *cell,
+			      struct dect_transceiver *trx)
+{
+	dect_initiate_scan(trx, &cell->idi.pari, NULL, dect_lock_fp);
+}
+
+/*
+ * Transmission: A- and B-Field MUXes
+ */
+
+static struct sk_buff *dect_u_mux(struct dect_cell *cell,
+				  struct dect_bearer *bearer)
+{
+	struct sk_buff *skb = NULL;
+	struct dect_tbc *tbc;
+	u8 b_field_size;
+
+	if (bearer->ops->state == DECT_TRAFFIC_BEARER) {
+		tbc = bearer->tbc;
+		skb = tbc->b_tx_skb;
+		tbc->b_tx_skb = NULL;
+	}
+
+	if (skb == NULL) {
+		b_field_size = dect_b_field_size(&bearer->chd);
+		skb = alloc_skb(b_field_size, GFP_ATOMIC);
+		if (skb == NULL)
+			return NULL;
+		skb_put(skb, b_field_size);
+		memset(skb->data, 0xff, b_field_size);
+		DECT_B_CB(skb)->id = DECT_BI_UTYPE_0;
+	}
+	return skb;
+}
+
+static struct sk_buff *dect_eu_mux(struct dect_cell *cell,
+				   struct dect_bearer *bearer)
+{
+	return dect_u_mux(cell, bearer);
+}
+
+static struct sk_buff *dect_b_map(struct dect_cell *cell,
+				  struct dect_bearer *bearer)
+{
+	return dect_eu_mux(cell, bearer);
+}
+
+#define tmux_debug(cell, fmt, args...) \
+	tx_debug(cell, "%s T-MUX: " fmt, \
+		 cell->mode == DECT_MODE_FP ? "FT" : "PT", ## args)
+
+/**
+ * dect_pt_t_mux - DECT T-MUX for PT transmissions
+ *
+ * @cell:	DECT cell
+ * @bearer:	MAC bearer
+ *
+ * The PT T-MUX sequence is used by PTs for all traffic bearers in connection
+ * oriented services and is defined as:
+ *
+ * Even frames: 	M_T, C_T, N_T
+ * Uneven frames:	N_T
+ *
+ * Exception: M_T tails containing "bearer request" or "bearer release"
+ * messages may be placed in any frame.
+ */
+static struct sk_buff *dect_pt_t_mux(struct dect_cell *cell,
+				     struct dect_bearer *bearer)
+{
+	struct dect_tbc *tbc = NULL;
+	struct sk_buff *skb;
+
+	switch (bearer->ops->state) {
+	case DECT_DUMMY_BEARER:
+	case DECT_CL_BEARER:
+	case DECT_MONITOR_BEARER:
+		WARN_ON(0);
+		break;
+	case DECT_TRAFFIC_BEARER:
+		tbc = bearer->tbc;
+		tbc->cs_tx_ok = false;
+		break;
+	}
+
+	if ((dect_framenum(cell, DECT_TIMER_TX) & 0x1) == 0) {
+		skb = skb_dequeue(&bearer->m_tx_queue);
+		if (skb != NULL) {
+			tmux_debug(cell, "M-channel\n");
+			return skb;
+		}
+		if (tbc != NULL && tbc->cs_tx_skb != NULL) {
+			skb = tbc->cs_tx_skb;
+			tbc->cs_tx_skb = NULL;
+			tbc->cs_tx_ok = true;
+			tmux_debug(cell, "C-channel\n");
+			return skb;
+		}
+	} else {
+		skb = skb_peek(&bearer->m_tx_queue);
+		if (skb != NULL && skb->priority == DECT_MT_HIGH_PRIORITY) {
+			tmux_debug(cell, "M-channel (high priority)\n");
+			skb_unlink(skb, &bearer->m_tx_queue);
+			return skb;
+		}
+	}
+
+	tmux_debug(cell, "N-channel\n");
+	return dect_bc_dequeue(cell, bearer, &tbc->bc, DECT_MC_N);
+}
+
+/**
+ * dect_rfp_t_mux - DECT T-MUX for RFP transmissions
+ *
+ * @cell:	DECT cell
+ * @bearer:	MAC bearer
+ *
+ * The RFP T-MUX sequence is used for all RFP transmissions and is defined as:
+ *
+ * Frame 8:		Q_T
+ * Frame 14:		N_T
+ * Other even frames: 	P_T, N_T
+ * Uneven frames:	M_T, C_T, N_T
+ *
+ * Exception: M_T tails sent in response to "bearer request" messages or during
+ * bearer release may be placed in any frame.
+ */
+static struct sk_buff *dect_rfp_t_mux(struct dect_cell *cell,
+				      struct dect_bearer *bearer)
+{
+	u8 framenum = dect_framenum(cell, DECT_TIMER_TX);
+	struct dect_tbc *tbc = NULL;
+	struct dect_bc *bc = NULL;
+	struct sk_buff *skb;
+
+	switch (bearer->ops->state) {
+	case DECT_DUMMY_BEARER:
+		bc = &bearer->dbc->bc;
+		break;
+	case DECT_TRAFFIC_BEARER:
+		tbc = bearer->tbc;
+		tbc->cs_tx_ok = false;
+		bc = &bearer->tbc->bc;
+		break;
+	case DECT_CL_BEARER:
+	case DECT_MONITOR_BEARER:
+		break;
+	}
+
+	if ((framenum & 0x1) == 0) {
+		skb = skb_peek(&bearer->m_tx_queue);
+		if (skb != NULL && skb->priority == DECT_MT_HIGH_PRIORITY) {
+			tmux_debug(cell, "M-channel (high priority)\n");
+			skb_unlink(skb, &bearer->m_tx_queue);
+			return skb;
+		}
+
+		if (framenum == 8) {
+			tmux_debug(cell, "Q-channel\n");
+			return dect_bc_dequeue(cell, bearer, bc, DECT_MC_Q);
+		}
+		if (framenum == 14) {
+			tmux_debug(cell, "N-channel\n");
+			return dect_bc_dequeue(cell, bearer, bc, DECT_MC_N);
+		}
+
+		skb = dect_bc_dequeue(cell, bearer, bc, DECT_MC_P);
+		if (skb != NULL) {
+			tmux_debug(cell, "P-channel\n");
+			return skb;
+		}
+	} else {
+		skb = skb_dequeue(&bearer->m_tx_queue);
+		if (skb != NULL) {
+			tmux_debug(cell, "M-channel\n");
+			return skb;
+		}
+		if (tbc != NULL && tbc->cs_tx_skb != NULL) {
+			skb = tbc->cs_tx_skb;
+			tbc->cs_tx_skb = NULL;
+			tbc->cs_tx_ok = true;
+			tmux_debug(cell, "C-channel\n");
+			return skb;
+		}
+	}
+
+	tmux_debug(cell, "N-channel\n");
+	return dect_bc_dequeue(cell, bearer, bc, DECT_MC_N);
+}
+
+/**
+ * dect_a_map - DECT A-Field mapping
+ *
+ * @cell:	DECT cell
+ * @bearer:	MAC bearer
+ *
+ * Combine the H-, T- and RA-Fields into the A-Field.
+ */
+static struct sk_buff *dect_a_map(struct dect_cell *cell,
+				  struct dect_bearer *bearer)
+{
+	struct sk_buff *skb;
+
+	switch (cell->mode) {
+	case DECT_MODE_PP:
+		skb = dect_pt_t_mux(cell, bearer);
+		break;
+	case DECT_MODE_FP:
+		skb = dect_rfp_t_mux(cell, bearer);
+		break;
+	}
+
+	if (skb == NULL)
+		return NULL;
+
+	/* Append empty RA-Field */
+	memset(skb_put(skb, DECT_RA_FIELD_SIZE), 0, DECT_RA_FIELD_SIZE);
+
+	/* Prepend Header field */
+	skb_push(skb, DECT_HDR_FIELD_SIZE);
+	skb->data[DECT_HDR_FIELD_OFF] = DECT_A_CB(skb)->id;
+	skb->data[DECT_HDR_FIELD_OFF] |= bearer->q;
+	bearer->q = 0;
+	return skb;
+}
+
+static struct sk_buff *dect_raw_tx_peek(struct dect_cell *cell)
+{
+	struct dect_timer_base *base = &cell->timer_base[DECT_TIMER_TX];
+	struct dect_skb_trx_cb *cb;
+	struct sk_buff *skb;
+
+	skb = skb_peek(&cell->raw_tx_queue);
+	if (skb == NULL)
+		return NULL;
+	cb = DECT_TRX_CB(skb);
+
+	if ((!cb->mfn || cb->mfn == base->mfn) &&
+	    (!cb->frame || cb->frame == base->framenum) &&
+	    cb->slot == dect_slotnum(cell, DECT_TIMER_TX))
+		return skb;
+
+	return NULL;
+}
+
+static void dect_raw_tx_configure(struct dect_cell *cell,
+				  struct dect_transceiver *trx,
+				  struct dect_transceiver_slot *ts)
+{
+	if (dect_raw_tx_peek(cell)) {
+		if (ts->state == DECT_SLOT_RX) {
+			tx_debug(cell, "enable raw TX\n");
+			dect_set_channel_mode(trx, &ts->chd, DECT_SLOT_TX);
+			dect_set_carrier(trx, ts->chd.slot, ts->chd.carrier);
+			ts->priv_flags |= DECT_SLOT_RAW_TX;
+		}
+	} else if (ts->priv_flags & DECT_SLOT_RAW_TX) {
+		tx_debug(cell, "disable raw TX\n");
+		dect_set_channel_mode(trx, &ts->chd, DECT_SLOT_RX);
+		dect_set_carrier(trx, ts->chd.slot, ts->chd.carrier);
+		ts->priv_flags &= ~DECT_SLOT_RAW_TX;
+	}
+}
+
+static struct sk_buff *dect_raw_tx(struct dect_cell *cell)
+{
+	struct sk_buff *skb;
+
+	skb = dect_raw_tx_peek(cell);
+	if (skb == NULL)
+		return NULL;
+
+	tx_debug(cell, "raw transmit\n");
+	skb_unlink(skb, &cell->raw_tx_queue);
+	return skb;
+}
+
+/**
+ * dect_d_map - DECT D-Field mapping
+ *
+ * @cell:	DECT cell
+ * @bearer:	MAC bearer
+ *
+ * Combine the A- and B-Fields from their respective MAPs into one D-Field.
+ */
+static struct sk_buff *dect_d_map(struct dect_cell *cell,
+				  struct dect_bearer *bearer)
+{
+	struct sk_buff *skb_a, *skb_b, *skb;
+
+	skb = dect_raw_tx(cell);
+	if (skb != NULL)
+		return skb;
+
+	skb_a = dect_a_map(cell, bearer);
+	if (skb_a == NULL)
+		goto err1;
+
+	if (bearer->chd.pkt != DECT_PACKET_P00) {
+		skb_b = dect_b_map(cell, bearer);
+		if (skb_b == NULL)
+			goto err2;
+		skb_a->data[DECT_HDR_BA_OFF] |= DECT_B_CB(skb_b)->id;
+
+		skb = skb_append_frag(skb_a, skb_b);
+		if (skb_linearize(skb) < 0) {
+			kfree_skb(skb);
+			skb = NULL;
+		}
+	} else {
+		skb_a->data[DECT_HDR_BA_OFF] |= DECT_BI_NONE;
+		skb = skb_a;
+	}
+
+	return skb;
+
+err2:
+	kfree_skb(skb_a);
+err1:
+	return NULL;
+}
+
+static void dect_mac_xmit_frame(struct dect_transceiver *trx,
+				struct dect_transceiver_slot *ts)
+{
+	struct dect_cell *cell = trx->cell;
+	struct dect_bearer *bearer = ts->bearer;
+	struct sk_buff *skb;
+
+	skb = dect_d_map(cell, bearer);
+	if (skb == NULL)
+		return;
+
+	tx_debug(cell, "%s: Q1: %d Q2: %d A/B: %02x carrier: %u PSCN: %u\n",
+		 trx->name,
+		 skb->data[DECT_HDR_Q1_OFF] & DECT_HDR_Q1_FLAG ? 1 : 0,
+		 skb->data[DECT_HDR_Q2_OFF] & DECT_HDR_Q2_FLAG ? 1 : 0,
+		 skb->data[DECT_HDR_TA_OFF] &
+		 (DECT_HDR_TA_MASK | DECT_HDR_BA_MASK),
+		 ts->chd.carrier, cell->si.ssi.pscn);
+
+	switch (cell->mode) {
+	case DECT_MODE_FP:
+		skb->mac_len = sizeof(dect_fp_preamble);
+		memcpy(skb_mac_header(skb), dect_fp_preamble, skb->mac_len);
+		break;
+	case DECT_MODE_PP:
+		skb->mac_len = sizeof(dect_pp_preamble);
+		memcpy(skb_mac_header(skb), dect_pp_preamble, skb->mac_len);
+		break;
+	}
+
+	DECT_TRX_CB(skb)->trx   = trx;
+	DECT_TRX_CB(skb)->slot  = ts->chd.slot;
+	DECT_TRX_CB(skb)->frame = dect_framenum(cell, DECT_TIMER_TX);
+	DECT_TRX_CB(skb)->mfn	= dect_mfn(cell, DECT_TIMER_TX);
+	dect_raw_rcv(skb);
+
+	dect_transceiver_tx(trx, skb);
+}
+
+void dect_mac_rcv(struct dect_transceiver *trx,
+		  struct dect_transceiver_slot *ts,
+		  struct sk_buff *skb)
+{
+	struct dect_cell *cell = trx->cell;
+
+	DECT_TRX_CB(skb)->frame = dect_framenum(cell, DECT_TIMER_RX);
+	DECT_TRX_CB(skb)->mfn	= dect_mfn(cell, DECT_TIMER_RX);
+
+	/* TX bearers can temporarily switch to RX mode for noise measurement */
+	if (ts->bearer != NULL &&
+	    ts->bearer->mode == DECT_BEARER_RX) {
+		rx_debug(cell, "%s: Q1: %d Q2: %d A/B: %02x carrier: %u %s%s%s",
+			 trx->name,
+			 skb->data[DECT_HDR_Q1_OFF] & DECT_HDR_Q1_FLAG ? 1 : 0,
+			 skb->data[DECT_HDR_Q2_OFF] & DECT_HDR_Q2_FLAG ? 1 : 0,
+			 skb->data[DECT_HDR_TA_OFF] &
+			 (DECT_HDR_TA_MASK | DECT_HDR_BA_MASK), ts->chd.carrier,
+			 DECT_TRX_CB(skb)->csum & DECT_CHECKSUM_A_CRC_OK ?
+			 "" : "A-CRC: 0 ",
+			 ts->chd.pkt == DECT_PACKET_P00 ||
+			 DECT_TRX_CB(skb)->csum & DECT_CHECKSUM_X_CRC_OK ?
+			 "" : "X-CRC: 0 ",
+			 ts->chd.pkt == DECT_PACKET_P00 ||
+			 DECT_TRX_CB(skb)->csum & DECT_CHECKSUM_Z_CRC_OK ?
+			 "" : "Z-CRC: 0 ");
+
+		ts->bearer->ops->rcv(cell, ts->bearer, skb);
+	} else
+		kfree_skb(skb);
+}
+
+void dect_mac_report_rssi(struct dect_transceiver *trx,
+			  struct dect_transceiver_slot *ts,
+			  u8 rssi)
+{
+	struct dect_cell *cell = trx->cell;
+
+	if (ts->bearer == NULL) {
+		pr_debug("%s: RSSI: slot: %u carrier: %u state: %u no bearer\n",
+			 trx->name, ts->chd.slot, ts->chd.carrier, ts->state);
+		return;
+	}
+	if (ts->bearer->state != DECT_BEARER_ENABLED)
+		dect_tx_bearer_report_rssi(cell, ts->bearer, rssi);
+	else if (ts->bearer->ops->report_rssi != NULL)
+		ts->bearer->ops->report_rssi(cell, ts->bearer, ts->chd.slot, rssi);
+}
+
+static void dect_fp_state_process(struct dect_cell *cell)
+{
+	if (list_empty(&cell->dbcs))
+		dect_dbc_init(cell, NULL);
+
+	if (time_before(cell->bfs_xmit_stamp + HZ, jiffies)) {
+		dect_cell_schedule_page(cell, (1 << DECT_TM_TYPE_BFS) |
+					      (1 << DECT_TM_TYPE_BD));
+		cell->bfs_xmit_stamp = jiffies;
+	}
+}
+
+static bool dect_pp_idle_timeout(const struct dect_cell *cell)
+{
+	u32 mfn;
+
+	mfn = dect_mfn_add(cell->timer_sync_stamp, DECT_CELL_TIMER_RESYNC_TIMEOUT);
+	if (dect_mfn_after(dect_mfn(cell, DECT_TIMER_RX), mfn) ||
+	    time_after(jiffies, cell->a_rcv_stamp + DECT_CELL_A_RCV_TIMEOUT) ||
+	    time_after(jiffies, cell->nt_rcv_stamp + DECT_CELL_NT_RCV_TIMEOUT)) {
+		pr_debug("timeout, unlock, a: %ld nt: %ld mfn: %d\n",
+			 jiffies - cell->a_rcv_stamp, jiffies - cell->nt_rcv_stamp,
+			 dect_mfn(cell, DECT_TIMER_RX) - cell->timer_sync_stamp);
+		return true;
+	}
+
+	return false;
+}
+
+static void dect_pp_state_process(struct dect_cell *cell)
+{
+	struct dect_transceiver *trx = cell->trg.trx[0];
+	struct dect_dbc *dbc, *next;
+
+	if (cell->tbc_num_est || !list_empty(&cell->dmbs)) {
+		/* Active locked state: release DBCs */
+		list_for_each_entry_safe(dbc, next, &cell->dbcs, list)
+			dect_dbc_release(dbc);
+	} else if (trx->state == DECT_TRANSCEIVER_LOCKED) {
+		/* Idle locked state: install DBC if none present */
+		if (list_empty(&cell->dbcs) &&
+		    list_empty(&cell->tbcs) &&
+		    list_empty(&cell->dmbs)) {
+			struct dect_channel_desc chd;
+
+			chd.pkt		= DECT_PACKET_P00;
+			chd.b_fmt	= DECT_B_NONE;
+			chd.slot	= cell->tbc_last_chd.slot;
+			chd.carrier	= cell->tbc_last_chd.carrier;
+
+			dect_dbc_init(cell, &chd);
+		}
+
+		if (!list_empty(&cell->dbcs) && dect_pp_idle_timeout(cell)) {
+			list_for_each_entry_safe(dbc, next, &cell->dbcs, list)
+				dect_dbc_release(dbc);
+
+			dect_irc_disable(cell, trx->irc);
+			dect_transceiver_unlock(trx);
+
+			/* Clear system information */
+			memset(&cell->si, 0, sizeof(cell->si));
+			dect_cell_mac_info_ind(cell);
+
+			dect_attempt_lock(cell, trx);
+		}
+	}
+}
+
+static void dect_cell_state_process(struct dect_cell *cell)
+{
+	if (list_empty(&cell->chanlists) && list_empty(&cell->chl_pending)) {
+		dect_chl_schedule_update(cell, DECT_PACKET_P00);
+		dect_chl_schedule_update(cell, DECT_PACKET_P32);
+	}
+
+	switch (cell->mode) {
+	case DECT_MODE_FP:
+		return dect_fp_state_process(cell);
+	case DECT_MODE_PP:
+		return dect_pp_state_process(cell);
+	}
+}
+
+static void dect_cluster_time_ind(struct dect_cell *cell,
+				  enum dect_timer_bases base,
+				  u8 slot)
+{
+	struct dect_cluster_handle *clh = cell->handle.clh;
+
+	clh->ops->time_ind(clh, base,
+			   dect_mfn(cell, base),
+			   dect_framenum(cell, base),
+			   slot);
+}
+
+void dect_mac_rx_tick(struct dect_transceiver_group *grp, u8 slot)
+{
+	struct dect_cell *cell = container_of(grp, struct dect_cell, trg);
+
+	dect_run_timers(cell, DECT_TIMER_RX);
+	dect_timer_base_update(cell, DECT_TIMER_RX, slot);
+	dect_cluster_time_ind(cell, DECT_TIMER_RX, slot);
+}
+
+void dect_mac_tx_tick(struct dect_transceiver_group *grp, u8 slot)
+{
+	struct dect_cell *cell = container_of(grp, struct dect_cell, trg);
+	struct dect_transceiver_slot *ts;
+	struct dect_transceiver *trx;
+	u8 scn;
+
+	/* TX timers run at the beginning of a slot, update the time first */
+	dect_timer_base_update(cell, DECT_TIMER_TX, slot);
+	dect_cluster_time_ind(cell, DECT_TIMER_TX, slot);
+	dect_run_timers(cell, DECT_TIMER_TX);
+
+	dect_cell_state_process(cell);
+
+	dect_foreach_transceiver(trx, grp) {
+		if (trx->state != DECT_TRANSCEIVER_LOCKED)
+			continue;
+		ts = &trx->slots[slot];
+
+		dect_raw_tx_configure(cell, trx, ts);
+
+		switch (ts->state) {
+		case DECT_SLOT_SCANNING:
+			scn = trx->irc->tx_scn;
+
+			if (cell->flags & DECT_CELL_MONITOR &&
+			    slot >= DECT_HALF_FRAME_SIZE)
+				scn = dect_prev_carrier(cell->si.ssi.rfcars, scn);
+
+			dect_set_carrier(trx, slot, scn);
+			break;
+		case DECT_SLOT_TX:
+			dect_mac_xmit_frame(trx, ts);
+			break;
+		}
+	}
+
+	if (slot == DECT_FRAME_SIZE - 1)
+		cell->si.ssi.pscn = dect_next_carrier(cell->si.ssi.rfcars,
+						      cell->si.ssi.pscn);
+}
+
+static void dect_fp_init_primary(struct dect_cell *cell,
+				 struct dect_transceiver *trx)
+{
+	dect_transceiver_enable(trx);
+	dect_irc_enable(cell, trx->irc);
+}
+
+static void dect_cell_enable_transceiver(struct dect_cell *cell,
+					 struct dect_transceiver *trx)
+{
+	/* The primary transceiver of a FP is a timing master. All other
+	 * transceivers need to synchronize.
+	 */
+	if (trx->index == 0 && cell->mode == DECT_MODE_FP &&
+	    !(cell->flags & DECT_CELL_SLAVE)) {
+		trx->mode = DECT_TRANSCEIVER_MASTER;
+		dect_fp_init_primary(cell, trx);
+	} else {
+		trx->mode = DECT_TRANSCEIVER_SLAVE;
+		dect_attempt_lock(cell, trx);
+	}
+}
+
+static int dect_cell_preload(const struct dect_cell_handle *ch,
+			     const struct dect_ari *pari, u8 rpn,
+			     const struct dect_si *si)
+{
+	struct dect_cell *cell = dect_cell(ch);
+
+	/* Initialise identity */
+	spin_lock_bh(&cell->lock);
+	cell->idi.e = false;
+	memcpy(&cell->idi.pari, pari, sizeof(cell->idi.pari));
+	cell->idi.rpn = rpn;
+	cell->fmid = dect_build_fmid(&cell->idi);
+
+	cell->si.ssi.rfcars = 0x3ff;
+	memcpy(&cell->si.erfc, &si->erfc, sizeof(cell->si.erfc));
+	memcpy(&cell->si.fpc, &si->fpc, sizeof(cell->si.fpc));
+	memcpy(&cell->si.efpc, &si->efpc, sizeof(cell->si.efpc));
+	memcpy(&cell->si.efpc2, &si->efpc2, sizeof(cell->si.efpc2));
+	memcpy(&cell->si.mfn, &si->mfn, sizeof(cell->si.mfn));
+	memcpy(cell->si.sari, si->sari, sizeof(cell->si.sari));
+	cell->si.num_saris = si->num_saris;
+	dect_timer_synchronize_mfn(cell, cell->si.mfn.num);
+	spin_unlock_bh(&cell->lock);
+	return 0;
+}
+
+static int dect_cell_enable(const struct dect_cell_handle *ch)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_transceiver *trx;
+
+	cell->state |= DECT_CELL_ENABLED;
+	dect_foreach_transceiver(trx, &cell->trg) {
+		dect_cell_enable_transceiver(cell, trx);
+		if (cell->mode == DECT_MODE_PP)
+			break;
+	}
+	return 0;
+}
+
+static void dect_scan_report(struct dect_cell *cell,
+			     struct dect_transceiver *trx,
+			     enum dect_scan_status status)
+{
+	const struct dect_cluster_handle *clh = cell->handle.clh;
+	const struct dect_irc *irc = trx->irc;
+	struct dect_scan_result res;
+
+	switch (status) {
+	case DECT_SCAN_FAIL:
+		break;
+	case DECT_SCAN_TIMEOUT:
+		pr_debug("timeout\n");
+	case DECT_SCAN_COMPLETE:
+		res.lreq = irc->lreq;
+		res.rssi = irc->rssi;
+		res.idi  = irc->idi;
+		res.si   = irc->si;
+		clh->ops->scan_report(clh, &res);
+		break;
+	}
+
+	return dect_restart_scan(cell, trx);
+}
+
+static int dect_cell_scan(const struct dect_cell_handle *ch,
+			  const struct dect_llme_req *lreq,
+			  const struct dect_ari *pari,
+			  const struct dect_ari *pari_mask)
+{
+	struct dect_cell *cell = dect_cell(ch);
+	struct dect_transceiver *trx = cell->trg.trx[0];
+
+	if (trx == NULL)
+		return -ENODEV;
+	// FIXME
+	memcpy(&trx->irc->lreq, lreq, sizeof(trx->irc->lreq));
+	dect_initiate_scan(trx, pari, pari_mask, dect_scan_report);
+	return 0;
+}
+
+static int dect_cell_set_mode(const struct dect_cell_handle *ch,
+			      enum dect_cluster_modes mode)
+{
+	struct dect_cell *cell = dect_cell(ch);
+
+	cell->mode = mode;
+	return 0;
+}
+
+static void dect_cell_page_req(const struct dect_cell_handle *ch,
+			       struct sk_buff *skb)
+{
+	struct dect_cell *cell = dect_cell(ch);
+
+	DECT_BMC_CB(skb)->stamp = dect_mfn(cell, DECT_TIMER_TX);
+	dect_queue_page(cell, skb);
+}
+
+static const struct dect_csf_ops dect_csf_ops = {
+	.set_mode		= dect_cell_set_mode,
+	.scan			= dect_cell_scan,
+	.enable			= dect_cell_enable,
+	.preload		= dect_cell_preload,
+	.page_req		= dect_cell_page_req,
+	.tbc_establish_req	= dect_tbc_establish_req,
+	.tbc_establish_res	= dect_tbc_establish_res,
+	.tbc_dis_req		= dect_tbc_dis_req,
+	.tbc_enc_key_req	= dect_tbc_enc_key_req,
+	.tbc_enc_eks_req	= dect_tbc_enc_eks_req,
+	.tbc_enc_req		= dect_tbc_enc_req,
+	.tbc_data_req		= dect_tbc_data_req,
+};
+
+static int dect_cell_bind(struct dect_cell *cell, u8 index)
+{
+	struct dect_cluster_handle *clh;
+	struct dect_cluster *cl;
+
+	if (cell->flags & DECT_CELL_CCP) {
+		clh = dect_ccp_cell_init(cell, index);
+		if (IS_ERR(clh))
+			return PTR_ERR(clh);
+	} else {
+		cl = dect_cluster_get_by_index(index);
+		if (cl == NULL)
+			return -ENOENT;
+		clh = &cl->handle;
+	}
+
+	return clh->ops->bind(clh, &cell->handle);
+}
+
+static void dect_cell_shutdown(struct dect_cell *cell)
+{
+	struct dect_cluster_handle *clh = cell->handle.clh;
+	struct dect_transceiver *trx;
+
+	if (clh != NULL)
+		clh->ops->unbind(clh, &cell->handle);
+
+	dect_foreach_transceiver(trx, &cell->trg)
+		dect_cell_detach_transceiver(cell, trx);
+	dect_cell_bmc_disable(cell);
+	skb_queue_purge(&cell->raw_tx_queue);
+}
+
+/**
+ * dect_mac_init_cell - Initialize a DECT cell
+ */
+static void dect_cell_init(struct dect_cell *cell)
+{
+	spin_lock_init(&cell->lock);
+	INIT_LIST_HEAD(&cell->bcs);
+	INIT_LIST_HEAD(&cell->dbcs);
+	INIT_LIST_HEAD(&cell->tbcs);
+	INIT_LIST_HEAD(&cell->dmbs);
+	INIT_LIST_HEAD(&cell->chl_pending);
+	INIT_LIST_HEAD(&cell->chanlists);
+	dect_timer_base_init(cell->timer_base, DECT_TIMER_TX);
+	dect_timer_base_init(cell->timer_base, DECT_TIMER_RX);
+	skb_queue_head_init(&cell->raw_tx_queue);
+	dect_cell_bmc_init(cell);
+	cell->blind_full_slots = (1 << DECT_HALF_FRAME_SIZE) - 1;
+	cell->trg_blind_full_slots = (1 << DECT_HALF_FRAME_SIZE) - 1;
+	dect_transceiver_group_init(&cell->trg);
+	cell->handle.ops = &dect_csf_ops;
+}
+
+/**
+ * dect_cell_attach_transceiver - attach a transceiver to a DECT cell
+ *
+ * Attach the transceiver to the cell's transceiver group and initialize
+ * an idle receiver control instance.
+ */
+int dect_cell_attach_transceiver(struct dect_cell *cell,
+				 struct dect_transceiver *trx)
+{
+	int err;
+
+	if (trx->cell != NULL)
+		return -EBUSY;
+
+	err = dect_transceiver_group_add(&cell->trg, trx);
+	if (err < 0)
+		goto err1;
+
+	err = -ENOMEM;
+	if (!dect_irc_init(cell, trx))
+		goto err2;
+
+	trx->cell = cell;
+	if (cell->state & DECT_CELL_ENABLED)
+		dect_cell_enable_transceiver(cell, trx);
+
+	return 0;
+
+err2:
+	dect_transceiver_group_remove(&cell->trg, trx);
+err1:
+	return err;
+}
+
+/**
+ * dect_cell_detach_transceiver - detach a transceiver from a DECT cell
+ *
+ * Detach the transceiver from the cell's transceiver group and release
+ * the associated resources.
+ */
+void dect_cell_detach_transceiver(struct dect_cell *cell,
+				  struct dect_transceiver *trx)
+{
+	dect_irc_disable(cell, trx->irc);
+	dect_transceiver_disable(trx);
+	dect_transceiver_group_remove(&cell->trg, trx);
+	kfree(trx->irc);
+	trx->cell = NULL;
+
+	dect_notify_cell(DECT_NEW_CELL, cell, NULL, 0);
+}
+
+/*
+ * Cell netlink interface
+ */
+
+static u32 dect_cell_alloc_index(void)
+{
+	static u32 index;
+
+	for (;;) {
+		if (++index == 0)
+			index = 1;
+		if (!dect_cell_get_by_index(index))
+			return index;
+	}
+}
+
+static int dect_fill_cell(struct sk_buff *skb,
+			  const struct dect_cell *cell,
+			  u16 type, u32 pid, u32 seq, u16 flags)
+{
+	const struct dect_transceiver *trx;
+	struct nlmsghdr *nlh;
+	struct dectmsg *dm;
+	struct nlattr *nest;
+
+	nlh = nlmsg_put(skb, pid, seq, type, sizeof(*dm), flags);
+	if (nlh == NULL)
+		return -EMSGSIZE;
+	dm = nlmsg_data(nlh);
+	dm->dm_index = cell->index;
+
+	NLA_PUT_STRING(skb, DECTA_CELL_NAME, cell->name);
+	if (cell->flags != 0)
+		NLA_PUT_U32(skb, DECTA_CELL_FLAGS, cell->flags);
+	if (cell->trg.trxmask != 0) {
+		nest = nla_nest_start(skb, DECTA_CELL_TRANSCEIVERS);
+		if (nest == NULL)
+			goto nla_put_failure;
+		dect_foreach_transceiver(trx, &cell->trg)
+			NLA_PUT_STRING(skb, DECTA_LIST_ELEM, trx->name);
+		nla_nest_end(skb, nest);
+	}
+	if (cell->handle.clh != NULL)
+		NLA_PUT_U8(skb, DECTA_CELL_CLUSTER, cell->handle.clh->index);
+
+	return nlmsg_end(skb, nlh);
+
+nla_put_failure:
+	nlmsg_cancel(skb, nlh);
+	return -EMSGSIZE;
+}
+
+static int dect_dump_cell(struct sk_buff *skb,
+			  struct netlink_callback *cb)
+{
+	const struct dect_cell *cell;
+	unsigned int idx, s_idx;
+
+	s_idx = cb->args[0];
+	idx = 0;
+	list_for_each_entry(cell, &dect_cell_list, list) {
+		if (idx < s_idx)
+			goto cont;
+		if (dect_fill_cell(skb, cell, DECT_NEW_CELL,
+				   NETLINK_CB(cb->skb).pid,
+				   cb->nlh->nlmsg_seq, NLM_F_MULTI) <= 0)
+			break;
+cont:
+		idx++;
+	}
+	cb->args[0] = idx;
+
+	return skb->len;
+}
+
+static void dect_notify_cell(u16 event, const struct dect_cell *cell,
+			     const struct nlmsghdr *nlh, u32 pid)
+{
+	struct sk_buff *skb;
+	bool report = nlh ? nlmsg_report(nlh) : 0;
+	u32 seq = nlh ? nlh->nlmsg_seq : 0;
+	int err = -ENOBUFS;
+
+	skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		goto err;
+
+	err = dect_fill_cell(skb, cell, event, pid, seq, NLMSG_DONE);
+	if (err < 0) {
+		WARN_ON(err == -EMSGSIZE);
+		kfree_skb(skb);
+		goto err;
+	}
+	nlmsg_notify(dect_nlsk, skb, pid, DECTNLGRP_CELL, report, GFP_KERNEL);
+err:
+	if (err < 0)
+		netlink_set_err(dect_nlsk, pid, DECTNLGRP_CELL, err);
+}
+
+static const struct nla_policy dect_cell_policy[DECTA_CELL_MAX + 1] = {
+	[DECTA_CELL_NAME]		= { .type = NLA_STRING, .len = DECTNAMSIZ },
+	[DECTA_CELL_FLAGS]		= { .type = NLA_U32 },
+	[DECTA_CELL_CLUSTER]		= { .type = NLA_U8 },
+};
+
+static int dect_new_cell(const struct sk_buff *skb,
+			 const struct nlmsghdr *nlh,
+			 const struct nlattr *tb[DECTA_CELL_MAX + 1])
+{
+	struct dect_cell *cell;
+	struct dectmsg *dm;
+	u32 flags = 0;
+	u8 cli = 0;
+	int err;
+
+	dm = nlmsg_data(nlh);
+	if (dm->dm_index != 0)
+		cell = dect_cell_get_by_index(dm->dm_index);
+	else if (tb[DECTA_CELL_NAME] != NULL)
+		cell = dect_cell_get_by_name(tb[DECTA_CELL_NAME]);
+	else
+		return -EINVAL;
+
+	if (tb[DECTA_CELL_FLAGS] != NULL) {
+		flags = nla_get_u32(tb[DECTA_CELL_FLAGS]);
+		if (flags & ~(DECT_CELL_CCP | DECT_CELL_SLAVE |
+			      DECT_CELL_MONITOR))
+			return -EINVAL;
+	}
+
+	if (tb[DECTA_CELL_CLUSTER] != NULL)
+		cli = nla_get_u8(tb[DECTA_CELL_CLUSTER]);
+
+	if (cell != NULL) {
+		if (nlh->nlmsg_flags & NLM_F_EXCL)
+			return -EEXIST;
+
+		if (tb[DECTA_CELL_CLUSTER] != NULL) {
+			if (cell->handle.clh != NULL)
+				return -EBUSY;
+			if (cli != 0)
+				return dect_cell_bind(cell, cli);
+		}
+		return 0;
+	}
+
+	if (!(nlh->nlmsg_flags & NLM_F_CREATE))
+		return -ENOENT;
+
+	cell = kzalloc(sizeof(*cell), GFP_KERNEL);
+	if (cell == NULL)
+		return -ENOMEM;
+	cell->index = dect_cell_alloc_index();
+	nla_strlcpy(cell->name, tb[DECTA_CELL_NAME], sizeof(cell->name));
+	cell->flags = flags;
+	dect_cell_init(cell);
+
+	if (cli != 0) {
+		err = dect_cell_bind(cell, cli);
+		if (err < 0)
+			goto err;
+	}
+
+	list_add_tail(&cell->list, &dect_cell_list);
+	dect_notify_cell(DECT_NEW_CELL, cell, nlh, NETLINK_CB(skb).pid);
+	return 0;
+
+err:
+	kfree(cell);
+	return err;
+}
+
+static int dect_del_cell(const struct sk_buff *skb,
+			 const struct nlmsghdr *nlh,
+			 const struct nlattr *tb[DECTA_CELL_MAX + 1])
+{
+	struct dect_cell *cell = NULL;
+	struct dectmsg *dm;
+
+	dm = nlmsg_data(nlh);
+	if (dm->dm_index != 0)
+		cell = dect_cell_get_by_index(dm->dm_index);
+	else if (tb[DECTA_CELL_NAME] != NULL)
+		cell = dect_cell_get_by_name(tb[DECTA_CELL_NAME]);
+	if (cell == NULL)
+		return -ENODEV;
+
+	cell = dect_cell_get_by_name(tb[DECTA_CELL_NAME]);
+	if (cell == NULL)
+		return -ENOENT;
+
+	dect_cell_shutdown(cell);
+	list_del(&cell->list);
+	dect_notify_cell(DECT_DEL_CELL, cell, nlh, NETLINK_CB(skb).pid);
+	kfree(cell);
+	return 0;
+}
+
+static int dect_get_cell(const struct sk_buff *in_skb,
+			 const struct nlmsghdr *nlh,
+			 const struct nlattr *tb[DECTA_CELL_MAX + 1])
+{
+	u32 pid = NETLINK_CB(in_skb).pid;
+	const struct dect_cell *cell = NULL;
+	struct dectmsg *dm;
+	struct sk_buff *skb;
+	int err;
+
+	dm = nlmsg_data(nlh);
+	if (dm->dm_index != 0)
+		cell = dect_cell_get_by_index(dm->dm_index);
+	else if (tb[DECTA_CELL_NAME] != NULL)
+		cell = dect_cell_get_by_name(tb[DECTA_CELL_NAME]);
+	if (cell == NULL)
+		return -ENODEV;
+
+	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		return -ENOMEM;
+	err = dect_fill_cell(skb, cell, DECT_NEW_CELL, pid, nlh->nlmsg_seq,
+			     NLMSG_DONE);
+	if (err < 0)
+		goto err1;
+	return nlmsg_unicast(dect_nlsk, skb, pid);
+
+err1:
+	kfree_skb(skb);
+	return err;
+}
+
+static const struct dect_netlink_handler dect_cell_handlers[] = {
+       {
+	       /* DECT_NEW_CELL */
+		.policy		= dect_cell_policy,
+		.maxtype	= DECTA_CELL_MAX,
+		.doit		= dect_new_cell,
+	},
+	{
+		/* DECT_DEL_CELL */
+		.policy		= dect_cell_policy,
+		.maxtype	= DECTA_CELL_MAX,
+		.doit		= dect_del_cell,
+	},
+	{
+		/* DECT_GET_CELL */
+		.policy		= dect_cell_policy,
+		.maxtype	= DECTA_CELL_MAX,
+		.doit		= dect_get_cell,
+		.dump		= dect_dump_cell,
+	},
+};
+
+static int __init dect_csf_module_init(void)
+{
+	int err;
+
+	err = dect_transceiver_module_init();
+	if (err < 0)
+		return err;
+
+	dect_netlink_register_handlers(dect_cell_handlers, DECT_NEW_CELL,
+				       ARRAY_SIZE(dect_cell_handlers));
+	return 0;
+}
+
+static void __exit dect_csf_module_exit(void)
+{
+	dect_netlink_unregister_handlers(DECT_NEW_CELL,
+				         ARRAY_SIZE(dect_cell_handlers));
+	dect_transceiver_module_exit();
+}
+
+module_init(dect_csf_module_init);
+module_exit(dect_csf_module_exit);
+MODULE_LICENSE("GPL");
diff --git a/net/dect/raw.c b/net/dect/raw.c
new file mode 100644
index 0000000..52a4d26
--- /dev/null
+++ b/net/dect/raw.c
@@ -0,0 +1,268 @@
+/*
+ * DECT RAW sockets
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/dect.h>
+#include <net/sock.h>
+#include <net/dect/dect.h>
+#include <net/dect/mac_csf.h>
+
+static HLIST_HEAD(dect_raw_sockets);
+
+struct dect_raw_sk {
+	struct sock		sk;
+};
+
+static inline struct dect_raw_sk *dect_raw_sk(struct sock *sk)
+{
+	return (struct dect_raw_sk *)sk;
+}
+
+static void __dect_raw_rcv(struct sk_buff *skb)
+{
+	struct dect_cell *cell = DECT_TRX_CB(skb)->trx->cell;
+	struct hlist_node *node;
+	struct sk_buff *skb2;
+	struct sock *sk;
+
+	sk_for_each_bound(sk, node, &dect_raw_sockets) {
+		if (sk->sk_bound_dev_if &&
+		    sk->sk_bound_dev_if != cell->index)
+			continue;
+		if (skb->sk == sk)
+			continue;
+
+		skb2 = skb_clone(skb, GFP_ATOMIC);
+		if (skb2 == NULL) {
+			sk->sk_err = -ENOMEM;
+			sk->sk_error_report(sk);
+		} else {
+			/* Release the transceiver reference, it is only valid
+			 * in IRQ and softirq context.
+			 */
+			DECT_TRX_CB(skb2)->trx = NULL;
+			if (dect_sock_queue_rcv_skb(sk, skb2) < 0)
+				kfree_skb(skb2);
+		}
+	}
+}
+
+static void dect_raw_close(struct sock *sk, long timeout)
+{
+	sk_common_release(sk);
+}
+
+static int dect_raw_bind(struct sock *sk, struct sockaddr *uaddr, int len)
+{
+	struct sockaddr_dect *addr = (struct sockaddr_dect *)uaddr;
+
+	if (len < sizeof(*addr) || addr->dect_family != AF_DECT)
+		return -EINVAL;
+
+	if (addr->dect_index != 0 &&
+	    !dect_cell_get_by_index(addr->dect_index))
+		return -ENODEV;
+
+	lock_sock(sk);
+	sk->sk_bound_dev_if = addr->dect_index;
+	if (!hlist_unhashed(&sk->sk_bind_node))
+		__sk_del_bind_node(sk);
+	sk_add_bind_node(sk, &dect_raw_sockets);
+	release_sock(sk);
+	return 0;
+}
+
+static void dect_raw_unhash(struct sock *sk)
+{
+	if (!hlist_unhashed(&sk->sk_bind_node))
+		__sk_del_bind_node(sk);
+}
+
+static int dect_raw_getname(struct sock *sk, struct sockaddr *uaddr, int *len,
+			    int peer)
+{
+	struct sockaddr_dect *addr = (struct sockaddr_dect *)uaddr;
+
+	if (peer)
+		return -EOPNOTSUPP;
+
+	addr->dect_family = AF_DECT;
+	addr->dect_index  = sk->sk_bound_dev_if;
+	*len = sizeof(*addr);
+	return 0;
+}
+
+static int dect_raw_recvmsg(struct kiocb *iocb, struct sock *sk,
+			    struct msghdr *msg, size_t len,
+			    int noblock, int flags, int *addrlen)
+{
+	struct sockaddr_dect *addr;
+	struct dect_raw_auxdata aux;
+	struct sk_buff *skb;
+	size_t copied = 0;
+	int err;
+
+	if (flags & MSG_OOB)
+		return -EOPNOTSUPP;
+
+	skb = skb_recv_datagram(sk, flags, noblock, &err);
+	if (skb == NULL)
+		goto out;
+
+	copied = skb->len;
+	if (len < copied) {
+		msg->msg_flags |= MSG_TRUNC;
+		copied = len;
+	}
+
+	err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+	if (err < 0)
+		goto out_free;
+
+	if (msg->msg_name != NULL) {
+		addr = (struct sockaddr_dect *)msg->msg_name;
+		addr->dect_family = AF_DECT;
+		addr->dect_index  = DECT_SK_CB(skb)->index;
+		msg->msg_namelen = sizeof(*addr);
+	}
+
+	sock_recv_timestamp(msg, sk, skb);
+
+	aux.mfn   = DECT_TRX_CB(skb)->mfn;
+	aux.frame = DECT_TRX_CB(skb)->frame;
+	aux.slot  = DECT_TRX_CB(skb)->slot;
+	aux.rssi  = DECT_TRX_CB(skb)->rssi;
+	put_cmsg(msg, SOL_DECT, DECT_RAW_AUXDATA, sizeof(aux), &aux);
+
+	if (flags & MSG_TRUNC)
+		copied = skb->len;
+out_free:
+	skb_free_datagram(sk, skb);
+out:
+	return err ? : copied;
+}
+
+static int dect_raw_sendmsg(struct kiocb *iocb, struct sock *sk,
+			    struct msghdr *msg, size_t len)
+{
+	struct sockaddr_dect *addr = msg->msg_name;
+	struct dect_raw_auxdata *aux = NULL;
+	struct dect_cell *cell;
+	struct sk_buff *skb;
+	struct cmsghdr *cmsg;
+	size_t size;
+	int index;
+	int err;
+
+	if (msg->msg_namelen) {
+		if (addr->dect_family != AF_DECT)
+			return -EINVAL;
+		index = addr->dect_index;
+	} else
+		index = sk->sk_bound_dev_if;
+
+	cell = dect_cell_get_by_index(index);
+	if (cell == NULL)
+		return -ENODEV;
+
+	for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+		if (!CMSG_OK(msg, cmsg))
+			return -EINVAL;
+		if (cmsg->cmsg_level != SOL_DECT)
+			continue;
+
+		switch (cmsg->cmsg_type) {
+		case DECT_RAW_AUXDATA:
+			if (cmsg->cmsg_len != CMSG_LEN(sizeof(*aux)))
+				return -EINVAL;
+			aux = (struct dect_raw_auxdata *)CMSG_DATA(cmsg);
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (aux == NULL)
+		return -EINVAL;
+
+	size = DECT_PREAMBLE_SIZE + len;
+	skb = sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT, &err);
+	if (skb == NULL)
+		goto err1;
+
+	/* Reserve space for preamble */
+	skb_reset_mac_header(skb);
+	skb_reserve(skb, DECT_PREAMBLE_SIZE);
+
+	err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+	if (err < 0)
+		goto err2;
+
+	DECT_TRX_CB(skb)->mfn   = aux->mfn;
+	DECT_TRX_CB(skb)->frame = aux->frame;
+	DECT_TRX_CB(skb)->slot  = aux->slot;
+
+	skb_queue_tail(&cell->raw_tx_queue, skb);
+	return len;
+
+err2:
+	kfree_skb(skb);
+err1:
+	return err;
+}
+
+static struct dect_proto dect_raw_proto = {
+	.type		= SOCK_RAW,
+	.protocol	= DECT_RAW,
+	.capability	= CAP_NET_RAW,
+	.ops		= &dect_dgram_ops,
+	.proto.name	= "DECT_RAW",
+	.proto.owner	= THIS_MODULE,
+	.proto.obj_size	= sizeof(struct dect_raw_sk),
+	.proto.close	= dect_raw_close,
+	.proto.bind	= dect_raw_bind,
+	.proto.unhash	= dect_raw_unhash,
+	.proto.recvmsg	= dect_raw_recvmsg,
+	.proto.sendmsg	= dect_raw_sendmsg,
+	.getname	= dect_raw_getname,
+};
+
+static int __init dect_raw_init(void)
+{
+	int err;
+
+	BUILD_BUG_ON(sizeof(struct sockaddr_dect) >
+		     sizeof(struct sockaddr));
+	err = dect_proto_register(&dect_raw_proto);
+	if (err < 0)
+		return err;
+	rcu_assign_pointer(dect_raw_rcv_hook, __dect_raw_rcv);
+	return 0;
+}
+
+static void __exit dect_raw_exit(void)
+{
+	rcu_assign_pointer(dect_raw_rcv_hook, NULL);
+	synchronize_rcu();
+	dect_proto_unregister(&dect_raw_proto);
+}
+
+module_init(dect_raw_init);
+module_exit(dect_raw_exit);
+
+MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
+MODULE_DESCRIPTION("DECT RAW sockets");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS_NET_PF_PROTO(PF_DECT, DECT_RAW);
diff --git a/net/dect/transceiver.c b/net/dect/transceiver.c
new file mode 100644
index 0000000..2949297
--- /dev/null
+++ b/net/dect/transceiver.c
@@ -0,0 +1,1025 @@
+/*
+ * DECT transceiver and transceiver group functions
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+//#define DEBUG
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/notifier.h>
+#include <net/dect/dect.h>
+#include <net/dect/mac_csf.h>
+#include <net/dect/transceiver.h>
+
+static LIST_HEAD(dect_transceiver_list);
+static int dect_transceiver_notify(struct dect_transceiver *trx,
+				   unsigned long event);
+
+#define trx_debug(trx, fmt, args...) \
+	pr_debug("%s: " fmt, trx->name, ## args)
+
+static const struct dect_band *dect_band[DECT_BAND_NUM];
+static const u8 dect_pkt_size[] = {
+	[DECT_PACKET_P00]	= DECT_P00_SIZE,
+	[DECT_PACKET_P08]	= DECT_P08_SIZE,
+	[DECT_PACKET_P32]	= DECT_P32_SIZE,
+	[DECT_PACKET_P640j]	= DECT_P640j_SIZE,
+	[DECT_PACKET_P80]	= DECT_P80_SIZE,
+};
+
+/**
+ * dect_transceiver_alloc_skb - allocate a transceiver RX skb
+ *
+ * @trx:	transceiver
+ * @slot:	slot number
+ *
+ * Allocate a skb according to the receiving channel characteristics.
+ */
+struct sk_buff *dect_transceiver_alloc_skb(struct dect_transceiver *trx, u8 slot)
+{
+	const struct dect_transceiver_slot *ts = &trx->slots[slot];
+	unsigned int align;
+	struct sk_buff *skb;
+
+	align = __alignof__(u64) - DECT_PREAMBLE_SIZE;
+
+	skb = alloc_skb(dect_pkt_size[ts->chd.pkt] + align, GFP_ATOMIC);
+	if (skb == NULL)
+		return NULL;
+	DECT_TRX_CB(skb)->trx = trx;
+	DECT_TRX_CB(skb)->slot = ts->chd.slot;
+	/* Reserve room for preamble and set up adjacent packet data pointer */
+	skb_reserve(skb, DECT_PREAMBLE_SIZE + align);
+	skb_put(skb, dect_pkt_size[ts->chd.pkt] - DECT_PREAMBLE_SIZE);
+	return skb;
+}
+EXPORT_SYMBOL_GPL(dect_transceiver_alloc_skb);
+
+/* Transceiver virtual clock maintenance:
+ *
+ * The transceiver layer processes the received frames from some slots in
+ * the past while the transceiver is transceiving on the following slots.
+ * Additionally the transceiver needs to be maintained for the upcoming
+ * slots. Therefore there are three different reference frames of time:
+ *
+ * 		[  RX  ][  TRX ][  TX  ]
+ * slot_0		   ->			slot_23
+ *
+ * - Real time, which is only known to the transceiver
+ *
+ * - RX time, which is a virtual clock following real time by at least one
+ *   slot and advancing as received slots are processed. A transceiver must
+ *   generate enough events so that the RX time never lags behind the TRX
+ *   time for more than one TDMA half frame.
+ *
+ * - TX time, which is a virtual clock leading RX time by a usually constant
+ *   amount of slots large enough so that packets queued to the transceiver
+ *   will reach the transceiver in time to be sent. It always leads real time,
+ *   but must never have a distance greater than one TDMA half frame from RX
+ *   time, resulting in a maximal distance of 11 to real time.
+ *
+ * Transceivers periodically notify the transceiver layer of an elapsed amount
+ * of time and the frames that were received during that period. The events
+ * batches generated by multiple transceivers contained in a group are merged
+ * and processed as a single chronological event stream.
+ *
+ * The following steps are performed during processing (for every slot):
+ *
+ * - Received packets are passed to the MAC layer
+ * - A RX tick is generated, ending the last RX slot
+ * - A TX tick with some offset in the future is generated, beginning the
+ *   next TX slot
+ *
+ * The RX and TX ticks are used by the MAC layer to maintain two timer bases
+ * for performing maintenance operations after a slot was received or before
+ * a slot will be transmitted.
+ */
+
+/**
+ * dect_transceiver_queue_event - queue a transceiver event for BH processing 
+ *
+ * @trx:	DECT transceiver
+ * @event:	Transceiver event
+ */
+void dect_transceiver_queue_event(struct dect_transceiver *trx,
+				  struct dect_transceiver_event *event)
+{
+	struct dect_transceiver_group *grp = &trx->cell->trg;
+
+	spin_lock(&grp->lock);
+	list_add_tail(&event->list, &grp->events);
+	spin_unlock(&grp->lock);
+
+	tasklet_hi_schedule(&grp->tasklet);
+}
+EXPORT_SYMBOL_GPL(dect_transceiver_queue_event);
+
+static struct dect_transceiver_event *
+dect_dequeue_event(struct dect_transceiver_group *grp)
+{
+	struct dect_transceiver_event *event;
+	unsigned long flags;
+
+	event = NULL;
+	spin_lock_irqsave(&grp->lock, flags);
+	if (!list_empty(&grp->events)) {
+		event = list_first_entry(&grp->events,
+					 struct dect_transceiver_event,
+					 list);
+		list_del(&event->list);
+	}
+	spin_unlock_irqrestore(&grp->lock, flags);
+	return event;
+}
+
+static void dect_tg_merge_events(struct dect_transceiver_group *grp,
+				 struct dect_transceiver *trx,
+				 struct dect_transceiver_event *event)
+{
+	struct sk_buff *skb;
+	u8 slot, idx, i;
+
+	/* Transfer the packets to the slot input queues and mark the
+	 * slot events.
+	 */
+	trx_debug(trx, "merge %u events pos %u rssi_mask %x\n",
+		  trx->ops->eventrate, event->slotpos, event->rssi_mask);
+
+	for (i = 0; i < trx->ops->eventrate; i++) {
+		slot = event->slotpos + i;
+		idx = slot % ARRAY_SIZE(grp->slots);
+
+		skb = skb_peek(&event->rx_queue);
+		if (skb != NULL && DECT_TRX_CB(skb)->slot == slot) {
+			__skb_unlink(skb, &event->rx_queue);
+			__skb_queue_tail(&grp->slots[idx].queue, skb);
+		} else if (event->rssi_mask & (1 << i))
+			grp->slots[idx].rssi[trx->index] = event->rssi[i];
+
+		grp->slots[idx].mask |= 1 << trx->index;
+	}
+}
+
+static bool seqno_before(u32 seq1, u32 seq2)
+{
+	return (s32)(seq2 - seq1) > 0;
+}
+
+static bool seqno_after(u32 seq1, u32 seq2)
+{
+	return seqno_before(seq2, seq1);
+}
+
+static void dect_tg_process_events(struct dect_transceiver_group *grp)
+{
+	struct dect_transceiver *trx;
+	struct dect_transceiver_slot *ts;
+	struct sk_buff *skb;
+	u8 idx, d, i;
+	u16 late;
+
+	pr_debug("process events slot_low=%u slot_high=%u distance=%u\n",
+		 grp->slot_low, grp->slot_high,
+		 dect_slot_distance(grp->slot_low, grp->slot_high));
+
+	while (grp->slot_low != grp->slot_high) {
+		/*
+		 * If more than one half frame is missing, only forward the
+		 * clock since the slot positions refer to slots in the
+		 * following half frame.
+		 */
+		d = dect_slot_distance(grp->slot_low, grp->slot_high);
+		if (d > ARRAY_SIZE(grp->slots))
+			goto tick;
+
+		idx = grp->slot_low % ARRAY_SIZE(grp->slots);
+
+		/* Check for transceivers which are lagging by more than their
+		 * event rate window and mark the current window entirely as
+		 * lost.
+		 */
+		late = grp->slots[idx].mask ^ grp->trxmask;
+		while (late != 0) {
+			trx = grp->trx[ffs(late) - 1];
+			late &= ~(1 << trx->index);
+
+			if (!seqno_before(trx->seqno + trx->ops->eventrate,
+					  grp->seqno) &&
+			    d <= trx->ops->eventrate)
+				continue;
+
+			trx_debug(trx, "late for window %u\n", grp->slot_low);
+			for (i = 0; i < trx->ops->eventrate; i++)
+				grp->slots[(idx + i) % 12].mask |= 1 << trx->index;
+			trx->stats.event_late++;
+		}
+
+		if (grp->slots[idx].mask != grp->trxmask) {
+			pr_debug("slot %u incomplete: mask %x trx %x\n",
+				grp->slot_low, grp->slots[idx].mask, grp->trxmask);
+			break;
+		}
+
+		while ((skb = __skb_dequeue(&grp->slots[idx].queue))) {
+			trx = DECT_TRX_CB(skb)->trx;
+			ts = &trx->slots[DECT_TRX_CB(skb)->slot];
+			dect_mac_rcv(trx, ts, skb);
+		}
+
+		for (i = 0; i < ARRAY_SIZE(grp->slots[idx].rssi); i++) {
+			if (grp->slots[idx].rssi[i] == 0)
+				continue;
+			trx = grp->trx[i];
+			ts = &trx->slots[grp->slot_low];
+			dect_mac_report_rssi(trx, ts, grp->slots[idx].rssi[i]);
+			grp->slots[idx].rssi[i] = 0;
+		}
+
+		grp->slots[idx].mask = 0;
+tick:
+		dect_mac_rx_tick(grp, dect_next_slotnum(grp->slot_low));
+		dect_mac_tx_tick(grp, dect_slot_add(grp->slot_low,
+						    2 * grp->latency));
+
+		grp->slot_low = dect_next_slotnum(grp->slot_low);
+	}
+}
+
+/*
+ * Softirq transceiver group event processing
+ */
+static void dect_transceiver_tasklet(unsigned long data)
+{
+	struct dect_transceiver_group *grp = (struct dect_transceiver_group *)data;
+	struct dect_transceiver *trx;
+	struct dect_transceiver_event *event;
+	struct sk_buff *skb;
+
+again:
+	event = dect_dequeue_event(grp);
+	if (event == NULL) {
+		dect_tg_process_events(grp);
+		return;
+	}
+
+	trx = event->trx;
+
+	trx_debug(trx, "event handler: trx: seq %u pos %u grp: seq %u pos %u\n",
+		  trx->seqno, event->slotpos, grp->seqno, grp->slot_low);
+
+	/* Before a transceiver is locked, its timing might vary and isn't
+	 * synchronized to the remaining group. The MAC layer handles this
+	 * manually.
+	 */
+	if (trx->state != DECT_TRANSCEIVER_LOCKED) {
+		skb = __skb_dequeue(&event->rx_queue);
+		if (skb != NULL)
+			dect_mac_irc_rcv(trx, skb);
+		dect_mac_irc_tick(trx);
+		goto out;
+	}
+
+	/* If a secondary transceiver enters locked state or a tranceiver missed
+	 * a previous window, its sequence number is out of sync. Resync it once
+	 * it starts reporting events in the current window.
+	 *
+	 * FIXME: driver should ignore frames with missed interrupts completely
+	 */
+	if (seqno_before(trx->seqno + trx->ops->eventrate, grp->seqno)) {
+		if (event->slotpos != grp->slot_low) {
+			trx_debug(trx, "unsynchronized\n");
+			__skb_queue_purge(&event->rx_queue);
+			goto out;
+		}
+
+		trx->seqno = grp->seqno;
+		if (grp->slot_high != grp->slot_low)
+			trx->seqno -= trx->ops->eventrate;
+
+		trx_debug(trx, "synchronized to seqno %u\n", trx->seqno);
+	}
+
+	/* Merge the events and update the sequence number. The transceiver
+	 * with the highest sequence number determines the slot position for
+	 * the entire group.
+	 */
+	dect_tg_merge_events(grp, trx, event);
+
+	trx->seqno += trx->ops->eventrate;
+	if (seqno_after(trx->seqno, grp->seqno)) {
+		grp->seqno = trx->seqno;
+		grp->slot_high =
+			dect_slot_add(event->slotpos, trx->ops->eventrate);
+	}
+
+out:
+	dect_release_transceiver_event(event);
+	goto again;
+}
+
+int dect_transceiver_group_add(struct dect_transceiver_group *grp,
+			       struct dect_transceiver *trx)
+{
+	u8 index;
+
+	index = ffz(grp->trxmask);
+	if (index >= ARRAY_SIZE(grp->trx))
+		return -EMFILE;
+
+	trx->index = index;
+	grp->trx[index] = trx;
+	if (trx->ops->latency > grp->latency)
+		grp->latency = trx->ops->latency;
+
+	grp->trxmask |= 1 << index;
+	return 0;
+}
+
+void dect_transceiver_group_remove(struct dect_transceiver_group *grp,
+				   struct dect_transceiver *trx)
+{
+	grp->trxmask &= ~(1 << trx->index);
+	/* Synchronize with interrupt and softirq processing */
+	synchronize_rcu();
+	grp->trx[trx->index] = NULL;
+}
+
+void dect_transceiver_group_init(struct dect_transceiver_group *grp)
+{
+	unsigned int i;
+
+	spin_lock_init(&grp->lock);
+	INIT_LIST_HEAD(&grp->events);
+	for (i = 0; i < ARRAY_SIZE(grp->slots); i++)
+		__skb_queue_head_init(&grp->slots[i].queue);
+
+	tasklet_init(&grp->tasklet, dect_transceiver_tasklet,
+		     (unsigned long)grp);
+}
+
+void dect_transceiver_disable(struct dect_transceiver *trx)
+{
+	trx->ops->disable(trx);
+	trx->state = DECT_TRANSCEIVER_STOPPED;
+}
+
+void dect_transceiver_enable(struct dect_transceiver *trx)
+{
+	if (trx->mode == DECT_TRANSCEIVER_MASTER)
+		trx->state = DECT_TRANSCEIVER_LOCKED;
+	else {
+		trx->state = DECT_TRANSCEIVER_UNLOCKED;
+		trx->slots[DECT_SCAN_SLOT].state = DECT_SLOT_SCANNING;
+	}
+	trx->ops->enable(trx);
+}
+
+void dect_transceiver_confirm(struct dect_transceiver *trx)
+{
+	trx_debug(trx, "confirm\n");
+	trx->state = DECT_TRANSCEIVER_LOCK_PENDING;
+	trx->slots[DECT_SCAN_SLOT].state = DECT_SLOT_RX;
+	trx->ops->confirm(trx);
+}
+
+void dect_transceiver_unlock(struct dect_transceiver *trx)
+{
+	trx_debug(trx, "unlock\n");
+	trx->ops->unlock(trx);
+	trx->slots[DECT_SCAN_SLOT].state = DECT_SLOT_SCANNING;
+	trx->state = DECT_TRANSCEIVER_UNLOCKED;
+}
+
+int dect_transceiver_set_band(struct dect_transceiver *trx, u8 bandnum)
+{
+	const struct dect_band *band;
+
+	band = dect_band[bandnum];
+	if (band == NULL)
+		return -ENOENT;
+	trx->carriers = trx->ops->set_band(trx, band);
+	trx->band = band;
+	return 0;
+}
+
+void dect_transceiver_lock(struct dect_transceiver *trx, u8 slot)
+{
+	trx_debug(trx, "lock to slot %u\n", slot);
+	trx->slots[DECT_SCAN_SLOT].state = DECT_SLOT_IDLE;
+	trx->state = DECT_TRANSCEIVER_LOCKED;
+	trx->ops->lock(trx, slot);
+}
+
+static void dect_transceiver_set_blind(struct dect_transceiver *trx, u8 slot)
+{
+	u8 n = DECT_FRAME_SIZE - 1 - slot;
+
+	trx->slots[slot].blinded++;
+	trx->blind_full_slots |= 1 << n;
+}
+
+static void dect_transceiver_set_visible(struct dect_transceiver *trx, u8 slot)
+{
+	u8 n = DECT_FRAME_SIZE - 1 - slot;
+
+	if (--trx->slots[slot].blinded == 0)
+		trx->blind_full_slots &= ~(1 << n);
+}
+
+static bool dect_transceiver_slot_blind(const struct dect_transceiver *trx, u8 slot)
+{
+	u8 n = DECT_FRAME_SIZE - 1 - slot;
+
+	return trx->blind_full_slots & (1 << n);
+}
+
+bool dect_transceiver_channel_available(const struct dect_transceiver *trx,
+				        const struct dect_channel_desc *chd)
+{
+	u8 slot = chd->slot, prev, next;
+
+	if (trx->slots[slot].state == DECT_SLOT_RX ||
+	    trx->slots[slot].state == DECT_SLOT_TX)
+		return false;
+
+	switch ((int)chd->pkt) {
+	case DECT_PACKET_P80:
+		if (dect_transceiver_slot_blind(trx, slot + 1))
+			return false;
+	case DECT_PACKET_P32:
+	case DECT_PACKET_P08:
+	case DECT_PACKET_P00:
+		if (dect_transceiver_slot_blind(trx, slot))
+			return false;
+		break;
+	}
+
+	/* In case of slow hopping transceivers the adjacent slots must be
+	 * available as well. Scanning slots are not blind, so they must be
+	 * checked for explicitly.
+	 */
+	if (trx->ops->features & DECT_TRANSCEIVER_SLOW_HOPPING) {
+		prev = dect_prev_slotnum(slot);
+		if (trx->slots[prev].state == DECT_SLOT_SCANNING)
+			return false;
+		next = dect_next_slotnum(slot);
+		if (trx->slots[next].state == DECT_SLOT_SCANNING)
+			return false;
+	}
+
+	return true;
+}
+
+static bool dect_tg_update_blind_full_slots(struct dect_transceiver_group *trg)
+{
+	const struct dect_transceiver *trx;
+	u32 blind_full_slots;
+
+	blind_full_slots = (~0U) & DECT_SLOT_MASK;
+	dect_foreach_transceiver(trx, trg)
+		blind_full_slots &= trx->blind_full_slots;
+
+	if (trg->blind_full_slots != blind_full_slots) {
+		trg->blind_full_slots = blind_full_slots;
+		return true;
+	} else
+		return false;
+}
+
+/**
+ * dect_transceiver_reserve - reserve transceiver resources for a physical channel
+ *
+ * Reserve the slot positions necessary to estabish the specified physical
+ * channel. The chosen transceivers and the global groups blind full slot
+ * masks are updated.
+ *
+ * Returns true when the global visibility state has changed.
+ */
+bool dect_transceiver_reserve(struct dect_transceiver_group *trg,
+			      struct dect_transceiver *trx,
+			      const struct dect_channel_desc *chd)
+{
+	u8 slot = chd->slot;
+
+	switch ((int)chd->pkt) {
+	case DECT_PACKET_P80:
+		dect_transceiver_set_blind(trx, slot + 1);
+	case DECT_PACKET_P32:
+	case DECT_PACKET_P08:
+	case DECT_PACKET_P00:
+		dect_transceiver_set_blind(trx, slot);
+		break;
+	}
+
+	/* Set adjacent slots blind if the transceiver is slow hopping */
+	if (trx->ops->features & DECT_TRANSCEIVER_SLOW_HOPPING) {
+		dect_transceiver_set_blind(trx, dect_prev_slotnum(slot));
+		dect_transceiver_set_blind(trx, dect_next_slotnum(slot));
+	}
+
+	return dect_tg_update_blind_full_slots(trg);
+}
+
+/**
+ * dect_transceiver_release - release transceiver resources of a phyiscal channel
+ *
+ * Release the slot positions used by the specified physical channel. The
+ * transceiver and the global group blind full slot masks are updated.
+ *
+ * Returns true when the global visibility state has changed.
+ */
+bool dect_transceiver_release(struct dect_transceiver_group *trg,
+			      struct dect_transceiver *trx,
+			      const struct dect_channel_desc *chd)
+{
+	u8 slot = chd->slot;
+
+	switch ((int)chd->pkt) {
+	case DECT_PACKET_P80:
+		dect_transceiver_set_visible(trx, slot + 1);
+	case DECT_PACKET_P32:
+	case DECT_PACKET_P08:
+	case DECT_PACKET_P00:
+		dect_transceiver_set_visible(trx, slot);
+		break;
+	}
+
+	/* Set adjacent slots unblind if the transceiver is slow hopping */
+	if (trx->ops->features & DECT_TRANSCEIVER_SLOW_HOPPING) {
+		dect_transceiver_set_visible(trx, dect_next_slotnum(slot));
+		dect_transceiver_set_visible(trx, dect_prev_slotnum(slot));
+	}
+
+	return dect_tg_update_blind_full_slots(trg);
+}
+
+struct dect_transceiver *dect_transceiver_alloc(const struct dect_transceiver_ops *ops,
+						unsigned int priv)
+{
+	struct dect_transceiver *trx;
+	unsigned int nevents, size, i;
+
+	/* Allocate enough event structures for one TDMA half frame */
+	nevents = DECT_HALF_FRAME_SIZE / ops->eventrate;
+	size = nevents * sizeof(trx->event[0]) + priv;
+
+	trx = kzalloc(sizeof(*trx) + size, GFP_KERNEL);
+	if (trx == NULL)
+		return NULL;
+
+	trx->state = DECT_TRANSCEIVER_STOPPED;
+	trx->ops = ops;
+	for (i = 0; i < DECT_FRAME_SIZE; i++)
+		trx->slots[i].chd.slot = i;
+
+	for (i = 0; i < nevents; i++) {
+		skb_queue_head_init(&trx->event[i].rx_queue);
+		trx->event[i].trx = trx;
+	}
+	return trx;
+}
+EXPORT_SYMBOL_GPL(dect_transceiver_alloc);
+
+void dect_transceiver_free(struct dect_transceiver *trx)
+{
+	kfree(trx);
+}
+EXPORT_SYMBOL_GPL(dect_transceiver_free);
+
+static int dect_transceiver_alloc_name(struct dect_transceiver *trx)
+{
+	struct dect_transceiver *t;
+	unsigned long *inuse;
+	int i;
+
+	inuse = (unsigned long *)get_zeroed_page(GFP_KERNEL);
+	if (inuse == NULL)
+		return -ENOMEM;
+
+	list_for_each_entry(t, &dect_transceiver_list, list) {
+		if (!sscanf(t->name, "trx%d", &i))
+			continue;
+		if (i > BITS_PER_BYTE * PAGE_SIZE)
+			continue;
+		set_bit(i, inuse);
+	}
+
+	i = find_first_zero_bit(inuse, BITS_PER_BYTE * PAGE_SIZE);
+	free_page((unsigned long)inuse);
+	if (i == BITS_PER_BYTE * PAGE_SIZE)
+		return -ENFILE;
+
+	snprintf(trx->name, sizeof(trx->name), "trx%d", i);
+	return 0;
+}
+
+int dect_register_transceiver(struct dect_transceiver *trx)
+{
+	int err;
+
+	dect_lock();
+	err = dect_transceiver_alloc_name(trx);
+	if (err < 0)
+		goto out;
+
+	err = dect_transceiver_set_band(trx, DECT_DEFAULT_BAND);
+	if (err < 0)
+		goto out;
+
+	list_add_tail(&trx->list, &dect_transceiver_list);
+	dect_transceiver_notify(trx, DECT_TRANSCEIVER_REGISTER);
+out:
+	dect_unlock();
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(dect_register_transceiver);
+
+void dect_unregister_transceiver(struct dect_transceiver *trx)
+{
+	dect_lock();
+	list_del(&trx->list);
+	dect_transceiver_notify(trx, DECT_TRANSCEIVER_UNREGISTER);
+	dect_unlock();
+
+	synchronize_rcu();
+	trx->ops->destructor(trx);
+}
+EXPORT_SYMBOL_GPL(dect_unregister_transceiver);
+
+/*
+ * Transceiver netlink interface
+ */
+
+static struct dect_transceiver *dect_transceiver_get_by_name(const struct nlattr *nla)
+{
+	struct dect_transceiver *trx;
+
+	list_for_each_entry(trx, &dect_transceiver_list, list) {
+		if (!nla_strcmp(nla, trx->name))
+			return trx;
+	}
+	return NULL;
+}
+
+static int dect_fill_slot(struct sk_buff *skb,
+			  const struct dect_transceiver *trx, u8 slot)
+{
+	const struct dect_transceiver_slot *ts = &trx->slots[slot];
+
+	NLA_PUT_U8(skb, DECTA_SLOT_NUM, slot);
+	NLA_PUT_U8(skb, DECTA_SLOT_STATE, ts->state);
+	NLA_PUT_U32(skb, DECTA_SLOT_FLAGS, ts->flags);
+	NLA_PUT_U8(skb, DECTA_SLOT_CARRIER, ts->chd.carrier);
+	NLA_PUT_U32(skb, DECTA_SLOT_FREQUENCY, trx->band->frequency[ts->chd.carrier]);
+	if (ts->state == DECT_SLOT_RX) {
+		NLA_PUT_U32(skb, DECTA_SLOT_PHASEOFF, ts->phaseoff);
+		NLA_PUT_U8(skb, DECTA_SLOT_RSSI,
+			   ts->rssi >> DECT_RSSI_AVG_SCALE);
+	}
+	NLA_PUT_U32(skb, DECTA_SLOT_RX_BYTES, ts->rx_bytes);
+	NLA_PUT_U32(skb, DECTA_SLOT_RX_PACKETS, ts->rx_packets);
+	NLA_PUT_U32(skb, DECTA_SLOT_RX_A_CRC_ERRORS, ts->rx_a_crc_errors);
+	NLA_PUT_U32(skb, DECTA_SLOT_RX_X_CRC_ERRORS, ts->rx_x_crc_errors);
+	NLA_PUT_U32(skb, DECTA_SLOT_RX_Z_CRC_ERRORS, ts->rx_z_crc_errors);
+	NLA_PUT_U32(skb, DECTA_SLOT_TX_BYTES, ts->tx_bytes);
+	NLA_PUT_U32(skb, DECTA_SLOT_TX_PACKETS, ts->tx_packets);
+	return 0;
+
+nla_put_failure:
+	return -1;
+}
+
+static int dect_fill_transceiver(struct sk_buff *skb,
+				 const struct dect_transceiver *trx,
+				 u16 type, u32 pid, u32 seq, u16 flags)
+{
+	const struct dect_transceiver_stats *stats = &trx->stats;
+	struct nlattr *nest, *chan;
+	struct nlmsghdr *nlh;
+	struct dectmsg *dm;
+	u8 slot;
+
+	nlh = nlmsg_put(skb, pid, seq, type, sizeof(*dm), flags);
+	if (nlh == NULL)
+		return -EMSGSIZE;
+
+	dm = nlmsg_data(nlh);
+
+	NLA_PUT_STRING(skb, DECTA_TRANSCEIVER_NAME, trx->name);
+	NLA_PUT_STRING(skb, DECTA_TRANSCEIVER_TYPE, trx->ops->name);
+	NLA_PUT_U32(skb, DECTA_TRANSCEIVER_FEATURES, trx->ops->features);
+	if (trx->cell != NULL)
+		NLA_PUT_U8(skb, DECTA_TRANSCEIVER_LINK, trx->cell->index);
+
+	nest = nla_nest_start(skb, DECTA_TRANSCEIVER_STATS);
+	if (nest == NULL)
+		goto nla_put_failure;
+	NLA_PUT_U32(skb, DECTA_TRANSCEIVER_STATS_EVENT_BUSY, stats->event_busy);
+	NLA_PUT_U32(skb, DECTA_TRANSCEIVER_STATS_EVENT_LATE, stats->event_late);
+	nla_nest_end(skb, nest);
+
+	NLA_PUT_U8(skb, DECTA_TRANSCEIVER_BAND, trx->band->band);
+
+	nest = nla_nest_start(skb, DECTA_TRANSCEIVER_SLOTS);
+	if (nest == NULL)
+		goto nla_put_failure;
+	for (slot = 0; slot < DECT_FRAME_SIZE; slot++) {
+		chan = nla_nest_start(skb, DECTA_LIST_ELEM);
+		if (chan == NULL)
+			goto nla_put_failure;
+		if (dect_fill_slot(skb, trx, slot) < 0)
+			goto nla_put_failure;
+		nla_nest_end(skb, chan);
+	}
+	nla_nest_end(skb, nest);
+
+	return nlmsg_end(skb, nlh);
+
+nla_put_failure:
+	nlmsg_cancel(skb, nlh);
+	return -EMSGSIZE;
+}
+
+static const struct nla_policy dect_transceiver_policy[DECTA_TRANSCEIVER_MAX + 1] = {
+	[DECTA_TRANSCEIVER_NAME]	= { .type = NLA_STRING, .len = DECTNAMSIZ },
+	[DECTA_TRANSCEIVER_LINK]	= { .type = NLA_U8 },
+};
+
+static int dect_new_transceiver(const struct sk_buff *in_skb,
+				const struct nlmsghdr *nlh,
+				const struct nlattr *tb[DECTA_TRANSCEIVER_MAX + 1])
+{
+	struct dect_transceiver *trx;
+	struct dect_cell *cell;
+	struct dectmsg *dm;
+	int index;
+
+	dm = nlmsg_data(nlh);
+
+	if (tb[DECTA_TRANSCEIVER_NAME] == NULL)
+		return -EINVAL;
+
+	trx = dect_transceiver_get_by_name(tb[DECTA_TRANSCEIVER_NAME]);
+	if (trx == NULL) {
+		if (nlh->nlmsg_flags & NLM_F_CREATE)
+			return -EOPNOTSUPP;
+		return -ENOENT;
+	}
+	if (nlh->nlmsg_flags & NLM_F_EXCL)
+		return -EEXIST;
+
+	if (tb[DECTA_TRANSCEIVER_LINK] != NULL) {
+		index = nla_get_u8(tb[DECTA_TRANSCEIVER_LINK]);
+		if (index == -1)
+			dect_cell_detach_transceiver(trx->cell, trx);
+		else {
+			cell = dect_cell_get_by_index(index);
+			if (cell == NULL)
+				return -ENOENT;
+			return dect_cell_attach_transceiver(cell, trx);
+		}
+	}
+	return 0;
+}
+
+static int dect_get_transceiver(const struct sk_buff *in_skb,
+				const struct nlmsghdr *nlh,
+				const struct nlattr *tb[DECTA_TRANSCEIVER_MAX + 1])
+{
+	u32 pid = NETLINK_CB(in_skb).pid;
+	const struct dect_transceiver *trx;
+	struct sk_buff *skb;
+	int err;
+
+	if (tb[DECTA_TRANSCEIVER_NAME] == NULL)
+		return -EINVAL;
+
+	trx = dect_transceiver_get_by_name(tb[DECTA_TRANSCEIVER_NAME]);
+	if (trx == NULL)
+		return -ENOENT;
+
+	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		return -ENOMEM;
+	err = dect_fill_transceiver(skb, trx, DECT_NEW_TRANSCEIVER, pid,
+				    nlh->nlmsg_seq, NLMSG_DONE);
+	if (err < 0)
+		goto err1;
+	return nlmsg_unicast(dect_nlsk, skb, pid);
+
+err1:
+	kfree_skb(skb);
+	return err;
+}
+
+static int dect_dump_transceiver(struct sk_buff *skb,
+				 struct netlink_callback *cb)
+{
+	const struct dect_transceiver *trx;
+	unsigned int idx, s_idx;
+
+	s_idx = cb->args[0];
+	idx = 0;
+	list_for_each_entry(trx, &dect_transceiver_list, list) {
+		if (idx < s_idx)
+			goto cont;
+		if (dect_fill_transceiver(skb, trx, DECT_NEW_TRANSCEIVER,
+					  NETLINK_CB(cb->skb).pid,
+					  cb->nlh->nlmsg_seq, NLM_F_MULTI) <= 0)
+			break;
+cont:
+		idx++;
+	}
+	cb->args[0] = idx;
+
+	return skb->len;
+}
+
+static void dect_notify_transceiver(u16 event, const struct dect_transceiver *trx,
+				    const struct nlmsghdr *nlh, u32 pid)
+{
+	struct sk_buff *skb;
+	bool report = nlh ? nlmsg_report(nlh) : 0;
+	u32 seq = nlh ? nlh->nlmsg_seq : 0;
+	int err = -ENOBUFS;
+
+	skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		goto err;
+
+	err = dect_fill_transceiver(skb, trx, event, pid, seq, NLMSG_DONE);
+	if (err < 0) {
+		WARN_ON(err == -EMSGSIZE);
+		kfree_skb(skb);
+		goto err;
+	}
+	nlmsg_notify(dect_nlsk, skb, pid, DECTNLGRP_TRANSCEIVER, report, GFP_KERNEL);
+err:
+	if (err < 0)
+		netlink_set_err(dect_nlsk, pid, DECTNLGRP_TRANSCEIVER, err);
+}
+
+static int dect_transceiver_notify(struct dect_transceiver *trx,
+				   unsigned long event)
+{
+	struct dect_cell *cell = trx->cell;
+
+	switch (event) {
+	case DECT_TRANSCEIVER_REGISTER:
+		dect_notify_transceiver(DECT_NEW_TRANSCEIVER, trx, NULL, 0);
+		break;
+	case DECT_TRANSCEIVER_UNREGISTER:
+		if (cell != NULL)
+			dect_cell_detach_transceiver(cell, trx);
+		dect_notify_transceiver(DECT_DEL_TRANSCEIVER, trx, NULL, 0);
+		break;
+	}
+	return 0;
+};
+
+
+static const struct dect_netlink_handler dect_transceiver_handlers[] = {
+	{
+		/* DECT_NEW_TRANSCEIVER */
+		.policy		= dect_transceiver_policy,
+		.maxtype	= DECTA_TRANSCEIVER_MAX,
+		.doit		= dect_new_transceiver,
+	},
+	{
+		/* DECT_DEL_TRANSCEIVER */
+	},
+	{
+		/* DECT_GET_TRANSCEIVER */
+		.policy		= dect_transceiver_policy,
+		.maxtype	= DECTA_TRANSCEIVER_MAX,
+		.doit		= dect_get_transceiver,
+		.dump		= dect_dump_transceiver,
+	},
+};
+
+/*
+ * RF-bands:
+ */
+
+struct dect_band_desc {
+	u8	band;
+	u8	carriers;
+	s16	c1_off;
+	u8	c2;
+	s16	c2_off;
+};
+
+static const struct dect_band_desc dect_band_desc[] __initdata = {
+	/* 1880 MHz to 1900 MHz RF band 00000 */
+	{ 0, 10, 0, -1,  0, },
+	/* 1880 MHz to 1978 MHz and 2010 MHz to 2025 MHz RF band 00001 */
+	{ 1, 64, 0, 56, 19, },
+	/* 1880 MHz to 1925 MHz and 2010 MHz to 2025 MHz RF band 00010 */
+	{ 2, 33, 0, 25, 50, },
+	/* 1880 MHz to 1900 MHz, 1915 MHz to 1940 MHz and 2010 MHz to
+	 * 2025 MHz RF band 00011 */
+	{ 3, 33, 10, 25, 50, },
+	/* 1880 MHz to 1900 MHz, 1935 MHz to 1960 MHz and 2010 MHz to
+	 * 2025 MHz RF band 00100 */
+	{ 4, 33, 22, 25, 50, },
+	/* 1880 MHz to 1900 MHZ, 1955 MHz to 1980 MHz and 2010 MHz to
+	 * 2025 MHz RF band 00101 */
+	{ 5, 33, 34, 25, 50, },
+	/* 902 MHz to 928 MHz RF band 01000 */
+	{ 8, 24, -576, -1, 0, },
+	/* 2400 MHz to 2483,5 MHz RF band 01001 */
+	{ 9, 55, 292, -1, 0, },
+};
+
+/*
+ * A nominal DECT RF carrier is one whose center frequency is generated by
+ * the formula:
+ *
+ * Fg = F0 - g × 1,728 MHz, where g is any integer
+ */
+static u32 __init dect_calc_frequency(s16 g)
+{
+	if (g >= 0 && g < 10)
+		return DECT_FREQUENCY_F0 - g * DECT_CARRIER_WIDTH;
+	else
+		return DECT_FREQUENCY_F0 + (g - 9) * DECT_CARRIER_WIDTH;
+}
+
+static int __init dect_init_band(const struct dect_band_desc *desc)
+{
+	struct dect_band *band;
+	unsigned int size;
+	u8 carrier;
+
+	size = sizeof(*band) + desc->carriers * sizeof(band->frequency[0]);
+	band = kmalloc(size, GFP_KERNEL);
+	if (band == NULL)
+		return -ENOMEM;
+
+	band->band     = desc->band;
+	band->carriers = desc->carriers;
+
+	for (carrier = 0; carrier < 10; carrier++)
+		band->frequency[carrier] =
+			dect_calc_frequency(carrier);
+
+	for (; carrier < min(desc->carriers, desc->c2); carrier++)
+		band->frequency[carrier] =
+			dect_calc_frequency(carrier + desc->c1_off);
+
+	for (; carrier < desc->carriers; carrier++)
+		band->frequency[carrier] =
+			dect_calc_frequency(carrier + desc->c2_off);
+
+	printk("RF-band %u:\n", band->band);
+	for (carrier = 0; carrier < band->carriers; carrier++) {
+		printk("  carrier %u: %u.%03uMHz\n", carrier,
+		       band->frequency[carrier] / 1000,
+		       band->frequency[carrier] % 1000);
+	}
+	printk("\n");
+
+	dect_band[band->band] = band;
+	return 0;
+}
+
+int __init dect_transceiver_module_init(void)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(dect_band_desc); i++) {
+		err = dect_init_band(&dect_band_desc[i]);
+		if (err < 0)
+			goto err1;
+	}
+
+	dect_netlink_register_handlers(dect_transceiver_handlers,
+				       DECT_NEW_TRANSCEIVER,
+				       ARRAY_SIZE(dect_transceiver_handlers));
+	return 0;
+
+err1:
+	dect_transceiver_module_exit();
+	return err;
+}
+
+void dect_transceiver_module_exit(void)
+{
+	u8 band;
+
+	dect_netlink_unregister_handlers(DECT_NEW_TRANSCEIVER,
+				         ARRAY_SIZE(dect_transceiver_handlers));
+
+	for (band = 0; band < ARRAY_SIZE(dect_band); band++)
+		kfree(dect_band[band]);
+}
