/*
- * Copyright (C) 2010 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2010-2013 Felix Fietkau <nbd@openwrt.org>
  *
  * 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
        }                                                               \
 }
 
+#define CCK_DURATION(_bitrate, _short, _len)           \
+       (10 /* SIFS */ +                                \
+        (_short ? 72 + 24 : 144 + 48 ) +               \
+        (8 * (_len + 4) * 10) / (_bitrate))
+
+#define CCK_ACK_DURATION(_bitrate, _short)                     \
+       (CCK_DURATION((_bitrate > 10 ? 20 : 10), false, 60) +   \
+        CCK_DURATION(_bitrate, _short, AVG_PKT_SIZE))
+
+#define CCK_DURATION_LIST(_short)                      \
+       CCK_ACK_DURATION(10, _short),                   \
+       CCK_ACK_DURATION(20, _short),                   \
+       CCK_ACK_DURATION(55, _short),                   \
+       CCK_ACK_DURATION(110, _short)
+
+#define CCK_GROUP                                              \
+       [MINSTREL_MAX_STREAMS * MINSTREL_STREAM_GROUPS] = {     \
+               .streams = 0,                                   \
+               .duration = {                                   \
+                       CCK_DURATION_LIST(false),               \
+                       CCK_DURATION_LIST(true)                 \
+               }                                               \
+       }
+
 /*
  * To enable sufficiently targeted rate sampling, MCS rates are divided into
  * groups, based on the number of streams and flags (HT40, SGI) that they
 #if MINSTREL_MAX_STREAMS >= 3
        MCS_GROUP(3, 1, 1),
 #endif
+
+       /* must be last */
+       CCK_GROUP
 };
 
+#define MINSTREL_CCK_GROUP     (ARRAY_SIZE(minstrel_mcs_groups) - 1)
+
 static u8 sample_table[SAMPLE_COLUMNS][MCS_GROUP_RATES];
 
 /*
                         !!(rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH));
 }
 
+static struct minstrel_rate_stats *
+minstrel_ht_get_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
+                     struct ieee80211_tx_rate *rate)
+{
+       int group, idx;
+
+       if (rate->flags & IEEE80211_TX_RC_MCS) {
+               group = minstrel_ht_get_group_idx(rate);
+               idx = rate->idx % MCS_GROUP_RATES;
+       } else {
+               group = MINSTREL_CCK_GROUP;
+
+               for (idx = 0; idx < ARRAY_SIZE(mp->cck_rates); idx++)
+                       if (rate->idx == mp->cck_rates[idx])
+                               break;
+
+               /* short preamble */
+               if (!(mi->groups[group].supported & BIT(idx)))
+                       idx += 4;
+       }
+       return &mi->groups[group].rates[idx];
+}
+
 static inline struct minstrel_rate_stats *
 minstrel_get_ratestats(struct minstrel_ht_sta *mi, int index)
 {
 minstrel_ht_calc_tp(struct minstrel_ht_sta *mi, int group, int rate)
 {
        struct minstrel_rate_stats *mr;
-       unsigned int usecs;
+       unsigned int usecs = 0;
 
        mr = &mi->groups[group].rates[rate];
 
                return;
        }
 
-       usecs = mi->overhead / MINSTREL_TRUNC(mi->avg_ampdu_len);
+       if (group != MINSTREL_CCK_GROUP)
+               usecs = mi->overhead / MINSTREL_TRUNC(mi->avg_ampdu_len);
+
        usecs += minstrel_mcs_groups[group].duration[rate];
        mr->cur_tp = MINSTREL_TRUNC((1000000 / usecs) * mr->probability);
 }
 }
 
 static bool
-minstrel_ht_txstat_valid(struct ieee80211_tx_rate *rate)
+minstrel_ht_txstat_valid(struct minstrel_priv *mp, struct ieee80211_tx_rate *rate)
 {
        if (rate->idx < 0)
                return false;
        if (!rate->count)
                return false;
 
-       return !!(rate->flags & IEEE80211_TX_RC_MCS);
+       if (rate->flags & IEEE80211_TX_RC_MCS)
+               return true;
+
+       return rate->idx == mp->cck_rates[0] ||
+              rate->idx == mp->cck_rates[1] ||
+              rate->idx == mp->cck_rates[2] ||
+              rate->idx == mp->cck_rates[3];
 }
 
 static void
        struct minstrel_rate_stats *rate, *rate2;
        struct minstrel_priv *mp = priv;
        bool last;
-       int group;
        int i;
 
        if (!msp->is_ht)
        if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)
                mi->sample_packets += info->status.ampdu_len;
 
-       last = !minstrel_ht_txstat_valid(&ar[0]);
+       last = !minstrel_ht_txstat_valid(mp, &ar[0]);
        for (i = 0; !last; i++) {
                last = (i == IEEE80211_TX_MAX_RATES - 1) ||
-                      !minstrel_ht_txstat_valid(&ar[i + 1]);
+                      !minstrel_ht_txstat_valid(mp, &ar[i + 1]);
 
-               group = minstrel_ht_get_group_idx(&ar[i]);
-               rate = &mi->groups[group].rates[ar[i].idx % 8];
+               rate = minstrel_ht_get_stats(mp, mi, &ar[i]);
 
                if (last)
                        rate->success += info->status.ampdu_ack_len;
 
        if (time_after(jiffies, mi->stats_update + (mp->update_interval / 2 * HZ) / 1000)) {
                minstrel_ht_update_stats(mp, mi);
-               if (!(info->flags & IEEE80211_TX_CTL_AMPDU))
+               if (!(info->flags & IEEE80211_TX_CTL_AMPDU) &&
+                   mi->max_prob_rate / MCS_GROUP_RATES != MINSTREL_CCK_GROUP)
                        minstrel_aggr_check(sta, skb);
        }
 }
        unsigned int ctime = 0;
        unsigned int t_slot = 9; /* FIXME */
        unsigned int ampdu_len = MINSTREL_TRUNC(mi->avg_ampdu_len);
+       unsigned int overhead = 0, overhead_rtscts = 0;
 
        mr = minstrel_get_ratestats(mi, index);
        if (mr->probability < MINSTREL_FRAC(1, 10)) {
        ctime += (t_slot * cw) >> 1;
        cw = min((cw << 1) | 1, mp->cw_max);
 
+       if (index / MCS_GROUP_RATES != MINSTREL_CCK_GROUP) {
+               overhead = mi->overhead;
+               overhead_rtscts = mi->overhead_rtscts;
+       }
+
        /* Total TX time for data and Contention after first 2 tries */
-       tx_time = ctime + 2 * (mi->overhead + tx_time_data);
-       tx_time_rtscts = ctime + 2 * (mi->overhead_rtscts + tx_time_data);
+       tx_time = ctime + 2 * (overhead + tx_time_data);
+       tx_time_rtscts = ctime + 2 * (overhead_rtscts + tx_time_data);
 
        /* See how many more tries we can fit inside segment size */
        do {
                cw = min((cw << 1) | 1, mp->cw_max);
 
                /* Total TX time after this try */
-               tx_time += ctime + mi->overhead + tx_time_data;
-               tx_time_rtscts += ctime + mi->overhead_rtscts + tx_time_data;
+               tx_time += ctime + overhead + tx_time_data;
+               tx_time_rtscts += ctime + overhead_rtscts + tx_time_data;
 
                if (tx_time_rtscts < mp->segment_size)
                        mr->retry_count_rtscts++;
        else
                rate->count = mr->retry_count;
 
-       rate->flags = IEEE80211_TX_RC_MCS | group->flags;
+       rate->flags = 0;
        if (rtscts)
                rate->flags |= IEEE80211_TX_RC_USE_RTS_CTS;
+
+       if (index / MCS_GROUP_RATES == MINSTREL_CCK_GROUP) {
+               rate->idx = mp->cck_rates[index % ARRAY_SIZE(mp->cck_rates)];
+               return;
+       }
+
+       rate->flags |= IEEE80211_TX_RC_MCS | group->flags;
        rate->idx = index % MCS_GROUP_RATES + (group->streams - 1) * MCS_GROUP_RATES;
 }
 
        return sample_idx;
 }
 
