Merge branches 'audio' and 'nfc' into combined
combined = flashable working image: v2 speaker audio + NFC reader. Excludes mic (WCD9378 capture, WIP) and nfc-ce (card emulation, broken).
This commit is contained in:
commit
d4c5ebbeee
7 changed files with 377 additions and 18 deletions
|
|
@ -698,9 +698,28 @@
|
|||
};
|
||||
|
||||
&i2c1 {
|
||||
/* Samsung NFC @ 0x27 */
|
||||
|
||||
status = "okay";
|
||||
|
||||
/*
|
||||
* Samsung S3NRN4V NFC controller. XI is driven by the RF_CLK2 PMIC
|
||||
* buffer; the chip has no oscillator of its own and gates the clock
|
||||
* via its CLK_REQ line, so the clock must be voted on in response
|
||||
* to it.
|
||||
*/
|
||||
nfc@27 {
|
||||
compatible = "samsung,s3nrn4v";
|
||||
reg = <0x27>;
|
||||
|
||||
clk-req-gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>;
|
||||
clocks = <&rpmhcc RPMH_RF_CLK2>;
|
||||
en-gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>;
|
||||
interrupt-parent = <&tlmm>;
|
||||
interrupts = <31 IRQ_TYPE_EDGE_RISING>;
|
||||
pinctrl-0 = <&nfc_clk_req_default>, <&nfc_irq_default>,
|
||||
<&nfc_pd_default>;
|
||||
pinctrl-names = "default";
|
||||
wake-gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
};
|
||||
|
||||
&i2c3 {
|
||||
|
|
@ -1089,6 +1108,13 @@
|
|||
<13 1>, /* NC */
|
||||
<63 2>; /* WLAN UART */
|
||||
|
||||
nfc_clk_req_default: nfc-clk-req-default-state {
|
||||
pins = "gpio6";
|
||||
function = "gpio";
|
||||
drive-strength = <2>;
|
||||
bias-disable;
|
||||
};
|
||||
|
||||
ts_active: ts-irq-active-state {
|
||||
pins = "gpio19";
|
||||
function = "gpio";
|
||||
|
|
@ -1103,6 +1129,13 @@
|
|||
bias-disable;
|
||||
};
|
||||
|
||||
nfc_irq_default: nfc-irq-default-state {
|
||||
pins = "gpio31";
|
||||
function = "gpio";
|
||||
drive-strength = <2>;
|
||||
bias-disable;
|
||||
};
|
||||
|
||||
qup_uart11_sleep_cts: qup-uart11-sleep-cts-state {
|
||||
pins = "gpio48";
|
||||
function = "gpio";
|
||||
|
|
@ -1170,6 +1203,17 @@
|
|||
bias-pull-down;
|
||||
};
|
||||
|
||||
/*
|
||||
* Pulled up so the NFC chip stays powered down while the line is
|
||||
* not driven (it is the chip's active-high power-down input).
|
||||
*/
|
||||
nfc_pd_default: nfc-pd-default-state {
|
||||
pins = "gpio56";
|
||||
function = "gpio";
|
||||
drive-strength = <2>;
|
||||
bias-pull-up;
|
||||
};
|
||||
|
||||
sdc2_card_det_n: sdc2-card-det-state {
|
||||
pins = "gpio65";
|
||||
function = "gpio";
|
||||
|
|
|
|||
|
|
@ -122,11 +122,47 @@ static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int s3fwrn5_nci_setup(struct nci_dev *ndev)
|
||||
{
|
||||
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
/*
|
||||
* Runs after CORE_RESET, before CORE_INIT. The S3NRN4V needs its
|
||||
* reference clock configured here (the downstream stack does it in the
|
||||
* bootloader, before CORE_RESET, but this is the earliest hook the NCI
|
||||
* core offers and the chip accepts it).
|
||||
*/
|
||||
if (info->variant == S3FWRN5_VARIANT_S3NRN4V)
|
||||
return s3fwrn5_nci_clk_cfg(info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
|
||||
{
|
||||
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||
int ret;
|
||||
|
||||
if (info->variant == S3FWRN5_VARIANT_S3NRN4V) {
|
||||
/*
|
||||
* The S3NRN4V ships with working firmware behind a bootloader
|
||||
* protocol this driver does not implement, so there is no
|
||||
* download step; the NCI core has already done CORE_RESET +
|
||||
* CORE_INIT. Just (re)load the RF registers via DUAL_OPTION.
|
||||
*/
|
||||
ret = s3fwrn5_nci_rf_configure_dual(info, "sec_s3nrn4v_hwreg.bin",
|
||||
"sec_s3nrn4v_swreg.bin");
|
||||
/*
|
||||
* Keep going even if the blobs could not be loaded: the chip
|
||||
* still enumerates and falls back to the RF registers programmed
|
||||
* in its flash, so NFC may work anyway.
|
||||
*/
|
||||
if (ret < 0)
|
||||
dev_warn(&ndev->nfc_dev->dev,
|
||||
"rfreg configure failed (%d)\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (s3fwrn5_firmware_init(info)) {
|
||||
//skip bootloader mode
|
||||
return 0;
|
||||
|
|
@ -152,13 +188,14 @@ static const struct nci_ops s3fwrn5_nci_ops = {
|
|||
.open = s3fwrn5_nci_open,
|
||||
.close = s3fwrn5_nci_close,
|
||||
.send = s3fwrn5_nci_send,
|
||||
.setup = s3fwrn5_nci_setup,
|
||||
.post_setup = s3fwrn5_nci_post_setup,
|
||||
.prop_ops = s3fwrn5_nci_prop_ops,
|
||||
.n_prop_ops = ARRAY_SIZE(s3fwrn5_nci_prop_ops),
|
||||
};
|
||||
|
||||
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
|
||||
const struct s3fwrn5_phy_ops *phy_ops)
|
||||
const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant)
|
||||
{
|
||||
struct s3fwrn5_info *info;
|
||||
int ret;
|
||||
|
|
@ -170,6 +207,7 @@ int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
|
|||
info->phy_id = phy_id;
|
||||
info->pdev = pdev;
|
||||
info->phy_ops = phy_ops;
|
||||
info->variant = variant;
|
||||
mutex_init(&info->mutex);
|
||||
|
||||
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
|
||||
|
|
|
|||
|
|
@ -23,9 +23,76 @@ struct s3fwrn5_i2c_phy {
|
|||
struct i2c_client *i2c_dev;
|
||||
struct clk *clk;
|
||||
|
||||
/*
|
||||
* Optional hardware clock-request handshake. When a CLK_REQ GPIO is
|
||||
* wired, the chip drives it high while it needs its XI clock -- notably
|
||||
* to generate the poll/reader carrier -- and the clock is gated on it
|
||||
* instead of being left always-on (which never lets the chip's TX PLL
|
||||
* lock on a fresh clock start, leaving it unable to poll).
|
||||
*/
|
||||
struct gpio_desc *gpio_clk_req;
|
||||
bool clk_on;
|
||||
struct mutex clk_lock; /* serialises clk_on against the CLK_REQ irq */
|
||||
|
||||
unsigned int irq_skip:1;
|
||||
};
|
||||
|
||||
static void s3fwrn5_i2c_clk_set_locked(struct s3fwrn5_i2c_phy *phy, bool on)
|
||||
{
|
||||
lockdep_assert_held(&phy->clk_lock);
|
||||
|
||||
if (on && !phy->clk_on) {
|
||||
int ret = clk_prepare_enable(phy->clk);
|
||||
|
||||
if (ret == 0)
|
||||
phy->clk_on = true;
|
||||
else
|
||||
dev_warn_once(&phy->i2c_dev->dev,
|
||||
"failed to enable clock (%d); NFC may not poll\n",
|
||||
ret);
|
||||
} else if (!on && phy->clk_on) {
|
||||
clk_disable_unprepare(phy->clk);
|
||||
phy->clk_on = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply the current CLK_REQ level. Reading the GPIO under clk_lock makes
|
||||
* concurrent callers (the CLK_REQ irq thread and the probe-time seeding)
|
||||
* safe: whoever runs last applies a level read after the earlier update,
|
||||
* never a stale one.
|
||||
*/
|
||||
static void s3fwrn5_i2c_clk_sync(struct s3fwrn5_i2c_phy *phy)
|
||||
{
|
||||
int level;
|
||||
|
||||
mutex_lock(&phy->clk_lock);
|
||||
level = gpiod_get_value_cansleep(phy->gpio_clk_req);
|
||||
if (level >= 0)
|
||||
s3fwrn5_i2c_clk_set_locked(phy, level > 0);
|
||||
else
|
||||
dev_warn_once(&phy->i2c_dev->dev,
|
||||
"failed to read CLK_REQ (%d); keeping clock state\n",
|
||||
level);
|
||||
mutex_unlock(&phy->clk_lock);
|
||||
}
|
||||
|
||||
static void s3fwrn5_i2c_clk_disable_action(void *data)
|
||||
{
|
||||
struct s3fwrn5_i2c_phy *phy = data;
|
||||
|
||||
mutex_lock(&phy->clk_lock);
|
||||
s3fwrn5_i2c_clk_set_locked(phy, false);
|
||||
mutex_unlock(&phy->clk_lock);
|
||||
}
|
||||
|
||||
static irqreturn_t s3fwrn5_i2c_clk_req_thread(int irq, void *phy_id)
|
||||
{
|
||||
s3fwrn5_i2c_clk_sync(phy_id);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode)
|
||||
{
|
||||
struct s3fwrn5_i2c_phy *phy = phy_id;
|
||||
|
|
@ -146,6 +213,7 @@ out:
|
|||
|
||||
static int s3fwrn5_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
enum s3fwrn5_variant variant;
|
||||
struct s3fwrn5_i2c_phy *phy;
|
||||
int ret;
|
||||
|
||||
|
|
@ -172,15 +240,61 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
|
|||
* S3FWRN5 depends on a clock input ("XI" pin) to function properly.
|
||||
* Depending on the hardware configuration this could be an always-on
|
||||
* oscillator or some external clock that must be explicitly enabled.
|
||||
* Make sure the clock is running before starting S3FWRN5.
|
||||
*
|
||||
* If a CLK_REQ GPIO is wired, the chip gates the clock itself (driving
|
||||
* CLK_REQ high when it needs XI); service that handshake. Otherwise just
|
||||
* make sure the clock is running before starting S3FWRN5.
|
||||
*/
|
||||
phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL);
|
||||
if (IS_ERR(phy->clk))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
|
||||
"failed to get clock\n");
|
||||
mutex_init(&phy->clk_lock);
|
||||
phy->gpio_clk_req = devm_gpiod_get_optional(&client->dev, "clk-req",
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(phy->gpio_clk_req))
|
||||
return PTR_ERR(phy->gpio_clk_req);
|
||||
|
||||
if (phy->gpio_clk_req) {
|
||||
int clk_req_irq;
|
||||
|
||||
phy->clk = devm_clk_get_optional(&client->dev, NULL);
|
||||
if (IS_ERR(phy->clk))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
|
||||
"failed to get clock\n");
|
||||
|
||||
/*
|
||||
* Unlike the always-on branch below, this clock is enabled by
|
||||
* hand from the CLK_REQ handler, so devm will not disable it on
|
||||
* unbind. Gate it off explicitly if it is still on at teardown.
|
||||
*/
|
||||
ret = devm_add_action_or_reset(&client->dev,
|
||||
s3fwrn5_i2c_clk_disable_action,
|
||||
phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_req_irq = gpiod_to_irq(phy->gpio_clk_req);
|
||||
if (clk_req_irq < 0)
|
||||
return clk_req_irq;
|
||||
|
||||
ret = devm_request_threaded_irq(&client->dev, clk_req_irq, NULL,
|
||||
s3fwrn5_i2c_clk_req_thread,
|
||||
IRQF_TRIGGER_RISING |
|
||||
IRQF_TRIGGER_FALLING |
|
||||
IRQF_ONESHOT,
|
||||
"s3fwrn5_clk_req", phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Seed the clock state from the current CLK_REQ level. */
|
||||
s3fwrn5_i2c_clk_sync(phy);
|
||||
} else {
|
||||
phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL);
|
||||
if (IS_ERR(phy->clk))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
|
||||
"failed to get clock\n");
|
||||
}
|
||||
|
||||
variant = (uintptr_t)i2c_get_match_data(client);
|
||||
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev,
|
||||
&i2c_phy_ops);
|
||||
&i2c_phy_ops, variant);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -205,13 +319,17 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client)
|
|||
}
|
||||
|
||||
static const struct i2c_device_id s3fwrn5_i2c_id_table[] = {
|
||||
{ S3FWRN5_I2C_DRIVER_NAME },
|
||||
{}
|
||||
{ .name = S3FWRN5_I2C_DRIVER_NAME, .driver_data = S3FWRN5_VARIANT_FWDL },
|
||||
{ .name = "s3nrn4v", .driver_data = S3FWRN5_VARIANT_S3NRN4V },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
|
||||
|
||||
static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = {
|
||||
{ .compatible = "samsung,s3fwrn5-i2c", },
|
||||
static const struct of_device_id of_s3fwrn5_i2c_match[] = {
|
||||
{ .compatible = "samsung,s3fwrn5-i2c",
|
||||
.data = (void *)S3FWRN5_VARIANT_FWDL, },
|
||||
{ .compatible = "samsung,s3nrn4v",
|
||||
.data = (void *)S3FWRN5_VARIANT_S3NRN4V, },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
|
||||
|
|
@ -219,7 +337,7 @@ MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
|
|||
static struct i2c_driver s3fwrn5_i2c_driver = {
|
||||
.driver = {
|
||||
.name = S3FWRN5_I2C_DRIVER_NAME,
|
||||
.of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),
|
||||
.of_match_table = of_s3fwrn5_i2c_match,
|
||||
},
|
||||
.probe = s3fwrn5_i2c_probe,
|
||||
.remove = s3fwrn5_i2c_remove,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#include "s3fwrn5.h"
|
||||
#include "nci.h"
|
||||
|
|
@ -20,7 +23,7 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
|
||||
const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {
|
||||
{
|
||||
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||
NCI_PROP_SET_RFREG),
|
||||
|
|
@ -41,6 +44,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
|
|||
NCI_PROP_FW_CFG),
|
||||
.rsp = s3fwrn5_nci_prop_rsp,
|
||||
},
|
||||
{
|
||||
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||
NCI_PROP_DUAL_OPTION),
|
||||
.rsp = s3fwrn5_nci_prop_rsp,
|
||||
},
|
||||
};
|
||||
|
||||
#define S3FWRN5_RFREG_SECTION_SIZE 252
|
||||
|
|
@ -117,3 +125,112 @@ out:
|
|||
release_firmware(fw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure the reference clock. The S3NRN4V expects the single-byte FW_CFG
|
||||
* form (just the clock-speed selector). The downstream stack sends this in the
|
||||
* bootloader before CORE_RESET; the earliest the mainline NCI core lets us in
|
||||
* is the ->setup hook (after CORE_RESET, before CORE_INIT), which works.
|
||||
*/
|
||||
int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info)
|
||||
{
|
||||
u8 clk_speed = NCI_PROP_FW_CFG_CLK_SPEED;
|
||||
|
||||
return nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG, 1, &clk_speed);
|
||||
}
|
||||
|
||||
/*
|
||||
* S3NRN4V RF register update. The HW and SW register blobs are merged into a
|
||||
* single stream (HW first) and pushed via the DUAL_OPTION command:
|
||||
* START_UPDATE, one SET_OPTION per 252-byte section, then STOP_UPDATE carrying
|
||||
* a 16-bit checksum (running sum of the merged stream as 32-bit words).
|
||||
*/
|
||||
int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
|
||||
const char *hw_name, const char *sw_name)
|
||||
{
|
||||
const struct firmware *hw_fw = NULL, *sw_fw = NULL;
|
||||
struct nci_prop_dual_set_option_cmd set_option;
|
||||
struct device *dev = &info->ndev->nfc_dev->dev;
|
||||
size_t merged_size, i, len;
|
||||
u8 *merged = NULL;
|
||||
u8 stop_cmd[3];
|
||||
u32 checksum;
|
||||
u8 sub_oid;
|
||||
int ret;
|
||||
|
||||
ret = request_firmware(&hw_fw, hw_name, dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = request_firmware(&sw_fw, sw_name, dev);
|
||||
if (ret < 0)
|
||||
goto out_hw;
|
||||
|
||||
merged_size = hw_fw->size + sw_fw->size;
|
||||
|
||||
/*
|
||||
* The stream is checksummed as 32-bit words and pushed in at most 256
|
||||
* sections (the section index is a single byte); reject blobs that
|
||||
* would silently break either.
|
||||
*/
|
||||
if (merged_size % 4 ||
|
||||
merged_size > 256 * NCI_PROP_DUAL_SECTION_SIZE) {
|
||||
dev_err(dev, "invalid rfreg blob size (%zu)\n", merged_size);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
merged = kmalloc(merged_size, GFP_KERNEL);
|
||||
if (!merged) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
memcpy(merged, hw_fw->data, hw_fw->size);
|
||||
memcpy(merged + hw_fw->size, sw_fw->data, sw_fw->size);
|
||||
|
||||
/* Running sum of the merged stream as little-endian 32-bit words. */
|
||||
checksum = 0;
|
||||
for (i = 0; i + 4 <= merged_size; i += 4)
|
||||
checksum += get_unaligned_le32(merged + i);
|
||||
|
||||
dev_dbg(dev, "rfreg dual-option update: %s + %s\n", hw_name, sw_name);
|
||||
|
||||
/* START_UPDATE */
|
||||
sub_oid = NCI_PROP_DUAL_SUB_START_UPDATE;
|
||||
ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Unable to start rfreg update\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* SET_OPTION per section */
|
||||
set_option.sub_oid = NCI_PROP_DUAL_SUB_SET_OPTION;
|
||||
set_option.index = 0;
|
||||
for (i = 0; i < merged_size; i += NCI_PROP_DUAL_SECTION_SIZE) {
|
||||
len = min_t(size_t, merged_size - i, NCI_PROP_DUAL_SECTION_SIZE);
|
||||
memcpy(set_option.data, merged + i, len);
|
||||
ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION,
|
||||
len + 2, (__u8 *)&set_option);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "rfreg update error (code=%d)\n", ret);
|
||||
goto out;
|
||||
}
|
||||
set_option.index++;
|
||||
}
|
||||
|
||||
/* STOP_UPDATE with checksum */
|
||||
stop_cmd[0] = NCI_PROP_DUAL_SUB_STOP_UPDATE;
|
||||
put_unaligned_le16(checksum, &stop_cmd[1]);
|
||||
ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 3, stop_cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Unable to stop rfreg update\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "rfreg dual-option update: success\n");
|
||||
out:
|
||||
kfree(merged);
|
||||
release_firmware(sw_fw);
|
||||
out_hw:
|
||||
release_firmware(hw_fw);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ struct nci_prop_stop_rfreg_rsp {
|
|||
|
||||
#define NCI_PROP_FW_CFG 0x28
|
||||
|
||||
/*
|
||||
* Single-byte FW_CFG payload (clock-speed selector) for the S3NRN4V reference
|
||||
* clock. Taken from the vendor configuration for this part (the encoding is
|
||||
* not documented).
|
||||
*/
|
||||
#define NCI_PROP_FW_CFG_CLK_SPEED 0x11
|
||||
|
||||
struct nci_prop_fw_cfg_cmd {
|
||||
__u8 clk_type;
|
||||
__u8 clk_speed;
|
||||
|
|
@ -50,7 +57,30 @@ struct nci_prop_fw_cfg_rsp {
|
|||
__u8 status;
|
||||
};
|
||||
|
||||
extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4];
|
||||
/*
|
||||
* The S3NRN4V updates its RF registers through a single "dual option" command
|
||||
* (a sub-OID selects the operation) instead of the START/SET/STOP_RFREG
|
||||
* opcodes above, and expects the HW and SW register blobs merged into one
|
||||
* stream.
|
||||
*/
|
||||
#define NCI_PROP_DUAL_OPTION 0x2a
|
||||
|
||||
#define NCI_PROP_DUAL_SUB_START_UPDATE 0x01
|
||||
#define NCI_PROP_DUAL_SUB_SET_OPTION 0x02
|
||||
#define NCI_PROP_DUAL_SUB_STOP_UPDATE 0x03
|
||||
|
||||
#define NCI_PROP_DUAL_SECTION_SIZE 252
|
||||
|
||||
struct nci_prop_dual_set_option_cmd {
|
||||
__u8 sub_oid; /* NCI_PROP_DUAL_SUB_SET_OPTION */
|
||||
__u8 index;
|
||||
__u8 data[NCI_PROP_DUAL_SECTION_SIZE];
|
||||
};
|
||||
|
||||
extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[5];
|
||||
int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);
|
||||
int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
|
||||
const char *hw_name, const char *sw_name);
|
||||
int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info);
|
||||
|
||||
#endif /* __LOCAL_S3FWRN5_NCI_H_ */
|
||||
|
|
|
|||
|
|
@ -21,6 +21,17 @@ enum s3fwrn5_mode {
|
|||
S3FWRN5_MODE_FW,
|
||||
};
|
||||
|
||||
enum s3fwrn5_variant {
|
||||
/* S3FWRN5 / S3FWRN82: firmware is downloaded by this driver */
|
||||
S3FWRN5_VARIANT_FWDL,
|
||||
/*
|
||||
* S3NRN4V: ships with working firmware behind a bootloader protocol
|
||||
* this driver does not implement; skip the download, configure the
|
||||
* clock (FW_CFG) and update the RF registers via the DUAL_OPTION cmd.
|
||||
*/
|
||||
S3FWRN5_VARIANT_S3NRN4V,
|
||||
};
|
||||
|
||||
struct s3fwrn5_phy_ops {
|
||||
void (*set_wake)(void *id, bool sleep);
|
||||
void (*set_mode)(void *id, enum s3fwrn5_mode);
|
||||
|
|
@ -36,6 +47,7 @@ struct s3fwrn5_info {
|
|||
const struct s3fwrn5_phy_ops *phy_ops;
|
||||
|
||||
struct s3fwrn5_fw_info fw_info;
|
||||
enum s3fwrn5_variant variant;
|
||||
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
|
@ -78,7 +90,7 @@ static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)
|
|||
}
|
||||
|
||||
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
|
||||
const struct s3fwrn5_phy_ops *phy_ops);
|
||||
const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant);
|
||||
void s3fwrn5_remove(struct nci_dev *ndev);
|
||||
|
||||
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev)
|
|||
}
|
||||
|
||||
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev,
|
||||
&uart_phy_ops);
|
||||
&uart_phy_ops, S3FWRN5_VARIANT_FWDL);
|
||||
if (ret < 0)
|
||||
goto err_serdev;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue