milos-linux/drivers/nfc/s3fwrn5/s3fwrn5.h
Jorijn van der Graaf 9cd00a65e8 nfc: s3fwrn5: support the S3NRN4V variant
The S3NRN4V (e.g. on the Fairphone 6, SM7635) is an S3FWRN5-family NFC
controller that needs different bring-up, selected with a new
samsung,s3nrn4v compatible:

 - It ships with working firmware behind a bootloader protocol this
   driver does not implement (GET_BOOTINFO times out), so the firmware
   download step is skipped. Its RF registers are (re)loaded with the
   proprietary DUAL_OPTION command (the HW and SW register blobs merged
   into a single stream) instead of the START/SET/STOP_RFREG sequence.

 - Its reference clock speed is configured with the single-byte FW_CFG
   form, sent from the ->setup hook (after CORE_RESET, before CORE_INIT).
   The selector value (0x11) is taken from the vendor configuration for
   this part; its encoding is not documented.

 - It gates its XI clock through a CLK_REQ line: the chip drives it high
   when it needs the clock, notably to synthesise the 13.56 MHz poll
   carrier. Left always-on, the free-running clock never lets the chip's
   TX PLL lock on a fresh start and it cannot poll (it falls back to
   listen only). Service the handshake when a clk-req GPIO is described,
   gating the clock on it; without one the clock stays always-on.

The variant is carried as match data by both the OF and the I2C device
id tables so the two match paths agree, and the OF table is now
referenced unconditionally for its match data, so drop the
of_match_ptr()/__maybe_unused annotations from it.

The error policy differs between the two configuration steps on purpose:
a clock misconfiguration is fatal (a ->setup failure aborts CORE_INIT),
whereas an RF-register update failure is only warned about and bring-up
continues, since the chip falls back to the RF registers programmed in
its flash and NFC may still work.

Unlike the host-endian word read in the legacy rfreg path, the
DUAL_OPTION checksum is accumulated with get_unaligned_le32() and emitted
little-endian explicitly, so it is correct regardless of CPU endianness.

Existing S3FWRN5 / S3FWRN82 setups keep the firmware-download path and
the always-on clock, unchanged.

Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-05 20:37:57 +02:00

99 lines
2.2 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* NCI based driver for Samsung S3FWRN5 NFC chip
*
* Copyright (C) 2015 Samsung Electronics
* Robert Baldyga <r.baldyga@samsung.com>
*/
#ifndef __LOCAL_S3FWRN5_H_
#define __LOCAL_S3FWRN5_H_
#include <linux/nfc.h>
#include <net/nfc/nci_core.h>
#include "firmware.h"
enum s3fwrn5_mode {
S3FWRN5_MODE_COLD,
S3FWRN5_MODE_NCI,
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);
enum s3fwrn5_mode (*get_mode)(void *id);
int (*write)(void *id, struct sk_buff *skb);
};
struct s3fwrn5_info {
struct nci_dev *ndev;
void *phy_id;
struct device *pdev;
const struct s3fwrn5_phy_ops *phy_ops;
struct s3fwrn5_fw_info fw_info;
enum s3fwrn5_variant variant;
struct mutex mutex;
};
static inline int s3fwrn5_set_mode(struct s3fwrn5_info *info,
enum s3fwrn5_mode mode)
{
if (!info->phy_ops->set_mode)
return -EOPNOTSUPP;
info->phy_ops->set_mode(info->phy_id, mode);
return 0;
}
static inline enum s3fwrn5_mode s3fwrn5_get_mode(struct s3fwrn5_info *info)
{
if (!info->phy_ops->get_mode)
return -EOPNOTSUPP;
return info->phy_ops->get_mode(info->phy_id);
}
static inline int s3fwrn5_set_wake(struct s3fwrn5_info *info, bool wake)
{
if (!info->phy_ops->set_wake)
return -EOPNOTSUPP;
info->phy_ops->set_wake(info->phy_id, wake);
return 0;
}
static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)
{
if (!info->phy_ops->write)
return -EOPNOTSUPP;
return info->phy_ops->write(info->phy_id, skb);
}
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
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,
enum s3fwrn5_mode mode);
#endif /* __LOCAL_S3FWRN5_H_ */