+static void
+minstrel_ht_check_cck_shortpreamble(struct minstrel_priv *mp,
+                                   struct minstrel_ht_sta *mi, bool val)
+{
+       u8 supported = mi->groups[MINSTREL_CCK_GROUP].supported;
+
+       if (!supported || !mi->cck_supported_short)
+               return;
+
+       if (supported & (mi->cck_supported_short << (val * 4)))
+               return;
+
+       supported ^= mi->cck_supported_short | (mi->cck_supported_short << 4);
+       mi->groups[MINSTREL_CCK_GROUP].supported = supported;
+}
+
 static void
 minstrel_ht_get_rate(void *priv, struct ieee80211_sta *sta, void *priv_sta,
                      struct ieee80211_tx_rate_control *txrc)
                return mac80211_minstrel.get_rate(priv, sta, &msp->legacy, txrc);
 
        info->flags |= mi->tx_flags;
+       minstrel_ht_check_cck_shortpreamble(mp, mi, txrc->short_preamble);
 
        /* Don't use EAPOL frames for sampling on non-mrr hw */
        if (mp->hw->max_rates == 1 &&
        }
 }
 
+static void
+minstrel_ht_update_cck(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
+                      struct ieee80211_supported_band *sband,
+                      struct ieee80211_sta *sta)
+{
+       int i;
+
+       if (sband->band != IEEE80211_BAND_2GHZ)
+               return;
+
+       mi->cck_supported = 0;
+       mi->cck_supported_short = 0;
+       for (i = 0; i < 4; i++) {
+               if (!rate_supported(sta, sband->band, mp->cck_rates[i]))
+                       continue;
+
+               mi->cck_supported |= BIT(i);
+               if (sband->bitrates[i].flags & IEEE80211_RATE_SHORT_PREAMBLE)
+                       mi->cck_supported_short |= BIT(i);
+       }
+
+       mi->groups[MINSTREL_CCK_GROUP].supported = mi->cck_supported;
+}
+
 static void
 minstrel_ht_update_caps(void *priv, struct ieee80211_supported_band *sband,
                         struct ieee80211_sta *sta, void *priv_sta)
                goto use_legacy;
 
        BUILD_BUG_ON(ARRAY_SIZE(minstrel_mcs_groups) !=
-               MINSTREL_MAX_STREAMS * MINSTREL_STREAM_GROUPS);
+               MINSTREL_MAX_STREAMS * MINSTREL_STREAM_GROUPS + 1);
 
        msp->is_ht = true;
        memset(mi, 0, sizeof(*mi));
                u16 req = 0;
 
                mi->groups[i].supported = 0;
