975 lines
26 KiB
C
975 lines
26 KiB
C
|
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#define pr_fmt(fmt) "AXI: %s(): " fmt, __func__
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <mach/msm_bus.h>
|
||
|
#include <mach/msm_bus_board.h>
|
||
|
#include <mach/board.h>
|
||
|
#include <mach/rpm.h>
|
||
|
#include "msm_bus_core.h"
|
||
|
#include "../rpm_resources.h"
|
||
|
|
||
|
void msm_bus_rpm_set_mt_mask()
|
||
|
{
|
||
|
#ifdef CONFIG_MSM_BUS_RPM_MULTI_TIER_ENABLED
|
||
|
struct msm_rpm_iv_pair mt[1];
|
||
|
int mask = MSM_RPMRS_MASK_RPM_CTL_MULTI_TIER;
|
||
|
mt[0].id = MSM_RPM_ID_RPM_CTL;
|
||
|
mt[0].value = 2;
|
||
|
msm_rpmrs_set_bits_noirq(MSM_RPM_CTX_SET_0, mt, 1,
|
||
|
&mask);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
bool msm_bus_rpm_is_mem_interleaved(void)
|
||
|
{
|
||
|
int status = 0;
|
||
|
struct msm_rpm_iv_pair il[2];
|
||
|
uint16_t id[2];
|
||
|
|
||
|
il[0].value = 0;
|
||
|
il[1].value = 0;
|
||
|
status = msm_bus_board_rpm_get_il_ids(id);
|
||
|
if (status) {
|
||
|
MSM_BUS_DBG("Dynamic check not supported, "
|
||
|
"default: Interleaved memory\n");
|
||
|
goto inter;
|
||
|
}
|
||
|
|
||
|
il[0].id = id[0];
|
||
|
il[1].id = id[1];
|
||
|
status = msm_rpm_get_status(il, ARRAY_SIZE(il));
|
||
|
if (status) {
|
||
|
MSM_BUS_ERR("Status read for interleaving returned: %d\n"
|
||
|
"Using interleaved memory by default\n",
|
||
|
status);
|
||
|
goto inter;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If the start address of EBI1-CH0 is the same as
|
||
|
* the start address of EBI1-CH1, the memory is interleaved.
|
||
|
* The start addresses are stored in the 16 MSBs of the status
|
||
|
* register
|
||
|
*/
|
||
|
if ((il[0].value & 0xFFFF0000) != (il[1].value & 0xFFFF0000)) {
|
||
|
MSM_BUS_DBG("Non-interleaved memory\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
inter:
|
||
|
MSM_BUS_DBG("Interleaved memory\n");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#ifndef CONFIG_MSM_BUS_RPM_MULTI_TIER_ENABLED
|
||
|
struct commit_data {
|
||
|
uint16_t *bwsum;
|
||
|
uint16_t *arb;
|
||
|
unsigned long *actarb;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* The following macros are used for various operations on commit data.
|
||
|
* Commit data is an array of 32 bit integers. The size of arrays is unique
|
||
|
* to the fabric. Commit arrays are allocated at run-time based on the number
|
||
|
* of masters, slaves and tiered-slaves registered.
|
||
|
*/
|
||
|
|
||
|
#define MSM_BUS_GET_BW_INFO(val, type, bw) \
|
||
|
do { \
|
||
|
(type) = MSM_BUS_GET_BW_TYPE(val); \
|
||
|
(bw) = MSM_BUS_GET_BW(val); \
|
||
|
} while (0)
|
||
|
|
||
|
|
||
|
#define MSM_BUS_GET_BW_INFO_BYTES (val, type, bw) \
|
||
|
do { \
|
||
|
(type) = MSM_BUS_GET_BW_TYPE(val); \
|
||
|
(bw) = msm_bus_get_bw_bytes(val); \
|
||
|
} while (0)
|
||
|
|
||
|
#define ROUNDED_BW_VAL_FROM_BYTES(bw) \
|
||
|
((((bw) >> 17) + 1) & 0x8000 ? 0x7FFF : (((bw) >> 17) + 1))
|
||
|
|
||
|
#define BW_VAL_FROM_BYTES(bw) \
|
||
|
((((bw) >> 17) & 0x8000) ? 0x7FFF : ((bw) >> 17))
|
||
|
|
||
|
static uint32_t msm_bus_set_bw_bytes(unsigned long bw)
|
||
|
{
|
||
|
return ((((bw) & 0x1FFFF) && (((bw) >> 17) == 0)) ?
|
||
|
ROUNDED_BW_VAL_FROM_BYTES(bw) : BW_VAL_FROM_BYTES(bw));
|
||
|
|
||
|
}
|
||
|
|
||
|
uint64_t msm_bus_get_bw_bytes(unsigned long val)
|
||
|
{
|
||
|
return ((val) & 0x7FFF) << 17;
|
||
|
}
|
||
|
|
||
|
uint16_t msm_bus_get_bw(unsigned long val)
|
||
|
{
|
||
|
return (val)&0x7FFF;
|
||
|
}
|
||
|
|
||
|
static uint16_t msm_bus_create_bw_tier_pair_bytes(uint8_t type,
|
||
|
unsigned long bw)
|
||
|
{
|
||
|
return ((((type) == MSM_BUS_BW_TIER1 ? 1 : 0) << 15) |
|
||
|
(msm_bus_set_bw_bytes(bw)));
|
||
|
};
|
||
|
|
||
|
uint16_t msm_bus_create_bw_tier_pair(uint8_t type, unsigned long bw)
|
||
|
{
|
||
|
return (((type) == MSM_BUS_BW_TIER1 ? 1 : 0) << 15) | ((bw) & 0x7FFF);
|
||
|
}
|
||
|
|
||
|
void msm_bus_rpm_fill_cdata_buffer(int *curr, char *buf, const int max_size,
|
||
|
void *cdata, int nmasters, int nslaves, int ntslaves)
|
||
|
{
|
||
|
int j, c;
|
||
|
struct commit_data *cd = (struct commit_data *)cdata;
|
||
|
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr, "BWSum:\n");
|
||
|
for (c = 0; c < nslaves; c++)
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr,
|
||
|
"0x%x\t", cd->bwsum[c]);
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr, "\nArb:");
|
||
|
for (c = 0; c < ntslaves; c++) {
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr,
|
||
|
"\nTSlave %d:\n", c);
|
||
|
for (j = 0; j < nmasters; j++)
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr,
|
||
|
" 0x%x\t", cd->arb[(c * nmasters) + j]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* allocate_commit_data() - Allocate the data for commit array in the
|
||
|
* format specified by RPM
|
||
|
* @fabric: Fabric device for which commit data is allocated
|
||
|
*/
|
||
|
static int msm_bus_rpm_allocate_commit_data(struct msm_bus_fabric_registration
|
||
|
*fab_pdata, void **cdata, int ctx)
|
||
|
{
|
||
|
struct commit_data **cd = (struct commit_data **)cdata;
|
||
|
*cd = kzalloc(sizeof(struct commit_data), GFP_KERNEL);
|
||
|
if (!*cd) {
|
||
|
MSM_BUS_DBG("Couldn't alloc mem for cdata\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
(*cd)->bwsum = kzalloc((sizeof(uint16_t) * fab_pdata->nslaves),
|
||
|
GFP_KERNEL);
|
||
|
if (!(*cd)->bwsum) {
|
||
|
MSM_BUS_DBG("Couldn't alloc mem for slaves\n");
|
||
|
kfree(*cd);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
(*cd)->arb = kzalloc(((sizeof(uint16_t *)) *
|
||
|
(fab_pdata->ntieredslaves * fab_pdata->nmasters) + 1),
|
||
|
GFP_KERNEL);
|
||
|
if (!(*cd)->arb) {
|
||
|
MSM_BUS_DBG("Couldn't alloc memory for"
|
||
|
" slaves\n");
|
||
|
kfree((*cd)->bwsum);
|
||
|
kfree(*cd);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
(*cd)->actarb = kzalloc(((sizeof(unsigned long *)) *
|
||
|
(fab_pdata->ntieredslaves * fab_pdata->nmasters) + 1),
|
||
|
GFP_KERNEL);
|
||
|
if (!(*cd)->actarb) {
|
||
|
MSM_BUS_DBG("Couldn't alloc memory for"
|
||
|
" slaves\n");
|
||
|
kfree((*cd)->bwsum);
|
||
|
kfree((*cd)->arb);
|
||
|
kfree(*cd);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void free_commit_data(void *cdata)
|
||
|
{
|
||
|
struct commit_data *cd = (struct commit_data *)cdata;
|
||
|
|
||
|
kfree(cd->bwsum);
|
||
|
kfree(cd->arb);
|
||
|
kfree(cd->actarb);
|
||
|
kfree(cd);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* allocate_rpm_data() - Allocate the id-value pairs to be
|
||
|
* sent to RPM
|
||
|
*/
|
||
|
static void *msm_bus_rpm_allocate_rpm_data(struct platform_device *pdev,
|
||
|
struct msm_bus_fabric_registration *fab_pdata)
|
||
|
{
|
||
|
struct msm_rpm_iv_pair *rpm_data;
|
||
|
uint16_t count = ((fab_pdata->nmasters * fab_pdata->ntieredslaves) +
|
||
|
fab_pdata->nslaves + 1)/2;
|
||
|
|
||
|
rpm_data = kmalloc((sizeof(struct msm_rpm_iv_pair) * count),
|
||
|
GFP_KERNEL);
|
||
|
return (void *)rpm_data;
|
||
|
}
|
||
|
|
||
|
#define BWMASK 0x7FFF
|
||
|
#define TIERMASK 0x8000
|
||
|
#define GET_TIER(n) (((n) & TIERMASK) >> 15)
|
||
|
|
||
|
static void msm_bus_rpm_update_bw(struct msm_bus_inode_info *hop,
|
||
|
struct msm_bus_inode_info *info,
|
||
|
struct msm_bus_fabric_registration *fab_pdata,
|
||
|
void *sel_cdata, int *master_tiers,
|
||
|
int64_t add_bw)
|
||
|
{
|
||
|
int index, i, j, tiers, ports;
|
||
|
struct commit_data *sel_cd = (struct commit_data *)sel_cdata;
|
||
|
|
||
|
add_bw = INTERLEAVED_BW(fab_pdata, add_bw, info->node_info->num_mports);
|
||
|
ports = INTERLEAVED_VAL(fab_pdata, info->node_info->num_mports);
|
||
|
tiers = INTERLEAVED_VAL(fab_pdata, hop->node_info->num_tiers);
|
||
|
for (i = 0; i < tiers; i++) {
|
||
|
for (j = 0; j < ports; j++) {
|
||
|
uint16_t hop_tier;
|
||
|
/*
|
||
|
* For interleaved gateway ports and slave ports,
|
||
|
* there is one-one mapping between gateway port and
|
||
|
* the slave port
|
||
|
*/
|
||
|
if (info->node_info->gateway && i != j &&
|
||
|
(hop->node_info->num_sports > 1))
|
||
|
continue;
|
||
|
|
||
|
if (!hop->node_info->tier)
|
||
|
hop_tier = MSM_BUS_BW_TIER2 - 1;
|
||
|
else
|
||
|
hop_tier = hop->node_info->tier[i] - 1;
|
||
|
index = ((hop_tier * fab_pdata->nmasters) +
|
||
|
(info->node_info->masterp[j]));
|
||
|
/* If there is tier, calculate arb for commit */
|
||
|
if (hop->node_info->tier) {
|
||
|
uint16_t tier;
|
||
|
unsigned long tieredbw = sel_cd->actarb[index];
|
||
|
if (GET_TIER(sel_cd->arb[index]))
|
||
|
tier = MSM_BUS_BW_TIER1;
|
||
|
else if (master_tiers)
|
||
|
/*
|
||
|
* By default master is only in the
|
||
|
* tier specified by default.
|
||
|
* To change the default tier, client
|
||
|
* needs to explicitly request for a
|
||
|
* different supported tier */
|
||
|
tier = master_tiers[0];
|
||
|
else
|
||
|
tier = MSM_BUS_BW_TIER2;
|
||
|
|
||
|
/*
|
||
|
* Make sure gateway to slave port bandwidth
|
||
|
* is not divided when slave is interleaved
|
||
|
*/
|
||
|
if (info->node_info->gateway
|
||
|
&& hop->node_info->num_sports > 1)
|
||
|
tieredbw += add_bw;
|
||
|
else
|
||
|
tieredbw += INTERLEAVED_BW(fab_pdata,
|
||
|
add_bw, hop->node_info->
|
||
|
num_sports);
|
||
|
|
||
|
/* If bw is 0, update tier to default */
|
||
|
if (!tieredbw)
|
||
|
tier = MSM_BUS_BW_TIER2;
|
||
|
/* Update Arb for fab,get HW Mport from enum */
|
||
|
sel_cd->arb[index] =
|
||
|
msm_bus_create_bw_tier_pair_bytes(tier,
|
||
|
tieredbw);
|
||
|
sel_cd->actarb[index] = tieredbw;
|
||
|
MSM_BUS_DBG("tr:%d mpor:%d tbw:%ld bws: %lld\n",
|
||
|
hop_tier, info->node_info->masterp[i],
|
||
|
tieredbw, *hop->link_info.sel_bw);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Update bwsum for slaves on fabric */
|
||
|
ports = INTERLEAVED_VAL(fab_pdata, hop->node_info->num_sports);
|
||
|
for (i = 0; i < ports; i++) {
|
||
|
sel_cd->bwsum[hop->node_info->slavep[i]]
|
||
|
= (uint16_t)msm_bus_create_bw_tier_pair_bytes(0,
|
||
|
(uint32_t)msm_bus_div64(hop->node_info->num_sports,
|
||
|
*hop->link_info.sel_bw));
|
||
|
MSM_BUS_DBG("slavep:%d, link_bw: %u\n",
|
||
|
hop->node_info->slavep[i], (uint32_t)
|
||
|
msm_bus_div64(hop->node_info->num_sports,
|
||
|
*hop->link_info.sel_bw));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define RPM_SHIFT_VAL 16
|
||
|
#define RPM_SHIFT(n) ((n) << RPM_SHIFT_VAL)
|
||
|
static int msm_bus_rpm_compare_cdata(
|
||
|
struct msm_bus_fabric_registration *fab_pdata,
|
||
|
struct commit_data *cd1, struct commit_data *cd2)
|
||
|
{
|
||
|
size_t n;
|
||
|
int ret;
|
||
|
n = sizeof(uint16_t) * fab_pdata->nslaves;
|
||
|
ret = memcmp(cd1->bwsum, cd2->bwsum, n);
|
||
|
if (ret) {
|
||
|
MSM_BUS_DBG("Commit Data bwsum not equal\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
n = sizeof(uint16_t *) * ((fab_pdata->ntieredslaves *
|
||
|
fab_pdata->nmasters) + 1);
|
||
|
ret = memcmp(cd1->arb, cd2->arb, n);
|
||
|
if (ret) {
|
||
|
MSM_BUS_DBG("Commit Data arb[%d] not equal\n", n);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int msm_bus_rpm_commit_arb(struct msm_bus_fabric_registration
|
||
|
*fab_pdata, int ctx, struct msm_rpm_iv_pair *rpm_data,
|
||
|
struct commit_data *cd, bool valid)
|
||
|
{
|
||
|
int i, j, offset = 0, status = 0, count, index = 0;
|
||
|
/*
|
||
|
* count is the number of 2-byte words required to commit the
|
||
|
* data to rpm. This is calculated by the following formula.
|
||
|
* Commit data is split into two arrays:
|
||
|
* 1. arb[nmasters * ntieredslaves]
|
||
|
* 2. bwsum[nslaves]
|
||
|
*/
|
||
|
count = ((fab_pdata->nmasters * fab_pdata->ntieredslaves)
|
||
|
+ (fab_pdata->nslaves) + 1)/2;
|
||
|
|
||
|
offset = fab_pdata->offset;
|
||
|
|
||
|
/*
|
||
|
* Copy bwsum to rpm data
|
||
|
* Since bwsum is uint16, the values need to be adjusted to
|
||
|
* be copied to value field of rpm-data, which is 32 bits.
|
||
|
*/
|
||
|
for (i = 0; i < (fab_pdata->nslaves - 1); i += 2) {
|
||
|
rpm_data[index].id = offset + index;
|
||
|
rpm_data[index].value = RPM_SHIFT(*(cd->bwsum + i + 1)) |
|
||
|
*(cd->bwsum + i);
|
||
|
index++;
|
||
|
}
|
||
|
/* Account for odd number of slaves */
|
||
|
if (fab_pdata->nslaves & 1) {
|
||
|
rpm_data[index].id = offset + index;
|
||
|
rpm_data[index].value = *(cd->arb);
|
||
|
rpm_data[index].value = RPM_SHIFT(rpm_data[index].value) |
|
||
|
*(cd->bwsum + i);
|
||
|
index++;
|
||
|
i = 1;
|
||
|
} else
|
||
|
i = 0;
|
||
|
|
||
|
/* Copy arb values to rpm data */
|
||
|
for (; i < (fab_pdata->ntieredslaves * fab_pdata->nmasters);
|
||
|
i += 2) {
|
||
|
rpm_data[index].id = offset + index;
|
||
|
rpm_data[index].value = RPM_SHIFT(*(cd->arb + i + 1)) |
|
||
|
*(cd->arb + i);
|
||
|
index++;
|
||
|
}
|
||
|
|
||
|
MSM_BUS_DBG("rpm data for fab: %d\n", fab_pdata->id);
|
||
|
for (i = 0; i < count; i++)
|
||
|
MSM_BUS_DBG("%d %x\n", rpm_data[i].id, rpm_data[i].value);
|
||
|
|
||
|
MSM_BUS_DBG("Commit Data: Fab: %d BWSum:\n", fab_pdata->id);
|
||
|
for (i = 0; i < fab_pdata->nslaves; i++)
|
||
|
MSM_BUS_DBG("fab_slaves:0x%x\n", cd->bwsum[i]);
|
||
|
MSM_BUS_DBG("Commit Data: Fab: %d Arb:\n", fab_pdata->id);
|
||
|
for (i = 0; i < fab_pdata->ntieredslaves; i++) {
|
||
|
MSM_BUS_DBG("tiered-slave: %d\n", i);
|
||
|
for (j = 0; j < fab_pdata->nmasters; j++)
|
||
|
MSM_BUS_DBG(" 0x%x\n",
|
||
|
cd->arb[(i * fab_pdata->nmasters) + j]);
|
||
|
}
|
||
|
|
||
|
MSM_BUS_DBG("calling msm_rpm_set: %d\n", status);
|
||
|
msm_bus_dbg_commit_data(fab_pdata->name, cd, fab_pdata->
|
||
|
nmasters, fab_pdata->nslaves, fab_pdata->ntieredslaves,
|
||
|
MSM_BUS_DBG_OP);
|
||
|
if (fab_pdata->rpm_enabled) {
|
||
|
if (valid) {
|
||
|
if (ctx == ACTIVE_CTX) {
|
||
|
status = msm_rpm_set(MSM_RPM_CTX_SET_0,
|
||
|
rpm_data, count);
|
||
|
MSM_BUS_DBG("msm_rpm_set returned: %d\n",
|
||
|
status);
|
||
|
} else if (ctx == DUAL_CTX) {
|
||
|
status = msm_rpm_set(MSM_RPM_CTX_SET_SLEEP,
|
||
|
rpm_data, count);
|
||
|
MSM_BUS_DBG("msm_rpm_set returned: %d\n",
|
||
|
status);
|
||
|
}
|
||
|
} else {
|
||
|
if (ctx == ACTIVE_CTX) {
|
||
|
status = msm_rpm_clear(MSM_RPM_CTX_SET_0,
|
||
|
rpm_data, count);
|
||
|
MSM_BUS_DBG("msm_rpm_clear returned: %d\n",
|
||
|
status);
|
||
|
} else if (ctx == DUAL_CTX) {
|
||
|
status = msm_rpm_clear(MSM_RPM_CTX_SET_SLEEP,
|
||
|
rpm_data, count);
|
||
|
MSM_BUS_DBG("msm_rpm_clear returned: %d\n",
|
||
|
status);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
|
||
|
#define NUM_TIERS 2
|
||
|
#define RPM_SHIFT24(n) ((n) << 24)
|
||
|
#define RPM_SHIFT16(n) ((n) << 16)
|
||
|
#define RPM_SHIFT8(n) ((n) << 8)
|
||
|
struct commit_data {
|
||
|
uint16_t *bwsum;
|
||
|
uint8_t *arb[NUM_TIERS];
|
||
|
unsigned long *actarb[NUM_TIERS];
|
||
|
};
|
||
|
|
||
|
#define MODE_BIT(val) ((val) & 0x80)
|
||
|
#define MODE0_IMM(val) ((val) & 0xF)
|
||
|
#define MODE0_SHIFT(val) (((val) & 0x70) >> 4)
|
||
|
#define MODE1_STEP 48 /* 48 MB */
|
||
|
#define MODE1_OFFSET 512 /* 512 MB */
|
||
|
#define MODE1_IMM(val) ((val) & 0x7F)
|
||
|
#define __CLZ(x) ((8 * sizeof(uint32_t)) - 1 - __fls(x))
|
||
|
|
||
|
static uint8_t msm_bus_set_bw_bytes(unsigned long val)
|
||
|
{
|
||
|
unsigned int shift;
|
||
|
unsigned int intVal;
|
||
|
unsigned char result;
|
||
|
|
||
|
/* Convert to MB */
|
||
|
intVal = (unsigned int)((val + ((1 << 20) - 1)) >> 20);
|
||
|
/**
|
||
|
* Divide by 2^20 and round up
|
||
|
* A value graeter than 0x1E0 will round up to 512 and overflow
|
||
|
* Mode 0 so it should be made Mode 1
|
||
|
*/
|
||
|
if (0x1E0 > intVal) {
|
||
|
/**
|
||
|
* MODE 0
|
||
|
* Compute the shift value
|
||
|
* Shift value is 32 - the number of leading zeroes -
|
||
|
* 4 to save the most significant 4 bits of the value
|
||
|
*/
|
||
|
shift = 32 - 4 - min((uint8_t)28, (uint8_t)__CLZ(intVal));
|
||
|
|
||
|
/* Add min value - 1 to force a round up when shifting right */
|
||
|
intVal += (1 << shift) - 1;
|
||
|
|
||
|
/* Recompute the shift value in case there was an overflow */
|
||
|
shift = 32 - 4 - min((uint8_t)28, (uint8_t)__CLZ(intVal));
|
||
|
|
||
|
/* Clear the mode bit (msb) and fill in the fields */
|
||
|
result = ((0x70 & (shift << 4)) |
|
||
|
(0x0F & (intVal >> shift)));
|
||
|
} else {
|
||
|
/* MODE 1 */
|
||
|
result = (unsigned char)(0x80 |
|
||
|
((intVal - MODE1_OFFSET + MODE1_STEP - 1) /
|
||
|
MODE1_STEP));
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
uint64_t msm_bus_get_bw(unsigned long val)
|
||
|
{
|
||
|
return MODE_BIT(val) ?
|
||
|
/* Mode 1 */
|
||
|
(MODE1_IMM(val) * MODE1_STEP + MODE1_OFFSET) :
|
||
|
/* Mode 0 */
|
||
|
(MODE0_IMM(val) << MODE0_SHIFT(val));
|
||
|
}
|
||
|
|
||
|
uint64_t msm_bus_get_bw_bytes(unsigned long val)
|
||
|
{
|
||
|
return msm_bus_get_bw(val) << 20;
|
||
|
}
|
||
|
|
||
|
static uint8_t msm_bus_create_bw_tier_pair_bytes(uint8_t type,
|
||
|
unsigned long bw)
|
||
|
{
|
||
|
return msm_bus_set_bw_bytes(bw);
|
||
|
};
|
||
|
|
||
|
uint8_t msm_bus_create_bw_tier_pair(uint8_t type, unsigned long bw)
|
||
|
{
|
||
|
return msm_bus_create_bw_tier_pair_bytes(type, bw);
|
||
|
};
|
||
|
|
||
|
static int msm_bus_rpm_allocate_commit_data(struct msm_bus_fabric_registration
|
||
|
*fab_pdata, void **cdata, int ctx)
|
||
|
{
|
||
|
struct commit_data **cd = (struct commit_data **)cdata;
|
||
|
int i;
|
||
|
|
||
|
*cd = kzalloc(sizeof(struct commit_data), GFP_KERNEL);
|
||
|
if (!*cd) {
|
||
|
MSM_BUS_DBG("Couldn't alloc mem for cdata\n");
|
||
|
goto cdata_err;
|
||
|
}
|
||
|
|
||
|
(*cd)->bwsum = kzalloc((sizeof(uint16_t) * fab_pdata->nslaves),
|
||
|
GFP_KERNEL);
|
||
|
if (!(*cd)->bwsum) {
|
||
|
MSM_BUS_DBG("Couldn't alloc mem for slaves\n");
|
||
|
goto bwsum_err;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < NUM_TIERS; i++) {
|
||
|
(*cd)->arb[i] = kzalloc(((sizeof(uint8_t *)) *
|
||
|
(fab_pdata->ntieredslaves * fab_pdata->nmasters) + 1),
|
||
|
GFP_KERNEL);
|
||
|
if (!(*cd)->arb[i]) {
|
||
|
MSM_BUS_DBG("Couldn't alloc memory for"
|
||
|
" slaves\n");
|
||
|
goto arb_err;
|
||
|
}
|
||
|
|
||
|
(*cd)->actarb[i] = kzalloc(((sizeof(unsigned long *)) *
|
||
|
(fab_pdata->ntieredslaves * fab_pdata->nmasters) + 1),
|
||
|
GFP_KERNEL);
|
||
|
if (!(*cd)->actarb[i]) {
|
||
|
MSM_BUS_DBG("Couldn't alloc memory for"
|
||
|
" slaves\n");
|
||
|
kfree((*cd)->arb[i]);
|
||
|
goto arb_err;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
arb_err:
|
||
|
for (i = i - 1; i >= 0; i--) {
|
||
|
kfree((*cd)->arb[i]);
|
||
|
kfree((*cd)->actarb[i]);
|
||
|
}
|
||
|
bwsum_err:
|
||
|
kfree((*cd)->bwsum);
|
||
|
cdata_err:
|
||
|
kfree(*cd);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
static void free_commit_data(void *cdata)
|
||
|
{
|
||
|
int i;
|
||
|
struct commit_data *cd = (struct commit_data *)cdata;
|
||
|
kfree(cd->bwsum);
|
||
|
for (i = 0; i < NUM_TIERS; i++) {
|
||
|
kfree(cd->arb[i]);
|
||
|
kfree(cd->actarb[i]);
|
||
|
}
|
||
|
kfree(cd);
|
||
|
}
|
||
|
|
||
|
static void *msm_bus_rpm_allocate_rpm_data(struct platform_device *pdev,
|
||
|
struct msm_bus_fabric_registration *fab_pdata)
|
||
|
{
|
||
|
struct msm_rpm_iv_pair *rpm_data;
|
||
|
uint16_t count = (((fab_pdata->nmasters * fab_pdata->ntieredslaves *
|
||
|
NUM_TIERS)/2) + fab_pdata->nslaves + 1)/2;
|
||
|
|
||
|
rpm_data = kmalloc((sizeof(struct msm_rpm_iv_pair) * count),
|
||
|
GFP_KERNEL);
|
||
|
return (void *)rpm_data;
|
||
|
}
|
||
|
|
||
|
static int msm_bus_rpm_compare_cdata(
|
||
|
struct msm_bus_fabric_registration *fab_pdata,
|
||
|
struct commit_data *cd1, struct commit_data *cd2)
|
||
|
{
|
||
|
size_t n;
|
||
|
int i, ret;
|
||
|
n = sizeof(uint16_t) * fab_pdata->nslaves;
|
||
|
ret = memcmp(cd1->bwsum, cd2->bwsum, n);
|
||
|
if (ret) {
|
||
|
MSM_BUS_DBG("Commit Data bwsum not equal\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
n = sizeof(uint8_t *) * ((fab_pdata->ntieredslaves *
|
||
|
fab_pdata->nmasters) + 1);
|
||
|
for (i = 0; i < NUM_TIERS; i++) {
|
||
|
ret = memcmp(cd1->arb[i], cd2->arb[i], n);
|
||
|
if (ret) {
|
||
|
MSM_BUS_DBG("Commit Data arb[%d] not equal\n", i);
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int msm_bus_rpm_commit_arb(struct msm_bus_fabric_registration
|
||
|
*fab_pdata, int ctx, struct msm_rpm_iv_pair *rpm_data,
|
||
|
struct commit_data *cd, bool valid)
|
||
|
{
|
||
|
int i, j, k, offset = 0, status = 0, count, index = 0;
|
||
|
/*
|
||
|
* count is the number of 2-byte words required to commit the
|
||
|
* data to rpm. This is calculated by the following formula.
|
||
|
* Commit data is split into two arrays:
|
||
|
* 1. arb[nmasters * ntieredslaves][num_tiers]
|
||
|
* 2. bwsum[nslaves]
|
||
|
*/
|
||
|
count = (((fab_pdata->nmasters * fab_pdata->ntieredslaves * NUM_TIERS)
|
||
|
/2) + fab_pdata->nslaves + 1)/2;
|
||
|
|
||
|
offset = fab_pdata->offset;
|
||
|
|
||
|
/*
|
||
|
* Copy bwsum to rpm data
|
||
|
* Since bwsum is uint16, the values need to be adjusted to
|
||
|
* be copied to value field of rpm-data, which is 32 bits.
|
||
|
*/
|
||
|
for (i = 0; i < (fab_pdata->nslaves - 1); i += 2) {
|
||
|
rpm_data[index].id = offset + index;
|
||
|
rpm_data[index].value = RPM_SHIFT16(*(cd->bwsum + i + 1)) |
|
||
|
*(cd->bwsum + i);
|
||
|
index++;
|
||
|
}
|
||
|
/* Account for odd number of slaves */
|
||
|
if (fab_pdata->nslaves & 1) {
|
||
|
rpm_data[index].id = offset + index;
|
||
|
rpm_data[index].value = RPM_SHIFT8(*cd->arb[1]) |
|
||
|
*(cd->arb[0]);
|
||
|
rpm_data[index].value = RPM_SHIFT16(rpm_data[index].value) |
|
||
|
*(cd->bwsum + i);
|
||
|
index++;
|
||
|
i = 1;
|
||
|
} else
|
||
|
i = 0;
|
||
|
|
||
|
/* Copy arb values to rpm data */
|
||
|
for (; i < (fab_pdata->ntieredslaves * fab_pdata->nmasters);
|
||
|
i += 2) {
|
||
|
uint16_t tv1, tv0;
|
||
|
rpm_data[index].id = offset + index;
|
||
|
tv0 = RPM_SHIFT8(*(cd->arb[1] + i)) | (*(cd->arb[0] + i));
|
||
|
tv1 = RPM_SHIFT8(*(cd->arb[1] + i + 1)) | (*(cd->arb[0] + i
|
||
|
+ 1));
|
||
|
rpm_data[index].value = RPM_SHIFT16(tv1) | tv0;
|
||
|
index++;
|
||
|
}
|
||
|
|
||
|
MSM_BUS_DBG("rpm data for fab: %d\n", fab_pdata->id);
|
||
|
for (i = 0; i < count; i++)
|
||
|
MSM_BUS_DBG("%d %x\n", rpm_data[i].id, rpm_data[i].value);
|
||
|
|
||
|
MSM_BUS_DBG("Commit Data: Fab: %d BWSum:\n", fab_pdata->id);
|
||
|
for (i = 0; i < fab_pdata->nslaves; i++)
|
||
|
MSM_BUS_DBG("fab_slaves:0x%x\n", cd->bwsum[i]);
|
||
|
MSM_BUS_DBG("Commit Data: Fab: %d Arb:\n", fab_pdata->id);
|
||
|
for (k = 0; k < NUM_TIERS; k++) {
|
||
|
MSM_BUS_DBG("Tier: %d\n", k);
|
||
|
for (i = 0; i < fab_pdata->ntieredslaves; i++) {
|
||
|
MSM_BUS_DBG("tiered-slave: %d\n", i);
|
||
|
for (j = 0; j < fab_pdata->nmasters; j++)
|
||
|
MSM_BUS_DBG(" 0x%x\n",
|
||
|
cd->arb[k][(i * fab_pdata->nmasters)
|
||
|
+ j]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
MSM_BUS_DBG("calling msm_rpm_set: %d\n", status);
|
||
|
msm_bus_dbg_commit_data(fab_pdata->name, (void *)cd, fab_pdata->
|
||
|
nmasters, fab_pdata->nslaves, fab_pdata->ntieredslaves,
|
||
|
MSM_BUS_DBG_OP);
|
||
|
if (fab_pdata->rpm_enabled) {
|
||
|
if (valid) {
|
||
|
if (ctx == ACTIVE_CTX) {
|
||
|
status = msm_rpm_set(MSM_RPM_CTX_SET_0,
|
||
|
rpm_data, count);
|
||
|
MSM_BUS_DBG("msm_rpm_set returned: %d\n",
|
||
|
status);
|
||
|
} else if (ctx == DUAL_CTX) {
|
||
|
status = msm_rpm_set(MSM_RPM_CTX_SET_SLEEP,
|
||
|
rpm_data, count);
|
||
|
MSM_BUS_DBG("msm_rpm_set returned: %d\n",
|
||
|
status);
|
||
|
}
|
||
|
} else {
|
||
|
if (ctx == ACTIVE_CTX) {
|
||
|
status = msm_rpm_clear(MSM_RPM_CTX_SET_0,
|
||
|
rpm_data, count);
|
||
|
MSM_BUS_DBG("msm_rpm_clear returned: %d\n",
|
||
|
status);
|
||
|
} else if (ctx == DUAL_CTX) {
|
||
|
status = msm_rpm_clear(MSM_RPM_CTX_SET_SLEEP,
|
||
|
rpm_data, count);
|
||
|
MSM_BUS_DBG("msm_rpm_clear returned: %d\n",
|
||
|
status);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
#define FORMAT_BW(x) \
|
||
|
((x < 0) ? \
|
||
|
-(msm_bus_get_bw_bytes(msm_bus_create_bw_tier_pair_bytes(0, -(x)))) : \
|
||
|
(msm_bus_get_bw_bytes(msm_bus_create_bw_tier_pair_bytes(0, x))))
|
||
|
|
||
|
static uint16_t msm_bus_pack_bwsum_bytes(unsigned long bw)
|
||
|
{
|
||
|
return (bw + ((1 << 20) - 1)) >> 20;
|
||
|
};
|
||
|
|
||
|
static void msm_bus_rpm_update_bw(struct msm_bus_inode_info *hop,
|
||
|
struct msm_bus_inode_info *info,
|
||
|
struct msm_bus_fabric_registration *fab_pdata,
|
||
|
void *sel_cdata, int *master_tiers,
|
||
|
int64_t add_bw)
|
||
|
{
|
||
|
int index, i, j, tiers, ports;
|
||
|
struct commit_data *sel_cd = (struct commit_data *)sel_cdata;
|
||
|
|
||
|
add_bw = INTERLEAVED_BW(fab_pdata, add_bw, info->node_info->num_mports);
|
||
|
ports = INTERLEAVED_VAL(fab_pdata, info->node_info->num_mports);
|
||
|
tiers = INTERLEAVED_VAL(fab_pdata, hop->node_info->num_tiers);
|
||
|
for (i = 0; i < tiers; i++) {
|
||
|
for (j = 0; j < ports; j++) {
|
||
|
uint16_t hop_tier;
|
||
|
/*
|
||
|
* For interleaved gateway ports and slave ports,
|
||
|
* there is one-one mapping between gateway port and
|
||
|
* the slave port
|
||
|
*/
|
||
|
if (info->node_info->gateway && i != j
|
||
|
&& hop->node_info->num_sports > 1)
|
||
|
continue;
|
||
|
|
||
|
if (!hop->node_info->tier)
|
||
|
hop_tier = MSM_BUS_BW_TIER2 - 1;
|
||
|
else
|
||
|
hop_tier = hop->node_info->tier[i] - 1;
|
||
|
index = ((hop_tier * fab_pdata->nmasters) +
|
||
|
(info->node_info->masterp[j]));
|
||
|
/* If there is tier, calculate arb for commit */
|
||
|
if (hop->node_info->tier) {
|
||
|
uint16_t tier;
|
||
|
unsigned long tieredbw;
|
||
|
if (master_tiers)
|
||
|
tier = master_tiers[0] - 1;
|
||
|
else
|
||
|
tier = MSM_BUS_BW_TIER2 - 1;
|
||
|
|
||
|
tieredbw = sel_cd->actarb[tier][index];
|
||
|
/*
|
||
|
* Make sure gateway to slave port bandwidth
|
||
|
* is not divided when slave is interleaved
|
||
|
*/
|
||
|
if (info->node_info->gateway
|
||
|
&& hop->node_info->num_sports > 1)
|
||
|
tieredbw += add_bw;
|
||
|
else
|
||
|
tieredbw += INTERLEAVED_BW(fab_pdata,
|
||
|
add_bw, hop->node_info->
|
||
|
num_sports);
|
||
|
|
||
|
/* Update Arb for fab,get HW Mport from enum */
|
||
|
sel_cd->arb[tier][index] =
|
||
|
msm_bus_create_bw_tier_pair_bytes(0, tieredbw);
|
||
|
sel_cd->actarb[tier][index] = tieredbw;
|
||
|
MSM_BUS_DBG("tr:%d mpor:%d tbw:%lu bws: %lld\n",
|
||
|
hop_tier, info->node_info->masterp[i], tieredbw,
|
||
|
*hop->link_info.sel_bw);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Update bwsum for slaves on fabric */
|
||
|
|
||
|
ports = INTERLEAVED_VAL(fab_pdata, hop->node_info->num_sports);
|
||
|
for (i = 0; i < ports; i++) {
|
||
|
sel_cd->bwsum[hop->node_info->slavep[i]]
|
||
|
= msm_bus_pack_bwsum_bytes((uint32_t)
|
||
|
msm_bus_div64(hop->node_info->num_sports,
|
||
|
*hop->link_info.sel_bw));
|
||
|
MSM_BUS_DBG("slavep:%d, link_bw: %lld\n",
|
||
|
hop->node_info->slavep[i],
|
||
|
msm_bus_div64(hop->node_info->num_sports,
|
||
|
*hop->link_info.sel_bw));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void msm_bus_rpm_fill_cdata_buffer(int *curr, char *buf, const int max_size,
|
||
|
void *cdata, int nmasters, int nslaves, int ntslaves)
|
||
|
{
|
||
|
int j, k, c;
|
||
|
struct commit_data *cd = (struct commit_data *)cdata;
|
||
|
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr, "BWSum:\n");
|
||
|
for (c = 0; c < nslaves; c++)
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr,
|
||
|
"0x%x\t", cd->bwsum[c]);
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr, "\nArb:");
|
||
|
for (k = 0; k < NUM_TIERS; k++) {
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr,
|
||
|
"\nTier %d:\n", k);
|
||
|
for (c = 0; c < ntslaves; c++) {
|
||
|
*curr += scnprintf(buf + *curr, max_size - *curr,
|
||
|
"TSlave %d:\n", c);
|
||
|
for (j = 0; j < nmasters; j++)
|
||
|
*curr += scnprintf(buf + *curr, max_size -
|
||
|
*curr, " 0x%x\t",
|
||
|
cd->arb[k][(c * nmasters) + j]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
* msm_bus_rpm_commit() - Commit the arbitration data to RPM
|
||
|
* @fabric: Fabric for which the data should be committed
|
||
|
**/
|
||
|
static int msm_bus_rpm_commit(struct msm_bus_fabric_registration
|
||
|
*fab_pdata, void *hw_data, void **cdata)
|
||
|
{
|
||
|
|
||
|
int ret;
|
||
|
bool valid;
|
||
|
struct commit_data *dual_cd, *act_cd;
|
||
|
struct msm_rpm_iv_pair *rpm_data = (struct msm_rpm_iv_pair *)hw_data;
|
||
|
dual_cd = (struct commit_data *)cdata[DUAL_CTX];
|
||
|
act_cd = (struct commit_data *)cdata[ACTIVE_CTX];
|
||
|
|
||
|
/*
|
||
|
* If the arb data for active set and sleep set is
|
||
|
* different, commit both sets.
|
||
|
* If the arb data for active set and sleep set is
|
||
|
* the same, invalidate the sleep set.
|
||
|
*/
|
||
|
ret = msm_bus_rpm_compare_cdata(fab_pdata, act_cd, dual_cd);
|
||
|
if (!ret)
|
||
|
/* Invalidate sleep set.*/
|
||
|
valid = false;
|
||
|
else
|
||
|
valid = true;
|
||
|
|
||
|
ret = msm_bus_rpm_commit_arb(fab_pdata, DUAL_CTX, rpm_data,
|
||
|
dual_cd, valid);
|
||
|
if (ret)
|
||
|
MSM_BUS_ERR("Error comiting fabric:%d in %d ctx\n",
|
||
|
fab_pdata->id, DUAL_CTX);
|
||
|
|
||
|
valid = true;
|
||
|
ret = msm_bus_rpm_commit_arb(fab_pdata, ACTIVE_CTX, rpm_data, act_cd,
|
||
|
valid);
|
||
|
if (ret)
|
||
|
MSM_BUS_ERR("Error comiting fabric:%d in %d ctx\n",
|
||
|
fab_pdata->id, ACTIVE_CTX);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int msm_bus_rpm_port_halt(uint32_t haltid, uint8_t mport)
|
||
|
{
|
||
|
int status = 0;
|
||
|
struct msm_bus_halt_vector hvector = {0, 0};
|
||
|
struct msm_rpm_iv_pair rpm_data[2];
|
||
|
|
||
|
MSM_BUS_MASTER_HALT(hvector.haltmask, hvector.haltval, mport);
|
||
|
rpm_data[0].id = haltid;
|
||
|
rpm_data[0].value = hvector.haltval;
|
||
|
rpm_data[1].id = haltid + 1;
|
||
|
rpm_data[1].value = hvector.haltmask;
|
||
|
|
||
|
MSM_BUS_DBG("ctx: %d, id: %d, value: %d\n",
|
||
|
MSM_RPM_CTX_SET_0, rpm_data[0].id, rpm_data[0].value);
|
||
|
MSM_BUS_DBG("ctx: %d, id: %d, value: %d\n",
|
||
|
MSM_RPM_CTX_SET_0, rpm_data[1].id, rpm_data[1].value);
|
||
|
|
||
|
status = msm_rpm_set(MSM_RPM_CTX_SET_0, rpm_data, 2);
|
||
|
if (status)
|
||
|
MSM_BUS_DBG("msm_rpm_set returned: %d\n", status);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
static int msm_bus_rpm_port_unhalt(uint32_t haltid, uint8_t mport)
|
||
|
{
|
||
|
int status = 0;
|
||
|
struct msm_bus_halt_vector hvector = {0, 0};
|
||
|
struct msm_rpm_iv_pair rpm_data[2];
|
||
|
|
||
|
MSM_BUS_MASTER_UNHALT(hvector.haltmask, hvector.haltval,
|
||
|
mport);
|
||
|
rpm_data[0].id = haltid;
|
||
|
rpm_data[0].value = hvector.haltval;
|
||
|
rpm_data[1].id = haltid + 1;
|
||
|
rpm_data[1].value = hvector.haltmask;
|
||
|
|
||
|
MSM_BUS_DBG("unalt: ctx: %d, id: %d, value: %d\n",
|
||
|
MSM_RPM_CTX_SET_SLEEP, rpm_data[0].id, rpm_data[0].value);
|
||
|
MSM_BUS_DBG("unhalt: ctx: %d, id: %d, value: %d\n",
|
||
|
MSM_RPM_CTX_SET_SLEEP, rpm_data[1].id, rpm_data[1].value);
|
||
|
|
||
|
status = msm_rpm_set(MSM_RPM_CTX_SET_0, rpm_data, 2);
|
||
|
if (status)
|
||
|
MSM_BUS_DBG("msm_rpm_set returned: %d\n", status);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
int msm_bus_remote_hw_commit(struct msm_bus_fabric_registration
|
||
|
*fab_pdata, void *hw_data, void **cdata)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int msm_bus_rpm_hw_init(struct msm_bus_fabric_registration *pdata,
|
||
|
struct msm_bus_hw_algorithm *hw_algo)
|
||
|
{
|
||
|
pdata->il_flag = msm_bus_rpm_is_mem_interleaved();
|
||
|
hw_algo->allocate_commit_data = msm_bus_rpm_allocate_commit_data;
|
||
|
hw_algo->allocate_hw_data = msm_bus_rpm_allocate_rpm_data;
|
||
|
hw_algo->node_init = NULL;
|
||
|
hw_algo->free_commit_data = free_commit_data;
|
||
|
hw_algo->update_bw = msm_bus_rpm_update_bw;
|
||
|
hw_algo->commit = msm_bus_rpm_commit;
|
||
|
hw_algo->port_halt = msm_bus_rpm_port_halt;
|
||
|
hw_algo->port_unhalt = msm_bus_rpm_port_unhalt;
|
||
|
if (!pdata->ahb)
|
||
|
pdata->rpm_enabled = 1;
|
||
|
return 0;
|
||
|
}
|