+               if (i == MINSTREL_CCK_GROUP) {
+                       minstrel_ht_update_cck(mp, mi, sband, sta);
+                       continue;
+               }
+
                if (minstrel_mcs_groups[i].flags & IEEE80211_TX_RC_SHORT_GI) {
                        if (minstrel_mcs_groups[i].flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
                                req |= IEEE80211_HT_CAP_SGI_40;
 
 #include "rc80211_minstrel.h"
 #include "rc80211_minstrel_ht.h"
 
+static char *
+minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
+{
+       unsigned int max_mcs = MINSTREL_MAX_STREAMS * MINSTREL_STREAM_GROUPS;
+       const struct mcs_group *mg;
+       unsigned int j, tp, prob, eprob;
+       char htmode = '2';
+       char gimode = 'L';
+
+       if (!mi->groups[i].supported)
+               return p;
+
+       mg = &minstrel_mcs_groups[i];
+       if (mg->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
+               htmode = '4';
+       if (mg->flags & IEEE80211_TX_RC_SHORT_GI)
+               gimode = 'S';
+
+       for (j = 0; j < MCS_GROUP_RATES; j++) {
+               struct minstrel_rate_stats *mr = &mi->groups[i].rates[j];
+               static const int bitrates[4] = { 10, 20, 55, 110 };
+               int idx = i * MCS_GROUP_RATES + j;
+
+               if (!(mi->groups[i].supported & BIT(j)))
+                       continue;
+
+               if (i == max_mcs)
+                       p += sprintf(p, "CCK/%cP   ", j < 4 ? 'L' : 'S');
+               else
+                       p += sprintf(p, "HT%c0/%cGI ", htmode, gimode);
+
+               *(p++) = (idx == mi->max_tp_rate) ? 'T' : ' ';
+               *(p++) = (idx == mi->max_tp_rate2) ? 't' : ' ';
+               *(p++) = (idx == mi->max_prob_rate) ? 'P' : ' ';
+
+               if (i == max_mcs) {
+                       int r = bitrates[j % 4];
+                       p += sprintf(p, " %2u.%1uM", r / 10, r % 10);
+               } else {
+                       p += sprintf(p, " MCS%-2u", (mg->streams - 1) *
+                                        MCS_GROUP_RATES + j);
+               }
+
+               tp = mr->cur_tp / 10;
+               prob = MINSTREL_TRUNC(mr->cur_prob * 1000);
+               eprob = MINSTREL_TRUNC(mr->probability * 1000);
+
+               p += sprintf(p, "      %6u.%1u   %6u.%1u    %6u.%1u    "
+                               "%3u            %3u(%3u)  %8llu    %8llu\n",
+                               tp / 10, tp % 10,
+                               eprob / 10, eprob % 10,
+                               prob / 10, prob % 10,
+                               mr->retry_count,
+                               mr->last_success,
+                               mr->last_attempts,
+                               (unsigned long long)mr->succ_hist,
+                               (unsigned long long)mr->att_hist);
+       }
+
+       return p;
+}
+
 static int
 minstrel_ht_stats_open(struct inode *inode, struct file *file)
 {
        struct minstrel_ht_sta_priv *msp = inode->i_private;
        struct minstrel_ht_sta *mi = &msp->ht;
        struct minstrel_debugfs_info *ms;
-       unsigned int i, j, tp, prob, eprob;
+       unsigned int i;
+       unsigned int max_mcs = MINSTREL_MAX_STREAMS * MINSTREL_STREAM_GROUPS;
        char *p;
        int ret;
 
        p = ms->buf;
        p += sprintf(p, "type         rate     throughput  ewma prob   this prob  "
                        "retry   this succ/attempt   success    attempts\n");
-       for (i = 0; i < MINSTREL_MAX_STREAMS * MINSTREL_STREAM_GROUPS; i++) {
-               char htmode = '2';
-               char gimode = 'L';
-
-               if (!mi->groups[i].supported)
-                       continue;
-
-               if (minstrel_mcs_groups[i].flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
-                       htmode = '4';
-               if (minstrel_mcs_groups[i].flags & IEEE80211_TX_RC_SHORT_GI)
-                       gimode = 'S';
 
-               for (j = 0; j < MCS_GROUP_RATES; j++) {
-                       struct minstrel_rate_stats *mr = &mi->groups[i].rates[j];
-                       int idx = i * MCS_GROUP_RATES + j;
+       p = minstrel_ht_stats_dump(mi, max_mcs, p);
+       for (i = 0; i < max_mcs; i++)
+               p = minstrel_ht_stats_dump(mi, i, p);
 
-                       if (!(mi->groups[i].supported & BIT(j)))
-                               continue;
-
-                       p += sprintf(p, "HT%c0/%cGI ", htmode, gimode);
-
-                       *(p++) = (idx == mi->max_tp_rate) ? 'T' : ' ';
-                       *(p++) = (idx == mi->max_tp_rate2) ? 't' : ' ';
-                       *(p++) = (idx == mi->max_prob_rate) ? 'P' : ' ';
-                       p += sprintf(p, " MCS%-2u", (minstrel_mcs_groups[i].streams - 1) *
-                                       MCS_GROUP_RATES + j);
-
-                       tp = mr->cur_tp / 10;
-                       prob = MINSTREL_TRUNC(mr->cur_prob * 1000);
-                       eprob = MINSTREL_TRUNC(mr->probability * 1000);
-
-                       p += sprintf(p, "      %6u.%1u   %6u.%1u    %6u.%1u    "
-                                       "%3u            %3u(%3u)  %8llu    %8llu\n",
-                                       tp / 10, tp % 10,
-                                       eprob / 10, eprob % 10,
-                                       prob / 10, prob % 10,
-                                       mr->retry_count,
-                                       mr->last_success,
-                                       mr->last_attempts,
-                                       (unsigned long long)mr->succ_hist,
-                                       (unsigned long long)mr->att_hist);
-               }
-       }
        p += sprintf(p, "\nTotal packet count::    ideal %d      "
                        "lookaround %d\n",
                        max(0, (int) mi->total_packets - (int) mi->sample_packets),