From 65793f3cd93abf4ca1109f78e07c1b7193abdfec Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Wed, 13 Jun 2018 16:26:46 -0400 Subject: MAINTAINERS: remove the outdated "LINUX SECURITY MODULE (LSM) FRAMEWORK" entry This has been replaced with the "SECURITY SUBSYSTEM" entry. Signed-off-by: Paul Moore Signed-off-by: James Morris --- MAINTAINERS | 5 ----- 1 file changed, 5 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 79bb02ff812f..6d2340f572e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8229,11 +8229,6 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git F: tools/memory-model/ F: Documentation/memory-barriers.txt -LINUX SECURITY MODULE (LSM) FRAMEWORK -M: Chris Wright -L: linux-security-module@vger.kernel.org -S: Supported - LIS3LV02D ACCELEROMETER DRIVER M: Eric Piel S: Maintained -- cgit v1.2.3-59-g8ed1b From cf76e1e5198aad2721c8da84c2cc0933d32223cf Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Mon, 18 Jun 2018 15:43:32 -0400 Subject: MAINTAINERS: update the LSM and SELinux subsystems The SELinux code, security/selinux/, already has a MAINTAINERS entry so exclude it from the security subsystem entry in an effort to better reflect current practices. Signed-off-by: Paul Moore --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9d5eeff51b5f..67e4c4f92ba9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12769,6 +12769,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security.git W: http://kernsec.org/ S: Supported F: security/ +X: security/selinux/ SELINUX SECURITY MODULE M: Paul Moore -- cgit v1.2.3-59-g8ed1b From 8ba6731ad4c7e9f612b2f7dae1b5ed6bfc884b67 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 18 Jun 2018 22:43:58 +0200 Subject: rfkill: add header files to MAINTAINERS These files weren't covered by the MAINTAINERS file, add them. Reported-by: Peter Meerwald-Stadler Signed-off-by: Johannes Berg --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2c7069037a15..1fe2aac28177 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12155,6 +12155,8 @@ S: Maintained F: Documentation/rfkill.txt F: Documentation/ABI/stable/sysfs-class-rfkill F: net/rfkill/ +F: include/linux/rfkill.h +F: include/uapi/linux/rfkill.h RHASHTABLE M: Thomas Graf -- cgit v1.2.3-59-g8ed1b From 0eb71a9da5796851fa87ddc1a534066c0fe54055 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 18 Jun 2018 12:52:50 +1000 Subject: rhashtable: split rhashtable.h Due to the use of rhashtables in net namespaces, rhashtable.h is included in lots of the kernel, so a small changes can required a large recompilation. This makes development painful. This patch splits out rhashtable-types.h which just includes the major type declarations, and does not include (non-trivial) inline code. rhashtable.h is no longer included by anything in the include/ directory. Common include files only include rhashtable-types.h so a large recompilation is only triggered when that changes. Acked-by: Herbert Xu Signed-off-by: NeilBrown Signed-off-by: David S. Miller --- MAINTAINERS | 2 + drivers/net/ethernet/chelsio/cxgb4/cxgb4.h | 1 + include/linux/ipc.h | 2 +- include/linux/ipc_namespace.h | 2 +- include/linux/mroute_base.h | 2 +- include/linux/rhashtable-types.h | 139 +++++++++++++++++++++++++++++ include/linux/rhashtable.h | 127 +------------------------- include/net/inet_frag.h | 2 +- include/net/netfilter/nf_flow_table.h | 2 +- include/net/sctp/structs.h | 2 +- include/net/seg6.h | 2 +- include/net/seg6_hmac.h | 2 +- ipc/msg.c | 1 + ipc/sem.c | 1 + ipc/shm.c | 1 + ipc/util.c | 1 + lib/rhashtable.c | 1 + net/ipv4/inet_fragment.c | 1 + net/ipv4/ipmr.c | 1 + net/ipv4/ipmr_base.c | 1 + net/ipv6/ip6mr.c | 1 + net/ipv6/seg6.c | 1 + net/ipv6/seg6_hmac.c | 1 + net/netfilter/nf_tables_api.c | 1 + net/sctp/input.c | 1 + net/sctp/socket.c | 1 + 26 files changed, 166 insertions(+), 133 deletions(-) create mode 100644 include/linux/rhashtable-types.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index edf3cf5ea691..99e5cef8172e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12162,7 +12162,9 @@ M: Herbert Xu L: netdev@vger.kernel.org S: Maintained F: lib/rhashtable.c +F: lib/test_rhashtable.c F: include/linux/rhashtable.h +F: include/linux/rhashtable-types.h RICOH R5C592 MEMORYSTICK DRIVER M: Maxim Levitsky diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h b/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h index 0dbe2d9e22d6..1adb968b8354 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include diff --git a/include/linux/ipc.h b/include/linux/ipc.h index 6cc2df7f7ac9..e1c9eea6015b 100644 --- a/include/linux/ipc.h +++ b/include/linux/ipc.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include diff --git a/include/linux/ipc_namespace.h b/include/linux/ipc_namespace.h index b5630c8eb2f3..6cea726612b7 100644 --- a/include/linux/ipc_namespace.h +++ b/include/linux/ipc_namespace.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include struct user_namespace; diff --git a/include/linux/mroute_base.h b/include/linux/mroute_base.h index d633f737b3c6..fd436cdd4725 100644 --- a/include/linux/mroute_base.h +++ b/include/linux/mroute_base.h @@ -2,7 +2,7 @@ #define __LINUX_MROUTE_BASE_H #include -#include +#include #include #include #include diff --git a/include/linux/rhashtable-types.h b/include/linux/rhashtable-types.h new file mode 100644 index 000000000000..9740063ff13b --- /dev/null +++ b/include/linux/rhashtable-types.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Resizable, Scalable, Concurrent Hash Table + * + * Simple structures that might be needed in include + * files. + */ + +#ifndef _LINUX_RHASHTABLE_TYPES_H +#define _LINUX_RHASHTABLE_TYPES_H + +#include +#include +#include +#include + +struct rhash_head { + struct rhash_head __rcu *next; +}; + +struct rhlist_head { + struct rhash_head rhead; + struct rhlist_head __rcu *next; +}; + +struct bucket_table; + +/** + * struct rhashtable_compare_arg - Key for the function rhashtable_compare + * @ht: Hash table + * @key: Key to compare against + */ +struct rhashtable_compare_arg { + struct rhashtable *ht; + const void *key; +}; + +typedef u32 (*rht_hashfn_t)(const void *data, u32 len, u32 seed); +typedef u32 (*rht_obj_hashfn_t)(const void *data, u32 len, u32 seed); +typedef int (*rht_obj_cmpfn_t)(struct rhashtable_compare_arg *arg, + const void *obj); + +/** + * struct rhashtable_params - Hash table construction parameters + * @nelem_hint: Hint on number of elements, should be 75% of desired size + * @key_len: Length of key + * @key_offset: Offset of key in struct to be hashed + * @head_offset: Offset of rhash_head in struct to be hashed + * @max_size: Maximum size while expanding + * @min_size: Minimum size while shrinking + * @locks_mul: Number of bucket locks to allocate per cpu (default: 32) + * @automatic_shrinking: Enable automatic shrinking of tables + * @nulls_base: Base value to generate nulls marker + * @hashfn: Hash function (default: jhash2 if !(key_len % 4), or jhash) + * @obj_hashfn: Function to hash object + * @obj_cmpfn: Function to compare key with object + */ +struct rhashtable_params { + u16 nelem_hint; + u16 key_len; + u16 key_offset; + u16 head_offset; + unsigned int max_size; + u16 min_size; + bool automatic_shrinking; + u8 locks_mul; + u32 nulls_base; + rht_hashfn_t hashfn; + rht_obj_hashfn_t obj_hashfn; + rht_obj_cmpfn_t obj_cmpfn; +}; + +/** + * struct rhashtable - Hash table handle + * @tbl: Bucket table + * @key_len: Key length for hashfn + * @max_elems: Maximum number of elements in table + * @p: Configuration parameters + * @rhlist: True if this is an rhltable + * @run_work: Deferred worker to expand/shrink asynchronously + * @mutex: Mutex to protect current/future table swapping + * @lock: Spin lock to protect walker list + * @nelems: Number of elements in table + */ +struct rhashtable { + struct bucket_table __rcu *tbl; + unsigned int key_len; + unsigned int max_elems; + struct rhashtable_params p; + bool rhlist; + struct work_struct run_work; + struct mutex mutex; + spinlock_t lock; + atomic_t nelems; +}; + +/** + * struct rhltable - Hash table with duplicate objects in a list + * @ht: Underlying rhtable + */ +struct rhltable { + struct rhashtable ht; +}; + +/** + * struct rhashtable_walker - Hash table walker + * @list: List entry on list of walkers + * @tbl: The table that we were walking over + */ +struct rhashtable_walker { + struct list_head list; + struct bucket_table *tbl; +}; + +/** + * struct rhashtable_iter - Hash table iterator + * @ht: Table to iterate through + * @p: Current pointer + * @list: Current hash list pointer + * @walker: Associated rhashtable walker + * @slot: Current slot + * @skip: Number of entries to skip in slot + */ +struct rhashtable_iter { + struct rhashtable *ht; + struct rhash_head *p; + struct rhlist_head *list; + struct rhashtable_walker walker; + unsigned int slot; + unsigned int skip; + bool end_of_table; +}; + +int rhashtable_init(struct rhashtable *ht, + const struct rhashtable_params *params); +int rhltable_init(struct rhltable *hlt, + const struct rhashtable_params *params); + +#endif /* _LINUX_RHASHTABLE_TYPES_H */ diff --git a/include/linux/rhashtable.h b/include/linux/rhashtable.h index 4e1f535c2034..48754ab07cdf 100644 --- a/include/linux/rhashtable.h +++ b/include/linux/rhashtable.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Resizable, Scalable, Concurrent Hash Table * @@ -17,16 +18,14 @@ #ifndef _LINUX_RHASHTABLE_H #define _LINUX_RHASHTABLE_H -#include -#include #include #include #include #include #include -#include #include +#include /* * The end of the chain is marked with a special nulls marks which has * the following format: @@ -64,15 +63,6 @@ */ #define RHT_ELASTICITY 16u -struct rhash_head { - struct rhash_head __rcu *next; -}; - -struct rhlist_head { - struct rhash_head rhead; - struct rhlist_head __rcu *next; -}; - /** * struct bucket_table - Table of hash buckets * @size: Number of hash buckets @@ -102,114 +92,6 @@ struct bucket_table { struct rhash_head __rcu *buckets[] ____cacheline_aligned_in_smp; }; -/** - * struct rhashtable_compare_arg - Key for the function rhashtable_compare - * @ht: Hash table - * @key: Key to compare against - */ -struct rhashtable_compare_arg { - struct rhashtable *ht; - const void *key; -}; - -typedef u32 (*rht_hashfn_t)(const void *data, u32 len, u32 seed); -typedef u32 (*rht_obj_hashfn_t)(const void *data, u32 len, u32 seed); -typedef int (*rht_obj_cmpfn_t)(struct rhashtable_compare_arg *arg, - const void *obj); - -struct rhashtable; - -/** - * struct rhashtable_params - Hash table construction parameters - * @nelem_hint: Hint on number of elements, should be 75% of desired size - * @key_len: Length of key - * @key_offset: Offset of key in struct to be hashed - * @head_offset: Offset of rhash_head in struct to be hashed - * @max_size: Maximum size while expanding - * @min_size: Minimum size while shrinking - * @locks_mul: Number of bucket locks to allocate per cpu (default: 32) - * @automatic_shrinking: Enable automatic shrinking of tables - * @nulls_base: Base value to generate nulls marker - * @hashfn: Hash function (default: jhash2 if !(key_len % 4), or jhash) - * @obj_hashfn: Function to hash object - * @obj_cmpfn: Function to compare key with object - */ -struct rhashtable_params { - u16 nelem_hint; - u16 key_len; - u16 key_offset; - u16 head_offset; - unsigned int max_size; - u16 min_size; - bool automatic_shrinking; - u8 locks_mul; - u32 nulls_base; - rht_hashfn_t hashfn; - rht_obj_hashfn_t obj_hashfn; - rht_obj_cmpfn_t obj_cmpfn; -}; - -/** - * struct rhashtable - Hash table handle - * @tbl: Bucket table - * @key_len: Key length for hashfn - * @max_elems: Maximum number of elements in table - * @p: Configuration parameters - * @rhlist: True if this is an rhltable - * @run_work: Deferred worker to expand/shrink asynchronously - * @mutex: Mutex to protect current/future table swapping - * @lock: Spin lock to protect walker list - * @nelems: Number of elements in table - */ -struct rhashtable { - struct bucket_table __rcu *tbl; - unsigned int key_len; - unsigned int max_elems; - struct rhashtable_params p; - bool rhlist; - struct work_struct run_work; - struct mutex mutex; - spinlock_t lock; - atomic_t nelems; -}; - -/** - * struct rhltable - Hash table with duplicate objects in a list - * @ht: Underlying rhtable - */ -struct rhltable { - struct rhashtable ht; -}; - -/** - * struct rhashtable_walker - Hash table walker - * @list: List entry on list of walkers - * @tbl: The table that we were walking over - */ -struct rhashtable_walker { - struct list_head list; - struct bucket_table *tbl; -}; - -/** - * struct rhashtable_iter - Hash table iterator - * @ht: Table to iterate through - * @p: Current pointer - * @list: Current hash list pointer - * @walker: Associated rhashtable walker - * @slot: Current slot - * @skip: Number of entries to skip in slot - */ -struct rhashtable_iter { - struct rhashtable *ht; - struct rhash_head *p; - struct rhlist_head *list; - struct rhashtable_walker walker; - unsigned int slot; - unsigned int skip; - bool end_of_table; -}; - static inline unsigned long rht_marker(const struct rhashtable *ht, u32 hash) { return NULLS_MARKER(ht->p.nulls_base + hash); @@ -376,11 +258,6 @@ static inline int lockdep_rht_bucket_is_held(const struct bucket_table *tbl, } #endif /* CONFIG_PROVE_LOCKING */ -int rhashtable_init(struct rhashtable *ht, - const struct rhashtable_params *params); -int rhltable_init(struct rhltable *hlt, - const struct rhashtable_params *params); - void *rhashtable_insert_slow(struct rhashtable *ht, const void *key, struct rhash_head *obj); diff --git a/include/net/inet_frag.h b/include/net/inet_frag.h index ed07e3786d98..f4272a29dc44 100644 --- a/include/net/inet_frag.h +++ b/include/net/inet_frag.h @@ -2,7 +2,7 @@ #ifndef __NET_FRAG_H__ #define __NET_FRAG_H__ -#include +#include struct netns_frags { /* sysctls */ diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index ba9fa4592f2b..0e355f4a3d76 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h index dbe1b911a24d..e0f962d27386 100644 --- a/include/net/sctp/structs.h +++ b/include/net/sctp/structs.h @@ -48,7 +48,7 @@ #define __sctp_structs_h__ #include -#include +#include #include /* linux/in.h needs this!! */ #include /* We get struct sockaddr_in. */ #include /* We get struct in6_addr */ diff --git a/include/net/seg6.h b/include/net/seg6.h index e029e301faa5..2567941a2f32 100644 --- a/include/net/seg6.h +++ b/include/net/seg6.h @@ -18,7 +18,7 @@ #include #include #include -#include +#include static inline void update_csum_diff4(struct sk_buff *skb, __be32 from, __be32 to) diff --git a/include/net/seg6_hmac.h b/include/net/seg6_hmac.h index 69c3a106056b..7fda469e2758 100644 --- a/include/net/seg6_hmac.h +++ b/include/net/seg6_hmac.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include #define SEG6_HMAC_MAX_DIGESTSIZE 160 #define SEG6_HMAC_RING_SIZE 256 diff --git a/ipc/msg.c b/ipc/msg.c index 3b6545302598..203281198079 100644 --- a/ipc/msg.c +++ b/ipc/msg.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include diff --git a/ipc/sem.c b/ipc/sem.c index 5af1943ad782..29c0347ef11d 100644 --- a/ipc/sem.c +++ b/ipc/sem.c @@ -86,6 +86,7 @@ #include #include #include +#include #include #include "util.h" diff --git a/ipc/shm.c b/ipc/shm.c index 051a3e1fb8df..d4daf78df6da 100644 --- a/ipc/shm.c +++ b/ipc/shm.c @@ -43,6 +43,7 @@ #include #include #include +#include #include diff --git a/ipc/util.c b/ipc/util.c index 4e81182fa0ac..fdffff41f65b 100644 --- a/ipc/util.c +++ b/ipc/util.c @@ -63,6 +63,7 @@ #include #include #include +#include #include diff --git a/lib/rhashtable.c b/lib/rhashtable.c index 9427b5766134..c9fafea7dc6e 100644 --- a/lib/rhashtable.c +++ b/lib/rhashtable.c @@ -28,6 +28,7 @@ #include #include #include +#include #define HASH_DEFAULT_SIZE 64UL #define HASH_MIN_SIZE 4U diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index c9e35b81d093..316518f87294 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 9f79b9803a16..82f914122f1b 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include diff --git a/net/ipv4/ipmr_base.c b/net/ipv4/ipmr_base.c index cafb0506c8c9..1ad9aa62a97b 100644 --- a/net/ipv4/ipmr_base.c +++ b/net/ipv4/ipmr_base.c @@ -2,6 +2,7 @@ * Common logic shared by IPv4 [ipmr] and IPv6 [ip6mr] implementation */ +#include #include /* Sets everything common except 'dev', since that is done under locking */ diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 0d0f0053bb11..d0b7e0249c13 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c index 0fdf2a55e746..8d0ba757a46c 100644 --- a/net/ipv6/seg6.c +++ b/net/ipv6/seg6.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include diff --git a/net/ipv6/seg6_hmac.c b/net/ipv6/seg6_hmac.c index 33fb35cbfac1..b1791129a875 100644 --- a/net/ipv6/seg6_hmac.c +++ b/net/ipv6/seg6_hmac.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 896d4a36081d..3f211e1025c1 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/net/sctp/input.c b/net/sctp/input.c index ba8a6e6c36fa..9bbc5f92c941 100644 --- a/net/sctp/input.c +++ b/net/sctp/input.c @@ -56,6 +56,7 @@ #include #include #include +#include /* Forward declarations for internal helpers. */ static int sctp_rcv_ootb(struct sk_buff *); diff --git a/net/sctp/socket.c b/net/sctp/socket.c index d20f7addee19..0e91e83eea5a 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -66,6 +66,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3-59-g8ed1b From 3ece641656ad6a392ee3a122700923972da53fe8 Mon Sep 17 00:00:00 2001 From: Federico Vaga Date: Wed, 6 Jun 2018 00:49:02 +0200 Subject: doc: add Italian language skeleton Signed-off-by: Federico Vaga Signed-off-by: Jonathan Corbet --- Documentation/translations/index.rst | 1 + .../translations/it_IT/disclaimer-ita.rst | 13 +++ Documentation/translations/it_IT/index.rst | 112 +++++++++++++++++++++ MAINTAINERS | 6 ++ 4 files changed, 132 insertions(+) create mode 100644 Documentation/translations/it_IT/disclaimer-ita.rst create mode 100644 Documentation/translations/it_IT/index.rst (limited to 'MAINTAINERS') diff --git a/Documentation/translations/index.rst b/Documentation/translations/index.rst index 4969c58920ef..7f77c52d33aa 100644 --- a/Documentation/translations/index.rst +++ b/Documentation/translations/index.rst @@ -8,5 +8,6 @@ Translations :maxdepth: 1 zh_CN/index + it_IT/index ko_KR/index ja_JP/index diff --git a/Documentation/translations/it_IT/disclaimer-ita.rst b/Documentation/translations/it_IT/disclaimer-ita.rst new file mode 100644 index 000000000000..d68e52de6a5d --- /dev/null +++ b/Documentation/translations/it_IT/disclaimer-ita.rst @@ -0,0 +1,13 @@ +:orphan: + +.. note:: + This document is maintained by Federico Vaga . + If you find any difference between this document and the original file or a + problem with the translation, please contact the maintainer of this file. + Following people helped to translate or review: + Alessia Mantegazza + +.. warning:: + The purpose of this file is to be easier to read and understand for Italian + speakers and is not intended as a fork. So, if you have any comments or + updates for this file please try to update the original English file first. diff --git a/Documentation/translations/it_IT/index.rst b/Documentation/translations/it_IT/index.rst new file mode 100644 index 000000000000..7613a9f9e6f9 --- /dev/null +++ b/Documentation/translations/it_IT/index.rst @@ -0,0 +1,112 @@ +.. _it_linux_doc: + +=================== +Traduzione italiana +=================== + +L'obiettivo di questa traduzione è di rendere più facile la lettura e +la comprensione per chi preferisce leggere in lingua italiana. +Tenete presente che la documentazione di riferimento rimane comunque +quella in lingua inglese: :ref:`linux_doc` + +Questa traduzione cerca di essere il più fedele possibile all'originale ma +è ovvio che alcune frasi vadano trasformate: non aspettatevi una traduzione +letterale. Quando possibile, si eviteranno gli inglesismi ed al loro posto +verranno utilizzate le corrispettive parole italiane. + +Se notate che la traduzione non è più aggiornata potete contattare +direttamente il manutentore della traduzione italiana. + +Se notate che la documentazione contiene errori o dimenticanze, allora +verificate la documentazione di riferimento in lingua inglese. Se il problema +è presente anche nella documentazione di riferimento, contattate il suo +manutentore. Se avete problemi a scrivere in inglese, potete comunque +riportare il problema al manutentore della traduzione italiana. + +Manutentore della traduzione italiana: Federico Vaga + +La documentazione del kernel Linux +================================== + +Questo è il livello principale della documentazione del kernel in +lingua italiana. La traduzione è incompleta, noterete degli avvisi +che vi segnaleranno la mancanza di una traduzione o di un gruppo di +traduzioni. + +Più in generale, la documentazione, come il kernel stesso, sono in +costante sviluppo; particolarmente vero in quanto stiamo lavorando +alla riorganizzazione della documentazione in modo più coerente. +I miglioramenti alla documentazione sono sempre i benvenuti; per cui, +se vuoi aiutare, iscriviti alla lista di discussione linux-doc presso +vger.kernel.org. + +Documentazione sulla licenza dei sorgenti +----------------------------------------- + +I seguenti documenti descrivono la licenza usata nei sorgenti del kernel Linux +(GPLv2), come licenziare i singoli file; inoltre troverete i riferimenti al +testo integrale della licenza. + +.. warning:: + + TODO ancora da tradurre + +Documentazione per gli utenti +----------------------------- + +I seguenti manuali sono scritti per gli *utenti* del kernel - ovvero, +coloro che cercano di farlo funzionare in modo ottimale su un dato sistema + +.. warning:: + + TODO ancora da tradurre + +Documentazione per gli sviluppatori di applicazioni +--------------------------------------------------- + +Il manuale delle API verso lo spazio utente è una collezione di documenti +che descrivono le interfacce del kernel viste dagli sviluppatori +di applicazioni. + +.. warning:: + + TODO ancora da tradurre + + +Introduzione allo sviluppo del kernel +------------------------------------- + +Questi manuali contengono informazioni su come contribuire allo sviluppo +del kernel. +Attorno al kernel Linux gira una comunità molto grande con migliaia di +sviluppatori che contribuiscono ogni anno. Come in ogni grande comunità, +sapere come le cose vengono fatte renderà il processo di integrazione delle +vostre modifiche molto più semplice + +.. warning:: + + TODO ancora da tradurre + +Documentazione della API del kernel +----------------------------------- + +Questi manuali forniscono dettagli su come funzionano i sottosistemi del +kernel dal punto di vista degli sviluppatori del kernel. Molte delle +informazioni contenute in questi manuali sono prese direttamente dai +file sorgenti, informazioni aggiuntive vengono aggiunte solo se necessarie +(o almeno ci proviamo — probabilmente *non* tutto quello che è davvero +necessario). + +.. warning:: + + TODO ancora da tradurre + +Documentazione specifica per architettura +----------------------------------------- + +Questi manuali forniscono dettagli di programmazione per le diverse +implementazioni d'architettura. + +.. warning:: + + TODO ancora da tradurre diff --git a/MAINTAINERS b/MAINTAINERS index 9d5eeff51b5f..71aaf3d790a2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4401,6 +4401,12 @@ X: Documentation/spi X: Documentation/media T: git git://git.lwn.net/linux.git docs-next +DOCUMENTATION/ITALIAN +M: Federico Vaga +L: linux-doc@vger.kernel.org +S: Maintained +F: Documentation/translations/it_IT + DONGWOON DW9714 LENS VOICE COIL DRIVER M: Sakari Ailus L: linux-media@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From 8cba1ae675f2b4662a62e8c83a4a172360bdf3ea Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 28 May 2018 12:37:08 -0400 Subject: media: i2c: rj54n1: Remove soc_camera dependencies Remove soc_camera framework dependencies from rj54n1 sensor driver. - Handle clock - Handle GPIOs (named 'powerup' and 'enable') - Register the async subdevice - Remove g/s_mbus_config as they're deprecated. - Adjust build system - List the driver as maintained for 'Odd Fixes' as I don't have HW to test. This commits does not remove the original soc_camera based driver. Compiled tested only. Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 8 +++ drivers/media/i2c/Kconfig | 11 +++ drivers/media/i2c/Makefile | 1 + drivers/media/i2c/rj54n1cb0c.c | 153 +++++++++++++++++++++++------------------ 4 files changed, 107 insertions(+), 66 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 6cfd16790add..5f4b8d14e75d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12845,6 +12845,14 @@ W: http://www.ibm.com/developerworks/linux/linux390/ S: Supported F: net/smc/ +SHARP RJ54N1CB0C SENSOR DRIVER +M: Jacopo Mondi +L: linux-media@vger.kernel.org +T: git git://linuxtv.org/media_tree.git +S: Odd fixes +F: drivers/media/i2c/rj54n1cb0c.c +F: include/media/i2c/rj54n1cb0c.h + SH_VEU V4L2 MEM2MEM DRIVER L: linux-media@vger.kernel.org S: Orphan diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 341452fe98df..1d5efef77e77 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -857,6 +857,17 @@ config VIDEO_NOON010PC30 source "drivers/media/i2c/m5mols/Kconfig" +config VIDEO_RJ54N1 + tristate "Sharp RJ54N1CB0C sensor support" + depends on I2C && VIDEO_V4L2 + depends on MEDIA_CAMERA_SUPPORT + help + This is a V4L2 sensor-level driver for Sharp RJ54N1CB0C CMOS image + sensor. + + To compile this driver as a module, choose M here: the + module will be called rj54n1. + config VIDEO_S5K6AA tristate "Samsung S5K6AAFX sensor support" depends on MEDIA_CAMERA_SUPPORT diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index d679d57cd3b3..33cf2a415657 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -86,6 +86,7 @@ obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o +obj-$(CONFIG_VIDEO_RJ54N1) += rj54n1cb0c.o obj-$(CONFIG_VIDEO_S5K6AA) += s5k6aa.o obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o obj-$(CONFIG_VIDEO_S5K4ECGX) += s5k4ecgx.o diff --git a/drivers/media/i2c/rj54n1cb0c.c b/drivers/media/i2c/rj54n1cb0c.c index 02398d0bc649..6ad998ad1b16 100644 --- a/drivers/media/i2c/rj54n1cb0c.c +++ b/drivers/media/i2c/rj54n1cb0c.c @@ -1,25 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Driver for RJ54N1CB0C CMOS Image Sensor from Sharp * - * Copyright (C) 2009, Guennadi Liakhovetski + * Copyright (C) 2018, Jacopo Mondi * - * 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, Guennadi Liakhovetski */ +#include #include +#include #include +#include #include #include #include -#include #include -#include -#include -#include +#include #include +#include #define RJ54N1_DEV_CODE 0x0400 #define RJ54N1_DEV_CODE2 0x0401 @@ -151,7 +151,9 @@ struct rj54n1_clock_div { struct rj54n1 { struct v4l2_subdev subdev; struct v4l2_ctrl_handler hdl; - struct v4l2_clk *clk; + struct clk *clk; + struct gpio_desc *pwup_gpio; + struct gpio_desc *enable_gpio; struct rj54n1_clock_div clk_div; const struct rj54n1_datafmt *fmt; struct v4l2_rect rect; /* Sensor window */ @@ -545,8 +547,7 @@ static int rj54n1_set_selection(struct v4l2_subdev *sd, struct i2c_client *client = v4l2_get_subdevdata(sd); struct rj54n1 *rj54n1 = to_rj54n1(client); const struct v4l2_rect *rect = &sel->r; - int dummy = 0, output_w, output_h, - input_w = rect->width, input_h = rect->height; + int output_w, output_h, input_w = rect->width, input_h = rect->height; int ret; if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || @@ -554,11 +555,8 @@ static int rj54n1_set_selection(struct v4l2_subdev *sd, return -EINVAL; /* arbitrary minimum width and height, edges unimportant */ - soc_camera_limit_side(&dummy, &input_w, - RJ54N1_COLUMN_SKIP, 8, RJ54N1_MAX_WIDTH); - - soc_camera_limit_side(&dummy, &input_h, - RJ54N1_ROW_SKIP, 8, RJ54N1_MAX_HEIGHT); + v4l_bound_align_image(&input_w, 8, RJ54N1_MAX_WIDTH, 0, + &input_h, 8, RJ54N1_MAX_HEIGHT, 0, 0); output_w = (input_w * 1024 + rj54n1->resize / 2) / rj54n1->resize; output_h = (input_h * 1024 + rj54n1->resize / 2) / rj54n1->resize; @@ -618,6 +616,9 @@ static int rj54n1_get_fmt(struct v4l2_subdev *sd, mf->code = rj54n1->fmt->code; mf->colorspace = rj54n1->fmt->colorspace; + mf->ycbcr_enc = V4L2_YCBCR_ENC_601; + mf->xfer_func = V4L2_XFER_FUNC_SRGB; + mf->quantization = V4L2_QUANTIZATION_DEFAULT; mf->field = V4L2_FIELD_NONE; mf->width = rj54n1->width; mf->height = rj54n1->height; @@ -1163,10 +1164,27 @@ static int rj54n1_s_register(struct v4l2_subdev *sd, static int rj54n1_s_power(struct v4l2_subdev *sd, int on) { struct i2c_client *client = v4l2_get_subdevdata(sd); - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client); struct rj54n1 *rj54n1 = to_rj54n1(client); - return soc_camera_set_power(&client->dev, ssdd, rj54n1->clk, on); + if (on) { + if (rj54n1->pwup_gpio) + gpiod_set_value(rj54n1->pwup_gpio, 1); + if (rj54n1->enable_gpio) + gpiod_set_value(rj54n1->enable_gpio, 1); + + msleep(1); + + return clk_prepare_enable(rj54n1->clk); + } + + clk_disable_unprepare(rj54n1->clk); + + if (rj54n1->enable_gpio) + gpiod_set_value(rj54n1->enable_gpio, 0); + if (rj54n1->pwup_gpio) + gpiod_set_value(rj54n1->pwup_gpio, 0); + + return 0; } static int rj54n1_s_ctrl(struct v4l2_ctrl *ctrl) @@ -1221,40 +1239,8 @@ static const struct v4l2_subdev_core_ops rj54n1_subdev_core_ops = { .s_power = rj54n1_s_power, }; -static int rj54n1_g_mbus_config(struct v4l2_subdev *sd, - struct v4l2_mbus_config *cfg) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client); - - cfg->flags = - V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING | - V4L2_MBUS_MASTER | V4L2_MBUS_DATA_ACTIVE_HIGH | - V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_HIGH; - cfg->type = V4L2_MBUS_PARALLEL; - cfg->flags = soc_camera_apply_board_flags(ssdd, cfg); - - return 0; -} - -static int rj54n1_s_mbus_config(struct v4l2_subdev *sd, - const struct v4l2_mbus_config *cfg) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client); - - /* Figures 2.5-1 to 2.5-3 - default falling pixclk edge */ - if (soc_camera_apply_board_flags(ssdd, cfg) & - V4L2_MBUS_PCLK_SAMPLE_RISING) - return reg_write(client, RJ54N1_OUT_SIGPO, 1 << 4); - else - return reg_write(client, RJ54N1_OUT_SIGPO, 0); -} - static const struct v4l2_subdev_video_ops rj54n1_subdev_video_ops = { .s_stream = rj54n1_s_stream, - .g_mbus_config = rj54n1_g_mbus_config, - .s_mbus_config = rj54n1_s_mbus_config, }; static const struct v4l2_subdev_pad_ops rj54n1_subdev_pad_ops = { @@ -1316,17 +1302,16 @@ static int rj54n1_probe(struct i2c_client *client, const struct i2c_device_id *did) { struct rj54n1 *rj54n1; - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client); - struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct i2c_adapter *adapter = client->adapter; struct rj54n1_pdata *rj54n1_priv; int ret; - if (!ssdd || !ssdd->drv_priv) { + if (!client->dev.platform_data) { dev_err(&client->dev, "RJ54N1CB0C: missing platform data!\n"); return -EINVAL; } - rj54n1_priv = ssdd->drv_priv; + rj54n1_priv = client->dev.platform_data; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { dev_warn(&adapter->dev, @@ -1364,32 +1349,68 @@ static int rj54n1_probe(struct i2c_client *client, rj54n1->tgclk_mhz = (rj54n1_priv->mclk_freq / PLL_L * PLL_N) / (clk_div.ratio_tg + 1) / (clk_div.ratio_t + 1); - rj54n1->clk = v4l2_clk_get(&client->dev, "mclk"); + rj54n1->clk = clk_get(&client->dev, NULL); if (IS_ERR(rj54n1->clk)) { ret = PTR_ERR(rj54n1->clk); - goto eclkget; + goto err_free_ctrl; } - ret = rj54n1_video_probe(client, rj54n1_priv); - if (ret < 0) { - v4l2_clk_put(rj54n1->clk); -eclkget: - v4l2_ctrl_handler_free(&rj54n1->hdl); + rj54n1->pwup_gpio = gpiod_get_optional(&client->dev, "powerup", + GPIOD_OUT_LOW); + if (IS_ERR(rj54n1->pwup_gpio)) { + dev_info(&client->dev, "Unable to get GPIO \"powerup\": %ld\n", + PTR_ERR(rj54n1->pwup_gpio)); + ret = PTR_ERR(rj54n1->pwup_gpio); + goto err_clk_put; + } + + rj54n1->enable_gpio = gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(rj54n1->enable_gpio)) { + dev_info(&client->dev, "Unable to get GPIO \"enable\": %ld\n", + PTR_ERR(rj54n1->enable_gpio)); + ret = PTR_ERR(rj54n1->enable_gpio); + goto err_gpio_put; } + ret = rj54n1_video_probe(client, rj54n1_priv); + if (ret < 0) + goto err_gpio_put; + + ret = v4l2_async_register_subdev(&rj54n1->subdev); + if (ret) + goto err_gpio_put; + + return 0; + +err_gpio_put: + if (rj54n1->enable_gpio) + gpiod_put(rj54n1->enable_gpio); + + if (rj54n1->pwup_gpio) + gpiod_put(rj54n1->pwup_gpio); + +err_clk_put: + clk_put(rj54n1->clk); + +err_free_ctrl: + v4l2_ctrl_handler_free(&rj54n1->hdl); + return ret; } static int rj54n1_remove(struct i2c_client *client) { struct rj54n1 *rj54n1 = to_rj54n1(client); - struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client); - v4l2_clk_put(rj54n1->clk); - v4l2_device_unregister_subdev(&rj54n1->subdev); - if (ssdd->free_bus) - ssdd->free_bus(ssdd); + if (rj54n1->enable_gpio) + gpiod_put(rj54n1->enable_gpio); + if (rj54n1->pwup_gpio) + gpiod_put(rj54n1->pwup_gpio); + + clk_put(rj54n1->clk); v4l2_ctrl_handler_free(&rj54n1->hdl); + v4l2_async_unregister_subdev(&rj54n1->subdev); return 0; } -- cgit v1.2.3-59-g8ed1b From 6882e01bf8b9e176f9a1f7f7431e269363c19b84 Mon Sep 17 00:00:00 2001 From: Yong Zhi Date: Wed, 30 May 2018 18:51:17 -0400 Subject: media: MAINTAINERS: Update entry for Intel IPU3 cio2 driver This patch adds Bingbu as additional maintainer, and both Tian Shu and Jian Xu as reviewers for IPU3 CIO2 driver. Signed-off-by: Yong Zhi Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 3 +++ 1 file changed, 3 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 5f4b8d14e75d..ed6f068e7ce1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7240,6 +7240,9 @@ F: drivers/dma/iop-adma.c INTEL IPU3 CSI-2 CIO2 DRIVER M: Yong Zhi M: Sakari Ailus +M: Bingbu Cao +R: Tian Shu Qiu +R: Jian Xu Zheng L: linux-media@vger.kernel.org S: Maintained F: drivers/media/pci/intel/ipu3/ -- cgit v1.2.3-59-g8ed1b From 5b0a205466578432ce8faadd08f8d7ef895a180f Mon Sep 17 00:00:00 2001 From: Alan Chiang Date: Tue, 24 Apr 2018 22:12:08 -0400 Subject: media: dw9807: Add dw9807 vcm driver DW9807 is a 10 bit DAC from Dongwoon, designed for linear control of voice coil motor. This driver creates a V4L2 subdevice and provides control to set the desired focus. Signed-off-by: Alan Chiang Signed-off-by: Andy Yeh Reviewed-by: Jacopo Mondi Reviewed-by: Tomasz Figa Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 7 + drivers/media/i2c/Kconfig | 10 ++ drivers/media/i2c/Makefile | 1 + drivers/media/i2c/dw9807.c | 329 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 347 insertions(+) create mode 100644 drivers/media/i2c/dw9807.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ed6f068e7ce1..bd147aee7f80 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4403,6 +4403,13 @@ T: git git://linuxtv.org/media_tree.git S: Maintained F: drivers/media/i2c/dw9714.c +DONGWOON DW9807 LENS VOICE COIL DRIVER +M: Sakari Ailus +L: linux-media@vger.kernel.org +T: git git://linuxtv.org/media_tree.git +S: Maintained +F: drivers/media/i2c/dw9807.c + DOUBLETALK DRIVER M: "James R. Van Zandt" L: blinux-list@redhat.com diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 1d5efef77e77..1cf91bce2613 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -336,6 +336,16 @@ config VIDEO_DW9714 capability. This is designed for linear control of voice coil motors, controlled via I2C serial interface. +config VIDEO_DW9807 + tristate "DW9807 lens voice coil support" + depends on I2C && VIDEO_V4L2 && MEDIA_CONTROLLER + depends on VIDEO_V4L2_SUBDEV_API + ---help--- + This is a driver for the DW9807 camera lens voice coil. + DW9807 is a 10 bit DAC with 100mA output current sink + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + config VIDEO_SAA7110 tristate "Philips SAA7110 video decoder" depends on VIDEO_V4L2 && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 33cf2a415657..1d994658a9d4 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o obj-$(CONFIG_VIDEO_AD5820) += ad5820.o obj-$(CONFIG_VIDEO_DW9714) += dw9714.o +obj-$(CONFIG_VIDEO_DW9807) += dw9807.o obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o diff --git a/drivers/media/i2c/dw9807.c b/drivers/media/i2c/dw9807.c new file mode 100644 index 000000000000..8ba3920b6e2f --- /dev/null +++ b/drivers/media/i2c/dw9807.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DW9807_MAX_FOCUS_POS 1023 +/* + * This sets the minimum granularity for the focus positions. + * A value of 1 gives maximum accuracy for a desired focus position. + */ +#define DW9807_FOCUS_STEPS 1 +/* + * This acts as the minimum granularity of lens movement. + * Keep this value power of 2, so the control steps can be + * uniformly adjusted for gradual lens movement, with desired + * number of control steps. + */ +#define DW9807_CTRL_STEPS 16 +#define DW9807_CTRL_DELAY_US 1000 + +#define DW9807_CTL_ADDR 0x02 +/* + * DW9807 separates two registers to control the VCM position. + * One for MSB value, another is LSB value. + */ +#define DW9807_MSB_ADDR 0x03 +#define DW9807_LSB_ADDR 0x04 +#define DW9807_STATUS_ADDR 0x05 +#define DW9807_MODE_ADDR 0x06 +#define DW9807_RESONANCE_ADDR 0x07 + +#define MAX_RETRY 10 + +struct dw9807_device { + struct v4l2_ctrl_handler ctrls_vcm; + struct v4l2_subdev sd; + u16 current_val; +}; + +static inline struct dw9807_device *sd_to_dw9807_vcm( + struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct dw9807_device, sd); +} + +static int dw9807_i2c_check(struct i2c_client *client) +{ + const char status_addr = DW9807_STATUS_ADDR; + char status_result; + int ret; + + ret = i2c_master_send(client, &status_addr, sizeof(status_addr)); + if (ret < 0) { + dev_err(&client->dev, "I2C write STATUS address fail ret = %d\n", + ret); + return ret; + } + + ret = i2c_master_recv(client, &status_result, sizeof(status_result)); + if (ret < 0) { + dev_err(&client->dev, "I2C read STATUS value fail ret = %d\n", + ret); + return ret; + } + + return status_result; +} + +static int dw9807_set_dac(struct i2c_client *client, u16 data) +{ + const char tx_data[3] = { + DW9807_MSB_ADDR, ((data >> 8) & 0x03), (data & 0xff) + }; + int val, ret; + + /* + * According to the datasheet, need to check the bus status before we + * write VCM position. This ensure that we really write the value + * into the register + */ + ret = readx_poll_timeout(dw9807_i2c_check, client, val, val <= 0, + DW9807_CTRL_DELAY_US, MAX_RETRY * DW9807_CTRL_DELAY_US); + + if (ret || val < 0) { + if (ret) { + dev_warn(&client->dev, + "Cannot do the write operation because VCM is busy\n"); + } + + return ret ? -EBUSY : val; + } + + /* Write VCM position to registers */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, + "I2C write MSB fail ret=%d\n", ret); + + return ret; + } + + return 0; +} + +static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9807_device *dev_vcm = container_of(ctrl->handler, + struct dw9807_device, ctrls_vcm); + + if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) { + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + + dev_vcm->current_val = ctrl->val; + return dw9807_set_dac(client, ctrl->val); + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops dw9807_vcm_ctrl_ops = { + .s_ctrl = dw9807_set_ctrl, +}; + +static int dw9807_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + int rval; + + rval = pm_runtime_get_sync(sd->dev); + if (rval < 0) { + pm_runtime_put_noidle(sd->dev); + return rval; + } + + return 0; +} + +static int dw9807_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9807_int_ops = { + .open = dw9807_open, + .close = dw9807_close, +}; + +static const struct v4l2_subdev_ops dw9807_ops = { }; + +static void dw9807_subdev_cleanup(struct dw9807_device *dw9807_dev) +{ + v4l2_async_unregister_subdev(&dw9807_dev->sd); + v4l2_ctrl_handler_free(&dw9807_dev->ctrls_vcm); + media_entity_cleanup(&dw9807_dev->sd.entity); +} + +static int dw9807_init_controls(struct dw9807_device *dev_vcm) +{ + struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm; + const struct v4l2_ctrl_ops *ops = &dw9807_vcm_ctrl_ops; + struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); + + v4l2_ctrl_handler_init(hdl, 1); + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, + 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0); + + dev_vcm->sd.ctrl_handler = hdl; + if (hdl->error) { + dev_err(&client->dev, "%s fail error: 0x%x\n", + __func__, hdl->error); + return hdl->error; + } + + return 0; +} + +static int dw9807_probe(struct i2c_client *client) +{ + struct dw9807_device *dw9807_dev; + int rval; + + dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev), + GFP_KERNEL); + if (dw9807_dev == NULL) + return -ENOMEM; + + v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops); + dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9807_dev->sd.internal_ops = &dw9807_int_ops; + + rval = dw9807_init_controls(dw9807_dev); + if (rval) + goto err_cleanup; + + rval = media_entity_pads_init(&dw9807_dev->sd.entity, 0, NULL); + if (rval < 0) + goto err_cleanup; + + dw9807_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + rval = v4l2_async_register_subdev(&dw9807_dev->sd); + if (rval < 0) + goto err_cleanup; + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + dw9807_subdev_cleanup(dw9807_dev); + + return rval; +} + +static int dw9807_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + dw9807_subdev_cleanup(dw9807_dev); + + return 0; +} + +/* + * This function sets the vcm position, so it consumes least current + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9807_vcm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; + int ret, val; + + for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1); + val >= 0; val -= DW9807_CTRL_STEPS) { + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_once(dev, "%s I2C failure: %d", __func__, ret); + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + /* Power down */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + return 0; +} + +/* + * This function sets the vcm position to the value set by the user + * through v4l2_ctrl_ops s_ctrl handler + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. + */ +static int __maybe_unused dw9807_vcm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; + int ret, val; + + /* Power on */ + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret < 0) { + dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret); + return ret; + } + + for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS; + val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1; + val += DW9807_CTRL_STEPS) { + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_ratelimited(dev, "%s I2C failure: %d", + __func__, ret); + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + return 0; +} + +static const struct of_device_id dw9807_of_table[] = { + { .compatible = "dongwoon,dw9807-vcm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dw9807_of_table); + +static const struct dev_pm_ops dw9807_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume) + SET_RUNTIME_PM_OPS(dw9807_vcm_suspend, dw9807_vcm_resume, NULL) +}; + +static struct i2c_driver dw9807_i2c_driver = { + .driver = { + .name = "dw9807", + .pm = &dw9807_pm_ops, + .of_match_table = dw9807_of_table, + }, + .probe_new = dw9807_probe, + .remove = dw9807_remove, +}; + +module_i2c_driver(dw9807_i2c_driver); + +MODULE_AUTHOR("Chiang, Alan "); +MODULE_DESCRIPTION("DW9807 VCM driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 5aeba3e89b3ee4e541dc882d23c7332893dd4b5a Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Sat, 30 Jun 2018 02:48:12 +0200 Subject: selftests: mlxsw: Add devlink_lib_spectrum.sh This library builds on top of devlink_lib.sh and contains functionality specific to Spectrum ASICs, e.g., re-partitioning the various KVD sub-parts. Signed-off-by: Yuval Mintz [petrm@mellanox.com: Split this out from another patch. Fix line length in devlink_sp_read_kvd_defaults().] Signed-off-by: Petr Machata Signed-off-by: David S. Miller --- MAINTAINERS | 1 + .../net/mlxsw/spectrum/devlink_lib_spectrum.sh | 119 +++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tools/testing/selftests/drivers/net/mlxsw/spectrum/devlink_lib_spectrum.sh (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3b2e592aa521..7ed37c057ebd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9154,6 +9154,7 @@ S: Supported W: http://www.mellanox.com Q: http://patchwork.ozlabs.org/project/netdev/list/ F: drivers/net/ethernet/mellanox/mlxsw/ +F: tools/testing/selftests/drivers/net/mlxsw/ MELLANOX FIRMWARE FLASH LIBRARY (mlxfw) M: mlxsw@mellanox.com diff --git a/tools/testing/selftests/drivers/net/mlxsw/spectrum/devlink_lib_spectrum.sh b/tools/testing/selftests/drivers/net/mlxsw/spectrum/devlink_lib_spectrum.sh new file mode 100644 index 000000000000..73035e25085d --- /dev/null +++ b/tools/testing/selftests/drivers/net/mlxsw/spectrum/devlink_lib_spectrum.sh @@ -0,0 +1,119 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +source "../../../../net/forwarding/devlink_lib.sh" + +if [ "$DEVLINK_VIDDID" != "15b3:cb84" ]; then + echo "SKIP: test is tailored for Mellanox Spectrum" + exit 1 +fi + +# Needed for returning to default +declare -A KVD_DEFAULTS + +KVD_CHILDREN="linear hash_single hash_double" +KVDL_CHILDREN="singles chunks large_chunks" + +devlink_sp_resource_minimize() +{ + local size + local i + + for i in $KVD_CHILDREN; do + size=$(devlink_resource_get kvd "$i" | jq '.["size_min"]') + devlink_resource_size_set "$size" kvd "$i" + done + + for i in $KVDL_CHILDREN; do + size=$(devlink_resource_get kvd linear "$i" | \ + jq '.["size_min"]') + devlink_resource_size_set "$size" kvd linear "$i" + done +} + +devlink_sp_size_kvd_to_default() +{ + local need_reload=0 + local i + + for i in $KVD_CHILDREN; do + local size=$(echo "${KVD_DEFAULTS[kvd_$i]}" | jq '.["size"]') + current_size=$(devlink_resource_size_get kvd "$i") + + if [ "$size" -ne "$current_size" ]; then + devlink_resource_size_set "$size" kvd "$i" + need_reload=1 + fi + done + + for i in $KVDL_CHILDREN; do + local size=$(echo "${KVD_DEFAULTS[kvd_linear_$i]}" | \ + jq '.["size"]') + current_size=$(devlink_resource_size_get kvd linear "$i") + + if [ "$size" -ne "$current_size" ]; then + devlink_resource_size_set "$size" kvd linear "$i" + need_reload=1 + fi + done + + if [ "$need_reload" -ne "0" ]; then + devlink_reload + fi +} + +devlink_sp_read_kvd_defaults() +{ + local key + local i + + KVD_DEFAULTS[kvd]=$(devlink_resource_get "kvd") + for i in $KVD_CHILDREN; do + key=kvd_$i + KVD_DEFAULTS[$key]=$(devlink_resource_get kvd "$i") + done + + for i in $KVDL_CHILDREN; do + key=kvd_linear_$i + KVD_DEFAULTS[$key]=$(devlink_resource_get kvd linear "$i") + done +} + +KVD_PROFILES="default scale ipv4_max" + +devlink_sp_resource_kvd_profile_set() +{ + local profile=$1 + + case "$profile" in + scale) + devlink_resource_size_set 64000 kvd linear + devlink_resource_size_set 15616 kvd linear singles + devlink_resource_size_set 32000 kvd linear chunks + devlink_resource_size_set 16384 kvd linear large_chunks + devlink_resource_size_set 128000 kvd hash_single + devlink_resource_size_set 48000 kvd hash_double + devlink_reload + ;; + ipv4_max) + devlink_resource_size_set 64000 kvd linear + devlink_resource_size_set 15616 kvd linear singles + devlink_resource_size_set 32000 kvd linear chunks + devlink_resource_size_set 16384 kvd linear large_chunks + devlink_resource_size_set 144000 kvd hash_single + devlink_resource_size_set 32768 kvd hash_double + devlink_reload + ;; + default) + devlink_resource_size_set 98304 kvd linear + devlink_resource_size_set 16384 kvd linear singles + devlink_resource_size_set 49152 kvd linear chunks + devlink_resource_size_set 32768 kvd linear large_chunks + devlink_resource_size_set 87040 kvd hash_single + devlink_resource_size_set 60416 kvd hash_double + devlink_reload + ;; + *) + check_err 1 "Unknown profile $profile" + esac +} -- cgit v1.2.3-59-g8ed1b From d7d9f8ec77fe90472a649d1c2adba43a2e306eeb Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 24 Jun 2018 23:27:25 +0200 Subject: mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver Add support for the NAND flash controller found on NVIDIA Tegra 2 SoCs. This implementation does not make use of the command queue feature. Regular operations using ->exec_op() use PIO mode for data transfers. Raw, ECC and OOB read/writes make use of the DMA mode for data transfer. Signed-off-by: Lucas Stach Signed-off-by: Stefan Agner Reviewed-by: Dmitry Osipenko Reviewed-by: Boris Brezillon Signed-off-by: Miquel Raynal --- MAINTAINERS | 7 + drivers/mtd/nand/raw/Kconfig | 10 + drivers/mtd/nand/raw/Makefile | 1 + drivers/mtd/nand/raw/tegra_nand.c | 1230 +++++++++++++++++++++++++++++++++++++ 4 files changed, 1248 insertions(+) create mode 100644 drivers/mtd/nand/raw/tegra_nand.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 07d1576fc766..63700cefbf56 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14054,6 +14054,13 @@ M: Laxman Dewangan S: Supported F: drivers/input/keyboard/tegra-kbc.c +TEGRA NAND DRIVER +M: Stefan Agner +M: Lucas Stach +S: Maintained +F: Documentation/devicetree/bindings/mtd/nvidia-tegra20-nand.txt +F: drivers/mtd/nand/raw/tegra_nand.c + TEGRA PWM DRIVER M: Thierry Reding S: Supported diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 6871ff0fd300..6074a946708a 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -530,4 +530,14 @@ config MTD_NAND_MTK Enables support for NAND controller on MTK SoCs. This controller is found on mt27xx, mt81xx, mt65xx SoCs. +config MTD_NAND_TEGRA + tristate "Support for NAND controller on NVIDIA Tegra" + depends on ARCH_TEGRA || COMPILE_TEST + help + Enables support for NAND flash controller on NVIDIA Tegra SoC. + The driver has been developed and tested on a Tegra 2 SoC. DMA + support, raw read/write page as well as HW ECC read/write page + is supported. Extra OOB bytes when using HW ECC are currently + not supported. + endif # MTD_NAND diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index 165b7ef9e9a1..d5a5f9832b88 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/ obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o +obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o nand-objs += nand_amd.o diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c new file mode 100644 index 000000000000..9f7de36be893 --- /dev/null +++ b/drivers/mtd/nand/raw/tegra_nand.c @@ -0,0 +1,1230 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Stefan Agner + * Copyright (C) 2014-2015 Lucas Stach + * Copyright (C) 2012 Avionic Design GmbH + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define COMMAND 0x00 +#define COMMAND_GO BIT(31) +#define COMMAND_CLE BIT(30) +#define COMMAND_ALE BIT(29) +#define COMMAND_PIO BIT(28) +#define COMMAND_TX BIT(27) +#define COMMAND_RX BIT(26) +#define COMMAND_SEC_CMD BIT(25) +#define COMMAND_AFT_DAT BIT(24) +#define COMMAND_TRANS_SIZE(size) ((((size) - 1) & 0xf) << 20) +#define COMMAND_A_VALID BIT(19) +#define COMMAND_B_VALID BIT(18) +#define COMMAND_RD_STATUS_CHK BIT(17) +#define COMMAND_RBSY_CHK BIT(16) +#define COMMAND_CE(x) BIT(8 + ((x) & 0x7)) +#define COMMAND_CLE_SIZE(size) ((((size) - 1) & 0x3) << 4) +#define COMMAND_ALE_SIZE(size) ((((size) - 1) & 0xf) << 0) + +#define STATUS 0x04 + +#define ISR 0x08 +#define ISR_CORRFAIL_ERR BIT(24) +#define ISR_UND BIT(7) +#define ISR_OVR BIT(6) +#define ISR_CMD_DONE BIT(5) +#define ISR_ECC_ERR BIT(4) + +#define IER 0x0c +#define IER_ERR_TRIG_VAL(x) (((x) & 0xf) << 16) +#define IER_UND BIT(7) +#define IER_OVR BIT(6) +#define IER_CMD_DONE BIT(5) +#define IER_ECC_ERR BIT(4) +#define IER_GIE BIT(0) + +#define CONFIG 0x10 +#define CONFIG_HW_ECC BIT(31) +#define CONFIG_ECC_SEL BIT(30) +#define CONFIG_ERR_COR BIT(29) +#define CONFIG_PIPE_EN BIT(28) +#define CONFIG_TVAL_4 (0 << 24) +#define CONFIG_TVAL_6 (1 << 24) +#define CONFIG_TVAL_8 (2 << 24) +#define CONFIG_SKIP_SPARE BIT(23) +#define CONFIG_BUS_WIDTH_16 BIT(21) +#define CONFIG_COM_BSY BIT(20) +#define CONFIG_PS_256 (0 << 16) +#define CONFIG_PS_512 (1 << 16) +#define CONFIG_PS_1024 (2 << 16) +#define CONFIG_PS_2048 (3 << 16) +#define CONFIG_PS_4096 (4 << 16) +#define CONFIG_SKIP_SPARE_SIZE_4 (0 << 14) +#define CONFIG_SKIP_SPARE_SIZE_8 (1 << 14) +#define CONFIG_SKIP_SPARE_SIZE_12 (2 << 14) +#define CONFIG_SKIP_SPARE_SIZE_16 (3 << 14) +#define CONFIG_TAG_BYTE_SIZE(x) ((x) & 0xff) + +#define TIMING_1 0x14 +#define TIMING_TRP_RESP(x) (((x) & 0xf) << 28) +#define TIMING_TWB(x) (((x) & 0xf) << 24) +#define TIMING_TCR_TAR_TRR(x) (((x) & 0xf) << 20) +#define TIMING_TWHR(x) (((x) & 0xf) << 16) +#define TIMING_TCS(x) (((x) & 0x3) << 14) +#define TIMING_TWH(x) (((x) & 0x3) << 12) +#define TIMING_TWP(x) (((x) & 0xf) << 8) +#define TIMING_TRH(x) (((x) & 0x3) << 4) +#define TIMING_TRP(x) (((x) & 0xf) << 0) + +#define RESP 0x18 + +#define TIMING_2 0x1c +#define TIMING_TADL(x) ((x) & 0xf) + +#define CMD_REG1 0x20 +#define CMD_REG2 0x24 +#define ADDR_REG1 0x28 +#define ADDR_REG2 0x2c + +#define DMA_MST_CTRL 0x30 +#define DMA_MST_CTRL_GO BIT(31) +#define DMA_MST_CTRL_IN (0 << 30) +#define DMA_MST_CTRL_OUT BIT(30) +#define DMA_MST_CTRL_PERF_EN BIT(29) +#define DMA_MST_CTRL_IE_DONE BIT(28) +#define DMA_MST_CTRL_REUSE BIT(27) +#define DMA_MST_CTRL_BURST_1 (2 << 24) +#define DMA_MST_CTRL_BURST_4 (3 << 24) +#define DMA_MST_CTRL_BURST_8 (4 << 24) +#define DMA_MST_CTRL_BURST_16 (5 << 24) +#define DMA_MST_CTRL_IS_DONE BIT(20) +#define DMA_MST_CTRL_EN_A BIT(2) +#define DMA_MST_CTRL_EN_B BIT(1) + +#define DMA_CFG_A 0x34 +#define DMA_CFG_B 0x38 + +#define FIFO_CTRL 0x3c +#define FIFO_CTRL_CLR_ALL BIT(3) + +#define DATA_PTR 0x40 +#define TAG_PTR 0x44 +#define ECC_PTR 0x48 + +#define DEC_STATUS 0x4c +#define DEC_STATUS_A_ECC_FAIL BIT(1) +#define DEC_STATUS_ERR_COUNT_MASK 0x00ff0000 +#define DEC_STATUS_ERR_COUNT_SHIFT 16 + +#define HWSTATUS_CMD 0x50 +#define HWSTATUS_MASK 0x54 +#define HWSTATUS_RDSTATUS_MASK(x) (((x) & 0xff) << 24) +#define HWSTATUS_RDSTATUS_VALUE(x) (((x) & 0xff) << 16) +#define HWSTATUS_RBSY_MASK(x) (((x) & 0xff) << 8) +#define HWSTATUS_RBSY_VALUE(x) (((x) & 0xff) << 0) + +#define BCH_CONFIG 0xcc +#define BCH_ENABLE BIT(0) +#define BCH_TVAL_4 (0 << 4) +#define BCH_TVAL_8 (1 << 4) +#define BCH_TVAL_14 (2 << 4) +#define BCH_TVAL_16 (3 << 4) + +#define DEC_STAT_RESULT 0xd0 +#define DEC_STAT_BUF 0xd4 +#define DEC_STAT_BUF_FAIL_SEC_FLAG_MASK 0xff000000 +#define DEC_STAT_BUF_FAIL_SEC_FLAG_SHIFT 24 +#define DEC_STAT_BUF_CORR_SEC_FLAG_MASK 0x00ff0000 +#define DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT 16 +#define DEC_STAT_BUF_MAX_CORR_CNT_MASK 0x00001f00 +#define DEC_STAT_BUF_MAX_CORR_CNT_SHIFT 8 + +#define OFFSET(val, off) ((val) < (off) ? 0 : (val) - (off)) + +#define SKIP_SPARE_BYTES 4 +#define BITS_PER_STEP_RS 18 +#define BITS_PER_STEP_BCH 13 + +#define INT_MASK (IER_UND | IER_OVR | IER_CMD_DONE | IER_GIE) +#define HWSTATUS_CMD_DEFAULT NAND_STATUS_READY +#define HWSTATUS_MASK_DEFAULT (HWSTATUS_RDSTATUS_MASK(1) | \ + HWSTATUS_RDSTATUS_VALUE(0) | \ + HWSTATUS_RBSY_MASK(NAND_STATUS_READY) | \ + HWSTATUS_RBSY_VALUE(NAND_STATUS_READY)) + +struct tegra_nand_controller { + struct nand_hw_control controller; + struct device *dev; + void __iomem *regs; + int irq; + struct clk *clk; + struct completion command_complete; + struct completion dma_complete; + bool last_read_error; + int cur_cs; + struct nand_chip *chip; +}; + +struct tegra_nand_chip { + struct nand_chip chip; + struct gpio_desc *wp_gpio; + struct mtd_oob_region ecc; + u32 config; + u32 config_ecc; + u32 bch_config; + int cs[1]; +}; + +static inline struct tegra_nand_controller * + to_tegra_ctrl(struct nand_hw_control *hw_ctrl) +{ + return container_of(hw_ctrl, struct tegra_nand_controller, controller); +} + +static inline struct tegra_nand_chip *to_tegra_chip(struct nand_chip *chip) +{ + return container_of(chip, struct tegra_nand_chip, chip); +} + +static int tegra_nand_ooblayout_rs_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int bytes_per_step = DIV_ROUND_UP(BITS_PER_STEP_RS * chip->ecc.strength, + BITS_PER_BYTE); + + if (section > 0) + return -ERANGE; + + oobregion->offset = SKIP_SPARE_BYTES; + oobregion->length = round_up(bytes_per_step * chip->ecc.steps, 4); + + return 0; +} + +static int tegra_nand_ooblayout_no_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + return -ERANGE; +} + +static const struct mtd_ooblayout_ops tegra_nand_oob_rs_ops = { + .ecc = tegra_nand_ooblayout_rs_ecc, + .free = tegra_nand_ooblayout_no_free, +}; + +static int tegra_nand_ooblayout_bch_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int bytes_per_step = DIV_ROUND_UP(BITS_PER_STEP_BCH * chip->ecc.strength, + BITS_PER_BYTE); + + if (section > 0) + return -ERANGE; + + oobregion->offset = SKIP_SPARE_BYTES; + oobregion->length = round_up(bytes_per_step * chip->ecc.steps, 4); + + return 0; +} + +static const struct mtd_ooblayout_ops tegra_nand_oob_bch_ops = { + .ecc = tegra_nand_ooblayout_bch_ecc, + .free = tegra_nand_ooblayout_no_free, +}; + +static irqreturn_t tegra_nand_irq(int irq, void *data) +{ + struct tegra_nand_controller *ctrl = data; + u32 isr, dma; + + isr = readl_relaxed(ctrl->regs + ISR); + dma = readl_relaxed(ctrl->regs + DMA_MST_CTRL); + dev_dbg(ctrl->dev, "isr %08x\n", isr); + + if (!isr && !(dma & DMA_MST_CTRL_IS_DONE)) + return IRQ_NONE; + + /* + * The bit name is somewhat missleading: This is also set when + * HW ECC was successful. The data sheet states: + * Correctable OR Un-correctable errors occurred in the DMA transfer... + */ + if (isr & ISR_CORRFAIL_ERR) + ctrl->last_read_error = true; + + if (isr & ISR_CMD_DONE) + complete(&ctrl->command_complete); + + if (isr & ISR_UND) + dev_err(ctrl->dev, "FIFO underrun\n"); + + if (isr & ISR_OVR) + dev_err(ctrl->dev, "FIFO overrun\n"); + + /* handle DMA interrupts */ + if (dma & DMA_MST_CTRL_IS_DONE) { + writel_relaxed(dma, ctrl->regs + DMA_MST_CTRL); + complete(&ctrl->dma_complete); + } + + /* clear interrupts */ + writel_relaxed(isr, ctrl->regs + ISR); + + return IRQ_HANDLED; +} + +static const char * const tegra_nand_reg_names[] = { + "COMMAND", + "STATUS", + "ISR", + "IER", + "CONFIG", + "TIMING", + NULL, + "TIMING2", + "CMD_REG1", + "CMD_REG2", + "ADDR_REG1", + "ADDR_REG2", + "DMA_MST_CTRL", + "DMA_CFG_A", + "DMA_CFG_B", + "FIFO_CTRL", +}; + +static void tegra_nand_dump_reg(struct tegra_nand_controller *ctrl) +{ + u32 reg; + int i; + + dev_err(ctrl->dev, "Tegra NAND controller register dump\n"); + for (i = 0; i < ARRAY_SIZE(tegra_nand_reg_names); i++) { + const char *reg_name = tegra_nand_reg_names[i]; + + if (!reg_name) + continue; + + reg = readl_relaxed(ctrl->regs + (i * 4)); + dev_err(ctrl->dev, "%s: 0x%08x\n", reg_name, reg); + } +} + +static void tegra_nand_controller_abort(struct tegra_nand_controller *ctrl) +{ + u32 isr, dma; + + disable_irq(ctrl->irq); + + /* Abort current command/DMA operation */ + writel_relaxed(0, ctrl->regs + DMA_MST_CTRL); + writel_relaxed(0, ctrl->regs + COMMAND); + + /* clear interrupts */ + isr = readl_relaxed(ctrl->regs + ISR); + writel_relaxed(isr, ctrl->regs + ISR); + dma = readl_relaxed(ctrl->regs + DMA_MST_CTRL); + writel_relaxed(dma, ctrl->regs + DMA_MST_CTRL); + + reinit_completion(&ctrl->command_complete); + reinit_completion(&ctrl->dma_complete); + + enable_irq(ctrl->irq); +} + +static int tegra_nand_cmd(struct nand_chip *chip, + const struct nand_subop *subop) +{ + const struct nand_op_instr *instr; + const struct nand_op_instr *instr_data_in = NULL; + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + unsigned int op_id, size = 0, offset = 0; + bool first_cmd = true; + u32 reg, cmd = 0; + int ret; + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + unsigned int naddrs, i; + const u8 *addrs; + u32 addr1 = 0, addr2 = 0; + + instr = &subop->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (first_cmd) { + cmd |= COMMAND_CLE; + writel_relaxed(instr->ctx.cmd.opcode, + ctrl->regs + CMD_REG1); + } else { + cmd |= COMMAND_SEC_CMD; + writel_relaxed(instr->ctx.cmd.opcode, + ctrl->regs + CMD_REG2); + } + first_cmd = false; + break; + + case NAND_OP_ADDR_INSTR: + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + + cmd |= COMMAND_ALE | COMMAND_ALE_SIZE(naddrs); + for (i = 0; i < min_t(unsigned int, 4, naddrs); i++) + addr1 |= *addrs++ << (BITS_PER_BYTE * i); + naddrs -= i; + for (i = 0; i < min_t(unsigned int, 4, naddrs); i++) + addr2 |= *addrs++ << (BITS_PER_BYTE * i); + + writel_relaxed(addr1, ctrl->regs + ADDR_REG1); + writel_relaxed(addr2, ctrl->regs + ADDR_REG2); + break; + + case NAND_OP_DATA_IN_INSTR: + size = nand_subop_get_data_len(subop, op_id); + offset = nand_subop_get_data_start_off(subop, op_id); + + cmd |= COMMAND_TRANS_SIZE(size) | COMMAND_PIO | + COMMAND_RX | COMMAND_A_VALID; + + instr_data_in = instr; + break; + + case NAND_OP_DATA_OUT_INSTR: + size = nand_subop_get_data_len(subop, op_id); + offset = nand_subop_get_data_start_off(subop, op_id); + + cmd |= COMMAND_TRANS_SIZE(size) | COMMAND_PIO | + COMMAND_TX | COMMAND_A_VALID; + memcpy(®, instr->ctx.data.buf.out + offset, size); + + writel_relaxed(reg, ctrl->regs + RESP); + break; + + case NAND_OP_WAITRDY_INSTR: + cmd |= COMMAND_RBSY_CHK; + break; + } + } + + cmd |= COMMAND_GO | COMMAND_CE(ctrl->cur_cs); + writel_relaxed(cmd, ctrl->regs + COMMAND); + ret = wait_for_completion_timeout(&ctrl->command_complete, + msecs_to_jiffies(500)); + if (!ret) { + dev_err(ctrl->dev, "COMMAND timeout\n"); + tegra_nand_dump_reg(ctrl); + tegra_nand_controller_abort(ctrl); + return -ETIMEDOUT; + } + + if (instr_data_in) { + reg = readl_relaxed(ctrl->regs + RESP); + memcpy(instr_data_in->ctx.data.buf.in + offset, ®, size); + } + + return 0; +} + +static const struct nand_op_parser tegra_nand_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(tegra_nand_cmd, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + NAND_OP_PARSER_PATTERN(tegra_nand_cmd, + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 4)), + NAND_OP_PARSER_PATTERN(tegra_nand_cmd, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 4)), + ); + +static int tegra_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + return nand_op_parser_exec_op(chip, &tegra_nand_op_parser, op, + check_only); +} + +static void tegra_nand_select_chip(struct mtd_info *mtd, int die_nr) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct tegra_nand_chip *nand = to_tegra_chip(chip); + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + + if (die_nr < 0 || die_nr > 1) { + ctrl->cur_cs = -1; + return; + } + + ctrl->cur_cs = nand->cs[die_nr]; +} + +static void tegra_nand_hw_ecc(struct tegra_nand_controller *ctrl, + struct nand_chip *chip, bool enable) +{ + struct tegra_nand_chip *nand = to_tegra_chip(chip); + + if (chip->ecc.algo == NAND_ECC_BCH && enable) + writel_relaxed(nand->bch_config, ctrl->regs + BCH_CONFIG); + else + writel_relaxed(0, ctrl->regs + BCH_CONFIG); + + if (enable) + writel_relaxed(nand->config_ecc, ctrl->regs + CONFIG); + else + writel_relaxed(nand->config, ctrl->regs + CONFIG); +} + +static int tegra_nand_page_xfer(struct mtd_info *mtd, struct nand_chip *chip, + void *buf, void *oob_buf, int oob_len, int page, + bool read) +{ + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + dma_addr_t dma_addr = 0, dma_addr_oob = 0; + u32 addr1, cmd, dma_ctrl; + int ret; + + if (read) { + writel_relaxed(NAND_CMD_READ0, ctrl->regs + CMD_REG1); + writel_relaxed(NAND_CMD_READSTART, ctrl->regs + CMD_REG2); + } else { + writel_relaxed(NAND_CMD_SEQIN, ctrl->regs + CMD_REG1); + writel_relaxed(NAND_CMD_PAGEPROG, ctrl->regs + CMD_REG2); + } + cmd = COMMAND_CLE | COMMAND_SEC_CMD; + + /* Lower 16-bits are column, by default 0 */ + addr1 = page << 16; + + if (!buf) + addr1 |= mtd->writesize; + writel_relaxed(addr1, ctrl->regs + ADDR_REG1); + + if (chip->options & NAND_ROW_ADDR_3) { + writel_relaxed(page >> 16, ctrl->regs + ADDR_REG2); + cmd |= COMMAND_ALE | COMMAND_ALE_SIZE(5); + } else { + cmd |= COMMAND_ALE | COMMAND_ALE_SIZE(4); + } + + if (buf) { + dma_addr = dma_map_single(ctrl->dev, buf, mtd->writesize, dir); + ret = dma_mapping_error(ctrl->dev, dma_addr); + if (ret) { + dev_err(ctrl->dev, "dma mapping error\n"); + return -EINVAL; + } + + writel_relaxed(mtd->writesize - 1, ctrl->regs + DMA_CFG_A); + writel_relaxed(dma_addr, ctrl->regs + DATA_PTR); + } + + if (oob_buf) { + dma_addr_oob = dma_map_single(ctrl->dev, oob_buf, mtd->oobsize, + dir); + ret = dma_mapping_error(ctrl->dev, dma_addr_oob); + if (ret) { + dev_err(ctrl->dev, "dma mapping error\n"); + ret = -EINVAL; + goto err_unmap_dma_page; + } + + writel_relaxed(oob_len - 1, ctrl->regs + DMA_CFG_B); + writel_relaxed(dma_addr_oob, ctrl->regs + TAG_PTR); + } + + dma_ctrl = DMA_MST_CTRL_GO | DMA_MST_CTRL_PERF_EN | + DMA_MST_CTRL_IE_DONE | DMA_MST_CTRL_IS_DONE | + DMA_MST_CTRL_BURST_16; + + if (buf) + dma_ctrl |= DMA_MST_CTRL_EN_A; + if (oob_buf) + dma_ctrl |= DMA_MST_CTRL_EN_B; + + if (read) + dma_ctrl |= DMA_MST_CTRL_IN | DMA_MST_CTRL_REUSE; + else + dma_ctrl |= DMA_MST_CTRL_OUT; + + writel_relaxed(dma_ctrl, ctrl->regs + DMA_MST_CTRL); + + cmd |= COMMAND_GO | COMMAND_RBSY_CHK | COMMAND_TRANS_SIZE(9) | + COMMAND_CE(ctrl->cur_cs); + + if (buf) + cmd |= COMMAND_A_VALID; + if (oob_buf) + cmd |= COMMAND_B_VALID; + + if (read) + cmd |= COMMAND_RX; + else + cmd |= COMMAND_TX | COMMAND_AFT_DAT; + + writel_relaxed(cmd, ctrl->regs + COMMAND); + + ret = wait_for_completion_timeout(&ctrl->command_complete, + msecs_to_jiffies(500)); + if (!ret) { + dev_err(ctrl->dev, "COMMAND timeout\n"); + tegra_nand_dump_reg(ctrl); + tegra_nand_controller_abort(ctrl); + ret = -ETIMEDOUT; + goto err_unmap_dma; + } + + ret = wait_for_completion_timeout(&ctrl->dma_complete, + msecs_to_jiffies(500)); + if (!ret) { + dev_err(ctrl->dev, "DMA timeout\n"); + tegra_nand_dump_reg(ctrl); + tegra_nand_controller_abort(ctrl); + ret = -ETIMEDOUT; + goto err_unmap_dma; + } + ret = 0; + +err_unmap_dma: + if (oob_buf) + dma_unmap_single(ctrl->dev, dma_addr_oob, mtd->oobsize, dir); +err_unmap_dma_page: + if (buf) + dma_unmap_single(ctrl->dev, dma_addr, mtd->writesize, dir); + + return ret; +} + +static int tegra_nand_read_page_raw(struct mtd_info *mtd, + struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + void *oob_buf = oob_required ? chip->oob_poi : NULL; + + return tegra_nand_page_xfer(mtd, chip, buf, oob_buf, + mtd->oobsize, page, true); +} + +static int tegra_nand_write_page_raw(struct mtd_info *mtd, + struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + void *oob_buf = oob_required ? chip->oob_poi : NULL; + + return tegra_nand_page_xfer(mtd, chip, (void *)buf, oob_buf, + mtd->oobsize, page, false); +} + +static int tegra_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + return tegra_nand_page_xfer(mtd, chip, NULL, chip->oob_poi, + mtd->oobsize, page, true); +} + +static int tegra_nand_write_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + return tegra_nand_page_xfer(mtd, chip, NULL, chip->oob_poi, + mtd->oobsize, page, false); +} + +static int tegra_nand_read_page_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + struct tegra_nand_chip *nand = to_tegra_chip(chip); + void *oob_buf = oob_required ? chip->oob_poi : NULL; + u32 dec_stat, max_corr_cnt; + unsigned long fail_sec_flag; + int ret; + + tegra_nand_hw_ecc(ctrl, chip, true); + ret = tegra_nand_page_xfer(mtd, chip, buf, oob_buf, 0, page, true); + tegra_nand_hw_ecc(ctrl, chip, false); + if (ret) + return ret; + + /* No correctable or un-correctable errors, page must have 0 bitflips */ + if (!ctrl->last_read_error) + return 0; + + /* + * Correctable or un-correctable errors occurred. Use DEC_STAT_BUF + * which contains information for all ECC selections. + * + * Note that since we do not use Command Queues DEC_RESULT does not + * state the number of pages we can read from the DEC_STAT_BUF. But + * since CORRFAIL_ERR did occur during page read we do have a valid + * result in DEC_STAT_BUF. + */ + ctrl->last_read_error = false; + dec_stat = readl_relaxed(ctrl->regs + DEC_STAT_BUF); + + fail_sec_flag = (dec_stat & DEC_STAT_BUF_FAIL_SEC_FLAG_MASK) >> + DEC_STAT_BUF_FAIL_SEC_FLAG_SHIFT; + + max_corr_cnt = (dec_stat & DEC_STAT_BUF_MAX_CORR_CNT_MASK) >> + DEC_STAT_BUF_MAX_CORR_CNT_SHIFT; + + if (fail_sec_flag) { + int bit, max_bitflips = 0; + + /* + * Since we do not support subpage writes, a complete page + * is either written or not. We can take a shortcut here by + * checking wheather any of the sector has been successful + * read. If at least one sectors has been read successfully, + * the page must have been a written previously. It cannot + * be an erased page. + * + * E.g. controller might return fail_sec_flag with 0x4, which + * would mean only the third sector failed to correct. The + * page must have been written and the third sector is really + * not correctable anymore. + */ + if (fail_sec_flag ^ GENMASK(chip->ecc.steps - 1, 0)) { + mtd->ecc_stats.failed += hweight8(fail_sec_flag); + return max_corr_cnt; + } + + /* + * All sectors failed to correct, but the ECC isn't smart + * enough to figure out if a page is really just erased. + * Read OOB data and check whether data/OOB is completely + * erased or if error correction just failed for all sub- + * pages. + */ + ret = tegra_nand_read_oob(mtd, chip, page); + if (ret < 0) + return ret; + + for_each_set_bit(bit, &fail_sec_flag, chip->ecc.steps) { + u8 *data = buf + (chip->ecc.size * bit); + u8 *oob = chip->oob_poi + nand->ecc.offset + + (chip->ecc.bytes * bit); + + ret = nand_check_erased_ecc_chunk(data, chip->ecc.size, + oob, chip->ecc.bytes, + NULL, 0, + chip->ecc.strength); + if (ret < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += ret; + max_bitflips = max(ret, max_bitflips); + } + } + + return max_t(unsigned int, max_corr_cnt, max_bitflips); + } else { + int corr_sec_flag; + + corr_sec_flag = (dec_stat & DEC_STAT_BUF_CORR_SEC_FLAG_MASK) >> + DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT; + + /* + * The value returned in the register is the maximum of + * bitflips encountered in any of the ECC regions. As there is + * no way to get the number of bitflips in a specific regions + * we are not able to deliver correct stats but instead + * overestimate the number of corrected bitflips by assuming + * that all regions where errors have been corrected + * encountered the maximum number of bitflips. + */ + mtd->ecc_stats.corrected += max_corr_cnt * hweight8(corr_sec_flag); + + return max_corr_cnt; + } +} + +static int tegra_nand_write_page_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + void *oob_buf = oob_required ? chip->oob_poi : NULL; + int ret; + + tegra_nand_hw_ecc(ctrl, chip, true); + ret = tegra_nand_page_xfer(mtd, chip, (void *)buf, oob_buf, + 0, page, false); + tegra_nand_hw_ecc(ctrl, chip, false); + + return ret; +} + +static void tegra_nand_setup_timing(struct tegra_nand_controller *ctrl, + const struct nand_sdr_timings *timings) +{ + /* + * The period (and all other timings in this function) is in ps, + * so need to take care here to avoid integer overflows. + */ + unsigned int rate = clk_get_rate(ctrl->clk) / 1000000; + unsigned int period = DIV_ROUND_UP(1000000, rate); + u32 val, reg = 0; + + val = DIV_ROUND_UP(max3(timings->tAR_min, timings->tRR_min, + timings->tRC_min), period); + reg |= TIMING_TCR_TAR_TRR(OFFSET(val, 3)); + + val = DIV_ROUND_UP(max(max(timings->tCS_min, timings->tCH_min), + max(timings->tALS_min, timings->tALH_min)), + period); + reg |= TIMING_TCS(OFFSET(val, 2)); + + val = DIV_ROUND_UP(max(timings->tRP_min, timings->tREA_max) + 6000, + period); + reg |= TIMING_TRP(OFFSET(val, 1)) | TIMING_TRP_RESP(OFFSET(val, 1)); + + reg |= TIMING_TWB(OFFSET(DIV_ROUND_UP(timings->tWB_max, period), 1)); + reg |= TIMING_TWHR(OFFSET(DIV_ROUND_UP(timings->tWHR_min, period), 1)); + reg |= TIMING_TWH(OFFSET(DIV_ROUND_UP(timings->tWH_min, period), 1)); + reg |= TIMING_TWP(OFFSET(DIV_ROUND_UP(timings->tWP_min, period), 1)); + reg |= TIMING_TRH(OFFSET(DIV_ROUND_UP(timings->tREH_min, period), 1)); + + writel_relaxed(reg, ctrl->regs + TIMING_1); + + val = DIV_ROUND_UP(timings->tADL_min, period); + reg = TIMING_TADL(OFFSET(val, 3)); + + writel_relaxed(reg, ctrl->regs + TIMING_2); +} + +static int tegra_nand_setup_data_interface(struct mtd_info *mtd, int csline, + const struct nand_data_interface *conf) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + const struct nand_sdr_timings *timings; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return PTR_ERR(timings); + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + tegra_nand_setup_timing(ctrl, timings); + + return 0; +} + +static const int rs_strength_bootable[] = { 4 }; +static const int rs_strength[] = { 4, 6, 8 }; +static const int bch_strength_bootable[] = { 8, 16 }; +static const int bch_strength[] = { 4, 8, 14, 16 }; + +static int tegra_nand_get_strength(struct nand_chip *chip, const int *strength, + int strength_len, int bits_per_step, + int oobsize) +{ + bool maximize = chip->ecc.options & NAND_ECC_MAXIMIZE; + int i; + + /* + * Loop through available strengths. Backwards in case we try to + * maximize the BCH strength. + */ + for (i = 0; i < strength_len; i++) { + int strength_sel, bytes_per_step, bytes_per_page; + + if (maximize) { + strength_sel = strength[strength_len - i - 1]; + } else { + strength_sel = strength[i]; + + if (strength_sel < chip->ecc_strength_ds) + continue; + } + + bytes_per_step = DIV_ROUND_UP(bits_per_step * strength_sel, + BITS_PER_BYTE); + bytes_per_page = round_up(bytes_per_step * chip->ecc.steps, 4); + + /* Check whether strength fits OOB */ + if (bytes_per_page < (oobsize - SKIP_SPARE_BYTES)) + return strength_sel; + } + + return -EINVAL; +} + +static int tegra_nand_select_strength(struct nand_chip *chip, int oobsize) +{ + const int *strength; + int strength_len, bits_per_step; + + switch (chip->ecc.algo) { + case NAND_ECC_RS: + bits_per_step = BITS_PER_STEP_RS; + if (chip->options & NAND_IS_BOOT_MEDIUM) { + strength = rs_strength_bootable; + strength_len = ARRAY_SIZE(rs_strength_bootable); + } else { + strength = rs_strength; + strength_len = ARRAY_SIZE(rs_strength); + } + break; + case NAND_ECC_BCH: + bits_per_step = BITS_PER_STEP_BCH; + if (chip->options & NAND_IS_BOOT_MEDIUM) { + strength = bch_strength_bootable; + strength_len = ARRAY_SIZE(bch_strength_bootable); + } else { + strength = bch_strength; + strength_len = ARRAY_SIZE(bch_strength); + } + break; + default: + return -EINVAL; + } + + return tegra_nand_get_strength(chip, strength, strength_len, + bits_per_step, oobsize); +} + +static int tegra_nand_chips_init(struct device *dev, + struct tegra_nand_controller *ctrl) +{ + struct device_node *np = dev->of_node; + struct device_node *np_nand; + int nsels, nchips = of_get_child_count(np); + struct tegra_nand_chip *nand; + struct mtd_info *mtd; + struct nand_chip *chip; + int bits_per_step; + int ret; + u32 cs; + + if (nchips != 1) { + dev_err(dev, "Currently only one NAND chip supported\n"); + return -EINVAL; + } + + np_nand = of_get_next_child(np, NULL); + + nsels = of_property_count_elems_of_size(np_nand, "reg", sizeof(u32)); + if (nsels != 1) { + dev_err(dev, "Missing/invalid reg property\n"); + return -EINVAL; + } + + /* Retrieve CS id, currently only single die NAND supported */ + ret = of_property_read_u32(np_nand, "reg", &cs); + if (ret) { + dev_err(dev, "could not retrieve reg property: %d\n", ret); + return ret; + } + + nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL); + if (!nand) + return -ENOMEM; + + nand->cs[0] = cs; + + nand->wp_gpio = devm_gpiod_get_optional(dev, "wp", GPIOD_OUT_LOW); + + if (IS_ERR(nand->wp_gpio)) { + ret = PTR_ERR(nand->wp_gpio); + dev_err(dev, "Failed to request WP GPIO: %d\n", ret); + return ret; + } + + chip = &nand->chip; + chip->controller = &ctrl->controller; + + mtd = nand_to_mtd(chip); + + mtd->dev.parent = dev; + mtd->owner = THIS_MODULE; + + nand_set_flash_node(chip, np_nand); + + if (!mtd->name) + mtd->name = "tegra_nand"; + + chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USE_BOUNCE_BUFFER; + chip->exec_op = tegra_nand_exec_op; + chip->select_chip = tegra_nand_select_chip; + chip->setup_data_interface = tegra_nand_setup_data_interface; + + ret = nand_scan_ident(mtd, 1, NULL); + if (ret) + return ret; + + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.size = 512; + chip->ecc.steps = mtd->writesize / chip->ecc.size; + if (chip->ecc_step_ds != 512) { + dev_err(dev, "Unsupported step size %d\n", chip->ecc_step_ds); + return -EINVAL; + } + + chip->ecc.read_page = tegra_nand_read_page_hwecc; + chip->ecc.write_page = tegra_nand_write_page_hwecc; + chip->ecc.read_page_raw = tegra_nand_read_page_raw; + chip->ecc.write_page_raw = tegra_nand_write_page_raw; + chip->ecc.read_oob = tegra_nand_read_oob; + chip->ecc.write_oob = tegra_nand_write_oob; + + if (chip->options & NAND_BUSWIDTH_16) + nand->config |= CONFIG_BUS_WIDTH_16; + + if (chip->ecc.algo == NAND_ECC_UNKNOWN) { + if (mtd->writesize < 2048) + chip->ecc.algo = NAND_ECC_RS; + else + chip->ecc.algo = NAND_ECC_BCH; + } + + if (chip->ecc.algo == NAND_ECC_BCH && mtd->writesize < 2048) { + dev_err(dev, "BCH supports 2K or 4K page size only\n"); + return -EINVAL; + } + + if (!chip->ecc.strength) { + ret = tegra_nand_select_strength(chip, mtd->oobsize); + if (ret < 0) { + dev_err(dev, "No valid strength found, minimum %d\n", + chip->ecc_strength_ds); + return ret; + } + + chip->ecc.strength = ret; + } + + nand->config_ecc = CONFIG_PIPE_EN | CONFIG_SKIP_SPARE | + CONFIG_SKIP_SPARE_SIZE_4; + + switch (chip->ecc.algo) { + case NAND_ECC_RS: + bits_per_step = BITS_PER_STEP_RS * chip->ecc.strength; + mtd_set_ooblayout(mtd, &tegra_nand_oob_rs_ops); + nand->config_ecc |= CONFIG_HW_ECC | CONFIG_ECC_SEL | + CONFIG_ERR_COR; + switch (chip->ecc.strength) { + case 4: + nand->config_ecc |= CONFIG_TVAL_4; + break; + case 6: + nand->config_ecc |= CONFIG_TVAL_6; + break; + case 8: + nand->config_ecc |= CONFIG_TVAL_8; + break; + default: + dev_err(dev, "ECC strength %d not supported\n", + chip->ecc.strength); + return -EINVAL; + } + break; + case NAND_ECC_BCH: + bits_per_step = BITS_PER_STEP_BCH * chip->ecc.strength; + mtd_set_ooblayout(mtd, &tegra_nand_oob_bch_ops); + nand->bch_config = BCH_ENABLE; + switch (chip->ecc.strength) { + case 4: + nand->bch_config |= BCH_TVAL_4; + break; + case 8: + nand->bch_config |= BCH_TVAL_8; + break; + case 14: + nand->bch_config |= BCH_TVAL_14; + break; + case 16: + nand->bch_config |= BCH_TVAL_16; + break; + default: + dev_err(dev, "ECC strength %d not supported\n", + chip->ecc.strength); + return -EINVAL; + } + break; + default: + dev_err(dev, "ECC algorithm not supported\n"); + return -EINVAL; + } + + dev_info(dev, "Using %s with strength %d per 512 byte step\n", + chip->ecc.algo == NAND_ECC_BCH ? "BCH" : "RS", + chip->ecc.strength); + + chip->ecc.bytes = DIV_ROUND_UP(bits_per_step, BITS_PER_BYTE); + + switch (mtd->writesize) { + case 256: + nand->config |= CONFIG_PS_256; + break; + case 512: + nand->config |= CONFIG_PS_512; + break; + case 1024: + nand->config |= CONFIG_PS_1024; + break; + case 2048: + nand->config |= CONFIG_PS_2048; + break; + case 4096: + nand->config |= CONFIG_PS_4096; + break; + default: + dev_err(dev, "Unsupported writesize %d\n", mtd->writesize); + return -ENODEV; + } + + /* Store complete configuration for HW ECC in config_ecc */ + nand->config_ecc |= nand->config; + + /* Non-HW ECC read/writes complete OOB */ + nand->config |= CONFIG_TAG_BYTE_SIZE(mtd->oobsize - 1); + writel_relaxed(nand->config, ctrl->regs + CONFIG); + + ret = nand_scan_tail(mtd); + if (ret) + return ret; + + mtd_ooblayout_ecc(mtd, 0, &nand->ecc); + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "Failed to register mtd device: %d\n", ret); + nand_cleanup(chip); + return ret; + } + + ctrl->chip = chip; + + return 0; +} + +static int tegra_nand_probe(struct platform_device *pdev) +{ + struct reset_control *rst; + struct tegra_nand_controller *ctrl; + struct resource *res; + int err = 0; + + ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->dev = &pdev->dev; + nand_hw_control_init(&ctrl->controller); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctrl->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ctrl->regs)) + return PTR_ERR(ctrl->regs); + + rst = devm_reset_control_get(&pdev->dev, "nand"); + if (IS_ERR(rst)) + return PTR_ERR(rst); + + ctrl->clk = devm_clk_get(&pdev->dev, "nand"); + if (IS_ERR(ctrl->clk)) + return PTR_ERR(ctrl->clk); + + err = clk_prepare_enable(ctrl->clk); + if (err) + return err; + + err = reset_control_reset(rst); + if (err) { + dev_err(ctrl->dev, "Failed to reset HW: %d\n", err); + goto err_disable_clk; + } + + writel_relaxed(HWSTATUS_CMD_DEFAULT, ctrl->regs + HWSTATUS_CMD); + writel_relaxed(HWSTATUS_MASK_DEFAULT, ctrl->regs + HWSTATUS_MASK); + writel_relaxed(INT_MASK, ctrl->regs + IER); + + init_completion(&ctrl->command_complete); + init_completion(&ctrl->dma_complete); + + ctrl->irq = platform_get_irq(pdev, 0); + err = devm_request_irq(&pdev->dev, ctrl->irq, tegra_nand_irq, 0, + dev_name(&pdev->dev), ctrl); + if (err) { + dev_err(ctrl->dev, "Failed to get IRQ: %d\n", err); + goto err_disable_clk; + } + + writel_relaxed(DMA_MST_CTRL_IS_DONE, ctrl->regs + DMA_MST_CTRL); + + err = tegra_nand_chips_init(ctrl->dev, ctrl); + if (err) + goto err_disable_clk; + + platform_set_drvdata(pdev, ctrl); + + return 0; + +err_disable_clk: + clk_disable_unprepare(ctrl->clk); + return err; +} + +static int tegra_nand_remove(struct platform_device *pdev) +{ + struct tegra_nand_controller *ctrl = platform_get_drvdata(pdev); + struct nand_chip *chip = ctrl->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = mtd_device_unregister(mtd); + if (ret) + return ret; + + nand_cleanup(chip); + + clk_disable_unprepare(ctrl->clk); + + return 0; +} + +static const struct of_device_id tegra_nand_of_match[] = { + { .compatible = "nvidia,tegra20-nand" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tegra_nand_of_match); + +static struct platform_driver tegra_nand_driver = { + .driver = { + .name = "tegra-nand", + .of_match_table = tegra_nand_of_match, + }, + .probe = tegra_nand_probe, + .remove = tegra_nand_remove, +}; +module_platform_driver(tegra_nand_driver); + +MODULE_DESCRIPTION("NVIDIA Tegra NAND driver"); +MODULE_AUTHOR("Thierry Reding "); +MODULE_AUTHOR("Lucas Stach "); +MODULE_AUTHOR("Stefan Agner "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 2f896e1c518c7678fb059e16dd92bbac1cb68c95 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 26 Jun 2018 06:49:07 -0300 Subject: MAINTAINERS: fix location of ina2xx.txt device tree file This file got moved and merged, causing the old reference to not exist anymore. Fix it. Fixes: 6e24d205a8aa ("hwmon: ina209: move binding docs to proper place") Fixes: 62bc9f15e443 ("dt-bindings: merge ina209 binding into ina2xx binding") Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 0aaaff932c46..bc0d050d99fb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7033,7 +7033,7 @@ M: Guenter Roeck L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/hwmon/ina209 -F: Documentation/devicetree/bindings/i2c/ina209.txt +F: Documentation/devicetree/bindings/hwmon/ina2xx.txt F: drivers/hwmon/ina209.c INA2XX HARDWARE MONITOR DRIVER -- cgit v1.2.3-59-g8ed1b From ad5145164b21eb4fcb091096b1b384a3faecac64 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Tue, 19 Jun 2018 02:01:46 -0400 Subject: media: dt-bindings: Add bindings for AKM ak7375 voice coil lens Add device tree bindings for AKM ak7375 voice coil lens driver. This chip is used to drive a lens in a camera module. Signed-off-by: Tianshu Qiu Signed-off-by: Bingbu Cao Reviewed-by: Rob Herring Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- Documentation/devicetree/bindings/media/i2c/ak7375.txt | 8 ++++++++ MAINTAINERS | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ak7375.txt (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/i2c/ak7375.txt b/Documentation/devicetree/bindings/media/i2c/ak7375.txt new file mode 100644 index 000000000000..aa3e24b41241 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ak7375.txt @@ -0,0 +1,8 @@ +Asahi Kasei Microdevices AK7375 voice coil lens driver + +AK7375 is a camera voice coil lens. + +Mandatory properties: + +- compatible: "asahi-kasei,ak7375" +- reg: I2C slave address diff --git a/MAINTAINERS b/MAINTAINERS index bd147aee7f80..bbd9b9b3d74f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2272,6 +2272,14 @@ L: linux-leds@vger.kernel.org S: Maintained F: drivers/leds/leds-as3645a.c +ASAHI KASEI AK7375 LENS VOICE COIL DRIVER +M: Tianshu Qiu +L: linux-media@vger.kernel.org +T: git git://linuxtv.org/media_tree.git +S: Maintained +F: drivers/media/i2c/ak7375.c +F: Documentation/devicetree/bindings/media/i2c/ak7375.txt + ASAHI KASEI AK8974 DRIVER M: Linus Walleij L: linux-iio@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From 26ec38872b5bc2105037f7a03fdd72afdf9fd241 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 29 Jun 2018 14:21:11 -0700 Subject: crypto: MAINTAINERS - fix file path for SHA multibuffer code "arch/x86/crypto/sha*-mb" needs a trailing slash, since it refers to directories. Otherwise get_maintainer.pl doesn't find the entry. Signed-off-by: Eric Biggers Signed-off-by: Herbert Xu --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9d5eeff51b5f..bfcd5a7a83c8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7350,7 +7350,7 @@ M: Megha Dey R: Tim Chen L: linux-crypto@vger.kernel.org S: Supported -F: arch/x86/crypto/sha*-mb +F: arch/x86/crypto/sha*-mb/ F: crypto/mcryptd.c INTEL TELEMETRY DRIVER -- cgit v1.2.3-59-g8ed1b From d7c7118caf99fc4fe3b2e1f47e3d23b2bbaf7929 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Thu, 5 Jul 2018 17:18:05 +0100 Subject: MAINTAINERS: arm64: Remove boot/dts/ directory from arm64 entry The arm-soc tree does a good job handling .dts files, so exclude them from the ARM64 entry in MAINTAINERS. Cc: Catalin Marinas Acked-by: Olof Johansson Signed-off-by: Will Deacon --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 07d1576fc766..61301440e66f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2264,6 +2264,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) T: git git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git S: Maintained F: arch/arm64/ +X: arch/arm64/boot/dts/ F: Documentation/arm64/ AS3645A LED FLASH CONTROLLER DRIVER -- cgit v1.2.3-59-g8ed1b From 54e45716a84ac9df9e5fef779ab54fc2f97ebdf3 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Fri, 6 Jul 2018 15:33:48 +0200 Subject: scsi: remove NCR_D700 driver The NCR DUAL 700 SCSI driver depends on microchannel support, but microchannel support was removed from the kernel with commit bb8187d35f82 ("MCA: delete all remaining traces of microchannel bus support.") over 6 years ago. So zap the D700 SCSI driver as well. Signed-off-by: Johannes Thumshirn Reviewed-by: Hannes Reinecke Acked-by: James E.J. Bottomley Signed-off-by: Martin K. Petersen --- MAINTAINERS | 6 - drivers/scsi/Kconfig | 12 -- drivers/scsi/Makefile | 1 - drivers/scsi/NCR_D700.c | 405 ------------------------------------------------ drivers/scsi/NCR_D700.h | 30 ---- 5 files changed, 454 deletions(-) delete mode 100644 drivers/scsi/NCR_D700.c delete mode 100644 drivers/scsi/NCR_D700.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9d5eeff51b5f..f3de5d37179a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9750,12 +9750,6 @@ F: drivers/scsi/mac_scsi.* F: drivers/scsi/sun3_scsi.* F: drivers/scsi/sun3_scsi_vme.c -NCR DUAL 700 SCSI DRIVER (MICROCHANNEL) -M: "James E.J. Bottomley" -L: linux-scsi@vger.kernel.org -S: Maintained -F: drivers/scsi/NCR_D700.* - NCT6775 HARDWARE MONITOR DRIVER M: Guenter Roeck L: linux-hwmon@vger.kernel.org diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index bd115bab162e..a7675302f9a7 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -842,18 +842,6 @@ config SCSI_IZIP_SLOW_CTR Generally, saying N is fine. -config SCSI_NCR_D700 - tristate "NCR Dual 700 MCA SCSI support" - depends on MCA && SCSI - select SCSI_SPI_ATTRS - help - This is a driver for the MicroChannel Dual 700 card produced by - NCR and commonly used in 345x/35xx/4100 class machines. It always - tries to negotiate sync and uses tag command queueing. - - Unless you have an NCR manufactured machine, the chances are that - you do not have this SCSI card, so say N. - config SCSI_LASI700 tristate "HP Lasi SCSI support for 53c700/710" depends on GSC && SCSI diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 80aca2456353..3729d0d1a5e7 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -76,7 +76,6 @@ obj-$(CONFIG_SCSI_PM8001) += pm8001/ obj-$(CONFIG_SCSI_ISCI) += isci/ obj-$(CONFIG_SCSI_IPS) += ips.o obj-$(CONFIG_SCSI_GENERIC_NCR5380) += g_NCR5380.o -obj-$(CONFIG_SCSI_NCR_D700) += 53c700.o NCR_D700.o obj-$(CONFIG_SCSI_NCR_Q720) += NCR_Q720_mod.o obj-$(CONFIG_SCSI_QLOGIC_FAS) += qlogicfas408.o qlogicfas.o obj-$(CONFIG_PCMCIA_QLOGIC) += qlogicfas408.o diff --git a/drivers/scsi/NCR_D700.c b/drivers/scsi/NCR_D700.c deleted file mode 100644 index b39a2409a507..000000000000 --- a/drivers/scsi/NCR_D700.c +++ /dev/null @@ -1,405 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8 -*- */ - -/* NCR Dual 700 MCA SCSI Driver - * - * Copyright (C) 2001 by James.Bottomley@HansenPartnership.com -**----------------------------------------------------------------------------- -** -** This program is free software; you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or -** (at your option) any later version. -** -** 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. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software -** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -** -**----------------------------------------------------------------------------- - */ - -/* Notes: - * - * Most of the work is done in the chip specific module, 53c700.o - * - * TODO List: - * - * 1. Extract the SCSI ID from the voyager CMOS table (necessary to - * support multi-host environments. - * - * */ - - -/* CHANGELOG - * - * Version 2.2 - * - * Added mca_set_adapter_name(). - * - * Version 2.1 - * - * Modularise the driver into a Board piece (this file) and a chip - * piece 53c700.[ch] and 53c700.scr, added module options. You can - * now specify the scsi id by the parameters - * - * NCR_D700=slot: [siop:] id: .... - * - * They need to be comma separated if compiled into the kernel - * - * Version 2.0 - * - * Initial implementation of TCQ (Tag Command Queueing). TCQ is full - * featured and uses the clock algorithm to keep track of outstanding - * tags and guard against individual tag starvation. Also fixed a bug - * in all of the 1.x versions where the D700_data_residue() function - * was returning results off by 32 bytes (and thus causing the same 32 - * bytes to be written twice corrupting the data block). It turns out - * the 53c700 only has a 6 bit DBC and DFIFO registers not 7 bit ones - * like the 53c710 (The 710 is the only data manual still available, - * which I'd been using to program the 700). - * - * Version 1.2 - * - * Much improved message handling engine - * - * Version 1.1 - * - * Add code to handle selection reasonably correctly. By the time we - * get the selection interrupt, we've already responded, but drop off the - * bus and hope the selector will go away. - * - * Version 1.0: - * - * Initial release. Fully functional except for procfs and tag - * command queueing. Has only been tested on cards with 53c700-66 - * chips and only single ended. Features are - * - * 1. Synchronous data transfers to offset 8 (limit of 700-66) and - * 100ns (10MHz) limit of SCSI-2 - * - * 2. Disconnection and reselection - * - * Testing: - * - * I've only really tested this with the 700-66 chip, but have done - * soak tests in multi-device environments to verify that - * disconnections and reselections are being processed correctly. - * */ - -#define NCR_D700_VERSION "2.2" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "53c700.h" -#include "NCR_D700.h" - -static char *NCR_D700; /* command line from insmod */ - -MODULE_AUTHOR("James Bottomley"); -MODULE_DESCRIPTION("NCR Dual700 SCSI Driver"); -MODULE_LICENSE("GPL"); -module_param(NCR_D700, charp, 0); - -static __u8 id_array[2*(MCA_MAX_SLOT_NR + 1)] = - { [0 ... 2*(MCA_MAX_SLOT_NR + 1)-1] = 7 }; - -#ifdef MODULE -#define ARG_SEP ' ' -#else -#define ARG_SEP ',' -#endif - -static int __init -param_setup(char *string) -{ - char *pos = string, *next; - int slot = -1, siop = -1; - - while(pos != NULL && (next = strchr(pos, ':')) != NULL) { - int val = (int)simple_strtoul(++next, NULL, 0); - - if(!strncmp(pos, "slot:", 5)) - slot = val; - else if(!strncmp(pos, "siop:", 5)) - siop = val; - else if(!strncmp(pos, "id:", 3)) { - if(slot == -1) { - printk(KERN_WARNING "NCR D700: Must specify slot for id parameter\n"); - } else if(slot > MCA_MAX_SLOT_NR) { - printk(KERN_WARNING "NCR D700: Illegal slot %d for id %d\n", slot, val); - } else { - if(siop != 0 && siop != 1) { - id_array[slot*2] = val; - id_array[slot*2 + 1] =val; - } else { - id_array[slot*2 + siop] = val; - } - } - } - if((pos = strchr(pos, ARG_SEP)) != NULL) - pos++; - } - return 1; -} - -/* Host template. The 53c700 routine NCR_700_detect will - * fill in all of the missing routines */ -static struct scsi_host_template NCR_D700_driver_template = { - .module = THIS_MODULE, - .name = "NCR Dual 700 MCA", - .proc_name = "NCR_D700", - .this_id = 7, -}; - -/* We needs this helper because we have two hosts per struct device */ -struct NCR_D700_private { - struct device *dev; - struct Scsi_Host *hosts[2]; - char name[30]; - char pad; -}; - -static int -NCR_D700_probe_one(struct NCR_D700_private *p, int siop, int irq, - int slot, u32 region, int differential) -{ - struct NCR_700_Host_Parameters *hostdata; - struct Scsi_Host *host; - int ret; - - hostdata = kzalloc(sizeof(*hostdata), GFP_KERNEL); - if (!hostdata) { - printk(KERN_ERR "NCR D700: SIOP%d: Failed to allocate host" - "data, detatching\n", siop); - return -ENOMEM; - } - - if (!request_region(region, 64, "NCR_D700")) { - printk(KERN_ERR "NCR D700: Failed to reserve IO region 0x%x\n", - region); - ret = -ENODEV; - goto region_failed; - } - - /* Fill in the three required pieces of hostdata */ - hostdata->base = ioport_map(region, 64); - hostdata->differential = (((1<clock = NCR_D700_CLOCK_MHZ; - hostdata->burst_length = 8; - - /* and register the siop */ - host = NCR_700_detect(&NCR_D700_driver_template, hostdata, p->dev); - if (!host) { - ret = -ENOMEM; - goto detect_failed; - } - - p->hosts[siop] = host; - /* FIXME: read this from SUS */ - host->this_id = id_array[slot * 2 + siop]; - host->irq = irq; - host->base = region; - scsi_scan_host(host); - - return 0; - - detect_failed: - release_region(region, 64); - region_failed: - kfree(hostdata); - - return ret; -} - -static irqreturn_t -NCR_D700_intr(int irq, void *data) -{ - struct NCR_D700_private *p = (struct NCR_D700_private *)data; - int i, found = 0; - - for (i = 0; i < 2; i++) - if (p->hosts[i] && - NCR_700_intr(irq, p->hosts[i]) == IRQ_HANDLED) - found++; - - return found ? IRQ_HANDLED : IRQ_NONE; -} - -/* Detect a D700 card. Note, because of the setup --- the chips are - * essentially connectecd to the MCA bus independently, it is easier - * to set them up as two separate host adapters, rather than one - * adapter with two channels */ -static int -NCR_D700_probe(struct device *dev) -{ - struct NCR_D700_private *p; - int differential; - static int banner = 1; - struct mca_device *mca_dev = to_mca_device(dev); - int slot = mca_dev->slot; - int found = 0; - int irq, i; - int pos3j, pos3k, pos3a, pos3b, pos4; - __u32 base_addr, offset_addr; - - /* enable board interrupt */ - pos4 = mca_device_read_pos(mca_dev, 4); - pos4 |= 0x4; - mca_device_write_pos(mca_dev, 4, pos4); - - mca_device_write_pos(mca_dev, 6, 9); - pos3j = mca_device_read_pos(mca_dev, 3); - mca_device_write_pos(mca_dev, 6, 10); - pos3k = mca_device_read_pos(mca_dev, 3); - mca_device_write_pos(mca_dev, 6, 0); - pos3a = mca_device_read_pos(mca_dev, 3); - mca_device_write_pos(mca_dev, 6, 1); - pos3b = mca_device_read_pos(mca_dev, 3); - - base_addr = ((pos3j << 8) | pos3k) & 0xfffffff0; - offset_addr = ((pos3a << 8) | pos3b) & 0xffffff70; - - irq = (pos4 & 0x3) + 11; - if(irq >= 13) - irq++; - if(banner) { - printk(KERN_NOTICE "NCR D700: Driver Version " NCR_D700_VERSION "\n" - "NCR D700: Copyright (c) 2001 by James.Bottomley@HansenPartnership.com\n" - "NCR D700:\n"); - banner = 0; - } - /* now do the bus related transforms */ - irq = mca_device_transform_irq(mca_dev, irq); - base_addr = mca_device_transform_ioport(mca_dev, base_addr); - offset_addr = mca_device_transform_ioport(mca_dev, offset_addr); - - printk(KERN_NOTICE "NCR D700: found in slot %d irq = %d I/O base = 0x%x\n", slot, irq, offset_addr); - - /*outb(BOARD_RESET, base_addr);*/ - - /* clear any pending interrupts */ - (void)inb(base_addr + 0x08); - /* get modctl, used later for setting diff bits */ - switch(differential = (inb(base_addr + 0x08) >> 6)) { - case 0x00: - /* only SIOP1 differential */ - differential = 0x02; - break; - case 0x01: - /* Both SIOPs differential */ - differential = 0x03; - break; - case 0x03: - /* No SIOPs differential */ - differential = 0x00; - break; - default: - printk(KERN_ERR "D700: UNEXPECTED DIFFERENTIAL RESULT 0x%02x\n", - differential); - differential = 0x00; - break; - } - - p = kzalloc(sizeof(*p), GFP_KERNEL); - if (!p) - return -ENOMEM; - - p->dev = dev; - snprintf(p->name, sizeof(p->name), "D700(%s)", dev_name(dev)); - if (request_irq(irq, NCR_D700_intr, IRQF_SHARED, p->name, p)) { - printk(KERN_ERR "D700: request_irq failed\n"); - kfree(p); - return -EBUSY; - } - /* plumb in both 700 chips */ - for (i = 0; i < 2; i++) { - int err; - - if ((err = NCR_D700_probe_one(p, i, irq, slot, - offset_addr + (0x80 * i), - differential)) != 0) - printk("D700: SIOP%d: probe failed, error = %d\n", - i, err); - else - found++; - } - - if (!found) { - kfree(p); - return -ENODEV; - } - - mca_device_set_claim(mca_dev, 1); - mca_device_set_name(mca_dev, "NCR_D700"); - dev_set_drvdata(dev, p); - return 0; -} - -static void -NCR_D700_remove_one(struct Scsi_Host *host) -{ - scsi_remove_host(host); - NCR_700_release(host); - kfree((struct NCR_700_Host_Parameters *)host->hostdata[0]); - free_irq(host->irq, host); - release_region(host->base, 64); -} - -static int -NCR_D700_remove(struct device *dev) -{ - struct NCR_D700_private *p = dev_get_drvdata(dev); - int i; - - for (i = 0; i < 2; i++) - NCR_D700_remove_one(p->hosts[i]); - - kfree(p); - return 0; -} - -static short NCR_D700_id_table[] = { NCR_D700_MCA_ID, 0 }; - -static struct mca_driver NCR_D700_driver = { - .id_table = NCR_D700_id_table, - .driver = { - .name = "NCR_D700", - .bus = &mca_bus_type, - .probe = NCR_D700_probe, - .remove = NCR_D700_remove, - }, -}; - -static int __init NCR_D700_init(void) -{ -#ifdef MODULE - if (NCR_D700) - param_setup(NCR_D700); -#endif - - return mca_register_driver(&NCR_D700_driver); -} - -static void __exit NCR_D700_exit(void) -{ - mca_unregister_driver(&NCR_D700_driver); -} - -module_init(NCR_D700_init); -module_exit(NCR_D700_exit); - -__setup("NCR_D700=", param_setup); diff --git a/drivers/scsi/NCR_D700.h b/drivers/scsi/NCR_D700.h deleted file mode 100644 index eb675d782ef6..000000000000 --- a/drivers/scsi/NCR_D700.h +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* -*- mode: c; c-basic-offset: 8 -*- */ - -/* NCR Dual 700 MCA SCSI Driver - * - * Copyright (C) 2001 by James.Bottomley@HansenPartnership.com - */ - -#ifndef _NCR_D700_H -#define _NCR_D700_H - -/* Don't turn on debugging messages */ -#undef NCR_D700_DEBUG - -/* The MCA identifier */ -#define NCR_D700_MCA_ID 0x0092 - -/* Defines for the Board registers */ -#define BOARD_RESET 0x80 /* board level reset */ -#define ADD_PARENB 0x04 /* Address Parity Enabled */ -#define DAT_PARENB 0x01 /* Data Parity Enabled */ -#define SFBK_ENB 0x10 /* SFDBK Interrupt Enabled */ -#define LED0GREEN 0x20 /* Led 0 (red 0; green 1) */ -#define LED1GREEN 0x40 /* Led 1 (red 0; green 1) */ -#define LED0RED 0xDF /* Led 0 (red 0; green 1) */ -#define LED1RED 0xBF /* Led 1 (red 0; green 1) */ - -#define NCR_D700_CLOCK_MHZ 50 - -#endif -- cgit v1.2.3-59-g8ed1b From cfe15038f4fc83503711a0880ba438f2deee91ff Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Mon, 11 Jun 2018 05:22:03 -0700 Subject: MAINTAINERS: Update RCU, SRCU, and TORTURE-TEST entries The RCU, SRCU, and TORTURE-TEST entries are missing some recent changes, so this commit brings them up to date. Reported-by: Andrea Parri Signed-off-by: Paul E. McKenney Cc: Davidlohr Bueso Cc: Josh Triplett Cc: Steven Rostedt Cc: Mathieu Desnoyers Cc: Lai Jiangshan --- MAINTAINERS | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9d5eeff51b5f..27bbc72c6191 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12021,9 +12021,9 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git F: Documentation/RCU/ X: Documentation/RCU/torture.txt F: include/linux/rcu* -X: include/linux/srcu.h +X: include/linux/srcu*.h F: kernel/rcu/ -X: kernel/torture.c +X: kernel/rcu/srcu*.c REAL TIME CLOCK (RTC) SUBSYSTEM M: Alessandro Zummo @@ -13060,8 +13060,8 @@ L: linux-kernel@vger.kernel.org W: http://www.rdrop.com/users/paulmck/RCU/ S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git -F: include/linux/srcu.h -F: kernel/rcu/srcu.c +F: include/linux/srcu*.h +F: kernel/rcu/srcu*.c SERIAL LOW-POWER INTER-CHIP MEDIA BUS (SLIMbus) M: Srinivas Kandagatla @@ -14420,6 +14420,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git F: Documentation/RCU/torture.txt F: kernel/torture.c F: kernel/rcu/rcutorture.c +F: kernel/rcu/rcuperf.c F: kernel/locking/locktorture.c TOSHIBA ACPI EXTRAS DRIVER -- cgit v1.2.3-59-g8ed1b From 9dcc7570b06836a9aa497bb921ff73c6b319d94c Mon Sep 17 00:00:00 2001 From: Dong Jia Shi Date: Fri, 6 Jul 2018 08:35:24 +0200 Subject: MAINTAINERS: Remove myself as maintainer I am leaving IBM and will move on to other working area, so remove myself as a vfio-ccw maintainer. Signed-off-by: Dong Jia Shi Message-Id: <20180706015743.41810-1-bjsdjshi@linux.ibm.com> Signed-off-by: Cornelia Huck Signed-off-by: Martin Schwidefsky --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 6cfd16790add..6f690fa7bd8a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12384,7 +12384,6 @@ F: drivers/pci/hotplug/s390_pci_hpc.c S390 VFIO-CCW DRIVER M: Cornelia Huck -M: Dong Jia Shi M: Halil Pasic L: linux-s390@vger.kernel.org L: kvm@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From 2caf412dc46149b8714e0f52c8337e8eda035a83 Mon Sep 17 00:00:00 2001 From: Palmer Dabbelt Date: Mon, 16 Jul 2018 11:05:54 -0700 Subject: MAINTAINERS: Add Daniel Lustig as an LKMM reviewer Dan runs the RISC-V memory model working group. I've been forwarding him LKMM emails that end up in my inbox, but I'm far from an expert in this stuff. He requested to be added as a reviewer, which seems sane to me as it'll take a human out of the loop. Signed-off-by: Palmer Dabbelt Signed-off-by: Paul E. McKenney Acked-by: Daniel Lustig Acked-by: Andrea Parri Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: akiyks@gmail.com Cc: boqun.feng@gmail.com Cc: dhowells@redhat.com Cc: j.alglave@ucl.ac.uk Cc: linux-arch@vger.kernel.org Cc: luc.maranget@inria.fr Cc: npiggin@gmail.com Cc: parri.andrea@gmail.com Cc: stern@rowland.harvard.edu Cc: will.deacon@arm.com Link: http://lkml.kernel.org/r/20180716180605.16115-3-paulmck@linux.vnet.ibm.com Signed-off-by: Ingo Molnar --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 192d7f73fd01..eb427596f3e0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8316,6 +8316,7 @@ M: Jade Alglave M: Luc Maranget M: "Paul E. McKenney" R: Akira Yokosawa +R: Daniel Lustig L: linux-kernel@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git -- cgit v1.2.3-59-g8ed1b From 70b83069f70d185356beba3202b9d167ee39f051 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Mon, 16 Jul 2018 11:06:00 -0700 Subject: tools/memory-model: Add informal LKMM documentation to MAINTAINERS The Linux-kernel memory model has been informal, with a number of text files documenting it. It would be good to make sure that these informal descriptions are kept up to date and/or pruned appropriately. This commit therefore brings more of those text files into the LKMM MAINTAINERS file entry. Signed-off-by: Paul E. McKenney Acked-by: Andrea Parri Cc: Akira Yokosawa Cc: Alan Stern Cc: Boqun Feng Cc: Daniel Lustig Cc: David Howells Cc: David S. Miller Cc: Jade Alglave Cc: Linus Torvalds Cc: Luc Maranget Cc: Nicholas Piggin Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: Will Deacon Cc: linux-arch@vger.kernel.org Cc: parri.andrea@gmail.com Link: http://lkml.kernel.org/r/20180716180605.16115-9-paulmck@linux.vnet.ibm.com Signed-off-by: Ingo Molnar --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index eb427596f3e0..900889a47627 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8318,9 +8318,14 @@ M: "Paul E. McKenney" R: Akira Yokosawa R: Daniel Lustig L: linux-kernel@vger.kernel.org +L: linux-arch@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git F: tools/memory-model/ +F: Documentation/atomic_bitops.txt +F: Documentation/atomic_t.txt +F: Documentation/core-api/atomic_ops.rst +F: Documentation/core-api/refcount-vs-atomic.rst F: Documentation/memory-barriers.txt LINUX SECURITY MODULE (LSM) FRAMEWORK -- cgit v1.2.3-59-g8ed1b From d8652956cf37c5caa8c19e0b99ce5ca235c6d5de Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 14 Jul 2018 11:45:55 +0200 Subject: net: dsa: realtek-smi: Add Realtek SMI driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a driver core for the Realtek SMI chips and a subdriver for the RTL8366RB. I just added this chip simply because it is all I can test. The code is a massaged variant of the code that has been sitting out-of-tree in OpenWRT for years in the absence of a proper switch subsystem. This creates a DSA driver for it. I have tried to credit the original authors wherever possible. The main changes I've done from the OpenWRT code: - Added an IRQ chip inside the RTL8366RB switch to demux and handle the line state IRQs. - Distributed the phy handling out to the PHY driver. - Added some RTL8366RB code that was missing in the driver at the time, such as setting up "green ethernet" with a funny jam table and forcing MAC5 (the CPU port) into 1 GBit. - Select jam table and add the default jam table from the vendor driver, also for ASIC "version 0" if need be. - Do not store jam tables in the device tree, store them in the driver. - Pick in the "initvals" jam tables from OpenWRT's driver and make those get selected per compatible for the whole system. It's apparently about electrical settings for this system and whatnot, not really configuration from device tree. - Implemented LED control: beware of bugs because there are no LEDs on the device I am using! We do not implement custom DSA tags. This is explained in a comment in the driver as well: this "tagging protocol" is not simply a few extra bytes tagged on to the ethernet frame as DSA is used to. Instead, enabling the CPU tags will make the switch start talking Realtek RRCP internally. For example a simple ping will make this kind of packets appear inside the switch: 0000 ff ff ff ff ff ff bc ae c5 6b a8 3d 88 99 a2 00 0010 08 06 00 01 08 00 06 04 00 01 bc ae c5 6b a8 3d 0020 a9 fe 01 01 00 00 00 00 00 00 a9 fe 01 02 00 00 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 As you can see a custom "8899" tagged packet using the protocol 0xa2. Norm RRCP appears to always have this protocol set to 0x01 according to OpenRRCP. You can also see that this is not a ping packet at all, instead the switch is starting to talk network management issues with the CPU port. So for now custom "tagging" is disabled. This was tested on the D-Link DIR-685 with initramfs and OpenWRT userspaces and works fine on all the LAN ports (lan0 .. lan3). The WAN port is yet not working. Cc: Antti Seppälä Cc: Roman Yeryomin Cc: Colin Leitner Cc: Gabor Juhos Cc: Florian Fainelli Signed-off-by: Linus Walleij Signed-off-by: David S. Miller --- MAINTAINERS | 7 + drivers/net/dsa/Kconfig | 11 + drivers/net/dsa/Makefile | 2 + drivers/net/dsa/realtek-smi.c | 487 ++++++++++++++ drivers/net/dsa/realtek-smi.h | 144 +++++ drivers/net/dsa/rtl8366.c | 516 +++++++++++++++ drivers/net/dsa/rtl8366rb.c | 1424 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 2591 insertions(+) create mode 100644 drivers/net/dsa/realtek-smi.c create mode 100644 drivers/net/dsa/realtek-smi.h create mode 100644 drivers/net/dsa/rtl8366.c create mode 100644 drivers/net/dsa/rtl8366rb.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index b40d702337f2..130ff2f4d33a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12063,6 +12063,13 @@ S: Maintained F: sound/soc/codecs/rt* F: include/sound/rt*.h +REALTEK RTL83xx SMI DSA ROUTER CHIPS +M: Linus Walleij +S: Maintained +F: Documentation/devicetree/bindings/net/dsa/realtek-smi.txt +F: drivers/net/dsa/realtek-smi* +F: drivers/net/dsa/rtl83* + REGISTER MAP ABSTRACTION M: Mark Brown L: linux-kernel@vger.kernel.org diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 733653d8359a..0b76a3a6977e 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -52,6 +52,17 @@ config NET_DSA_QCA8K This enables support for the Qualcomm Atheros QCA8K Ethernet switch chips. +config NET_DSA_REALTEK_SMI + tristate "Realtek SMI Ethernet switch family support" + depends on NET_DSA + select FIXED_PHY + select IRQ_DOMAIN + select REALTEK_PHY + select REGMAP + ---help--- + This enables support for the Realtek SMI-based switch + chips, currently only RTL8366RB. + config NET_DSA_SMSC_LAN9303 tristate select NET_DSA_TAG_LAN9303 diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index d4f873ae2f6a..46c1cba91ffe 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -8,6 +8,8 @@ endif obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o +obj-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek.o +realtek-objs := realtek-smi.o rtl8366.o rtl8366rb.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303_I2C) += lan9303_i2c.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o diff --git a/drivers/net/dsa/realtek-smi.c b/drivers/net/dsa/realtek-smi.c new file mode 100644 index 000000000000..f941f4582825 --- /dev/null +++ b/drivers/net/dsa/realtek-smi.c @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Realtek Simple Management Interface (SMI) driver + * It can be discussed how "simple" this interface is. + * + * The SMI protocol piggy-backs the MDIO MDC and MDIO signals levels + * but the protocol is not MDIO at all. Instead it is a Realtek + * pecularity that need to bit-bang the lines in a special way to + * communicate with the switch. + * + * ASICs we intend to support with this driver: + * + * RTL8366 - The original version, apparently + * RTL8369 - Similar enough to have the same datsheet as RTL8366 + * RTL8366RB - Probably reads out "RTL8366 revision B", has a quite + * different register layout from the other two + * RTL8366S - Is this "RTL8366 super"? + * RTL8367 - Has an OpenWRT driver as well + * RTL8368S - Seems to be an alternative name for RTL8366RB + * RTL8370 - Also uses SMI + * + * Copyright (C) 2017 Linus Walleij + * Copyright (C) 2010 Antti Seppälä + * Copyright (C) 2010 Roman Yeryomin + * Copyright (C) 2011 Colin Leitner + * Copyright (C) 2009-2010 Gabor Juhos + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "realtek-smi.h" + +#define REALTEK_SMI_ACK_RETRY_COUNT 5 +#define REALTEK_SMI_HW_STOP_DELAY 25 /* msecs */ +#define REALTEK_SMI_HW_START_DELAY 100 /* msecs */ + +static inline void realtek_smi_clk_delay(struct realtek_smi *smi) +{ + ndelay(smi->clk_delay); +} + +static void realtek_smi_start(struct realtek_smi *smi) +{ + /* Set GPIO pins to output mode, with initial state: + * SCK = 0, SDA = 1 + */ + gpiod_direction_output(smi->mdc, 0); + gpiod_direction_output(smi->mdio, 1); + realtek_smi_clk_delay(smi); + + /* CLK 1: 0 -> 1, 1 -> 0 */ + gpiod_set_value(smi->mdc, 1); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdc, 0); + realtek_smi_clk_delay(smi); + + /* CLK 2: */ + gpiod_set_value(smi->mdc, 1); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdio, 0); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdc, 0); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdio, 1); +} + +static void realtek_smi_stop(struct realtek_smi *smi) +{ + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdio, 0); + gpiod_set_value(smi->mdc, 1); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdio, 1); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdc, 1); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdc, 0); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdc, 1); + + /* Add a click */ + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdc, 0); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdc, 1); + + /* Set GPIO pins to input mode */ + gpiod_direction_input(smi->mdio); + gpiod_direction_input(smi->mdc); +} + +static void realtek_smi_write_bits(struct realtek_smi *smi, u32 data, u32 len) +{ + for (; len > 0; len--) { + realtek_smi_clk_delay(smi); + + /* Prepare data */ + gpiod_set_value(smi->mdio, !!(data & (1 << (len - 1)))); + realtek_smi_clk_delay(smi); + + /* Clocking */ + gpiod_set_value(smi->mdc, 1); + realtek_smi_clk_delay(smi); + gpiod_set_value(smi->mdc, 0); + } +} + +static void realtek_smi_read_bits(struct realtek_smi *smi, u32 len, u32 *data) +{ + gpiod_direction_input(smi->mdio); + + for (*data = 0; len > 0; len--) { + u32 u; + + realtek_smi_clk_delay(smi); + + /* Clocking */ + gpiod_set_value(smi->mdc, 1); + realtek_smi_clk_delay(smi); + u = !!gpiod_get_value(smi->mdio); + gpiod_set_value(smi->mdc, 0); + + *data |= (u << (len - 1)); + } + + gpiod_direction_output(smi->mdio, 0); +} + +static int realtek_smi_wait_for_ack(struct realtek_smi *smi) +{ + int retry_cnt; + + retry_cnt = 0; + do { + u32 ack; + + realtek_smi_read_bits(smi, 1, &ack); + if (ack == 0) + break; + + if (++retry_cnt > REALTEK_SMI_ACK_RETRY_COUNT) { + dev_err(smi->dev, "ACK timeout\n"); + return -ETIMEDOUT; + } + } while (1); + + return 0; +} + +static int realtek_smi_write_byte(struct realtek_smi *smi, u8 data) +{ + realtek_smi_write_bits(smi, data, 8); + return realtek_smi_wait_for_ack(smi); +} + +static int realtek_smi_write_byte_noack(struct realtek_smi *smi, u8 data) +{ + realtek_smi_write_bits(smi, data, 8); + return 0; +} + +static int realtek_smi_read_byte0(struct realtek_smi *smi, u8 *data) +{ + u32 t; + + /* Read data */ + realtek_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* Send an ACK */ + realtek_smi_write_bits(smi, 0x00, 1); + + return 0; +} + +static int realtek_smi_read_byte1(struct realtek_smi *smi, u8 *data) +{ + u32 t; + + /* Read data */ + realtek_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* Send an ACK */ + realtek_smi_write_bits(smi, 0x01, 1); + + return 0; +} + +static int realtek_smi_read_reg(struct realtek_smi *smi, u32 addr, u32 *data) +{ + unsigned long flags; + u8 lo = 0; + u8 hi = 0; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + realtek_smi_start(smi); + + /* Send READ command */ + ret = realtek_smi_write_byte(smi, smi->cmd_read); + if (ret) + goto out; + + /* Set ADDR[7:0] */ + ret = realtek_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* Set ADDR[15:8] */ + ret = realtek_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* Read DATA[7:0] */ + realtek_smi_read_byte0(smi, &lo); + /* Read DATA[15:8] */ + realtek_smi_read_byte1(smi, &hi); + + *data = ((u32)lo) | (((u32)hi) << 8); + + ret = 0; + + out: + realtek_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +static int realtek_smi_write_reg(struct realtek_smi *smi, + u32 addr, u32 data, bool ack) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + realtek_smi_start(smi); + + /* Send WRITE command */ + ret = realtek_smi_write_byte(smi, smi->cmd_write); + if (ret) + goto out; + + /* Set ADDR[7:0] */ + ret = realtek_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* Set ADDR[15:8] */ + ret = realtek_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* Write DATA[7:0] */ + ret = realtek_smi_write_byte(smi, data & 0xff); + if (ret) + goto out; + + /* Write DATA[15:8] */ + if (ack) + ret = realtek_smi_write_byte(smi, data >> 8); + else + ret = realtek_smi_write_byte_noack(smi, data >> 8); + if (ret) + goto out; + + ret = 0; + + out: + realtek_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +/* There is one single case when we need to use this accessor and that + * is when issueing soft reset. Since the device reset as soon as we write + * that bit, no ACK will come back for natural reasons. + */ +int realtek_smi_write_reg_noack(struct realtek_smi *smi, u32 addr, + u32 data) +{ + return realtek_smi_write_reg(smi, addr, data, false); +} +EXPORT_SYMBOL_GPL(realtek_smi_write_reg_noack); + +/* Regmap accessors */ + +static int realtek_smi_write(void *ctx, u32 reg, u32 val) +{ + struct realtek_smi *smi = ctx; + + return realtek_smi_write_reg(smi, reg, val, true); +} + +static int realtek_smi_read(void *ctx, u32 reg, u32 *val) +{ + struct realtek_smi *smi = ctx; + + return realtek_smi_read_reg(smi, reg, val); +} + +static const struct regmap_config realtek_smi_mdio_regmap_config = { + .reg_bits = 10, /* A4..A0 R4..R0 */ + .val_bits = 16, + .reg_stride = 1, + /* PHY regs are at 0x8000 */ + .max_register = 0xffff, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .reg_read = realtek_smi_read, + .reg_write = realtek_smi_write, + .cache_type = REGCACHE_NONE, +}; + +static int realtek_smi_mdio_read(struct mii_bus *bus, int addr, int regnum) +{ + struct realtek_smi *smi = bus->priv; + + return smi->ops->phy_read(smi, addr, regnum); +} + +static int realtek_smi_mdio_write(struct mii_bus *bus, int addr, int regnum, + u16 val) +{ + struct realtek_smi *smi = bus->priv; + + return smi->ops->phy_write(smi, addr, regnum, val); +} + +int realtek_smi_setup_mdio(struct realtek_smi *smi) +{ + struct device_node *mdio_np; + int ret; + + mdio_np = of_find_compatible_node(smi->dev->of_node, NULL, + "realtek,smi-mdio"); + if (!mdio_np) { + dev_err(smi->dev, "no MDIO bus node\n"); + return -ENODEV; + } + + smi->slave_mii_bus = devm_mdiobus_alloc(smi->dev); + if (!smi->slave_mii_bus) + return -ENOMEM; + smi->slave_mii_bus->priv = smi; + smi->slave_mii_bus->name = "SMI slave MII"; + smi->slave_mii_bus->read = realtek_smi_mdio_read; + smi->slave_mii_bus->write = realtek_smi_mdio_write; + snprintf(smi->slave_mii_bus->id, MII_BUS_ID_SIZE, "SMI-%d", + smi->ds->index); + smi->slave_mii_bus->dev.of_node = mdio_np; + smi->slave_mii_bus->parent = smi->dev; + smi->ds->slave_mii_bus = smi->slave_mii_bus; + + ret = of_mdiobus_register(smi->slave_mii_bus, mdio_np); + if (ret) { + dev_err(smi->dev, "unable to register MDIO bus %s\n", + smi->slave_mii_bus->id); + of_node_put(mdio_np); + } + + return 0; +} + +static int realtek_smi_probe(struct platform_device *pdev) +{ + const struct realtek_smi_variant *var; + struct device *dev = &pdev->dev; + struct realtek_smi *smi; + struct device_node *np; + int ret; + + var = of_device_get_match_data(dev); + np = dev->of_node; + + smi = devm_kzalloc(dev, sizeof(*smi), GFP_KERNEL); + if (!smi) + return -ENOMEM; + smi->map = devm_regmap_init(dev, NULL, smi, + &realtek_smi_mdio_regmap_config); + if (IS_ERR(smi->map)) { + ret = PTR_ERR(smi->map); + dev_err(dev, "regmap init failed: %d\n", ret); + return ret; + } + + /* Link forward and backward */ + smi->dev = dev; + smi->clk_delay = var->clk_delay; + smi->cmd_read = var->cmd_read; + smi->cmd_write = var->cmd_write; + smi->ops = var->ops; + + dev_set_drvdata(dev, smi); + spin_lock_init(&smi->lock); + + /* TODO: if power is software controlled, set up any regulators here */ + + /* Assert then deassert RESET */ + smi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(smi->reset)) { + dev_err(dev, "failed to get RESET GPIO\n"); + return PTR_ERR(smi->reset); + } + msleep(REALTEK_SMI_HW_STOP_DELAY); + gpiod_set_value(smi->reset, 0); + msleep(REALTEK_SMI_HW_START_DELAY); + dev_info(dev, "deasserted RESET\n"); + + /* Fetch MDIO pins */ + smi->mdc = devm_gpiod_get_optional(dev, "mdc", GPIOD_OUT_LOW); + if (IS_ERR(smi->mdc)) + return PTR_ERR(smi->mdc); + smi->mdio = devm_gpiod_get_optional(dev, "mdio", GPIOD_OUT_LOW); + if (IS_ERR(smi->mdio)) + return PTR_ERR(smi->mdio); + + smi->leds_disabled = of_property_read_bool(np, "realtek,disable-leds"); + + ret = smi->ops->detect(smi); + if (ret) { + dev_err(dev, "unable to detect switch\n"); + return ret; + } + + smi->ds = dsa_switch_alloc(dev, smi->num_ports); + if (!smi->ds) + return -ENOMEM; + smi->ds->priv = smi; + + smi->ds->ops = var->ds_ops; + ret = dsa_register_switch(smi->ds); + if (ret) { + dev_err(dev, "unable to register switch ret = %d\n", ret); + return ret; + } + return 0; +} + +static int realtek_smi_remove(struct platform_device *pdev) +{ + struct realtek_smi *smi = dev_get_drvdata(&pdev->dev); + + dsa_unregister_switch(smi->ds); + gpiod_set_value(smi->reset, 1); + + return 0; +} + +static const struct of_device_id realtek_smi_of_match[] = { + { + .compatible = "realtek,rtl8366rb", + .data = &rtl8366rb_variant, + }, + { + /* FIXME: add support for RTL8366S and more */ + .compatible = "realtek,rtl8366s", + .data = NULL, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, realtek_smi_of_match); + +static struct platform_driver realtek_smi_driver = { + .driver = { + .name = "realtek-smi", + .of_match_table = of_match_ptr(realtek_smi_of_match), + }, + .probe = realtek_smi_probe, + .remove = realtek_smi_remove, +}; +module_platform_driver(realtek_smi_driver); diff --git a/drivers/net/dsa/realtek-smi.h b/drivers/net/dsa/realtek-smi.h new file mode 100644 index 000000000000..9a63b51e1d82 --- /dev/null +++ b/drivers/net/dsa/realtek-smi.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Realtek SMI interface driver defines + * + * Copyright (C) 2017 Linus Walleij + * Copyright (C) 2009-2010 Gabor Juhos + */ + +#ifndef _REALTEK_SMI_H +#define _REALTEK_SMI_H + +#include +#include +#include +#include + +struct realtek_smi_ops; +struct dentry; +struct inode; +struct file; + +struct rtl8366_mib_counter { + unsigned int base; + unsigned int offset; + unsigned int length; + const char *name; +}; + +struct rtl8366_vlan_mc { + u16 vid; + u16 untag; + u16 member; + u8 fid; + u8 priority; +}; + +struct rtl8366_vlan_4k { + u16 vid; + u16 untag; + u16 member; + u8 fid; +}; + +struct realtek_smi { + struct device *dev; + struct gpio_desc *reset; + struct gpio_desc *mdc; + struct gpio_desc *mdio; + struct regmap *map; + struct mii_bus *slave_mii_bus; + + unsigned int clk_delay; + u8 cmd_read; + u8 cmd_write; + spinlock_t lock; /* Locks around command writes */ + struct dsa_switch *ds; + struct irq_domain *irqdomain; + bool leds_disabled; + + unsigned int cpu_port; + unsigned int num_ports; + unsigned int num_vlan_mc; + unsigned int num_mib_counters; + struct rtl8366_mib_counter *mib_counters; + + const struct realtek_smi_ops *ops; + + int vlan_enabled; + int vlan4k_enabled; + + char buf[4096]; +}; + +/** + * struct realtek_smi_ops - vtable for the per-SMI-chiptype operations + * @detect: detects the chiptype + */ +struct realtek_smi_ops { + int (*detect)(struct realtek_smi *smi); + int (*reset_chip)(struct realtek_smi *smi); + int (*setup)(struct realtek_smi *smi); + void (*cleanup)(struct realtek_smi *smi); + int (*get_mib_counter)(struct realtek_smi *smi, + int port, + struct rtl8366_mib_counter *mib, + u64 *mibvalue); + int (*get_vlan_mc)(struct realtek_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc); + int (*set_vlan_mc)(struct realtek_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc); + int (*get_vlan_4k)(struct realtek_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k); + int (*set_vlan_4k)(struct realtek_smi *smi, + const struct rtl8366_vlan_4k *vlan4k); + int (*get_mc_index)(struct realtek_smi *smi, int port, int *val); + int (*set_mc_index)(struct realtek_smi *smi, int port, int index); + bool (*is_vlan_valid)(struct realtek_smi *smi, unsigned int vlan); + int (*enable_vlan)(struct realtek_smi *smi, bool enable); + int (*enable_vlan4k)(struct realtek_smi *smi, bool enable); + int (*enable_port)(struct realtek_smi *smi, int port, bool enable); + int (*phy_read)(struct realtek_smi *smi, int phy, int regnum); + int (*phy_write)(struct realtek_smi *smi, int phy, int regnum, + u16 val); +}; + +struct realtek_smi_variant { + const struct dsa_switch_ops *ds_ops; + const struct realtek_smi_ops *ops; + unsigned int clk_delay; + u8 cmd_read; + u8 cmd_write; +}; + +/* SMI core calls */ +int realtek_smi_write_reg_noack(struct realtek_smi *smi, u32 addr, + u32 data); +int realtek_smi_setup_mdio(struct realtek_smi *smi); + +/* RTL8366 library helpers */ +int rtl8366_mc_is_used(struct realtek_smi *smi, int mc_index, int *used); +int rtl8366_set_vlan(struct realtek_smi *smi, int vid, u32 member, + u32 untag, u32 fid); +int rtl8366_get_pvid(struct realtek_smi *smi, int port, int *val); +int rtl8366_set_pvid(struct realtek_smi *smi, unsigned int port, + unsigned int vid); +int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable); +int rtl8366_enable_vlan(struct realtek_smi *smi, bool enable); +int rtl8366_reset_vlan(struct realtek_smi *smi); +int rtl8366_init_vlan(struct realtek_smi *smi); +int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, + bool vlan_filtering); +int rtl8366_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan); +void rtl8366_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan); +int rtl8366_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan); +void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset, + uint8_t *data); +int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset); +void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data); + +extern const struct realtek_smi_variant rtl8366rb_variant; + +#endif /* _REALTEK_SMI_H */ diff --git a/drivers/net/dsa/rtl8366.c b/drivers/net/dsa/rtl8366.c new file mode 100644 index 000000000000..58d8534f7ecb --- /dev/null +++ b/drivers/net/dsa/rtl8366.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Realtek SMI library helpers for the RTL8366x variants + * RTL8366RB and RTL8366S + * + * Copyright (C) 2017 Linus Walleij + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * Copyright (C) 2010 Roman Yeryomin + * Copyright (C) 2011 Colin Leitner + */ +#include +#include + +#include "realtek-smi.h" + +int rtl8366_mc_is_used(struct realtek_smi *smi, int mc_index, int *used) +{ + int ret; + int i; + + *used = 0; + for (i = 0; i < smi->num_ports; i++) { + int index = 0; + + ret = smi->ops->get_mc_index(smi, i, &index); + if (ret) + return ret; + + if (mc_index == index) { + *used = 1; + break; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_mc_is_used); + +int rtl8366_set_vlan(struct realtek_smi *smi, int vid, u32 member, + u32 untag, u32 fid) +{ + struct rtl8366_vlan_4k vlan4k; + int ret; + int i; + + /* Update the 4K table */ + ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (ret) + return ret; + + vlan4k.member = member; + vlan4k.untag = untag; + vlan4k.fid = fid; + ret = smi->ops->set_vlan_4k(smi, &vlan4k); + if (ret) + return ret; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + struct rtl8366_vlan_mc vlanmc; + + ret = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (ret) + return ret; + + if (vid == vlanmc.vid) { + /* update the MC entry */ + vlanmc.member = member; + vlanmc.untag = untag; + vlanmc.fid = fid; + + ret = smi->ops->set_vlan_mc(smi, i, &vlanmc); + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(rtl8366_set_vlan); + +int rtl8366_get_pvid(struct realtek_smi *smi, int port, int *val) +{ + struct rtl8366_vlan_mc vlanmc; + int ret; + int index; + + ret = smi->ops->get_mc_index(smi, port, &index); + if (ret) + return ret; + + ret = smi->ops->get_vlan_mc(smi, index, &vlanmc); + if (ret) + return ret; + + *val = vlanmc.vid; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_get_pvid); + +int rtl8366_set_pvid(struct realtek_smi *smi, unsigned int port, + unsigned int vid) +{ + struct rtl8366_vlan_mc vlanmc; + struct rtl8366_vlan_4k vlan4k; + int ret; + int i; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + ret = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (ret) + return ret; + + if (vid == vlanmc.vid) { + ret = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (ret) + return ret; + + ret = smi->ops->set_mc_index(smi, port, i); + return ret; + } + } + + /* We have no MC entry for this VID, try to find an empty one */ + for (i = 0; i < smi->num_vlan_mc; i++) { + ret = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (ret) + return ret; + + if (vlanmc.vid == 0 && vlanmc.member == 0) { + /* Update the entry from the 4K table */ + ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (ret) + return ret; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + ret = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (ret) + return ret; + + ret = smi->ops->set_mc_index(smi, port, i); + return ret; + } + } + + /* MC table is full, try to find an unused entry and replace it */ + for (i = 0; i < smi->num_vlan_mc; i++) { + int used; + + ret = rtl8366_mc_is_used(smi, i, &used); + if (ret) + return ret; + + if (!used) { + /* Update the entry from the 4K table */ + ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (ret) + return ret; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + ret = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (ret) + return ret; + + ret = smi->ops->set_mc_index(smi, port, i); + return ret; + } + } + + dev_err(smi->dev, + "all VLAN member configurations are in use\n"); + + return -ENOSPC; +} +EXPORT_SYMBOL_GPL(rtl8366_set_pvid); + +int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable) +{ + int ret; + + /* To enable 4k VLAN, ordinary VLAN must be enabled first, + * but if we disable 4k VLAN it is fine to leave ordinary + * VLAN enabled. + */ + if (enable) { + /* Make sure VLAN is ON */ + ret = smi->ops->enable_vlan(smi, true); + if (ret) + return ret; + + smi->vlan_enabled = true; + } + + ret = smi->ops->enable_vlan4k(smi, enable); + if (ret) + return ret; + + smi->vlan4k_enabled = enable; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_vlan4k); + +int rtl8366_enable_vlan(struct realtek_smi *smi, bool enable) +{ + int ret; + + ret = smi->ops->enable_vlan(smi, enable); + if (ret) + return ret; + + smi->vlan_enabled = enable; + + /* If we turn VLAN off, make sure that we turn off + * 4k VLAN as well, if that happened to be on. + */ + if (!enable) { + smi->vlan4k_enabled = false; + ret = smi->ops->enable_vlan4k(smi, false); + } + + return ret; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_vlan); + +int rtl8366_reset_vlan(struct realtek_smi *smi) +{ + struct rtl8366_vlan_mc vlanmc; + struct rtl8366_vlan_4k vlan4k; + int ret; + int i; + + rtl8366_enable_vlan(smi, false); + rtl8366_enable_vlan4k(smi, false); + + /* Clear the 16 VLAN member configurations */ + vlanmc.vid = 0; + vlanmc.priority = 0; + vlanmc.member = 0; + vlanmc.untag = 0; + vlanmc.fid = 0; + for (i = 0; i < smi->num_vlan_mc; i++) { + ret = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); + +int rtl8366_init_vlan(struct realtek_smi *smi) +{ + int port; + int ret; + + ret = rtl8366_reset_vlan(smi); + if (ret) + return ret; + + /* Loop over the available ports, for each port, associate + * it with the VLAN (port+1) + */ + for (port = 0; port < smi->num_ports; port++) { + u32 mask; + + if (port == smi->cpu_port) + /* For the CPU port, make all ports members of this + * VLAN. + */ + mask = GENMASK(smi->num_ports - 1, 0); + else + /* For all other ports, enable itself plus the + * CPU port. + */ + mask = BIT(port) | BIT(smi->cpu_port); + + /* For each port, set the port as member of VLAN (port+1) + * and untagged, except for the CPU port: the CPU port (5) is + * member of VLAN 6 and so are ALL the other ports as well. + * Use filter 0 (no filter). + */ + dev_info(smi->dev, "VLAN%d port mask for port %d, %08x\n", + (port + 1), port, mask); + ret = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0); + if (ret) + return ret; + + dev_info(smi->dev, "VLAN%d port %d, PVID set to %d\n", + (port + 1), port, (port + 1)); + ret = rtl8366_set_pvid(smi, port, (port + 1)); + if (ret) + return ret; + } + + return rtl8366_enable_vlan(smi, true); +} +EXPORT_SYMBOL_GPL(rtl8366_init_vlan); + +int rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering) +{ + struct realtek_smi *smi = ds->priv; + struct rtl8366_vlan_4k vlan4k; + int ret; + + if (!smi->ops->is_vlan_valid(smi, port)) + return -EINVAL; + + dev_info(smi->dev, "%s filtering on port %d\n", + vlan_filtering ? "enable" : "disable", + port); + + /* TODO: + * The hardware support filter ID (FID) 0..7, I have no clue how to + * support this in the driver when the callback only says on/off. + */ + ret = smi->ops->get_vlan_4k(smi, port, &vlan4k); + if (ret) + return ret; + + /* Just set the filter to FID 1 for now then */ + ret = rtl8366_set_vlan(smi, port, + vlan4k.member, + vlan4k.untag, + 1); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_vlan_filtering); + +int rtl8366_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct realtek_smi *smi = ds->priv; + int ret; + + if (!smi->ops->is_vlan_valid(smi, port)) + return -EINVAL; + + dev_info(smi->dev, "prepare VLANs %04x..%04x\n", + vlan->vid_begin, vlan->vid_end); + + /* Enable VLAN in the hardware + * FIXME: what's with this 4k business? + * Just rtl8366_enable_vlan() seems inconclusive. + */ + ret = rtl8366_enable_vlan4k(smi, true); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_vlan_prepare); + +void rtl8366_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); + bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); + struct realtek_smi *smi = ds->priv; + u32 member = 0; + u32 untag = 0; + u16 vid; + int ret; + + if (!smi->ops->is_vlan_valid(smi, port)) + return; + + dev_info(smi->dev, "add VLAN on port %d, %s, %s\n", + port, + untagged ? "untagged" : "tagged", + pvid ? " PVID" : "no PVID"); + + if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port)) + dev_err(smi->dev, "port is DSA or CPU port\n"); + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + int pvid_val = 0; + + dev_info(smi->dev, "add VLAN %04x\n", vid); + member |= BIT(port); + + if (untagged) + untag |= BIT(port); + + /* To ensure that we have a valid MC entry for this VLAN, + * initialize the port VLAN ID here. + */ + ret = rtl8366_get_pvid(smi, port, &pvid_val); + if (ret < 0) { + dev_err(smi->dev, "could not lookup PVID for port %d\n", + port); + return; + } + if (pvid_val == 0) { + ret = rtl8366_set_pvid(smi, port, vid); + if (ret < 0) + return; + } + } + + ret = rtl8366_set_vlan(smi, port, member, untag, 0); + if (ret) + dev_err(smi->dev, + "failed to set up VLAN %04x", + vid); +} +EXPORT_SYMBOL_GPL(rtl8366_vlan_add); + +int rtl8366_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct realtek_smi *smi = ds->priv; + u16 vid; + int ret; + + dev_info(smi->dev, "del VLAN on port %d\n", port); + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + int i; + + dev_info(smi->dev, "del VLAN %04x\n", vid); + + for (i = 0; i < smi->num_vlan_mc; i++) { + struct rtl8366_vlan_mc vlanmc; + + ret = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (ret) + return ret; + + if (vid == vlanmc.vid) { + /* clear VLAN member configurations */ + vlanmc.vid = 0; + vlanmc.priority = 0; + vlanmc.member = 0; + vlanmc.untag = 0; + vlanmc.fid = 0; + + ret = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (ret) { + dev_err(smi->dev, + "failed to remove VLAN %04x\n", + vid); + return ret; + } + break; + } + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_vlan_del); + +void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset, + uint8_t *data) +{ + struct realtek_smi *smi = ds->priv; + struct rtl8366_mib_counter *mib; + int i; + + if (port >= smi->num_ports) + return; + + for (i = 0; i < smi->num_mib_counters; i++) { + mib = &smi->mib_counters[i]; + strncpy(data + i * ETH_GSTRING_LEN, + mib->name, ETH_GSTRING_LEN); + } +} +EXPORT_SYMBOL_GPL(rtl8366_get_strings); + +int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset) +{ + struct realtek_smi *smi = ds->priv; + + /* We only support SS_STATS */ + if (sset != ETH_SS_STATS) + return 0; + if (port >= smi->num_ports) + return -EINVAL; + + return smi->num_mib_counters; +} +EXPORT_SYMBOL_GPL(rtl8366_get_sset_count); + +void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data) +{ + struct realtek_smi *smi = ds->priv; + int i; + int ret; + + if (port >= smi->num_ports) + return; + + for (i = 0; i < smi->num_mib_counters; i++) { + struct rtl8366_mib_counter *mib; + u64 mibvalue = 0; + + mib = &smi->mib_counters[i]; + ret = smi->ops->get_mib_counter(smi, port, mib, &mibvalue); + if (ret) { + dev_err(smi->dev, "error reading MIB counter %s\n", + mib->name); + } + data[i] = mibvalue; + } +} +EXPORT_SYMBOL_GPL(rtl8366_get_ethtool_stats); diff --git a/drivers/net/dsa/rtl8366rb.c b/drivers/net/dsa/rtl8366rb.c new file mode 100644 index 000000000000..1e55b9bf8b56 --- /dev/null +++ b/drivers/net/dsa/rtl8366rb.c @@ -0,0 +1,1424 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Realtek SMI subdriver for the Realtek RTL8366RB ethernet switch + * + * This is a sparsely documented chip, the only viable documentation seems + * to be a patched up code drop from the vendor that appear in various + * GPL source trees. + * + * Copyright (C) 2017 Linus Walleij + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * Copyright (C) 2010 Roman Yeryomin + * Copyright (C) 2011 Colin Leitner + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "realtek-smi.h" + +#define RTL8366RB_PORT_NUM_CPU 5 +#define RTL8366RB_NUM_PORTS 6 +#define RTL8366RB_PHY_NO_MAX 4 +#define RTL8366RB_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366RB_SGCR 0x0000 +#define RTL8366RB_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366RB_SGCR_MAX_LENGTH(a) ((a) << 4) +#define RTL8366RB_SGCR_MAX_LENGTH_MASK RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_MAX_LENGTH_1522 RTL8366RB_SGCR_MAX_LENGTH(0x0) +#define RTL8366RB_SGCR_MAX_LENGTH_1536 RTL8366RB_SGCR_MAX_LENGTH(0x1) +#define RTL8366RB_SGCR_MAX_LENGTH_1552 RTL8366RB_SGCR_MAX_LENGTH(0x2) +#define RTL8366RB_SGCR_MAX_LENGTH_9216 RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_EN_VLAN BIT(13) +#define RTL8366RB_SGCR_EN_VLAN_4KTB BIT(14) + +/* Port Enable Control register */ +#define RTL8366RB_PECR 0x0001 + +/* Switch Security Control registers */ +#define RTL8366RB_SSCR0 0x0002 +#define RTL8366RB_SSCR1 0x0003 +#define RTL8366RB_SSCR2 0x0004 +#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA BIT(0) + +/* Port Mirror Control Register */ +#define RTL8366RB_PMCR 0x0007 +#define RTL8366RB_PMCR_SOURCE_PORT(a) (a) +#define RTL8366RB_PMCR_SOURCE_PORT_MASK 0x000f +#define RTL8366RB_PMCR_MONITOR_PORT(a) ((a) << 4) +#define RTL8366RB_PMCR_MONITOR_PORT_MASK 0x00f0 +#define RTL8366RB_PMCR_MIRROR_RX BIT(8) +#define RTL8366RB_PMCR_MIRROR_TX BIT(9) +#define RTL8366RB_PMCR_MIRROR_SPC BIT(10) +#define RTL8366RB_PMCR_MIRROR_ISO BIT(11) + +/* bits 0..7 = port 0, bits 8..15 = port 1 */ +#define RTL8366RB_PAACR0 0x0010 +/* bits 0..7 = port 2, bits 8..15 = port 3 */ +#define RTL8366RB_PAACR1 0x0011 +/* bits 0..7 = port 4, bits 8..15 = port 5 */ +#define RTL8366RB_PAACR2 0x0012 +#define RTL8366RB_PAACR_SPEED_10M 0 +#define RTL8366RB_PAACR_SPEED_100M 1 +#define RTL8366RB_PAACR_SPEED_1000M 2 +#define RTL8366RB_PAACR_FULL_DUPLEX BIT(2) +#define RTL8366RB_PAACR_LINK_UP BIT(4) +#define RTL8366RB_PAACR_TX_PAUSE BIT(5) +#define RTL8366RB_PAACR_RX_PAUSE BIT(6) +#define RTL8366RB_PAACR_AN BIT(7) + +#define RTL8366RB_PAACR_CPU_PORT (RTL8366RB_PAACR_SPEED_1000M | \ + RTL8366RB_PAACR_FULL_DUPLEX | \ + RTL8366RB_PAACR_LINK_UP | \ + RTL8366RB_PAACR_TX_PAUSE | \ + RTL8366RB_PAACR_RX_PAUSE) + +/* bits 0..7 = port 0, bits 8..15 = port 1 */ +#define RTL8366RB_PSTAT0 0x0014 +/* bits 0..7 = port 2, bits 8..15 = port 3 */ +#define RTL8366RB_PSTAT1 0x0015 +/* bits 0..7 = port 4, bits 8..15 = port 5 */ +#define RTL8366RB_PSTAT2 0x0016 + +#define RTL8366RB_POWER_SAVING_REG 0x0021 + +/* CPU port control reg */ +#define RTL8368RB_CPU_CTRL_REG 0x0061 +#define RTL8368RB_CPU_PORTS_MSK 0x00FF +/* Enables inserting custom tag length/type 0x8899 */ +#define RTL8368RB_CPU_INSTAG BIT(15) + +#define RTL8366RB_SMAR0 0x0070 /* bits 0..15 */ +#define RTL8366RB_SMAR1 0x0071 /* bits 16..31 */ +#define RTL8366RB_SMAR2 0x0072 /* bits 32..47 */ + +#define RTL8366RB_RESET_CTRL_REG 0x0100 +#define RTL8366RB_CHIP_CTRL_RESET_HW BIT(0) +#define RTL8366RB_CHIP_CTRL_RESET_SW BIT(1) + +#define RTL8366RB_CHIP_ID_REG 0x0509 +#define RTL8366RB_CHIP_ID_8366 0x5937 +#define RTL8366RB_CHIP_VERSION_CTRL_REG 0x050A +#define RTL8366RB_CHIP_VERSION_MASK 0xf + +/* PHY registers control */ +#define RTL8366RB_PHY_ACCESS_CTRL_REG 0x8000 +#define RTL8366RB_PHY_CTRL_READ BIT(0) +#define RTL8366RB_PHY_CTRL_WRITE 0 +#define RTL8366RB_PHY_ACCESS_BUSY_REG 0x8001 +#define RTL8366RB_PHY_INT_BUSY BIT(0) +#define RTL8366RB_PHY_EXT_BUSY BIT(4) +#define RTL8366RB_PHY_ACCESS_DATA_REG 0x8002 +#define RTL8366RB_PHY_EXT_CTRL_REG 0x8010 +#define RTL8366RB_PHY_EXT_WRDATA_REG 0x8011 +#define RTL8366RB_PHY_EXT_RDDATA_REG 0x8012 + +#define RTL8366RB_PHY_REG_MASK 0x1f +#define RTL8366RB_PHY_PAGE_OFFSET 5 +#define RTL8366RB_PHY_PAGE_MASK (0xf << 5) +#define RTL8366RB_PHY_NO_OFFSET 9 +#define RTL8366RB_PHY_NO_MASK (0x1f << 9) + +#define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f + +/* LED control registers */ +#define RTL8366RB_LED_BLINKRATE_REG 0x0430 +#define RTL8366RB_LED_BLINKRATE_MASK 0x0007 +#define RTL8366RB_LED_BLINKRATE_28MS 0x0000 +#define RTL8366RB_LED_BLINKRATE_56MS 0x0001 +#define RTL8366RB_LED_BLINKRATE_84MS 0x0002 +#define RTL8366RB_LED_BLINKRATE_111MS 0x0003 +#define RTL8366RB_LED_BLINKRATE_222MS 0x0004 +#define RTL8366RB_LED_BLINKRATE_446MS 0x0005 + +#define RTL8366RB_LED_CTRL_REG 0x0431 +#define RTL8366RB_LED_OFF 0x0 +#define RTL8366RB_LED_DUP_COL 0x1 +#define RTL8366RB_LED_LINK_ACT 0x2 +#define RTL8366RB_LED_SPD1000 0x3 +#define RTL8366RB_LED_SPD100 0x4 +#define RTL8366RB_LED_SPD10 0x5 +#define RTL8366RB_LED_SPD1000_ACT 0x6 +#define RTL8366RB_LED_SPD100_ACT 0x7 +#define RTL8366RB_LED_SPD10_ACT 0x8 +#define RTL8366RB_LED_SPD100_10_ACT 0x9 +#define RTL8366RB_LED_FIBER 0xa +#define RTL8366RB_LED_AN_FAULT 0xb +#define RTL8366RB_LED_LINK_RX 0xc +#define RTL8366RB_LED_LINK_TX 0xd +#define RTL8366RB_LED_MASTER 0xe +#define RTL8366RB_LED_FORCE 0xf +#define RTL8366RB_LED_0_1_CTRL_REG 0x0432 +#define RTL8366RB_LED_1_OFFSET 6 +#define RTL8366RB_LED_2_3_CTRL_REG 0x0433 +#define RTL8366RB_LED_3_OFFSET 6 + +#define RTL8366RB_MIB_COUNT 33 +#define RTL8366RB_GLOBAL_MIB_COUNT 1 +#define RTL8366RB_MIB_COUNTER_PORT_OFFSET 0x0050 +#define RTL8366RB_MIB_COUNTER_BASE 0x1000 +#define RTL8366RB_MIB_CTRL_REG 0x13F0 +#define RTL8366RB_MIB_CTRL_USER_MASK 0x0FFC +#define RTL8366RB_MIB_CTRL_BUSY_MASK BIT(0) +#define RTL8366RB_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8366RB_MIB_CTRL_PORT_RESET(_p) BIT(2 + (_p)) +#define RTL8366RB_MIB_CTRL_GLOBAL_RESET BIT(11) + +#define RTL8366RB_PORT_VLAN_CTRL_BASE 0x0063 +#define RTL8366RB_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366RB_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + +#define RTL8366RB_VLAN_TABLE_READ_BASE 0x018C +#define RTL8366RB_VLAN_TABLE_WRITE_BASE 0x0185 + +#define RTL8366RB_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366RB_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366RB_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366RB_VLAN_MC_BASE(_x) (0x0020 + (_x) * 3) + +#define RTL8366RB_PORT_LINK_STATUS_BASE 0x0014 +#define RTL8366RB_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366RB_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366RB_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366RB_PORT_STATUS_AN_MASK 0x0080 + +#define RTL8366RB_NUM_VLANS 16 +#define RTL8366RB_NUM_LEDGROUPS 4 +#define RTL8366RB_NUM_VIDS 4096 +#define RTL8366RB_PRIORITYMAX 7 +#define RTL8366RB_FIDMAX 7 + +#define RTL8366RB_PORT_1 BIT(0) /* In userspace port 0 */ +#define RTL8366RB_PORT_2 BIT(1) /* In userspace port 1 */ +#define RTL8366RB_PORT_3 BIT(2) /* In userspace port 2 */ +#define RTL8366RB_PORT_4 BIT(3) /* In userspace port 3 */ +#define RTL8366RB_PORT_5 BIT(4) /* In userspace port 4 */ + +#define RTL8366RB_PORT_CPU BIT(5) /* CPU port */ + +#define RTL8366RB_PORT_ALL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5 | \ + RTL8366RB_PORT_CPU) + +#define RTL8366RB_PORT_ALL_BUT_CPU (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5) + +#define RTL8366RB_PORT_ALL_EXTERNAL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4) + +#define RTL8366RB_PORT_ALL_INTERNAL RTL8366RB_PORT_CPU + +/* First configuration word per member config, VID and prio */ +#define RTL8366RB_VLAN_VID_MASK 0xfff +#define RTL8366RB_VLAN_PRIORITY_SHIFT 12 +#define RTL8366RB_VLAN_PRIORITY_MASK 0x7 +/* Second configuration word per member config, member and untagged */ +#define RTL8366RB_VLAN_UNTAG_SHIFT 8 +#define RTL8366RB_VLAN_UNTAG_MASK 0xff +#define RTL8366RB_VLAN_MEMBER_MASK 0xff +/* Third config word per member config, STAG currently unused */ +#define RTL8366RB_VLAN_STAG_MBR_MASK 0xff +#define RTL8366RB_VLAN_STAG_MBR_SHIFT 8 +#define RTL8366RB_VLAN_STAG_IDX_MASK 0x7 +#define RTL8366RB_VLAN_STAG_IDX_SHIFT 5 +#define RTL8366RB_VLAN_FID_MASK 0x7 + +/* Port ingress bandwidth control */ +#define RTL8366RB_IB_BASE 0x0200 +#define RTL8366RB_IB_REG(pnum) (RTL8366RB_IB_BASE + (pnum)) +#define RTL8366RB_IB_BDTH_MASK 0x3fff +#define RTL8366RB_IB_PREIFG BIT(14) + +/* Port egress bandwidth control */ +#define RTL8366RB_EB_BASE 0x02d1 +#define RTL8366RB_EB_REG(pnum) (RTL8366RB_EB_BASE + (pnum)) +#define RTL8366RB_EB_BDTH_MASK 0x3fff +#define RTL8366RB_EB_PREIFG_REG 0x02f8 +#define RTL8366RB_EB_PREIFG BIT(9) + +#define RTL8366RB_BDTH_SW_MAX 1048512 /* 1048576? */ +#define RTL8366RB_BDTH_UNIT 64 +#define RTL8366RB_BDTH_REG_DEFAULT 16383 + +/* QOS */ +#define RTL8366RB_QOS BIT(15) +/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */ +#define RTL8366RB_QOS_DEFAULT_PREIFG 1 + +/* Interrupt handling */ +#define RTL8366RB_INTERRUPT_CONTROL_REG 0x0440 +#define RTL8366RB_INTERRUPT_POLARITY BIT(0) +#define RTL8366RB_P4_RGMII_LED BIT(2) +#define RTL8366RB_INTERRUPT_MASK_REG 0x0441 +#define RTL8366RB_INTERRUPT_LINK_CHGALL GENMASK(11, 0) +#define RTL8366RB_INTERRUPT_ACLEXCEED BIT(8) +#define RTL8366RB_INTERRUPT_STORMEXCEED BIT(9) +#define RTL8366RB_INTERRUPT_P4_FIBER BIT(12) +#define RTL8366RB_INTERRUPT_P4_UTP BIT(13) +#define RTL8366RB_INTERRUPT_VALID (RTL8366RB_INTERRUPT_LINK_CHGALL | \ + RTL8366RB_INTERRUPT_ACLEXCEED | \ + RTL8366RB_INTERRUPT_STORMEXCEED | \ + RTL8366RB_INTERRUPT_P4_FIBER | \ + RTL8366RB_INTERRUPT_P4_UTP) +#define RTL8366RB_INTERRUPT_STATUS_REG 0x0442 +#define RTL8366RB_NUM_INTERRUPT 14 /* 0..13 */ + +/* bits 0..5 enable force when cleared */ +#define RTL8366RB_MAC_FORCE_CTRL_REG 0x0F11 + +#define RTL8366RB_OAM_PARSER_REG 0x0F14 +#define RTL8366RB_OAM_MULTIPLEXER_REG 0x0F15 + +#define RTL8366RB_GREEN_FEATURE_REG 0x0F51 +#define RTL8366RB_GREEN_FEATURE_MSK 0x0007 +#define RTL8366RB_GREEN_FEATURE_TX BIT(0) +#define RTL8366RB_GREEN_FEATURE_RX BIT(2) + +static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, +}; + +static int rtl8366rb_get_mib_counter(struct realtek_smi *smi, + int port, + struct rtl8366_mib_counter *mib, + u64 *mibvalue) +{ + u32 addr, val; + int ret; + int i; + + addr = RTL8366RB_MIB_COUNTER_BASE + + RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) + + mib->offset; + + /* Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + ret = regmap_write(smi->map, addr, 0); /* Write whatever */ + if (ret) + return ret; + + /* Read MIB control register */ + ret = regmap_read(smi->map, RTL8366RB_MIB_CTRL_REG, &val); + if (ret) + return -EIO; + + if (val & RTL8366RB_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (val & RTL8366RB_MIB_CTRL_RESET_MASK) + return -EIO; + + /* Read each individual MIB 16 bits at the time */ + *mibvalue = 0; + for (i = mib->length; i > 0; i--) { + ret = regmap_read(smi->map, addr + (i - 1), &val); + if (ret) + return ret; + *mibvalue = (*mibvalue << 16) | (val & 0xFFFF); + } + return 0; +} + +static u32 rtl8366rb_get_irqmask(struct irq_data *d) +{ + int line = irqd_to_hwirq(d); + u32 val; + + /* For line interrupts we combine link down in bits + * 6..11 with link up in bits 0..5 into one interrupt. + */ + if (line < 12) + val = BIT(line) | BIT(line + 6); + else + val = BIT(line); + return val; +} + +static void rtl8366rb_mask_irq(struct irq_data *d) +{ + struct realtek_smi *smi = irq_data_get_irq_chip_data(d); + int ret; + + ret = regmap_update_bits(smi->map, RTL8366RB_INTERRUPT_MASK_REG, + rtl8366rb_get_irqmask(d), 0); + if (ret) + dev_err(smi->dev, "could not mask IRQ\n"); +} + +static void rtl8366rb_unmask_irq(struct irq_data *d) +{ + struct realtek_smi *smi = irq_data_get_irq_chip_data(d); + int ret; + + ret = regmap_update_bits(smi->map, RTL8366RB_INTERRUPT_MASK_REG, + rtl8366rb_get_irqmask(d), + rtl8366rb_get_irqmask(d)); + if (ret) + dev_err(smi->dev, "could not unmask IRQ\n"); +} + +static irqreturn_t rtl8366rb_irq(int irq, void *data) +{ + struct realtek_smi *smi = data; + u32 stat; + int ret; + + /* This clears the IRQ status register */ + ret = regmap_read(smi->map, RTL8366RB_INTERRUPT_STATUS_REG, + &stat); + if (ret) { + dev_err(smi->dev, "can't read interrupt status\n"); + return IRQ_NONE; + } + stat &= RTL8366RB_INTERRUPT_VALID; + if (!stat) + return IRQ_NONE; + while (stat) { + int line = __ffs(stat); + int child_irq; + + stat &= ~BIT(line); + /* For line interrupts we combine link down in bits + * 6..11 with link up in bits 0..5 into one interrupt. + */ + if (line < 12 && line > 5) + line -= 5; + child_irq = irq_find_mapping(smi->irqdomain, line); + handle_nested_irq(child_irq); + } + return IRQ_HANDLED; +} + +static struct irq_chip rtl8366rb_irq_chip = { + .name = "RTL8366RB", + .irq_mask = rtl8366rb_mask_irq, + .irq_unmask = rtl8366rb_unmask_irq, +}; + +static int rtl8366rb_irq_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, domain->host_data); + irq_set_chip_and_handler(irq, &rtl8366rb_irq_chip, handle_simple_irq); + irq_set_nested_thread(irq, 1); + irq_set_noprobe(irq); + + return 0; +} + +static void rtl8366rb_irq_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_nested_thread(irq, 0); + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops rtl8366rb_irqdomain_ops = { + .map = rtl8366rb_irq_map, + .unmap = rtl8366rb_irq_unmap, + .xlate = irq_domain_xlate_onecell, +}; + +static int rtl8366rb_setup_cascaded_irq(struct realtek_smi *smi) +{ + struct device_node *intc; + unsigned long irq_trig; + int irq; + int ret; + u32 val; + int i; + + intc = of_get_child_by_name(smi->dev->of_node, "interrupt-controller"); + if (!intc) { + dev_err(smi->dev, "missing child interrupt-controller node\n"); + return -EINVAL; + } + /* RB8366RB IRQs cascade off this one */ + irq = of_irq_get(intc, 0); + if (irq <= 0) { + dev_err(smi->dev, "failed to get parent IRQ\n"); + return irq ? irq : -EINVAL; + } + + /* This clears the IRQ status register */ + ret = regmap_read(smi->map, RTL8366RB_INTERRUPT_STATUS_REG, + &val); + if (ret) { + dev_err(smi->dev, "can't read interrupt status\n"); + return ret; + } + + /* Fetch IRQ edge information from the descriptor */ + irq_trig = irqd_get_trigger_type(irq_get_irq_data(irq)); + switch (irq_trig) { + case IRQF_TRIGGER_RISING: + case IRQF_TRIGGER_HIGH: + dev_info(smi->dev, "active high/rising IRQ\n"); + val = 0; + break; + case IRQF_TRIGGER_FALLING: + case IRQF_TRIGGER_LOW: + dev_info(smi->dev, "active low/falling IRQ\n"); + val = RTL8366RB_INTERRUPT_POLARITY; + break; + } + ret = regmap_update_bits(smi->map, RTL8366RB_INTERRUPT_CONTROL_REG, + RTL8366RB_INTERRUPT_POLARITY, + val); + if (ret) { + dev_err(smi->dev, "could not configure IRQ polarity\n"); + return ret; + } + + ret = devm_request_threaded_irq(smi->dev, irq, NULL, + rtl8366rb_irq, IRQF_ONESHOT, + "RTL8366RB", smi); + if (ret) { + dev_err(smi->dev, "unable to request irq: %d\n", ret); + return ret; + } + smi->irqdomain = irq_domain_add_linear(intc, + RTL8366RB_NUM_INTERRUPT, + &rtl8366rb_irqdomain_ops, + smi); + if (!smi->irqdomain) { + dev_err(smi->dev, "failed to create IRQ domain\n"); + return -EINVAL; + } + for (i = 0; i < smi->num_ports; i++) + irq_set_parent(irq_create_mapping(smi->irqdomain, i), irq); + + return 0; +} + +static int rtl8366rb_set_addr(struct realtek_smi *smi) +{ + u8 addr[ETH_ALEN]; + u16 val; + int ret; + + eth_random_addr(addr); + + dev_info(smi->dev, "set MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + val = addr[0] << 8 | addr[1]; + ret = regmap_write(smi->map, RTL8366RB_SMAR0, val); + if (ret) + return ret; + val = addr[2] << 8 | addr[3]; + ret = regmap_write(smi->map, RTL8366RB_SMAR1, val); + if (ret) + return ret; + val = addr[4] << 8 | addr[5]; + ret = regmap_write(smi->map, RTL8366RB_SMAR2, val); + if (ret) + return ret; + + return 0; +} + +/* Found in a vendor driver */ + +/* For the "version 0" early silicon, appear in most source releases */ +static const u16 rtl8366rb_init_jam_ver_0[] = { + 0x000B, 0x0001, 0x03A6, 0x0100, 0x03A7, 0x0001, 0x02D1, 0x3FFF, + 0x02D2, 0x3FFF, 0x02D3, 0x3FFF, 0x02D4, 0x3FFF, 0x02D5, 0x3FFF, + 0x02D6, 0x3FFF, 0x02D7, 0x3FFF, 0x02D8, 0x3FFF, 0x022B, 0x0688, + 0x022C, 0x0FAC, 0x03D0, 0x4688, 0x03D1, 0x01F5, 0x0000, 0x0830, + 0x02F9, 0x0200, 0x02F7, 0x7FFF, 0x02F8, 0x03FF, 0x0080, 0x03E8, + 0x0081, 0x00CE, 0x0082, 0x00DA, 0x0083, 0x0230, 0xBE0F, 0x2000, + 0x0231, 0x422A, 0x0232, 0x422A, 0x0233, 0x422A, 0x0234, 0x422A, + 0x0235, 0x422A, 0x0236, 0x422A, 0x0237, 0x422A, 0x0238, 0x422A, + 0x0239, 0x422A, 0x023A, 0x422A, 0x023B, 0x422A, 0x023C, 0x422A, + 0x023D, 0x422A, 0x023E, 0x422A, 0x023F, 0x422A, 0x0240, 0x422A, + 0x0241, 0x422A, 0x0242, 0x422A, 0x0243, 0x422A, 0x0244, 0x422A, + 0x0245, 0x422A, 0x0246, 0x422A, 0x0247, 0x422A, 0x0248, 0x422A, + 0x0249, 0x0146, 0x024A, 0x0146, 0x024B, 0x0146, 0xBE03, 0xC961, + 0x024D, 0x0146, 0x024E, 0x0146, 0x024F, 0x0146, 0x0250, 0x0146, + 0xBE64, 0x0226, 0x0252, 0x0146, 0x0253, 0x0146, 0x024C, 0x0146, + 0x0251, 0x0146, 0x0254, 0x0146, 0xBE62, 0x3FD0, 0x0084, 0x0320, + 0x0255, 0x0146, 0x0256, 0x0146, 0x0257, 0x0146, 0x0258, 0x0146, + 0x0259, 0x0146, 0x025A, 0x0146, 0x025B, 0x0146, 0x025C, 0x0146, + 0x025D, 0x0146, 0x025E, 0x0146, 0x025F, 0x0146, 0x0260, 0x0146, + 0x0261, 0xA23F, 0x0262, 0x0294, 0x0263, 0xA23F, 0x0264, 0x0294, + 0x0265, 0xA23F, 0x0266, 0x0294, 0x0267, 0xA23F, 0x0268, 0x0294, + 0x0269, 0xA23F, 0x026A, 0x0294, 0x026B, 0xA23F, 0x026C, 0x0294, + 0x026D, 0xA23F, 0x026E, 0x0294, 0x026F, 0xA23F, 0x0270, 0x0294, + 0x02F5, 0x0048, 0xBE09, 0x0E00, 0xBE1E, 0x0FA0, 0xBE14, 0x8448, + 0xBE15, 0x1007, 0xBE4A, 0xA284, 0xC454, 0x3F0B, 0xC474, 0x3F0B, + 0xBE48, 0x3672, 0xBE4B, 0x17A7, 0xBE4C, 0x0B15, 0xBE52, 0x0EDD, + 0xBE49, 0x8C00, 0xBE5B, 0x785C, 0xBE5C, 0x785C, 0xBE5D, 0x785C, + 0xBE61, 0x368A, 0xBE63, 0x9B84, 0xC456, 0xCC13, 0xC476, 0xCC13, + 0xBE65, 0x307D, 0xBE6D, 0x0005, 0xBE6E, 0xE120, 0xBE2E, 0x7BAF, +}; + +/* This v1 init sequence is from Belkin F5D8235 U-Boot release */ +static const u16 rtl8366rb_init_jam_ver_1[] = { + 0x0000, 0x0830, 0x0001, 0x8000, 0x0400, 0x8130, 0xBE78, 0x3C3C, + 0x0431, 0x5432, 0xBE37, 0x0CE4, 0x02FA, 0xFFDF, 0x02FB, 0xFFE0, + 0xC44C, 0x1585, 0xC44C, 0x1185, 0xC44C, 0x1585, 0xC46C, 0x1585, + 0xC46C, 0x1185, 0xC46C, 0x1585, 0xC451, 0x2135, 0xC471, 0x2135, + 0xBE10, 0x8140, 0xBE15, 0x0007, 0xBE6E, 0xE120, 0xBE69, 0xD20F, + 0xBE6B, 0x0320, 0xBE24, 0xB000, 0xBE23, 0xFF51, 0xBE22, 0xDF20, + 0xBE21, 0x0140, 0xBE20, 0x00BB, 0xBE24, 0xB800, 0xBE24, 0x0000, + 0xBE24, 0x7000, 0xBE23, 0xFF51, 0xBE22, 0xDF60, 0xBE21, 0x0140, + 0xBE20, 0x0077, 0xBE24, 0x7800, 0xBE24, 0x0000, 0xBE2E, 0x7B7A, + 0xBE36, 0x0CE4, 0x02F5, 0x0048, 0xBE77, 0x2940, 0x000A, 0x83E0, + 0xBE79, 0x3C3C, 0xBE00, 0x1340, +}; + +/* This v2 init sequence is from Belkin F5D8235 U-Boot release */ +static const u16 rtl8366rb_init_jam_ver_2[] = { + 0x0450, 0x0000, 0x0400, 0x8130, 0x000A, 0x83ED, 0x0431, 0x5432, + 0xC44F, 0x6250, 0xC46F, 0x6250, 0xC456, 0x0C14, 0xC476, 0x0C14, + 0xC44C, 0x1C85, 0xC44C, 0x1885, 0xC44C, 0x1C85, 0xC46C, 0x1C85, + 0xC46C, 0x1885, 0xC46C, 0x1C85, 0xC44C, 0x0885, 0xC44C, 0x0881, + 0xC44C, 0x0885, 0xC46C, 0x0885, 0xC46C, 0x0881, 0xC46C, 0x0885, + 0xBE2E, 0x7BA7, 0xBE36, 0x1000, 0xBE37, 0x1000, 0x8000, 0x0001, + 0xBE69, 0xD50F, 0x8000, 0x0000, 0xBE69, 0xD50F, 0xBE6E, 0x0320, + 0xBE77, 0x2940, 0xBE78, 0x3C3C, 0xBE79, 0x3C3C, 0xBE6E, 0xE120, + 0x8000, 0x0001, 0xBE15, 0x1007, 0x8000, 0x0000, 0xBE15, 0x1007, + 0xBE14, 0x0448, 0xBE1E, 0x00A0, 0xBE10, 0x8160, 0xBE10, 0x8140, + 0xBE00, 0x1340, 0x0F51, 0x0010, +}; + +/* Appears in a DDWRT code dump */ +static const u16 rtl8366rb_init_jam_ver_3[] = { + 0x0000, 0x0830, 0x0400, 0x8130, 0x000A, 0x83ED, 0x0431, 0x5432, + 0x0F51, 0x0017, 0x02F5, 0x0048, 0x02FA, 0xFFDF, 0x02FB, 0xFFE0, + 0xC456, 0x0C14, 0xC476, 0x0C14, 0xC454, 0x3F8B, 0xC474, 0x3F8B, + 0xC450, 0x2071, 0xC470, 0x2071, 0xC451, 0x226B, 0xC471, 0x226B, + 0xC452, 0xA293, 0xC472, 0xA293, 0xC44C, 0x1585, 0xC44C, 0x1185, + 0xC44C, 0x1585, 0xC46C, 0x1585, 0xC46C, 0x1185, 0xC46C, 0x1585, + 0xC44C, 0x0185, 0xC44C, 0x0181, 0xC44C, 0x0185, 0xC46C, 0x0185, + 0xC46C, 0x0181, 0xC46C, 0x0185, 0xBE24, 0xB000, 0xBE23, 0xFF51, + 0xBE22, 0xDF20, 0xBE21, 0x0140, 0xBE20, 0x00BB, 0xBE24, 0xB800, + 0xBE24, 0x0000, 0xBE24, 0x7000, 0xBE23, 0xFF51, 0xBE22, 0xDF60, + 0xBE21, 0x0140, 0xBE20, 0x0077, 0xBE24, 0x7800, 0xBE24, 0x0000, + 0xBE2E, 0x7BA7, 0xBE36, 0x1000, 0xBE37, 0x1000, 0x8000, 0x0001, + 0xBE69, 0xD50F, 0x8000, 0x0000, 0xBE69, 0xD50F, 0xBE6B, 0x0320, + 0xBE77, 0x2800, 0xBE78, 0x3C3C, 0xBE79, 0x3C3C, 0xBE6E, 0xE120, + 0x8000, 0x0001, 0xBE10, 0x8140, 0x8000, 0x0000, 0xBE10, 0x8140, + 0xBE15, 0x1007, 0xBE14, 0x0448, 0xBE1E, 0x00A0, 0xBE10, 0x8160, + 0xBE10, 0x8140, 0xBE00, 0x1340, 0x0450, 0x0000, 0x0401, 0x0000, +}; + +/* Belkin F5D8235 v1, "belkin,f5d8235-v1" */ +static const u16 rtl8366rb_init_jam_f5d8235[] = { + 0x0242, 0x02BF, 0x0245, 0x02BF, 0x0248, 0x02BF, 0x024B, 0x02BF, + 0x024E, 0x02BF, 0x0251, 0x02BF, 0x0254, 0x0A3F, 0x0256, 0x0A3F, + 0x0258, 0x0A3F, 0x025A, 0x0A3F, 0x025C, 0x0A3F, 0x025E, 0x0A3F, + 0x0263, 0x007C, 0x0100, 0x0004, 0xBE5B, 0x3500, 0x800E, 0x200F, + 0xBE1D, 0x0F00, 0x8001, 0x5011, 0x800A, 0xA2F4, 0x800B, 0x17A3, + 0xBE4B, 0x17A3, 0xBE41, 0x5011, 0xBE17, 0x2100, 0x8000, 0x8304, + 0xBE40, 0x8304, 0xBE4A, 0xA2F4, 0x800C, 0xA8D5, 0x8014, 0x5500, + 0x8015, 0x0004, 0xBE4C, 0xA8D5, 0xBE59, 0x0008, 0xBE09, 0x0E00, + 0xBE36, 0x1036, 0xBE37, 0x1036, 0x800D, 0x00FF, 0xBE4D, 0x00FF, +}; + +/* DGN3500, "netgear,dgn3500", "netgear,dgn3500b" */ +static const u16 rtl8366rb_init_jam_dgn3500[] = { + 0x0000, 0x0830, 0x0400, 0x8130, 0x000A, 0x83ED, 0x0F51, 0x0017, + 0x02F5, 0x0048, 0x02FA, 0xFFDF, 0x02FB, 0xFFE0, 0x0450, 0x0000, + 0x0401, 0x0000, 0x0431, 0x0960, +}; + +/* This jam table activates "green ethernet", which means low power mode + * and is claimed to detect the cable length and not use more power than + * necessary, and the ports should enter power saving mode 10 seconds after + * a cable is disconnected. Seems to always be the same. + */ +static const u16 rtl8366rb_green_jam[][2] = { + {0xBE78, 0x323C}, {0xBE77, 0x5000}, {0xBE2E, 0x7BA7}, + {0xBE59, 0x3459}, {0xBE5A, 0x745A}, {0xBE5B, 0x785C}, + {0xBE5C, 0x785C}, {0xBE6E, 0xE120}, {0xBE79, 0x323C}, +}; + +static int rtl8366rb_setup(struct dsa_switch *ds) +{ + struct realtek_smi *smi = ds->priv; + const u16 *jam_table; + u32 chip_ver = 0; + u32 chip_id = 0; + int jam_size; + u32 val; + int ret; + int i; + + ret = regmap_read(smi->map, RTL8366RB_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->dev, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366RB_CHIP_ID_8366: + break; + default: + dev_err(smi->dev, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = regmap_read(smi->map, RTL8366RB_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->dev, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->dev, "RTL%04x ver %u chip found\n", + chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK); + + /* Do the init dance using the right jam table */ + switch (chip_ver) { + case 0: + jam_table = rtl8366rb_init_jam_ver_0; + jam_size = ARRAY_SIZE(rtl8366rb_init_jam_ver_0); + break; + case 1: + jam_table = rtl8366rb_init_jam_ver_1; + jam_size = ARRAY_SIZE(rtl8366rb_init_jam_ver_1); + break; + case 2: + jam_table = rtl8366rb_init_jam_ver_2; + jam_size = ARRAY_SIZE(rtl8366rb_init_jam_ver_2); + break; + default: + jam_table = rtl8366rb_init_jam_ver_3; + jam_size = ARRAY_SIZE(rtl8366rb_init_jam_ver_3); + break; + } + + /* Special jam tables for special routers + * TODO: are these necessary? Maintainers, please test + * without them, using just the off-the-shelf tables. + */ + if (of_machine_is_compatible("belkin,f5d8235-v1")) { + jam_table = rtl8366rb_init_jam_f5d8235; + jam_size = ARRAY_SIZE(rtl8366rb_init_jam_f5d8235); + } + if (of_machine_is_compatible("netgear,dgn3500") || + of_machine_is_compatible("netgear,dgn3500b")) { + jam_table = rtl8366rb_init_jam_dgn3500; + jam_size = ARRAY_SIZE(rtl8366rb_init_jam_dgn3500); + } + + i = 0; + while (i < jam_size) { + if ((jam_table[i] & 0xBE00) == 0xBE00) { + ret = regmap_read(smi->map, + RTL8366RB_PHY_ACCESS_BUSY_REG, + &val); + if (ret) + return ret; + if (!(val & RTL8366RB_PHY_INT_BUSY)) { + ret = regmap_write(smi->map, + RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_WRITE); + if (ret) + return ret; + } + } + dev_dbg(smi->dev, "jam %04x into register %04x\n", + jam_table[i + 1], + jam_table[i]); + ret = regmap_write(smi->map, + jam_table[i], + jam_table[i + 1]); + if (ret) + return ret; + i += 2; + } + + /* Set up the "green ethernet" feature */ + i = 0; + while (i < ARRAY_SIZE(rtl8366rb_green_jam)) { + ret = regmap_read(smi->map, RTL8366RB_PHY_ACCESS_BUSY_REG, + &val); + if (ret) + return ret; + if (!(val & RTL8366RB_PHY_INT_BUSY)) { + ret = regmap_write(smi->map, + RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_WRITE); + if (ret) + return ret; + ret = regmap_write(smi->map, + rtl8366rb_green_jam[i][0], + rtl8366rb_green_jam[i][1]); + if (ret) + return ret; + i++; + } + } + ret = regmap_write(smi->map, + RTL8366RB_GREEN_FEATURE_REG, + (chip_ver == 1) ? 0x0007 : 0x0003); + if (ret) + return ret; + + /* Vendor driver sets 0x240 in registers 0xc and 0xd (undocumented) */ + ret = regmap_write(smi->map, 0x0c, 0x240); + if (ret) + return ret; + ret = regmap_write(smi->map, 0x0d, 0x240); + if (ret) + return ret; + + /* Set some random MAC address */ + ret = rtl8366rb_set_addr(smi); + if (ret) + return ret; + + /* Enable CPU port and enable inserting CPU tag + * + * Disabling RTL8368RB_CPU_INSTAG here will change the behaviour + * of the switch totally and it will start talking Realtek RRCP + * internally. It is probably possible to experiment with this, + * but then the kernel needs to understand and handle RRCP first. + */ + ret = regmap_update_bits(smi->map, RTL8368RB_CPU_CTRL_REG, + 0xFFFF, + RTL8368RB_CPU_INSTAG | BIT(smi->cpu_port)); + if (ret) + return ret; + + /* Make sure we default-enable the fixed CPU port */ + ret = regmap_update_bits(smi->map, RTL8366RB_PECR, + BIT(smi->cpu_port), + 0); + if (ret) + return ret; + + /* Set maximum packet length to 1536 bytes */ + ret = regmap_update_bits(smi->map, RTL8366RB_SGCR, + RTL8366RB_SGCR_MAX_LENGTH_MASK, + RTL8366RB_SGCR_MAX_LENGTH_1536); + if (ret) + return ret; + + /* Enable learning for all ports */ + ret = regmap_write(smi->map, RTL8366RB_SSCR0, 0); + if (ret) + return ret; + + /* Enable auto ageing for all ports */ + ret = regmap_write(smi->map, RTL8366RB_SSCR1, 0); + if (ret) + return ret; + + /* Discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + ret = regmap_write(smi->map, RTL8366RB_VLAN_INGRESS_CTRL2_REG, + RTL8366RB_PORT_ALL); + if (ret) + return ret; + + /* Don't drop packets whose DA has not been learned */ + ret = regmap_update_bits(smi->map, RTL8366RB_SSCR2, + RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0); + if (ret) + return ret; + + /* Set blinking, TODO: make this configurable */ + ret = regmap_update_bits(smi->map, RTL8366RB_LED_BLINKRATE_REG, + RTL8366RB_LED_BLINKRATE_MASK, + RTL8366RB_LED_BLINKRATE_56MS); + if (ret) + return ret; + + /* Set up LED activity: + * Each port has 4 LEDs, we configure all ports to the same + * behaviour (no individual config) but we can set up each + * LED separately. + */ + if (smi->leds_disabled) { + /* Turn everything off */ + regmap_update_bits(smi->map, + RTL8366RB_LED_0_1_CTRL_REG, + 0x0FFF, 0); + regmap_update_bits(smi->map, + RTL8366RB_LED_2_3_CTRL_REG, + 0x0FFF, 0); + regmap_update_bits(smi->map, + RTL8366RB_INTERRUPT_CONTROL_REG, + RTL8366RB_P4_RGMII_LED, + 0); + val = RTL8366RB_LED_OFF; + } else { + /* TODO: make this configurable per LED */ + val = RTL8366RB_LED_FORCE; + } + for (i = 0; i < 4; i++) { + ret = regmap_update_bits(smi->map, + RTL8366RB_LED_CTRL_REG, + 0xf << (i * 4), + val << (i * 4)); + if (ret) + return ret; + } + + ret = rtl8366_init_vlan(smi); + if (ret) + return ret; + + ret = rtl8366rb_setup_cascaded_irq(smi); + if (ret) + dev_info(smi->dev, "no interrupt support\n"); + + ret = realtek_smi_setup_mdio(smi); + if (ret) { + dev_info(smi->dev, "could not set up MDIO bus\n"); + return -ENODEV; + } + + return 0; +} + +static enum dsa_tag_protocol rtl8366_get_tag_protocol(struct dsa_switch *ds, + int port) +{ + /* For now, the RTL switches are handled without any custom tags. + * + * It is possible to turn on "custom tags" by removing the + * RTL8368RB_CPU_INSTAG flag when enabling the port but what it + * does is unfamiliar to DSA: ethernet frames of type 8899, the Realtek + * Remote Control Protocol (RRCP) start to appear on the CPU port of + * the device. So this is not the ordinary few extra bytes in the + * frame. Instead it appears that the switch starts to talk Realtek + * RRCP internally which means a pretty complex RRCP implementation + * decoding and responding the RRCP protocol is needed to exploit this. + * + * The OpenRRCP project (dormant since 2009) have reverse-egineered + * parts of the protocol. + */ + return DSA_TAG_PROTO_NONE; +} + +static void rtl8366rb_adjust_link(struct dsa_switch *ds, int port, + struct phy_device *phydev) +{ + struct realtek_smi *smi = ds->priv; + int ret; + + if (port != smi->cpu_port) + return; + + dev_info(smi->dev, "adjust link on CPU port (%d)\n", port); + + /* Force the fixed CPU port into 1Gbit mode, no autonegotiation */ + ret = regmap_update_bits(smi->map, RTL8366RB_MAC_FORCE_CTRL_REG, + BIT(port), BIT(port)); + if (ret) + return; + + ret = regmap_update_bits(smi->map, RTL8366RB_PAACR2, + 0xFF00U, + RTL8366RB_PAACR_CPU_PORT << 8); + if (ret) + return; + + /* Enable the CPU port */ + ret = regmap_update_bits(smi->map, RTL8366RB_PECR, BIT(port), + 0); + if (ret) + return; +} + +static void rb8366rb_set_port_led(struct realtek_smi *smi, + int port, bool enable) +{ + u16 val = enable ? 0x3f : 0; + int ret; + + if (smi->leds_disabled) + return; + + switch (port) { + case 0: + ret = regmap_update_bits(smi->map, + RTL8366RB_LED_0_1_CTRL_REG, + 0x3F, val); + break; + case 1: + ret = regmap_update_bits(smi->map, + RTL8366RB_LED_0_1_CTRL_REG, + 0x3F << RTL8366RB_LED_1_OFFSET, + val << RTL8366RB_LED_1_OFFSET); + break; + case 2: + ret = regmap_update_bits(smi->map, + RTL8366RB_LED_2_3_CTRL_REG, + 0x3F, val); + break; + case 3: + ret = regmap_update_bits(smi->map, + RTL8366RB_LED_2_3_CTRL_REG, + 0x3F << RTL8366RB_LED_3_OFFSET, + val << RTL8366RB_LED_3_OFFSET); + break; + case 4: + ret = regmap_update_bits(smi->map, + RTL8366RB_INTERRUPT_CONTROL_REG, + RTL8366RB_P4_RGMII_LED, + enable ? RTL8366RB_P4_RGMII_LED : 0); + break; + default: + dev_err(smi->dev, "no LED for port %d\n", port); + return; + } + if (ret) + dev_err(smi->dev, "error updating LED on port %d\n", port); +} + +static int +rtl8366rb_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct realtek_smi *smi = ds->priv; + int ret; + + dev_dbg(smi->dev, "enable port %d\n", port); + ret = regmap_update_bits(smi->map, RTL8366RB_PECR, BIT(port), + 0); + if (ret) + return ret; + + rb8366rb_set_port_led(smi, port, true); + return 0; +} + +static void +rtl8366rb_port_disable(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct realtek_smi *smi = ds->priv; + int ret; + + dev_dbg(smi->dev, "disable port %d\n", port); + ret = regmap_update_bits(smi->map, RTL8366RB_PECR, BIT(port), + BIT(port)); + if (ret) + return; + + rb8366rb_set_port_led(smi, port, false); +} + +static int rtl8366rb_get_vlan_4k(struct realtek_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int ret; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366RB_NUM_VIDS) + return -EINVAL; + + /* write VID */ + ret = regmap_write(smi->map, RTL8366RB_VLAN_TABLE_WRITE_BASE, + vid & RTL8366RB_VLAN_VID_MASK); + if (ret) + return ret; + + /* write table access control word */ + ret = regmap_write(smi->map, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_READ_CTRL); + if (ret) + return ret; + + for (i = 0; i < 3; i++) { + ret = regmap_read(smi->map, + RTL8366RB_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (ret) + return ret; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_4k(struct realtek_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int ret; + int i; + + if (vlan4k->vid >= RTL8366RB_NUM_VIDS || + vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + ret = regmap_write(smi->map, + RTL8366RB_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (ret) + return ret; + } + + /* write table access control word */ + ret = regmap_write(smi->map, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_WRITE_CTRL); + + return ret; +} + +static int rtl8366rb_get_vlan_mc(struct realtek_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int ret; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 3; i++) { + ret = regmap_read(smi->map, + RTL8366RB_VLAN_MC_BASE(index) + i, + &data[i]); + if (ret) + return ret; + } + + vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) & + RTL8366RB_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_mc(struct realtek_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int ret; + int i; + + if (index >= RTL8366RB_NUM_VLANS || + vlanmc->vid >= RTL8366RB_NUM_VIDS || + vlanmc->priority > RTL8366RB_PRIORITYMAX || + vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) << + RTL8366RB_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + ret = regmap_write(smi->map, + RTL8366RB_VLAN_MC_BASE(index) + i, + data[i]); + if (ret) + return ret; + } + + return 0; +} + +static int rtl8366rb_get_mc_index(struct realtek_smi *smi, int port, int *val) +{ + u32 data; + int ret; + + if (port >= smi->num_ports) + return -EINVAL; + + ret = regmap_read(smi->map, RTL8366RB_PORT_VLAN_CTRL_REG(port), + &data); + if (ret) + return ret; + + *val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366RB_PORT_VLAN_CTRL_MASK; + + return 0; +} + +static int rtl8366rb_set_mc_index(struct realtek_smi *smi, int port, int index) +{ + if (port >= smi->num_ports || index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + return regmap_update_bits(smi->map, RTL8366RB_PORT_VLAN_CTRL_REG(port), + RTL8366RB_PORT_VLAN_CTRL_MASK << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366RB_PORT_VLAN_CTRL_MASK) << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)); +} + +static bool rtl8366rb_is_vlan_valid(struct realtek_smi *smi, unsigned int vlan) +{ + unsigned int max = RTL8366RB_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366RB_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return false; + + return true; +} + +static int rtl8366rb_enable_vlan(struct realtek_smi *smi, bool enable) +{ + dev_dbg(smi->dev, "%s VLAN\n", enable ? "enable" : "disable"); + return regmap_update_bits(smi->map, + RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN, + enable ? RTL8366RB_SGCR_EN_VLAN : 0); +} + +static int rtl8366rb_enable_vlan4k(struct realtek_smi *smi, bool enable) +{ + dev_dbg(smi->dev, "%s VLAN 4k\n", enable ? "enable" : "disable"); + return regmap_update_bits(smi->map, RTL8366RB_SGCR, + RTL8366RB_SGCR_EN_VLAN_4KTB, + enable ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0); +} + +static int rtl8366rb_phy_read(struct realtek_smi *smi, int phy, int regnum) +{ + u32 val; + u32 reg; + int ret; + + if (phy > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + ret = regmap_write(smi->map, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy + RTL8366RB_PHY_NO_OFFSET)) | regnum; + + ret = regmap_write(smi->map, reg, 0); + if (ret) { + dev_err(smi->dev, + "failed to write PHY%d reg %04x @ %04x, ret %d\n", + phy, regnum, reg, ret); + return ret; + } + + ret = regmap_read(smi->map, RTL8366RB_PHY_ACCESS_DATA_REG, &val); + if (ret) + return ret; + + dev_dbg(smi->dev, "read PHY%d register 0x%04x @ %08x, val <- %04x\n", + phy, regnum, reg, val); + + return val; +} + +static int rtl8366rb_phy_write(struct realtek_smi *smi, int phy, int regnum, + u16 val) +{ + u32 reg; + int ret; + + if (phy > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + ret = regmap_write(smi->map, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy + RTL8366RB_PHY_NO_OFFSET)) | regnum; + + dev_dbg(smi->dev, "write PHY%d register 0x%04x @ %04x, val -> %04x\n", + phy, regnum, reg, val); + + ret = regmap_write(smi->map, reg, val); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_reset_chip(struct realtek_smi *smi) +{ + int timeout = 10; + u32 val; + int ret; + + realtek_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG, + RTL8366RB_CHIP_CTRL_RESET_HW); + do { + usleep_range(20000, 25000); + ret = regmap_read(smi->map, RTL8366RB_RESET_CTRL_REG, &val); + if (ret) + return ret; + + if (!(val & RTL8366RB_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + dev_err(smi->dev, "timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366rb_detect(struct realtek_smi *smi) +{ + struct device *dev = smi->dev; + int ret; + u32 val; + + /* Detect device */ + ret = regmap_read(smi->map, 0x5c, &val); + if (ret) { + dev_err(dev, "can't get chip ID (%d)\n", ret); + return ret; + } + + switch (val) { + case 0x6027: + dev_info(dev, "found an RTL8366S switch\n"); + dev_err(dev, "this switch is not yet supported, submit patches!\n"); + return -ENODEV; + case 0x5937: + dev_info(dev, "found an RTL8366RB switch\n"); + smi->cpu_port = RTL8366RB_PORT_NUM_CPU; + smi->num_ports = RTL8366RB_NUM_PORTS; + smi->num_vlan_mc = RTL8366RB_NUM_VLANS; + smi->mib_counters = rtl8366rb_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters); + break; + default: + dev_info(dev, "found an Unknown Realtek switch (id=0x%04x)\n", + val); + break; + } + + ret = rtl8366rb_reset_chip(smi); + if (ret) + return ret; + + return 0; +} + +static const struct dsa_switch_ops rtl8366rb_switch_ops = { + .get_tag_protocol = rtl8366_get_tag_protocol, + .setup = rtl8366rb_setup, + .adjust_link = rtl8366rb_adjust_link, + .get_strings = rtl8366_get_strings, + .get_ethtool_stats = rtl8366_get_ethtool_stats, + .get_sset_count = rtl8366_get_sset_count, + .port_vlan_filtering = rtl8366_vlan_filtering, + .port_vlan_prepare = rtl8366_vlan_prepare, + .port_vlan_add = rtl8366_vlan_add, + .port_vlan_del = rtl8366_vlan_del, + .port_enable = rtl8366rb_port_enable, + .port_disable = rtl8366rb_port_disable, +}; + +static const struct realtek_smi_ops rtl8366rb_smi_ops = { + .detect = rtl8366rb_detect, + .get_vlan_mc = rtl8366rb_get_vlan_mc, + .set_vlan_mc = rtl8366rb_set_vlan_mc, + .get_vlan_4k = rtl8366rb_get_vlan_4k, + .set_vlan_4k = rtl8366rb_set_vlan_4k, + .get_mc_index = rtl8366rb_get_mc_index, + .set_mc_index = rtl8366rb_set_mc_index, + .get_mib_counter = rtl8366rb_get_mib_counter, + .is_vlan_valid = rtl8366rb_is_vlan_valid, + .enable_vlan = rtl8366rb_enable_vlan, + .enable_vlan4k = rtl8366rb_enable_vlan4k, + .phy_read = rtl8366rb_phy_read, + .phy_write = rtl8366rb_phy_write, +}; + +const struct realtek_smi_variant rtl8366rb_variant = { + .ds_ops = &rtl8366rb_switch_ops, + .ops = &rtl8366rb_smi_ops, + .clk_delay = 10, + .cmd_read = 0xa9, + .cmd_write = 0xa8, +}; +EXPORT_SYMBOL_GPL(rtl8366rb_variant); -- cgit v1.2.3-59-g8ed1b From e1548b1ba1643d4b5015f7168c64f240ebe114c8 Mon Sep 17 00:00:00 2001 From: Arnaud Pouliquen Date: Fri, 20 Jul 2018 10:02:50 +0200 Subject: MAINTAINERS: add entry for STI audio drivers Add sound/soc/sti drivers entry for STI audio drivers from ST Microelectronics. Signed-off-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9d5eeff51b5f..b44d7167a9e9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13557,6 +13557,13 @@ L: linux-block@vger.kernel.org S: Maintained F: drivers/block/skd*[ch] +STI AUDIO (ASoC) DRIVERS +M: Arnaud Pouliquen +L: alsa-devel@alsa-project.org (moderated for non-subscribers) +S: Maintained +F: Documentation/devicetree/bindings/sound/st,sti-asoc-card.txt +F: sound/soc/sti/ + STI CEC DRIVER M: Benjamin Gaignard S: Maintained -- cgit v1.2.3-59-g8ed1b From faa80b66c767ef4995e33835e9844a5df142c6cc Mon Sep 17 00:00:00 2001 From: Arnaud Pouliquen Date: Fri, 20 Jul 2018 10:02:51 +0200 Subject: MAINTAINERS: add entry for STM32 audio drivers Add sound/soc/stm drivers entry for STM32 audio drivers from ST Microelectronics. Signed-off-by: Arnaud Pouliquen Signed-off-by: Mark Brown --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index b44d7167a9e9..fc711d23dc83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13577,6 +13577,14 @@ T: git git://linuxtv.org/media_tree.git S: Maintained F: drivers/media/usb/stk1160/ +STM32 AUDIO (ASoC) DRIVERS +M: Olivier Moysan +M: Arnaud Pouliquen +L: alsa-devel@alsa-project.org (moderated for non-subscribers) +S: Maintained +F: Documentation/devicetree/bindings/sound/st,stm32-*.txt +F: sound/soc/stm/ + STM32 TIMER/LPTIMER DRIVERS M: Fabrice Gasnier S: Maintained -- cgit v1.2.3-59-g8ed1b From 256bf813ba39f7f9277a5cd05b5c152dbbaf4aae Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 20 Jul 2018 03:52:50 -0400 Subject: media: vicodec: add the virtual codec driver Add the virtual codec driver that uses the Fast Walsh Hadamard Transform. Keiichi Watanabe contributed the multiplanar support. Signed-off-by: Hans Verkuil Co-Developed-by: Keiichi Watanabe Signed-off-by: Keiichi Watanabe Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 8 + drivers/media/platform/Kconfig | 3 + drivers/media/platform/Makefile | 1 + drivers/media/platform/vicodec/Kconfig | 13 + drivers/media/platform/vicodec/Makefile | 4 + drivers/media/platform/vicodec/vicodec-core.c | 1506 +++++++++++++++++++++++++ 6 files changed, 1535 insertions(+) create mode 100644 drivers/media/platform/vicodec/Kconfig create mode 100644 drivers/media/platform/vicodec/Makefile create mode 100644 drivers/media/platform/vicodec/vicodec-core.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index bbd9b9b3d74f..67781db8b077 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15153,6 +15153,14 @@ L: netdev@vger.kernel.org S: Maintained F: drivers/net/ethernet/via/via-velocity.* +VICODEC VIRTUAL CODEC DRIVER +M: Hans Verkuil +L: linux-media@vger.kernel.org +T: git git://linuxtv.org/media_tree.git +W: https://linuxtv.org +S: Maintained +F: drivers/media/platform/vicodec/* + VIDEO MULTIPLEXER DRIVER M: Philipp Zabel L: linux-media@vger.kernel.org diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 84cb97eccb2a..40bd3d96ca82 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -514,6 +514,9 @@ config VIDEO_VIM2M ---help--- This is a virtual test device for the memory-to-memory driver framework. + +source "drivers/media/platform/vicodec/Kconfig" + endif #V4L_TEST_DRIVERS menuconfig DVB_PLATFORM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 04bc1502a30e..8e5f88eb7559 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o obj-$(CONFIG_VIDEO_VIMC) += vimc/ obj-$(CONFIG_VIDEO_VIVID) += vivid/ obj-$(CONFIG_VIDEO_VIM2M) += vim2m.o +obj-$(CONFIG_VIDEO_VICODEC) += vicodec/ obj-$(CONFIG_VIDEO_TI_VPE) += ti-vpe/ diff --git a/drivers/media/platform/vicodec/Kconfig b/drivers/media/platform/vicodec/Kconfig new file mode 100644 index 000000000000..2503bcb1529f --- /dev/null +++ b/drivers/media/platform/vicodec/Kconfig @@ -0,0 +1,13 @@ +config VIDEO_VICODEC + tristate "Virtual Codec Driver" + depends on VIDEO_DEV && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_VMALLOC + select V4L2_MEM2MEM_DEV + default n + help + Driver for a Virtual Codec + + This driver can be compared to the vim2m driver for emulating + a video device node that exposes an emulated hardware codec. + + When in doubt, say N. diff --git a/drivers/media/platform/vicodec/Makefile b/drivers/media/platform/vicodec/Makefile new file mode 100644 index 000000000000..197229428953 --- /dev/null +++ b/drivers/media/platform/vicodec/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +vicodec-objs := vicodec-core.o vicodec-codec.o + +obj-$(CONFIG_VIDEO_VICODEC) += vicodec.o diff --git a/drivers/media/platform/vicodec/vicodec-core.c b/drivers/media/platform/vicodec/vicodec-core.c new file mode 100644 index 000000000000..408cd55d3580 --- /dev/null +++ b/drivers/media/platform/vicodec/vicodec-core.c @@ -0,0 +1,1506 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * A virtual codec example device. + * + * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This is a virtual codec device driver for testing the codec framework. + * It simulates a device that uses memory buffers for both source and + * destination and encodes or decodes the data. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "vicodec-codec.h" + +MODULE_DESCRIPTION("Virtual codec device"); +MODULE_AUTHOR("Hans Verkuil "); +MODULE_LICENSE("GPL v2"); + +static bool multiplanar; +module_param(multiplanar, bool, 0444); +MODULE_PARM_DESC(multiplanar, + " use multi-planar API instead of single-planar API"); + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, " activates debug info"); + +#define VICODEC_NAME "vicodec" +#define MAX_WIDTH 4096U +#define MIN_WIDTH 640U +#define MAX_HEIGHT 2160U +#define MIN_HEIGHT 480U + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + + +static void vicodec_dev_release(struct device *dev) +{ +} + +static struct platform_device vicodec_pdev = { + .name = VICODEC_NAME, + .dev.release = vicodec_dev_release, +}; + +/* Per-queue, driver-specific private data */ +struct vicodec_q_data { + unsigned int width; + unsigned int height; + unsigned int flags; + unsigned int sizeimage; + unsigned int sequence; + u32 fourcc; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +struct vicodec_dev { + struct v4l2_device v4l2_dev; + struct video_device enc_vfd; + struct video_device dec_vfd; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device mdev; +#endif + + struct mutex enc_mutex; + struct mutex dec_mutex; + spinlock_t enc_lock; + spinlock_t dec_lock; + + struct v4l2_m2m_dev *enc_dev; + struct v4l2_m2m_dev *dec_dev; +}; + +struct vicodec_ctx { + struct v4l2_fh fh; + struct vicodec_dev *dev; + bool is_enc; + spinlock_t *lock; + + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *ctrl_gop_size; + unsigned int gop_size; + unsigned int gop_cnt; + + /* Abort requested by m2m */ + int aborting; + struct vb2_v4l2_buffer *last_src_buf; + struct vb2_v4l2_buffer *last_dst_buf; + + enum v4l2_colorspace colorspace; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_xfer_func xfer_func; + enum v4l2_quantization quantization; + + /* Source and destination queue data */ + struct vicodec_q_data q_data[2]; + struct raw_frame ref_frame; + u8 *compressed_frame; + u32 cur_buf_offset; + u32 comp_max_size; + u32 comp_size; + u32 comp_magic_cnt; + u32 comp_frame_size; + bool comp_has_frame; + bool comp_has_next_frame; +}; + +static const u32 pixfmts_yuv[] = { + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, +}; + +static inline struct vicodec_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct vicodec_ctx, fh); +} + +static struct vicodec_q_data *get_q_data(struct vicodec_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return &ctx->q_data[V4L2_M2M_SRC]; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + return &ctx->q_data[V4L2_M2M_DST]; + default: + WARN_ON(1); + break; + } + return NULL; +} + +static void encode(struct vicodec_ctx *ctx, + struct vicodec_q_data *q_data, + u8 *p_in, u8 *p_out) +{ + unsigned int size = q_data->width * q_data->height; + struct cframe_hdr *p_hdr; + struct cframe cf; + struct raw_frame rf; + u32 encoding; + + rf.width = q_data->width; + rf.height = q_data->height; + rf.luma = p_in; + + switch (q_data->fourcc) { + case V4L2_PIX_FMT_YUV420: + rf.cb = rf.luma + size; + rf.cr = rf.cb + size / 4; + rf.chroma_step = 1; + break; + case V4L2_PIX_FMT_YVU420: + rf.cr = rf.luma + size; + rf.cb = rf.cr + size / 4; + rf.chroma_step = 1; + break; + case V4L2_PIX_FMT_NV12: + rf.cb = rf.luma + size; + rf.cr = rf.cb + 1; + rf.chroma_step = 2; + break; + case V4L2_PIX_FMT_NV21: + rf.cr = rf.luma + size; + rf.cb = rf.cr + 1; + rf.chroma_step = 2; + break; + } + + cf.width = q_data->width; + cf.height = q_data->height; + cf.rlc_data = (__be16 *)(p_out + sizeof(*p_hdr)); + + encoding = encode_frame(&rf, &ctx->ref_frame, &cf, !ctx->gop_cnt, + ctx->gop_cnt == ctx->gop_size - 1); + if (encoding != FRAME_PCODED) + ctx->gop_cnt = 0; + if (++ctx->gop_cnt == ctx->gop_size) + ctx->gop_cnt = 0; + + p_hdr = (struct cframe_hdr *)p_out; + p_hdr->magic1 = VICODEC_MAGIC1; + p_hdr->magic2 = VICODEC_MAGIC2; + p_hdr->version = htonl(VICODEC_VERSION); + p_hdr->width = htonl(cf.width); + p_hdr->height = htonl(cf.height); + p_hdr->flags = htonl(q_data->flags); + if (encoding & LUMA_UNENCODED) + p_hdr->flags |= htonl(VICODEC_FL_LUMA_IS_UNCOMPRESSED); + if (encoding & CB_UNENCODED) + p_hdr->flags |= htonl(VICODEC_FL_CB_IS_UNCOMPRESSED); + if (encoding & CR_UNENCODED) + p_hdr->flags |= htonl(VICODEC_FL_CR_IS_UNCOMPRESSED); + p_hdr->colorspace = htonl(ctx->colorspace); + p_hdr->xfer_func = htonl(ctx->xfer_func); + p_hdr->ycbcr_enc = htonl(ctx->ycbcr_enc); + p_hdr->quantization = htonl(ctx->quantization); + p_hdr->size = htonl(cf.size); + ctx->ref_frame.width = cf.width; + ctx->ref_frame.height = cf.height; +} + +static int decode(struct vicodec_ctx *ctx, + struct vicodec_q_data *q_data, + u8 *p_in, u8 *p_out) +{ + unsigned int size = q_data->width * q_data->height; + unsigned int i; + struct cframe_hdr *p_hdr; + struct cframe cf; + u8 *p; + + p_hdr = (struct cframe_hdr *)p_in; + cf.width = ntohl(p_hdr->width); + cf.height = ntohl(p_hdr->height); + q_data->flags = ntohl(p_hdr->flags); + ctx->colorspace = ntohl(p_hdr->colorspace); + ctx->xfer_func = ntohl(p_hdr->xfer_func); + ctx->ycbcr_enc = ntohl(p_hdr->ycbcr_enc); + ctx->quantization = ntohl(p_hdr->quantization); + cf.rlc_data = (__be16 *)(p_in + sizeof(*p_hdr)); + + if (p_hdr->magic1 != VICODEC_MAGIC1 || + p_hdr->magic2 != VICODEC_MAGIC2 || + ntohl(p_hdr->version) != VICODEC_VERSION || + cf.width < VICODEC_MIN_WIDTH || + cf.width > VICODEC_MAX_WIDTH || + cf.height < VICODEC_MIN_HEIGHT || + cf.height > VICODEC_MAX_HEIGHT || + (cf.width & 7) || (cf.height & 7)) + return -EINVAL; + + /* TODO: support resolution changes */ + if (cf.width != q_data->width || cf.height != q_data->height) + return -EINVAL; + + decode_frame(&cf, &ctx->ref_frame, q_data->flags); + memcpy(p_out, ctx->ref_frame.luma, size); + p_out += size; + + switch (q_data->fourcc) { + case V4L2_PIX_FMT_YUV420: + memcpy(p_out, ctx->ref_frame.cb, size / 4); + p_out += size / 4; + memcpy(p_out, ctx->ref_frame.cr, size / 4); + break; + case V4L2_PIX_FMT_YVU420: + memcpy(p_out, ctx->ref_frame.cr, size / 4); + p_out += size / 4; + memcpy(p_out, ctx->ref_frame.cb, size / 4); + break; + case V4L2_PIX_FMT_NV12: + for (i = 0, p = p_out; i < size / 4; i++, p += 2) + *p = ctx->ref_frame.cb[i]; + for (i = 0, p = p_out + 1; i < size / 4; i++, p += 2) + *p = ctx->ref_frame.cr[i]; + break; + case V4L2_PIX_FMT_NV21: + for (i = 0, p = p_out; i < size / 4; i++, p += 2) + *p = ctx->ref_frame.cr[i]; + for (i = 0, p = p_out + 1; i < size / 4; i++, p += 2) + *p = ctx->ref_frame.cb[i]; + break; + } + return 0; +} + +static int device_process(struct vicodec_ctx *ctx, + struct vb2_v4l2_buffer *in_vb, + struct vb2_v4l2_buffer *out_vb) +{ + struct vicodec_dev *dev = ctx->dev; + struct vicodec_q_data *q_out, *q_cap; + u8 *p_in, *p_out; + int ret; + + q_out = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + q_cap = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (ctx->is_enc) + p_in = vb2_plane_vaddr(&in_vb->vb2_buf, 0); + else + p_in = ctx->compressed_frame; + p_out = vb2_plane_vaddr(&out_vb->vb2_buf, 0); + if (!p_in || !p_out) { + v4l2_err(&dev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return -EFAULT; + } + + if (ctx->is_enc) { + struct cframe_hdr *p_hdr = (struct cframe_hdr *)p_out; + + encode(ctx, q_out, p_in, p_out); + vb2_set_plane_payload(&out_vb->vb2_buf, 0, + sizeof(*p_hdr) + ntohl(p_hdr->size)); + } else { + ret = decode(ctx, q_cap, p_in, p_out); + if (ret) + return ret; + vb2_set_plane_payload(&out_vb->vb2_buf, 0, + q_cap->width * q_cap->height * 3 / 2); + } + + out_vb->sequence = q_cap->sequence++; + out_vb->vb2_buf.timestamp = in_vb->vb2_buf.timestamp; + + if (in_vb->flags & V4L2_BUF_FLAG_TIMECODE) + out_vb->timecode = in_vb->timecode; + out_vb->field = in_vb->field; + out_vb->flags &= ~V4L2_BUF_FLAG_LAST; + out_vb->flags |= in_vb->flags & + (V4L2_BUF_FLAG_TIMECODE | + V4L2_BUF_FLAG_KEYFRAME | + V4L2_BUF_FLAG_PFRAME | + V4L2_BUF_FLAG_BFRAME | + V4L2_BUF_FLAG_TSTAMP_SRC_MASK); + + return 0; +} + +/* + * mem2mem callbacks + */ + +/* device_run() - prepares and starts the device */ +static void device_run(void *priv) +{ + static const struct v4l2_event eos_event = { + .type = V4L2_EVENT_EOS + }; + struct vicodec_ctx *ctx = priv; + struct vicodec_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct vicodec_q_data *q_out; + u32 state; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + q_out = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + state = VB2_BUF_STATE_DONE; + if (device_process(ctx, src_buf, dst_buf)) + state = VB2_BUF_STATE_ERROR; + ctx->last_dst_buf = dst_buf; + + spin_lock(ctx->lock); + if (!ctx->comp_has_next_frame && src_buf == ctx->last_src_buf) { + dst_buf->flags |= V4L2_BUF_FLAG_LAST; + v4l2_event_queue_fh(&ctx->fh, &eos_event); + } + if (ctx->is_enc) { + src_buf->sequence = q_out->sequence++; + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, state); + } else if (vb2_get_plane_payload(&src_buf->vb2_buf, 0) == ctx->cur_buf_offset) { + src_buf->sequence = q_out->sequence++; + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, state); + ctx->cur_buf_offset = 0; + ctx->comp_has_next_frame = false; + } + v4l2_m2m_buf_done(dst_buf, state); + ctx->comp_size = 0; + ctx->comp_magic_cnt = 0; + ctx->comp_has_frame = false; + spin_unlock(ctx->lock); + + if (ctx->is_enc) + v4l2_m2m_job_finish(dev->enc_dev, ctx->fh.m2m_ctx); + else + v4l2_m2m_job_finish(dev->dec_dev, ctx->fh.m2m_ctx); +} + +static void job_remove_out_buf(struct vicodec_ctx *ctx, u32 state) +{ + struct vb2_v4l2_buffer *src_buf; + struct vicodec_q_data *q_out; + + q_out = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + spin_lock(ctx->lock); + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + src_buf->sequence = q_out->sequence++; + v4l2_m2m_buf_done(src_buf, state); + ctx->cur_buf_offset = 0; + spin_unlock(ctx->lock); +} + +static int job_ready(void *priv) +{ + static const u8 magic[] = { + 0x4f, 0x4f, 0x4f, 0x4f, 0xff, 0xff, 0xff, 0xff + }; + struct vicodec_ctx *ctx = priv; + struct vb2_v4l2_buffer *src_buf; + u8 *p_out; + u8 *p; + u32 sz; + u32 state; + + if (ctx->is_enc || ctx->comp_has_frame) + return 1; + +restart: + ctx->comp_has_next_frame = false; + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src_buf) + return 0; + p_out = vb2_plane_vaddr(&src_buf->vb2_buf, 0); + sz = vb2_get_plane_payload(&src_buf->vb2_buf, 0); + p = p_out + ctx->cur_buf_offset; + + state = VB2_BUF_STATE_DONE; + + if (!ctx->comp_size) { + state = VB2_BUF_STATE_ERROR; + for (; p < p_out + sz; p++) { + u32 copy; + + p = memchr(p, magic[ctx->comp_magic_cnt], sz); + if (!p) { + ctx->comp_magic_cnt = 0; + break; + } + copy = sizeof(magic) - ctx->comp_magic_cnt; + if (p_out + sz - p < copy) + copy = p_out + sz - p; + memcpy(ctx->compressed_frame + ctx->comp_magic_cnt, + p, copy); + ctx->comp_magic_cnt += copy; + if (!memcmp(ctx->compressed_frame, magic, ctx->comp_magic_cnt)) { + p += copy; + state = VB2_BUF_STATE_DONE; + break; + } + ctx->comp_magic_cnt = 0; + } + if (ctx->comp_magic_cnt < sizeof(magic)) { + job_remove_out_buf(ctx, state); + goto restart; + } + ctx->comp_size = sizeof(magic); + } + if (ctx->comp_size < sizeof(struct cframe_hdr)) { + struct cframe_hdr *p_hdr = (struct cframe_hdr *)ctx->compressed_frame; + u32 copy = sizeof(struct cframe_hdr) - ctx->comp_size; + + if (copy > p_out + sz - p) + copy = p_out + sz - p; + memcpy(ctx->compressed_frame + ctx->comp_size, + p, copy); + p += copy; + ctx->comp_size += copy; + if (ctx->comp_size < sizeof(struct cframe_hdr)) { + job_remove_out_buf(ctx, state); + goto restart; + } + ctx->comp_frame_size = ntohl(p_hdr->size) + sizeof(*p_hdr); + if (ctx->comp_frame_size > ctx->comp_max_size) + ctx->comp_frame_size = ctx->comp_max_size; + } + if (ctx->comp_size < ctx->comp_frame_size) { + u32 copy = ctx->comp_frame_size - ctx->comp_size; + + if (copy > p_out + sz - p) + copy = p_out + sz - p; + memcpy(ctx->compressed_frame + ctx->comp_size, + p, copy); + p += copy; + ctx->comp_size += copy; + if (ctx->comp_size < ctx->comp_frame_size) { + job_remove_out_buf(ctx, state); + goto restart; + } + } + ctx->cur_buf_offset = p - p_out; + ctx->comp_has_frame = true; + ctx->comp_has_next_frame = false; + if (sz - ctx->cur_buf_offset >= sizeof(struct cframe_hdr)) { + struct cframe_hdr *p_hdr = (struct cframe_hdr *)p; + u32 frame_size = ntohl(p_hdr->size); + u32 remaining = sz - ctx->cur_buf_offset - sizeof(*p_hdr); + + if (!memcmp(p, magic, sizeof(magic))) + ctx->comp_has_next_frame = remaining >= frame_size; + } + return 1; +} + +static void job_abort(void *priv) +{ + struct vicodec_ctx *ctx = priv; + + /* Will cancel the transaction in the next interrupt handler */ + ctx->aborting = 1; +} + +/* + * video ioctls + */ + +static u32 find_fmt(u32 fmt) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pixfmts_yuv); i++) + if (pixfmts_yuv[i] == fmt) + return fmt; + return pixfmts_yuv[0]; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strncpy(cap->driver, VICODEC_NAME, sizeof(cap->driver) - 1); + strncpy(cap->card, VICODEC_NAME, sizeof(cap->card) - 1); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", VICODEC_NAME); + cap->device_caps = V4L2_CAP_STREAMING | + (multiplanar ? + V4L2_CAP_VIDEO_M2M_MPLANE : + V4L2_CAP_VIDEO_M2M); + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, bool is_enc, bool is_out) +{ + bool is_yuv = (is_enc && is_out) || (!is_enc && !is_out); + + if (V4L2_TYPE_IS_MULTIPLANAR(f->type) && !multiplanar) + return -EINVAL; + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type) && multiplanar) + return -EINVAL; + if (f->index >= (is_yuv ? ARRAY_SIZE(pixfmts_yuv) : 1)) + return -EINVAL; + + if (is_yuv) + f->pixelformat = pixfmts_yuv[f->index]; + else + f->pixelformat = V4L2_PIX_FMT_FWHT; + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct vicodec_ctx *ctx = file2ctx(file); + + return enum_fmt(f, ctx->is_enc, false); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct vicodec_ctx *ctx = file2ctx(file); + + return enum_fmt(f, ctx->is_enc, true); +} + +static int vidioc_g_fmt(struct vicodec_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct vicodec_q_data *q_data; + struct v4l2_pix_format_mplane *pix_mp; + struct v4l2_pix_format *pix; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (multiplanar) + return -EINVAL; + pix = &f->fmt.pix; + pix->width = q_data->width; + pix->height = q_data->height; + pix->field = V4L2_FIELD_NONE; + pix->pixelformat = q_data->fourcc; + if (q_data->fourcc == V4L2_PIX_FMT_FWHT) + pix->bytesperline = 0; + else + pix->bytesperline = q_data->width; + pix->sizeimage = q_data->sizeimage; + pix->colorspace = ctx->colorspace; + pix->xfer_func = ctx->xfer_func; + pix->ycbcr_enc = ctx->ycbcr_enc; + pix->quantization = ctx->quantization; + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (!multiplanar) + return -EINVAL; + pix_mp = &f->fmt.pix_mp; + pix_mp->width = q_data->width; + pix_mp->height = q_data->height; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->pixelformat = q_data->fourcc; + pix_mp->num_planes = 1; + if (q_data->fourcc == V4L2_PIX_FMT_FWHT) + pix_mp->plane_fmt[0].bytesperline = 0; + else + pix_mp->plane_fmt[0].bytesperline = q_data->width; + pix_mp->plane_fmt[0].sizeimage = q_data->sizeimage; + pix_mp->colorspace = ctx->colorspace; + pix_mp->xfer_func = ctx->xfer_func; + pix_mp->ycbcr_enc = ctx->ycbcr_enc; + pix_mp->quantization = ctx->quantization; + memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); + memset(pix_mp->plane_fmt[0].reserved, 0, + sizeof(pix_mp->plane_fmt[0].reserved)); + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(file2ctx(file), f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(file2ctx(file), f); +} + +static int vidioc_try_fmt(struct vicodec_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp; + struct v4l2_pix_format *pix; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + pix = &f->fmt.pix; + pix->width = clamp(pix->width, MIN_WIDTH, MAX_WIDTH) & ~7; + pix->height = clamp(pix->height, MIN_HEIGHT, MAX_HEIGHT) & ~7; + pix->bytesperline = pix->width; + pix->sizeimage = pix->width * pix->height * 3 / 2; + pix->field = V4L2_FIELD_NONE; + if (pix->pixelformat == V4L2_PIX_FMT_FWHT) { + pix->bytesperline = 0; + pix->sizeimage += sizeof(struct cframe_hdr); + } + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + pix_mp = &f->fmt.pix_mp; + pix_mp->width = clamp(pix_mp->width, MIN_WIDTH, MAX_WIDTH) & ~7; + pix_mp->height = + clamp(pix_mp->height, MIN_HEIGHT, MAX_HEIGHT) & ~7; + pix_mp->plane_fmt[0].bytesperline = pix_mp->width; + pix_mp->plane_fmt[0].sizeimage = + pix_mp->width * pix_mp->height * 3 / 2; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->num_planes = 1; + if (pix_mp->pixelformat == V4L2_PIX_FMT_FWHT) { + pix_mp->plane_fmt[0].bytesperline = 0; + pix_mp->plane_fmt[0].sizeimage += + sizeof(struct cframe_hdr); + } + memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); + memset(pix_mp->plane_fmt[0].reserved, 0, + sizeof(pix_mp->plane_fmt[0].reserved)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vicodec_ctx *ctx = file2ctx(file); + struct v4l2_pix_format_mplane *pix_mp; + struct v4l2_pix_format *pix; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (multiplanar) + return -EINVAL; + pix = &f->fmt.pix; + pix->pixelformat = ctx->is_enc ? V4L2_PIX_FMT_FWHT : + find_fmt(f->fmt.pix.pixelformat); + pix->colorspace = ctx->colorspace; + pix->xfer_func = ctx->xfer_func; + pix->ycbcr_enc = ctx->ycbcr_enc; + pix->quantization = ctx->quantization; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (!multiplanar) + return -EINVAL; + pix_mp = &f->fmt.pix_mp; + pix_mp->pixelformat = ctx->is_enc ? V4L2_PIX_FMT_FWHT : + find_fmt(pix_mp->pixelformat); + pix_mp->colorspace = ctx->colorspace; + pix_mp->xfer_func = ctx->xfer_func; + pix_mp->ycbcr_enc = ctx->ycbcr_enc; + pix_mp->quantization = ctx->quantization; + memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); + memset(pix_mp->plane_fmt[0].reserved, 0, + sizeof(pix_mp->plane_fmt[0].reserved)); + break; + default: + return -EINVAL; + } + + return vidioc_try_fmt(ctx, f); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vicodec_ctx *ctx = file2ctx(file); + struct v4l2_pix_format_mplane *pix_mp; + struct v4l2_pix_format *pix; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (multiplanar) + return -EINVAL; + pix = &f->fmt.pix; + pix->pixelformat = !ctx->is_enc ? V4L2_PIX_FMT_FWHT : + find_fmt(pix->pixelformat); + if (!pix->colorspace) + pix->colorspace = V4L2_COLORSPACE_REC709; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (!multiplanar) + return -EINVAL; + pix_mp = &f->fmt.pix_mp; + pix_mp->pixelformat = !ctx->is_enc ? V4L2_PIX_FMT_FWHT : + find_fmt(pix_mp->pixelformat); + if (!pix_mp->colorspace) + pix_mp->colorspace = V4L2_COLORSPACE_REC709; + break; + default: + return -EINVAL; + } + + return vidioc_try_fmt(ctx, f); +} + +static int vidioc_s_fmt(struct vicodec_ctx *ctx, struct v4l2_format *f) +{ + struct vicodec_q_data *q_data; + struct vb2_queue *vq; + bool fmt_changed = true; + struct v4l2_pix_format_mplane *pix_mp; + struct v4l2_pix_format *pix; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + pix = &f->fmt.pix; + if (ctx->is_enc && V4L2_TYPE_IS_OUTPUT(f->type)) + fmt_changed = + q_data->fourcc != pix->pixelformat || + q_data->width != pix->width || + q_data->height != pix->height; + + if (vb2_is_busy(vq) && fmt_changed) + return -EBUSY; + + q_data->fourcc = pix->pixelformat; + q_data->width = pix->width; + q_data->height = pix->height; + q_data->sizeimage = pix->sizeimage; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + pix_mp = &f->fmt.pix_mp; + if (ctx->is_enc && V4L2_TYPE_IS_OUTPUT(f->type)) + fmt_changed = + q_data->fourcc != pix_mp->pixelformat || + q_data->width != pix_mp->width || + q_data->height != pix_mp->height; + + if (vb2_is_busy(vq) && fmt_changed) + return -EBUSY; + + q_data->fourcc = pix_mp->pixelformat; + q_data->width = pix_mp->width; + q_data->height = pix_mp->height; + q_data->sizeimage = pix_mp->plane_fmt[0].sizeimage; + break; + default: + return -EINVAL; + } + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fourcc: %08x\n", + f->type, q_data->width, q_data->height, q_data->fourcc); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(file2ctx(file), f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vicodec_ctx *ctx = file2ctx(file); + struct v4l2_pix_format_mplane *pix_mp; + struct v4l2_pix_format *pix; + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = vidioc_s_fmt(file2ctx(file), f); + if (!ret) { + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + pix = &f->fmt.pix; + ctx->colorspace = pix->colorspace; + ctx->xfer_func = pix->xfer_func; + ctx->ycbcr_enc = pix->ycbcr_enc; + ctx->quantization = pix->quantization; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + pix_mp = &f->fmt.pix_mp; + ctx->colorspace = pix_mp->colorspace; + ctx->xfer_func = pix_mp->xfer_func; + ctx->ycbcr_enc = pix_mp->ycbcr_enc; + ctx->quantization = pix_mp->quantization; + break; + default: + break; + } + } + return ret; +} + +static void vicodec_mark_last_buf(struct vicodec_ctx *ctx) +{ + static const struct v4l2_event eos_event = { + .type = V4L2_EVENT_EOS + }; + + spin_lock(ctx->lock); + ctx->last_src_buf = v4l2_m2m_last_src_buf(ctx->fh.m2m_ctx); + if (!ctx->last_src_buf && ctx->last_dst_buf) { + ctx->last_dst_buf->flags |= V4L2_BUF_FLAG_LAST; + v4l2_event_queue_fh(&ctx->fh, &eos_event); + } + spin_unlock(ctx->lock); +} + +static int vicodec_try_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *ec) +{ + if (ec->cmd != V4L2_ENC_CMD_STOP) + return -EINVAL; + + if (ec->flags & V4L2_ENC_CMD_STOP_AT_GOP_END) + return -EINVAL; + + return 0; +} + +static int vicodec_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *ec) +{ + struct vicodec_ctx *ctx = file2ctx(file); + int ret; + + ret = vicodec_try_encoder_cmd(file, fh, ec); + if (ret < 0) + return ret; + + vicodec_mark_last_buf(ctx); + return 0; +} + +static int vicodec_try_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *dc) +{ + if (dc->cmd != V4L2_DEC_CMD_STOP) + return -EINVAL; + + if (dc->flags & V4L2_DEC_CMD_STOP_TO_BLACK) + return -EINVAL; + + if (!(dc->flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) && (dc->stop.pts != 0)) + return -EINVAL; + + return 0; +} + +static int vicodec_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *dc) +{ + struct vicodec_ctx *ctx = file2ctx(file); + int ret; + + ret = vicodec_try_decoder_cmd(file, fh, dc); + if (ret < 0) + return ret; + + vicodec_mark_last_buf(ctx); + return 0; +} + +static int vicodec_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + switch (fsize->pixel_format) { + case V4L2_PIX_FMT_FWHT: + break; + default: + if (find_fmt(fsize->pixel_format) == fsize->pixel_format) + break; + return -EINVAL; + } + + if (fsize->index) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + fsize->stepwise.min_width = MIN_WIDTH; + fsize->stepwise.max_width = MAX_WIDTH; + fsize->stepwise.step_width = 8; + fsize->stepwise.min_height = MIN_HEIGHT; + fsize->stepwise.max_height = MAX_HEIGHT; + fsize->stepwise.step_height = 8; + + return 0; +} + +static int vicodec_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static const struct v4l2_ioctl_ops vicodec_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_try_encoder_cmd = vicodec_try_encoder_cmd, + .vidioc_encoder_cmd = vicodec_encoder_cmd, + .vidioc_try_decoder_cmd = vicodec_try_decoder_cmd, + .vidioc_decoder_cmd = vicodec_decoder_cmd, + .vidioc_enum_framesizes = vicodec_enum_framesizes, + + .vidioc_subscribe_event = vicodec_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + + +/* + * Queue operations + */ + +static int vicodec_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct vicodec_ctx *ctx = vb2_get_drv_priv(vq); + struct vicodec_q_data *q_data = get_q_data(ctx, vq->type); + unsigned int size = q_data->sizeimage; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + return 0; +} + +static int vicodec_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vicodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vicodec_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) { + dprintk(ctx->dev, "%s field isn't supported\n", + __func__); + return -EINVAL; + } + } + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, + "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + return 0; +} + +static void vicodec_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vicodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void vicodec_return_bufs(struct vb2_queue *q, u32 state) +{ + struct vicodec_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (vbuf == NULL) + return; + spin_lock(ctx->lock); + v4l2_m2m_buf_done(vbuf, state); + spin_unlock(ctx->lock); + } +} + +static int vicodec_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct vicodec_ctx *ctx = vb2_get_drv_priv(q); + struct vicodec_q_data *q_data = get_q_data(ctx, q->type); + unsigned int size = q_data->width * q_data->height; + + q_data->sequence = 0; + + if (!V4L2_TYPE_IS_OUTPUT(q->type)) + return 0; + + ctx->ref_frame.width = ctx->ref_frame.height = 0; + ctx->ref_frame.luma = kvmalloc(size * 3 / 2, GFP_KERNEL); + ctx->comp_max_size = size * 3 / 2 + sizeof(struct cframe_hdr); + ctx->compressed_frame = kvmalloc(ctx->comp_max_size, GFP_KERNEL); + if (!ctx->ref_frame.luma || !ctx->compressed_frame) { + kvfree(ctx->ref_frame.luma); + kvfree(ctx->compressed_frame); + vicodec_return_bufs(q, VB2_BUF_STATE_QUEUED); + return -ENOMEM; + } + ctx->ref_frame.cb = ctx->ref_frame.luma + size; + ctx->ref_frame.cr = ctx->ref_frame.cb + size / 4; + ctx->last_src_buf = NULL; + ctx->last_dst_buf = NULL; + v4l2_ctrl_grab(ctx->ctrl_gop_size, true); + ctx->gop_size = v4l2_ctrl_g_ctrl(ctx->ctrl_gop_size); + ctx->gop_cnt = 0; + ctx->cur_buf_offset = 0; + ctx->comp_size = 0; + ctx->comp_magic_cnt = 0; + ctx->comp_has_frame = false; + + return 0; +} + +static void vicodec_stop_streaming(struct vb2_queue *q) +{ + struct vicodec_ctx *ctx = vb2_get_drv_priv(q); + + vicodec_return_bufs(q, VB2_BUF_STATE_ERROR); + + if (!V4L2_TYPE_IS_OUTPUT(q->type)) + return; + + kvfree(ctx->ref_frame.luma); + kvfree(ctx->compressed_frame); + v4l2_ctrl_grab(ctx->ctrl_gop_size, false); +} + +static const struct vb2_ops vicodec_qops = { + .queue_setup = vicodec_queue_setup, + .buf_prepare = vicodec_buf_prepare, + .buf_queue = vicodec_buf_queue, + .start_streaming = vicodec_start_streaming, + .stop_streaming = vicodec_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct vicodec_ctx *ctx = priv; + int ret; + + src_vq->type = (multiplanar ? + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : + V4L2_BUF_TYPE_VIDEO_OUTPUT); + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &vicodec_qops; + src_vq->mem_ops = &vb2_vmalloc_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = ctx->is_enc ? &ctx->dev->enc_mutex : + &ctx->dev->dec_mutex; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = (multiplanar ? + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : + V4L2_BUF_TYPE_VIDEO_CAPTURE); + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &vicodec_qops; + dst_vq->mem_ops = &vb2_vmalloc_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = src_vq->lock; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int vicodec_open(struct file *file) +{ + struct video_device *vfd = video_devdata(file); + struct vicodec_dev *dev = video_drvdata(file); + struct vicodec_ctx *ctx = NULL; + struct v4l2_ctrl_handler *hdl; + unsigned int size; + int rc = 0; + + if (mutex_lock_interruptible(vfd->lock)) + return -ERESTARTSYS; + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + rc = -ENOMEM; + goto open_unlock; + } + + if (vfd == &dev->enc_vfd) + ctx->is_enc = true; + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + hdl = &ctx->hdl; + v4l2_ctrl_handler_init(hdl, 4); + ctx->ctrl_gop_size = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 1, 16, 1, 10); + if (hdl->error) { + rc = hdl->error; + v4l2_ctrl_handler_free(hdl); + kfree(ctx); + goto open_unlock; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + ctx->q_data[V4L2_M2M_SRC].fourcc = + ctx->is_enc ? V4L2_PIX_FMT_YUV420 : V4L2_PIX_FMT_FWHT; + ctx->q_data[V4L2_M2M_SRC].width = 1280; + ctx->q_data[V4L2_M2M_SRC].height = 720; + size = 1280 * 720 * 3 / 2; + ctx->q_data[V4L2_M2M_SRC].sizeimage = size; + ctx->q_data[V4L2_M2M_DST] = ctx->q_data[V4L2_M2M_SRC]; + ctx->q_data[V4L2_M2M_DST].fourcc = + ctx->is_enc ? V4L2_PIX_FMT_FWHT : V4L2_PIX_FMT_YUV420; + ctx->colorspace = V4L2_COLORSPACE_REC709; + + size += sizeof(struct cframe_hdr); + if (ctx->is_enc) { + ctx->q_data[V4L2_M2M_DST].sizeimage = size; + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->enc_dev, ctx, + &queue_init); + ctx->lock = &dev->enc_lock; + } else { + ctx->q_data[V4L2_M2M_SRC].sizeimage = size; + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->dec_dev, ctx, + &queue_init); + ctx->lock = &dev->dec_lock; + } + + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc = PTR_ERR(ctx->fh.m2m_ctx); + + v4l2_ctrl_handler_free(hdl); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + goto open_unlock; + } + + v4l2_fh_add(&ctx->fh); + +open_unlock: + mutex_unlock(vfd->lock); + return rc; +} + +static int vicodec_release(struct file *file) +{ + struct video_device *vfd = video_devdata(file); + struct vicodec_ctx *ctx = file2ctx(file); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(vfd->lock); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(vfd->lock); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations vicodec_fops = { + .owner = THIS_MODULE, + .open = vicodec_open, + .release = vicodec_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device vicodec_videodev = { + .name = VICODEC_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &vicodec_fops, + .ioctl_ops = &vicodec_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = device_run, + .job_abort = job_abort, + .job_ready = job_ready, +}; + +static int vicodec_probe(struct platform_device *pdev) +{ + struct vicodec_dev *dev; + struct video_device *vfd; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->enc_lock); + spin_lock_init(&dev->dec_lock); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + return ret; + +#ifdef CONFIG_MEDIA_CONTROLLER + dev->mdev.dev = &pdev->dev; + strlcpy(dev->mdev.model, "vicodec", sizeof(dev->mdev.model)); + media_device_init(&dev->mdev); + dev->v4l2_dev.mdev = &dev->mdev; +#endif + + mutex_init(&dev->enc_mutex); + mutex_init(&dev->dec_mutex); + + platform_set_drvdata(pdev, dev); + + dev->enc_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(dev->enc_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init vicodec device\n"); + ret = PTR_ERR(dev->enc_dev); + goto unreg_dev; + } + + dev->dec_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(dev->dec_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init vicodec device\n"); + ret = PTR_ERR(dev->dec_dev); + goto err_enc_m2m; + } + + dev->enc_vfd = vicodec_videodev; + vfd = &dev->enc_vfd; + vfd->lock = &dev->enc_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + strlcpy(vfd->name, "vicodec-enc", sizeof(vfd->name)); + v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD); + video_set_drvdata(vfd, dev); + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto err_dec_m2m; + } + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + dev->dec_vfd = vicodec_videodev; + vfd = &dev->dec_vfd; + vfd->lock = &dev->dec_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + strlcpy(vfd->name, "vicodec-dec", sizeof(vfd->name)); + v4l2_disable_ioctl(vfd, VIDIOC_ENCODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_ENCODER_CMD); + video_set_drvdata(vfd, dev); + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto unreg_enc; + } + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + +#ifdef CONFIG_MEDIA_CONTROLLER + ret = v4l2_m2m_register_media_controller(dev->enc_dev, + &dev->enc_vfd, MEDIA_ENT_F_PROC_VIDEO_ENCODER); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); + goto unreg_m2m; + } + + ret = v4l2_m2m_register_media_controller(dev->dec_dev, + &dev->dec_vfd, MEDIA_ENT_F_PROC_VIDEO_DECODER); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); + goto unreg_m2m_enc_mc; + } + + ret = media_device_register(&dev->mdev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register mem2mem media device\n"); + goto unreg_m2m_dec_mc; + } +#endif + return 0; + +#ifdef CONFIG_MEDIA_CONTROLLER +unreg_m2m_dec_mc: + v4l2_m2m_unregister_media_controller(dev->dec_dev); +unreg_m2m_enc_mc: + v4l2_m2m_unregister_media_controller(dev->enc_dev); +unreg_m2m: + video_unregister_device(&dev->dec_vfd); +#endif +unreg_enc: + video_unregister_device(&dev->enc_vfd); +err_dec_m2m: + v4l2_m2m_release(dev->dec_dev); +err_enc_m2m: + v4l2_m2m_release(dev->enc_dev); +unreg_dev: + v4l2_device_unregister(&dev->v4l2_dev); + + return ret; +} + +static int vicodec_remove(struct platform_device *pdev) +{ + struct vicodec_dev *dev = platform_get_drvdata(pdev); + + v4l2_info(&dev->v4l2_dev, "Removing " VICODEC_NAME); + +#ifdef CONFIG_MEDIA_CONTROLLER + media_device_unregister(&dev->mdev); + v4l2_m2m_unregister_media_controller(dev->enc_dev); + v4l2_m2m_unregister_media_controller(dev->dec_dev); + media_device_cleanup(&dev->mdev); +#endif + + v4l2_m2m_release(dev->enc_dev); + v4l2_m2m_release(dev->dec_dev); + video_unregister_device(&dev->enc_vfd); + video_unregister_device(&dev->dec_vfd); + v4l2_device_unregister(&dev->v4l2_dev); + + return 0; +} + +static struct platform_driver vicodec_pdrv = { + .probe = vicodec_probe, + .remove = vicodec_remove, + .driver = { + .name = VICODEC_NAME, + }, +}; + +static void __exit vicodec_exit(void) +{ + platform_driver_unregister(&vicodec_pdrv); + platform_device_unregister(&vicodec_pdev); +} + +static int __init vicodec_init(void) +{ + int ret; + + ret = platform_device_register(&vicodec_pdev); + if (ret) + return ret; + + ret = platform_driver_register(&vicodec_pdrv); + if (ret) + platform_device_unregister(&vicodec_pdev); + + return ret; +} + +module_init(vicodec_init); +module_exit(vicodec_exit); -- cgit v1.2.3-59-g8ed1b From d4de804e02a3bff973a91b13784ecd5376baf9c1 Mon Sep 17 00:00:00 2001 From: Hugues Fruchet Date: Thu, 5 Jul 2018 05:01:42 -0400 Subject: media: MAINTAINERS: Add entry for STM32 DCMI media driver Add an entry to make myself a maintainer of STM32 DCMI media driver. Signed-off-by: Hugues Fruchet Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 67781db8b077..59a9e92caed9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8969,6 +8969,14 @@ T: git git://linuxtv.org/media_tree.git S: Maintained F: drivers/media/dvb-frontends/stv6111* +MEDIA DRIVERS FOR STM32 - DCMI +M: Hugues Fruchet +L: linux-media@vger.kernel.org +T: git git://linuxtv.org/media_tree.git +S: Supported +F: Documentation/devicetree/bindings/media/st,stm32-dcmi.txt +F: drivers/media/platform/stm32/stm32-dcmi.c + MEDIA DRIVERS FOR NVIDIA TEGRA - VDE M: Dmitry Osipenko L: linux-media@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From 122b5a05987c8bcc0f88df2fdfa39397b6002737 Mon Sep 17 00:00:00 2001 From: Christian König Date: Wed, 18 Jul 2018 13:06:02 +0200 Subject: MAINTAINERS: add new TTM maintainers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roger unfortunately doesn't work for AMD any longer. So add Rui and Jerry as co-maintainer as well. Signed-off-by: Christian König Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 07d1576fc766..646f40f8c691 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4883,7 +4883,8 @@ F: Documentation/gpu/xen-front.rst DRM TTM SUBSYSTEM M: Christian Koenig -M: Roger He +M: Huang Rui +M: Junwei Zhang T: git git://people.freedesktop.org/~agd5f/linux S: Maintained L: dri-devel@lists.freedesktop.org -- cgit v1.2.3-59-g8ed1b From dbae59466f3737acbdecaa565faf3edb8c8c4f67 Mon Sep 17 00:00:00 2001 From: Christian König Date: Wed, 18 Jul 2018 13:16:06 +0200 Subject: MAINTAINERS: Add separate section for DC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note that Harry and Leo Li are maintainers for that stuff. Signed-off-by: Christian König Reviewed-by: Michel Dänzer Reviewed-by: Harry Wentland Reviewed-by: Leo Li Signed-off-by: Alex Deucher --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 646f40f8c691..2ba4cf8f5eef 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -728,6 +728,14 @@ S: Supported F: drivers/crypto/ccp/ F: include/linux/ccp.h +AMD DISPLAY CORE +M: Harry Wentland +M: Leo Li +L: amd-gfx@lists.freedesktop.org +T: git git://people.freedesktop.org/~agd5f/linux +S: Supported +F: drivers/gpu/drm/amd/display/ + AMD FAM15H PROCESSOR POWER MONITORING DRIVER M: Huang Rui L: linux-hwmon@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From 6beccb15c447356c40972da825e0c1ed803cfb10 Mon Sep 17 00:00:00 2001 From: Christian König Date: Thu, 19 Jul 2018 11:39:37 +0200 Subject: MAINTAINERS: add entry for AMD PP code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add separate entry for the power managent code on AMD GPUs. Signed-off-by: Christian König Reviewed-by: Rex Zhu Reviewed-by: Evan Quan Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2ba4cf8f5eef..93f189f0d60d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -785,6 +785,14 @@ F: drivers/gpu/drm/amd/include/vi_structs.h F: drivers/gpu/drm/amd/include/v9_structs.h F: include/uapi/linux/kfd_ioctl.h +AMD POWERPLAY +M: Rex Zhu +M: Evan Quan +L: amd-gfx@lists.freedesktop.org +S: Supported +F: drivers/gpu/drm/amd/powerplay/ +T: git git://people.freedesktop.org/~agd5f/linux + AMD SEATTLE DEVICE TREE SUPPORT M: Brijesh Singh M: Suravee Suthikulpanit -- cgit v1.2.3-59-g8ed1b From 7c98c5ad80021cc0bc0d072da120441ce9190261 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 25 Jul 2018 09:44:30 -0400 Subject: media: dt-bindings: media: i2c: Document MT9V111 bindings Add documentation for Aptina MT9V111 image sensor. Reviewed-by: Rob Herring Signed-off-by: Jacopo Mondi Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- .../bindings/media/i2c/aptina,mt9v111.txt | 46 ++++++++++++++++++++++ MAINTAINERS | 8 ++++ 2 files changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/aptina,mt9v111.txt (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/i2c/aptina,mt9v111.txt b/Documentation/devicetree/bindings/media/i2c/aptina,mt9v111.txt new file mode 100644 index 000000000000..bd896e9f67d1 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/aptina,mt9v111.txt @@ -0,0 +1,46 @@ +* Aptina MT9V111 CMOS sensor +---------------------------- + +The Aptina MT9V111 is a 1/4-Inch VGA-format digital image sensor with a core +based on Aptina MT9V011 sensor and an integrated Image Flow Processor (IFP). + +The sensor has an active pixel array of 640x480 pixels and can output a number +of image resolution and formats controllable through a simple two-wires +interface. + +Required properties: +-------------------- + +- compatible: shall be "aptina,mt9v111". +- clocks: reference to the system clock input provider. + +Optional properties: +-------------------- + +- enable-gpios: output enable signal, pin name "OE#". Active low. +- standby-gpios: low power state control signal, pin name "STANDBY". + Active high. +- reset-gpios: chip reset signal, pin name "RESET#". Active low. + +The device node must contain one 'port' child node with one 'endpoint' child +sub-node for its digital output video port, in accordance with the video +interface bindings defined in: +Documentation/devicetree/bindings/media/video-interfaces.txt + +Example: +-------- + + &i2c1 { + camera@48 { + compatible = "aptina,mt9v111"; + reg = <0x48>; + + clocks = <&camera_clk>; + + port { + mt9v111_out: endpoint { + remote-endpoint = <&ceu_in>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 59a9e92caed9..a13344d6149a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9659,6 +9659,14 @@ F: Documentation/devicetree/bindings/media/i2c/mt9v032.txt F: drivers/media/i2c/mt9v032.c F: include/media/i2c/mt9v032.h +MT9V111 APTINA CAMERA SENSOR +M: Jacopo Mondi +L: linux-media@vger.kernel.org +T: git git://linuxtv.org/media_tree.git +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/aptina,mt9v111.txt +F: drivers/media/i2c/mt9v111.c + MULTIFUNCTION DEVICES (MFD) M: Lee Jones T: git git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git -- cgit v1.2.3-59-g8ed1b From 1434e3b34816bede31400f9d2bc0c8bc6850459a Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Tue, 3 Jul 2018 10:08:02 -0400 Subject: media: ov2680: dt: Add bindings for OV2680 Add device tree binding documentation for the OV2680 camera sensor. [Sakari Ailus: Squash MAINTAINERS entry from Rui] CC: devicetree@vger.kernel.org Signed-off-by: Rui Miguel Silva Reviewed-by: Rob Herring Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/i2c/ov2680.txt | 46 ++++++++++++++++++++++ MAINTAINERS | 8 ++++ 2 files changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ov2680.txt (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/i2c/ov2680.txt b/Documentation/devicetree/bindings/media/i2c/ov2680.txt new file mode 100644 index 000000000000..11e925ed9dad --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ov2680.txt @@ -0,0 +1,46 @@ +* Omnivision OV2680 MIPI CSI-2 sensor + +Required Properties: +- compatible: should be "ovti,ov2680". +- clocks: reference to the xvclk input clock. +- clock-names: should be "xvclk". +- DOVDD-supply: Digital I/O voltage supply. +- DVDD-supply: Digital core voltage supply. +- AVDD-supply: Analog voltage supply. + +Optional Properties: +- reset-gpios: reference to the GPIO connected to the powerdown/reset pin, + if any. This is an active low signal to the OV2680. + +The device node must contain one 'port' child node for its digital output +video port, and this port must have a single endpoint in accordance with + the video interface bindings defined in +Documentation/devicetree/bindings/media/video-interfaces.txt. + +Endpoint node required properties for CSI-2 connection are: +- remote-endpoint: a phandle to the bus receiver's endpoint node. +- clock-lanes: should be set to <0> (clock lane on hardware lane 0). +- data-lanes: should be set to <1> (one CSI-2 lane supported). + +Example: + +&i2c2 { + ov2680: camera-sensor@36 { + compatible = "ovti,ov2680"; + reg = <0x36>; + clocks = <&osc>; + clock-names = "xvclk"; + reset-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; + DOVDD-supply = <&sw2_reg>; + DVDD-supply = <&sw2_reg>; + AVDD-supply = <®_peri_3p15v>; + + port { + ov2680_to_mipi: endpoint { + remote-endpoint = <&mipi_from_sensor>; + clock-lanes = <0>; + data-lanes = <1>; + }; + }; + }; +}; diff --git a/MAINTAINERS b/MAINTAINERS index a13344d6149a..ab739894d257 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10490,6 +10490,14 @@ T: git git://linuxtv.org/media_tree.git S: Maintained F: drivers/media/i2c/ov13858.c +OMNIVISION OV2680 SENSOR DRIVER +M: Rui Miguel Silva +L: linux-media@vger.kernel.org +T: git git://linuxtv.org/media_tree.git +S: Maintained +F: drivers/media/i2c/ov2680.c +F: Documentation/devicetree/bindings/media/i2c/ov2680.txt + OMNIVISION OV2685 SENSOR DRIVER M: Shunqian Zheng L: linux-media@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From 8d9404ad6a6dbcca36a788049f716675a1110da8 Mon Sep 17 00:00:00 2001 From: Akihiro Tsukada Date: Sun, 10 Jun 2018 10:59:31 -0400 Subject: MAINTAINERS: add entries for several media drivers add entries for the following drivers: - earth_pt{1,3} DVB adapter drivers - mxl301rf DVB tuner drivers - qm1d1{b0004, c0042} DVB tuner drivers - tc90522 DVB demod driver Signed-off-by: Akihiro Tsukada Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ab739894d257..3908908b1ec8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5066,6 +5066,18 @@ T: git git://linuxtv.org/anttip/media_tree.git S: Maintained F: drivers/media/tuners/e4000* +EARTH_PT1 MEDIA DRIVER +M: Akihiro Tsukada +L: linux-media@vger.kernel.org +S: Odd Fixes +F: drivers/media/pci/pt1/ + +EARTH_PT3 MEDIA DRIVER +M: Akihiro Tsukada +L: linux-media@vger.kernel.org +S: Odd Fixes +F: drivers/media/pci/pt3/ + EC100 MEDIA DRIVER M: Antti Palosaari L: linux-media@vger.kernel.org @@ -9711,6 +9723,12 @@ L: linux-usb@vger.kernel.org S: Maintained F: drivers/usb/musb/ +MXL301RF MEDIA DRIVER +M: Akihiro Tsukada +L: linux-media@vger.kernel.org +S: Odd Fixes +F: drivers/media/tuners/mxl301rf* + MXL5007T MEDIA DRIVER M: Michael Krufky L: linux-media@vger.kernel.org @@ -11792,6 +11810,18 @@ L: netdev@vger.kernel.org S: Supported F: drivers/net/ethernet/qlogic/qlge/ +QM1D1B0004 MEDIA DRIVER +M: Akihiro Tsukada +L: linux-media@vger.kernel.org +S: Odd Fixes +F: drivers/media/tuners/qm1d1b0004* + +QM1D1C0042 MEDIA DRIVER +M: Akihiro Tsukada +L: linux-media@vger.kernel.org +S: Odd Fixes +F: drivers/media/tuners/qm1d1c0042* + QNX4 FILESYSTEM M: Anders Larsen W: http://www.alarsen.net/linux/qnx4fs/ @@ -13896,6 +13926,12 @@ F: include/uapi/linux/tc_act/ F: include/uapi/linux/tc_ematch/ F: net/sched/ +TC90522 MEDIA DRIVER +M: Akihiro Tsukada +L: linux-media@vger.kernel.org +S: Odd Fixes +F: drivers/media/dvb-frontends/tc90522* + TCP LOW PRIORITY MODULE M: "Wong Hoi Sing, Edison" M: "Hung Hing Lun, Mike" -- cgit v1.2.3-59-g8ed1b From ed128e8b757b4e759e5585df720888b30a7f2196 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 25 Jul 2018 16:56:28 +0200 Subject: MAINTAINERS: drop Wenyou Yang from Atmel NAND driver support Mails to wenyou.yang@microchip.com are not deliverable. Drop him as Microchip/Atmel NAND controller driver maintainer. Signed-off-by: Miquel Raynal Acked-by: Boris Brezillon Acked-by: Alexandre Belloni --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 63700cefbf56..418e7b966e07 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9345,7 +9345,6 @@ F: drivers/media/platform/atmel/atmel-isc-regs.h F: devicetree/bindings/media/atmel-isc.txt MICROCHIP / ATMEL NAND DRIVER -M: Wenyou Yang M: Josh Wu L: linux-mtd@lists.infradead.org S: Supported -- cgit v1.2.3-59-g8ed1b From 16b7db4c8150ba12e7eb63a5de962e1b05b3b612 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Mon, 30 Jul 2018 18:24:31 +0200 Subject: MAINTAINERS: Add .clang-format entry As discussed in https://lkml.org/lkml/2018/6/25/877 Signed-off-by: Miguel Ojeda --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 32fbc6f732d4..db759f040fcf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3514,6 +3514,11 @@ M: Dave Goodell S: Supported F: drivers/infiniband/hw/usnic/ +CLANG-FORMAT FILE +M: Miguel Ojeda +S: Maintained +F: .clang-format + CLEANCACHE API M: Konrad Rzeszutek Wilk L: linux-kernel@vger.kernel.org -- cgit v1.2.3-59-g8ed1b From ec6859b23f2212275fbd476df14d7a94839c2a35 Mon Sep 17 00:00:00 2001 From: Todor Tomov Date: Wed, 25 Jul 2018 12:38:13 -0400 Subject: media: Rename CAMSS driver path Support for camera subsystem on QComm MSM8996/APQ8096 is to be added so remove hardware version from CAMSS driver's path. Signed-off-by: Todor Tomov Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 2 +- drivers/media/platform/Kconfig | 2 +- drivers/media/platform/Makefile | 2 +- drivers/media/platform/qcom/camss-8x16/Makefile | 11 - .../media/platform/qcom/camss-8x16/camss-csid.c | 1094 ------- .../media/platform/qcom/camss-8x16/camss-csid.h | 82 - .../media/platform/qcom/camss-8x16/camss-csiphy.c | 893 ------ .../media/platform/qcom/camss-8x16/camss-csiphy.h | 77 - .../media/platform/qcom/camss-8x16/camss-ispif.c | 1178 -------- .../media/platform/qcom/camss-8x16/camss-ispif.h | 85 - drivers/media/platform/qcom/camss-8x16/camss-vfe.c | 3093 -------------------- drivers/media/platform/qcom/camss-8x16/camss-vfe.h | 123 - .../media/platform/qcom/camss-8x16/camss-video.c | 859 ------ .../media/platform/qcom/camss-8x16/camss-video.h | 70 - drivers/media/platform/qcom/camss-8x16/camss.c | 751 ----- drivers/media/platform/qcom/camss-8x16/camss.h | 106 - drivers/media/platform/qcom/camss/Makefile | 11 + drivers/media/platform/qcom/camss/camss-csid.c | 1094 +++++++ drivers/media/platform/qcom/camss/camss-csid.h | 82 + drivers/media/platform/qcom/camss/camss-csiphy.c | 893 ++++++ drivers/media/platform/qcom/camss/camss-csiphy.h | 77 + drivers/media/platform/qcom/camss/camss-ispif.c | 1178 ++++++++ drivers/media/platform/qcom/camss/camss-ispif.h | 85 + drivers/media/platform/qcom/camss/camss-vfe.c | 3093 ++++++++++++++++++++ drivers/media/platform/qcom/camss/camss-vfe.h | 123 + drivers/media/platform/qcom/camss/camss-video.c | 859 ++++++ drivers/media/platform/qcom/camss/camss-video.h | 70 + drivers/media/platform/qcom/camss/camss.c | 751 +++++ drivers/media/platform/qcom/camss/camss.h | 106 + 29 files changed, 8425 insertions(+), 8425 deletions(-) delete mode 100644 drivers/media/platform/qcom/camss-8x16/Makefile delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-csid.c delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-csid.h delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-csiphy.c delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-csiphy.h delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-ispif.c delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-ispif.h delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-vfe.c delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-vfe.h delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-video.c delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss-video.h delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss.c delete mode 100644 drivers/media/platform/qcom/camss-8x16/camss.h create mode 100644 drivers/media/platform/qcom/camss/Makefile create mode 100644 drivers/media/platform/qcom/camss/camss-csid.c create mode 100644 drivers/media/platform/qcom/camss/camss-csid.h create mode 100644 drivers/media/platform/qcom/camss/camss-csiphy.c create mode 100644 drivers/media/platform/qcom/camss/camss-csiphy.h create mode 100644 drivers/media/platform/qcom/camss/camss-ispif.c create mode 100644 drivers/media/platform/qcom/camss/camss-ispif.h create mode 100644 drivers/media/platform/qcom/camss/camss-vfe.c create mode 100644 drivers/media/platform/qcom/camss/camss-vfe.h create mode 100644 drivers/media/platform/qcom/camss/camss-video.c create mode 100644 drivers/media/platform/qcom/camss/camss-video.h create mode 100644 drivers/media/platform/qcom/camss/camss.c create mode 100644 drivers/media/platform/qcom/camss/camss.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3908908b1ec8..da68e6da9981 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11870,7 +11870,7 @@ L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/qcom,camss.txt F: Documentation/media/v4l-drivers/qcom_camss.rst -F: drivers/media/platform/qcom/camss-8x16/ +F: drivers/media/platform/qcom/camss/ QUALCOMM CPUFREQ DRIVER MSM8996/APQ8096 M: Ilia Lin diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 40bd3d96ca82..b25c8d3c1c31 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -90,7 +90,7 @@ config VIDEO_PXA27x This is a v4l2 driver for the PXA27x Quick Capture Interface config VIDEO_QCOM_CAMSS - tristate "Qualcomm 8x16 V4L2 Camera Subsystem driver" + tristate "Qualcomm V4L2 Camera Subsystem driver" depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST select VIDEOBUF2_DMA_SG diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 8e5f88eb7559..08640ba87fc2 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -89,7 +89,7 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/ obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/ -obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss-8x16/ +obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/ obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/ diff --git a/drivers/media/platform/qcom/camss-8x16/Makefile b/drivers/media/platform/qcom/camss-8x16/Makefile deleted file mode 100644 index 3c4024fbb768..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -# Makefile for Qualcomm CAMSS driver - -qcom-camss-objs += \ - camss.o \ - camss-csid.o \ - camss-csiphy.o \ - camss-ispif.o \ - camss-vfe.o \ - camss-video.o \ - -obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o diff --git a/drivers/media/platform/qcom/camss-8x16/camss-csid.c b/drivers/media/platform/qcom/camss-8x16/camss-csid.c deleted file mode 100644 index 226f36ef7419..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-csid.c +++ /dev/null @@ -1,1094 +0,0 @@ -/* - * camss-csid.c - * - * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module - * - * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "camss-csid.h" -#include "camss.h" - -#define MSM_CSID_NAME "msm_csid" - -#define CAMSS_CSID_HW_VERSION 0x0 -#define CAMSS_CSID_CORE_CTRL_0 0x004 -#define CAMSS_CSID_CORE_CTRL_1 0x008 -#define CAMSS_CSID_RST_CMD 0x00c -#define CAMSS_CSID_CID_LUT_VC_n(n) (0x010 + 0x4 * (n)) -#define CAMSS_CSID_CID_n_CFG(n) (0x020 + 0x4 * (n)) -#define CAMSS_CSID_IRQ_CLEAR_CMD 0x060 -#define CAMSS_CSID_IRQ_MASK 0x064 -#define CAMSS_CSID_IRQ_STATUS 0x068 -#define CAMSS_CSID_TG_CTRL 0x0a0 -#define CAMSS_CSID_TG_CTRL_DISABLE 0xa06436 -#define CAMSS_CSID_TG_CTRL_ENABLE 0xa06437 -#define CAMSS_CSID_TG_VC_CFG 0x0a4 -#define CAMSS_CSID_TG_VC_CFG_H_BLANKING 0x3ff -#define CAMSS_CSID_TG_VC_CFG_V_BLANKING 0x7f -#define CAMSS_CSID_TG_DT_n_CGG_0(n) (0x0ac + 0xc * (n)) -#define CAMSS_CSID_TG_DT_n_CGG_1(n) (0x0b0 + 0xc * (n)) -#define CAMSS_CSID_TG_DT_n_CGG_2(n) (0x0b4 + 0xc * (n)) - -#define DATA_TYPE_EMBEDDED_DATA_8BIT 0x12 -#define DATA_TYPE_YUV422_8BIT 0x1e -#define DATA_TYPE_RAW_6BIT 0x28 -#define DATA_TYPE_RAW_8BIT 0x2a -#define DATA_TYPE_RAW_10BIT 0x2b -#define DATA_TYPE_RAW_12BIT 0x2c - -#define DECODE_FORMAT_UNCOMPRESSED_6_BIT 0x0 -#define DECODE_FORMAT_UNCOMPRESSED_8_BIT 0x1 -#define DECODE_FORMAT_UNCOMPRESSED_10_BIT 0x2 -#define DECODE_FORMAT_UNCOMPRESSED_12_BIT 0x3 - -#define CSID_RESET_TIMEOUT_MS 500 - -struct csid_fmts { - u32 code; - u8 data_type; - u8 decode_format; - u8 bpp; - u8 spp; /* bus samples per pixel */ -}; - -static const struct csid_fmts csid_input_fmts[] = { - { - MEDIA_BUS_FMT_UYVY8_2X8, - DATA_TYPE_YUV422_8BIT, - DECODE_FORMAT_UNCOMPRESSED_8_BIT, - 8, - 2, - }, - { - MEDIA_BUS_FMT_VYUY8_2X8, - DATA_TYPE_YUV422_8BIT, - DECODE_FORMAT_UNCOMPRESSED_8_BIT, - 8, - 2, - }, - { - MEDIA_BUS_FMT_YUYV8_2X8, - DATA_TYPE_YUV422_8BIT, - DECODE_FORMAT_UNCOMPRESSED_8_BIT, - 8, - 2, - }, - { - MEDIA_BUS_FMT_YVYU8_2X8, - DATA_TYPE_YUV422_8BIT, - DECODE_FORMAT_UNCOMPRESSED_8_BIT, - 8, - 2, - }, - { - MEDIA_BUS_FMT_SBGGR8_1X8, - DATA_TYPE_RAW_8BIT, - DECODE_FORMAT_UNCOMPRESSED_8_BIT, - 8, - 1, - }, - { - MEDIA_BUS_FMT_SGBRG8_1X8, - DATA_TYPE_RAW_8BIT, - DECODE_FORMAT_UNCOMPRESSED_8_BIT, - 8, - 1, - }, - { - MEDIA_BUS_FMT_SGRBG8_1X8, - DATA_TYPE_RAW_8BIT, - DECODE_FORMAT_UNCOMPRESSED_8_BIT, - 8, - 1, - }, - { - MEDIA_BUS_FMT_SRGGB8_1X8, - DATA_TYPE_RAW_8BIT, - DECODE_FORMAT_UNCOMPRESSED_8_BIT, - 8, - 1, - }, - { - MEDIA_BUS_FMT_SBGGR10_1X10, - DATA_TYPE_RAW_10BIT, - DECODE_FORMAT_UNCOMPRESSED_10_BIT, - 10, - 1, - }, - { - MEDIA_BUS_FMT_SGBRG10_1X10, - DATA_TYPE_RAW_10BIT, - DECODE_FORMAT_UNCOMPRESSED_10_BIT, - 10, - 1, - }, - { - MEDIA_BUS_FMT_SGRBG10_1X10, - DATA_TYPE_RAW_10BIT, - DECODE_FORMAT_UNCOMPRESSED_10_BIT, - 10, - 1, - }, - { - MEDIA_BUS_FMT_SRGGB10_1X10, - DATA_TYPE_RAW_10BIT, - DECODE_FORMAT_UNCOMPRESSED_10_BIT, - 10, - 1, - }, - { - MEDIA_BUS_FMT_SBGGR12_1X12, - DATA_TYPE_RAW_12BIT, - DECODE_FORMAT_UNCOMPRESSED_12_BIT, - 12, - 1, - }, - { - MEDIA_BUS_FMT_SGBRG12_1X12, - DATA_TYPE_RAW_12BIT, - DECODE_FORMAT_UNCOMPRESSED_12_BIT, - 12, - 1, - }, - { - MEDIA_BUS_FMT_SGRBG12_1X12, - DATA_TYPE_RAW_12BIT, - DECODE_FORMAT_UNCOMPRESSED_12_BIT, - 12, - 1, - }, - { - MEDIA_BUS_FMT_SRGGB12_1X12, - DATA_TYPE_RAW_12BIT, - DECODE_FORMAT_UNCOMPRESSED_12_BIT, - 12, - 1, - } -}; - -static const struct csid_fmts *csid_get_fmt_entry(u32 code) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(csid_input_fmts); i++) - if (code == csid_input_fmts[i].code) - return &csid_input_fmts[i]; - - WARN(1, "Unknown format\n"); - - return &csid_input_fmts[0]; -} - -/* - * csid_isr - CSID module interrupt handler - * @irq: Interrupt line - * @dev: CSID device - * - * Return IRQ_HANDLED on success - */ -static irqreturn_t csid_isr(int irq, void *dev) -{ - struct csid_device *csid = dev; - u32 value; - - value = readl_relaxed(csid->base + CAMSS_CSID_IRQ_STATUS); - writel_relaxed(value, csid->base + CAMSS_CSID_IRQ_CLEAR_CMD); - - if ((value >> 11) & 0x1) - complete(&csid->reset_complete); - - return IRQ_HANDLED; -} - -/* - * csid_set_clock_rates - Calculate and set clock rates on CSID module - * @csiphy: CSID device - */ -static int csid_set_clock_rates(struct csid_device *csid) -{ - struct device *dev = to_device_index(csid, csid->id); - u32 pixel_clock; - int i, j; - int ret; - - ret = camss_get_pixel_clock(&csid->subdev.entity, &pixel_clock); - if (ret) - pixel_clock = 0; - - for (i = 0; i < csid->nclocks; i++) { - struct camss_clock *clock = &csid->clock[i]; - - if (!strcmp(clock->name, "csi0") || - !strcmp(clock->name, "csi1")) { - u8 bpp = csid_get_fmt_entry( - csid->fmt[MSM_CSIPHY_PAD_SINK].code)->bpp; - u8 num_lanes = csid->phy.lane_cnt; - u64 min_rate = pixel_clock * bpp / (2 * num_lanes * 4); - long rate; - - camss_add_clock_margin(&min_rate); - - for (j = 0; j < clock->nfreqs; j++) - if (min_rate < clock->freq[j]) - break; - - if (j == clock->nfreqs) { - dev_err(dev, - "Pixel clock is too high for CSID\n"); - return -EINVAL; - } - - /* if sensor pixel clock is not available */ - /* set highest possible CSID clock rate */ - if (min_rate == 0) - j = clock->nfreqs - 1; - - rate = clk_round_rate(clock->clk, clock->freq[j]); - if (rate < 0) { - dev_err(dev, "clk round rate failed: %ld\n", - rate); - return -EINVAL; - } - - ret = clk_set_rate(clock->clk, rate); - if (ret < 0) { - dev_err(dev, "clk set rate failed: %d\n", ret); - return ret; - } - } - } - - return 0; -} - -/* - * csid_reset - Trigger reset on CSID module and wait to complete - * @csid: CSID device - * - * Return 0 on success or a negative error code otherwise - */ -static int csid_reset(struct csid_device *csid) -{ - unsigned long time; - - reinit_completion(&csid->reset_complete); - - writel_relaxed(0x7fff, csid->base + CAMSS_CSID_RST_CMD); - - time = wait_for_completion_timeout(&csid->reset_complete, - msecs_to_jiffies(CSID_RESET_TIMEOUT_MS)); - if (!time) { - dev_err(to_device_index(csid, csid->id), - "CSID reset timeout\n"); - return -EIO; - } - - return 0; -} - -/* - * csid_set_power - Power on/off CSID module - * @sd: CSID V4L2 subdevice - * @on: Requested power state - * - * Return 0 on success or a negative error code otherwise - */ -static int csid_set_power(struct v4l2_subdev *sd, int on) -{ - struct csid_device *csid = v4l2_get_subdevdata(sd); - struct device *dev = to_device_index(csid, csid->id); - int ret; - - if (on) { - u32 hw_version; - - ret = regulator_enable(csid->vdda); - if (ret < 0) - return ret; - - ret = csid_set_clock_rates(csid); - if (ret < 0) { - regulator_disable(csid->vdda); - return ret; - } - - ret = camss_enable_clocks(csid->nclocks, csid->clock, dev); - if (ret < 0) { - regulator_disable(csid->vdda); - return ret; - } - - enable_irq(csid->irq); - - ret = csid_reset(csid); - if (ret < 0) { - disable_irq(csid->irq); - camss_disable_clocks(csid->nclocks, csid->clock); - regulator_disable(csid->vdda); - return ret; - } - - hw_version = readl_relaxed(csid->base + CAMSS_CSID_HW_VERSION); - dev_dbg(dev, "CSID HW Version = 0x%08x\n", hw_version); - } else { - disable_irq(csid->irq); - camss_disable_clocks(csid->nclocks, csid->clock); - ret = regulator_disable(csid->vdda); - } - - return ret; -} - -/* - * csid_set_stream - Enable/disable streaming on CSID module - * @sd: CSID V4L2 subdevice - * @enable: Requested streaming state - * - * Main configuration of CSID module is also done here. - * - * Return 0 on success or a negative error code otherwise - */ -static int csid_set_stream(struct v4l2_subdev *sd, int enable) -{ - struct csid_device *csid = v4l2_get_subdevdata(sd); - struct csid_testgen_config *tg = &csid->testgen; - u32 val; - - if (enable) { - u8 vc = 0; /* Virtual Channel 0 */ - u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */ - u8 dt, dt_shift, df; - int ret; - - ret = v4l2_ctrl_handler_setup(&csid->ctrls); - if (ret < 0) { - dev_err(to_device_index(csid, csid->id), - "could not sync v4l2 controls: %d\n", ret); - return ret; - } - - if (!tg->enabled && - !media_entity_remote_pad(&csid->pads[MSM_CSID_PAD_SINK])) - return -ENOLINK; - - dt = csid_get_fmt_entry(csid->fmt[MSM_CSID_PAD_SRC].code)-> - data_type; - - if (tg->enabled) { - /* Config Test Generator */ - struct v4l2_mbus_framefmt *f = - &csid->fmt[MSM_CSID_PAD_SRC]; - u8 bpp = csid_get_fmt_entry(f->code)->bpp; - u8 spp = csid_get_fmt_entry(f->code)->spp; - u32 num_bytes_per_line = f->width * bpp * spp / 8; - u32 num_lines = f->height; - - /* 31:24 V blank, 23:13 H blank, 3:2 num of active DT */ - /* 1:0 VC */ - val = ((CAMSS_CSID_TG_VC_CFG_V_BLANKING & 0xff) << 24) | - ((CAMSS_CSID_TG_VC_CFG_H_BLANKING & 0x7ff) << 13); - writel_relaxed(val, csid->base + CAMSS_CSID_TG_VC_CFG); - - /* 28:16 bytes per lines, 12:0 num of lines */ - val = ((num_bytes_per_line & 0x1fff) << 16) | - (num_lines & 0x1fff); - writel_relaxed(val, csid->base + - CAMSS_CSID_TG_DT_n_CGG_0(0)); - - /* 5:0 data type */ - val = dt; - writel_relaxed(val, csid->base + - CAMSS_CSID_TG_DT_n_CGG_1(0)); - - /* 2:0 output test pattern */ - val = tg->payload_mode; - writel_relaxed(val, csid->base + - CAMSS_CSID_TG_DT_n_CGG_2(0)); - } else { - struct csid_phy_config *phy = &csid->phy; - - val = phy->lane_cnt - 1; - val |= phy->lane_assign << 4; - - writel_relaxed(val, - csid->base + CAMSS_CSID_CORE_CTRL_0); - - val = phy->csiphy_id << 17; - val |= 0x9; - - writel_relaxed(val, - csid->base + CAMSS_CSID_CORE_CTRL_1); - } - - /* Config LUT */ - - dt_shift = (cid % 4) * 8; - df = csid_get_fmt_entry(csid->fmt[MSM_CSID_PAD_SINK].code)-> - decode_format; - - val = readl_relaxed(csid->base + CAMSS_CSID_CID_LUT_VC_n(vc)); - val &= ~(0xff << dt_shift); - val |= dt << dt_shift; - writel_relaxed(val, csid->base + CAMSS_CSID_CID_LUT_VC_n(vc)); - - val = (df << 4) | 0x3; - writel_relaxed(val, csid->base + CAMSS_CSID_CID_n_CFG(cid)); - - if (tg->enabled) { - val = CAMSS_CSID_TG_CTRL_ENABLE; - writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL); - } - } else { - if (tg->enabled) { - val = CAMSS_CSID_TG_CTRL_DISABLE; - writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL); - } - } - - return 0; -} - -/* - * __csid_get_format - Get pointer to format structure - * @csid: CSID device - * @cfg: V4L2 subdev pad configuration - * @pad: pad from which format is requested - * @which: TRY or ACTIVE format - * - * Return pointer to TRY or ACTIVE format structure - */ -static struct v4l2_mbus_framefmt * -__csid_get_format(struct csid_device *csid, - struct v4l2_subdev_pad_config *cfg, - unsigned int pad, - enum v4l2_subdev_format_whence which) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return v4l2_subdev_get_try_format(&csid->subdev, cfg, pad); - - return &csid->fmt[pad]; -} - -/* - * csid_try_format - Handle try format by pad subdev method - * @csid: CSID device - * @cfg: V4L2 subdev pad configuration - * @pad: pad on which format is requested - * @fmt: pointer to v4l2 format structure - * @which: wanted subdev format - */ -static void csid_try_format(struct csid_device *csid, - struct v4l2_subdev_pad_config *cfg, - unsigned int pad, - struct v4l2_mbus_framefmt *fmt, - enum v4l2_subdev_format_whence which) -{ - unsigned int i; - - switch (pad) { - case MSM_CSID_PAD_SINK: - /* Set format on sink pad */ - - for (i = 0; i < ARRAY_SIZE(csid_input_fmts); i++) - if (fmt->code == csid_input_fmts[i].code) - break; - - /* If not found, use UYVY as default */ - if (i >= ARRAY_SIZE(csid_input_fmts)) - fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; - - fmt->width = clamp_t(u32, fmt->width, 1, 8191); - fmt->height = clamp_t(u32, fmt->height, 1, 8191); - - fmt->field = V4L2_FIELD_NONE; - fmt->colorspace = V4L2_COLORSPACE_SRGB; - - break; - - case MSM_CSID_PAD_SRC: - if (csid->testgen_mode->cur.val == 0) { - /* Test generator is disabled, keep pad formats */ - /* in sync - set and return a format same as sink pad */ - struct v4l2_mbus_framefmt format; - - format = *__csid_get_format(csid, cfg, - MSM_CSID_PAD_SINK, which); - *fmt = format; - } else { - /* Test generator is enabled, set format on source*/ - /* pad to allow test generator usage */ - - for (i = 0; i < ARRAY_SIZE(csid_input_fmts); i++) - if (csid_input_fmts[i].code == fmt->code) - break; - - /* If not found, use UYVY as default */ - if (i >= ARRAY_SIZE(csid_input_fmts)) - fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; - - fmt->width = clamp_t(u32, fmt->width, 1, 8191); - fmt->height = clamp_t(u32, fmt->height, 1, 8191); - - fmt->field = V4L2_FIELD_NONE; - } - break; - } - - fmt->colorspace = V4L2_COLORSPACE_SRGB; -} - -/* - * csid_enum_mbus_code - Handle pixel format enumeration - * @sd: CSID V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @code: pointer to v4l2_subdev_mbus_code_enum structure - * return -EINVAL or zero on success - */ -static int csid_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_mbus_code_enum *code) -{ - struct csid_device *csid = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - if (code->pad == MSM_CSID_PAD_SINK) { - if (code->index >= ARRAY_SIZE(csid_input_fmts)) - return -EINVAL; - - code->code = csid_input_fmts[code->index].code; - } else { - if (csid->testgen_mode->cur.val == 0) { - if (code->index > 0) - return -EINVAL; - - format = __csid_get_format(csid, cfg, MSM_CSID_PAD_SINK, - code->which); - - code->code = format->code; - } else { - if (code->index >= ARRAY_SIZE(csid_input_fmts)) - return -EINVAL; - - code->code = csid_input_fmts[code->index].code; - } - } - - return 0; -} - -/* - * csid_enum_frame_size - Handle frame size enumeration - * @sd: CSID V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fse: pointer to v4l2_subdev_frame_size_enum structure - * return -EINVAL or zero on success - */ -static int csid_enum_frame_size(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_frame_size_enum *fse) -{ - struct csid_device *csid = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt format; - - if (fse->index != 0) - return -EINVAL; - - format.code = fse->code; - format.width = 1; - format.height = 1; - csid_try_format(csid, cfg, fse->pad, &format, fse->which); - fse->min_width = format.width; - fse->min_height = format.height; - - if (format.code != fse->code) - return -EINVAL; - - format.code = fse->code; - format.width = -1; - format.height = -1; - csid_try_format(csid, cfg, fse->pad, &format, fse->which); - fse->max_width = format.width; - fse->max_height = format.height; - - return 0; -} - -/* - * csid_get_format - Handle get format by pads subdev method - * @sd: CSID V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fmt: pointer to v4l2 subdev format structure - * - * Return -EINVAL or zero on success - */ -static int csid_get_format(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct csid_device *csid = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - format = __csid_get_format(csid, cfg, fmt->pad, fmt->which); - if (format == NULL) - return -EINVAL; - - fmt->format = *format; - - return 0; -} - -/* - * csid_set_format - Handle set format by pads subdev method - * @sd: CSID V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fmt: pointer to v4l2 subdev format structure - * - * Return -EINVAL or zero on success - */ -static int csid_set_format(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct csid_device *csid = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - format = __csid_get_format(csid, cfg, fmt->pad, fmt->which); - if (format == NULL) - return -EINVAL; - - csid_try_format(csid, cfg, fmt->pad, &fmt->format, fmt->which); - *format = fmt->format; - - /* Propagate the format from sink to source */ - if (fmt->pad == MSM_CSID_PAD_SINK) { - format = __csid_get_format(csid, cfg, MSM_CSID_PAD_SRC, - fmt->which); - - *format = fmt->format; - csid_try_format(csid, cfg, MSM_CSID_PAD_SRC, format, - fmt->which); - } - - return 0; -} - -/* - * csid_init_formats - Initialize formats on all pads - * @sd: CSID V4L2 subdevice - * @fh: V4L2 subdev file handle - * - * Initialize all pad formats with default values. - * - * Return 0 on success or a negative error code otherwise - */ -static int csid_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct v4l2_subdev_format format = { - .pad = MSM_CSID_PAD_SINK, - .which = fh ? V4L2_SUBDEV_FORMAT_TRY : - V4L2_SUBDEV_FORMAT_ACTIVE, - .format = { - .code = MEDIA_BUS_FMT_UYVY8_2X8, - .width = 1920, - .height = 1080 - } - }; - - return csid_set_format(sd, fh ? fh->pad : NULL, &format); -} - -static const char * const csid_test_pattern_menu[] = { - "Disabled", - "Incrementing", - "Alternating 0x55/0xAA", - "All Zeros 0x00", - "All Ones 0xFF", - "Pseudo-random Data", -}; - -/* - * csid_set_test_pattern - Set test generator's pattern mode - * @csid: CSID device - * @value: desired test pattern mode - * - * Return 0 on success or a negative error code otherwise - */ -static int csid_set_test_pattern(struct csid_device *csid, s32 value) -{ - struct csid_testgen_config *tg = &csid->testgen; - - /* If CSID is linked to CSIPHY, do not allow to enable test generator */ - if (value && media_entity_remote_pad(&csid->pads[MSM_CSID_PAD_SINK])) - return -EBUSY; - - tg->enabled = !!value; - - switch (value) { - case 1: - tg->payload_mode = CSID_PAYLOAD_MODE_INCREMENTING; - break; - case 2: - tg->payload_mode = CSID_PAYLOAD_MODE_ALTERNATING_55_AA; - break; - case 3: - tg->payload_mode = CSID_PAYLOAD_MODE_ALL_ZEROES; - break; - case 4: - tg->payload_mode = CSID_PAYLOAD_MODE_ALL_ONES; - break; - case 5: - tg->payload_mode = CSID_PAYLOAD_MODE_RANDOM; - break; - } - - return 0; -} - -/* - * csid_s_ctrl - Handle set control subdev method - * @ctrl: pointer to v4l2 control structure - * - * Return 0 on success or a negative error code otherwise - */ -static int csid_s_ctrl(struct v4l2_ctrl *ctrl) -{ - struct csid_device *csid = container_of(ctrl->handler, - struct csid_device, ctrls); - int ret = -EINVAL; - - switch (ctrl->id) { - case V4L2_CID_TEST_PATTERN: - ret = csid_set_test_pattern(csid, ctrl->val); - break; - } - - return ret; -} - -static const struct v4l2_ctrl_ops csid_ctrl_ops = { - .s_ctrl = csid_s_ctrl, -}; - -/* - * msm_csid_subdev_init - Initialize CSID device structure and resources - * @csid: CSID device - * @res: CSID module resources table - * @id: CSID module id - * - * Return 0 on success or a negative error code otherwise - */ -int msm_csid_subdev_init(struct csid_device *csid, - const struct resources *res, u8 id) -{ - struct device *dev = to_device_index(csid, id); - struct platform_device *pdev = to_platform_device(dev); - struct resource *r; - int i, j; - int ret; - - csid->id = id; - - /* Memory */ - - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[0]); - csid->base = devm_ioremap_resource(dev, r); - if (IS_ERR(csid->base)) { - dev_err(dev, "could not map memory\n"); - return PTR_ERR(csid->base); - } - - /* Interrupt */ - - r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, - res->interrupt[0]); - if (!r) { - dev_err(dev, "missing IRQ\n"); - return -EINVAL; - } - - csid->irq = r->start; - snprintf(csid->irq_name, sizeof(csid->irq_name), "%s_%s%d", - dev_name(dev), MSM_CSID_NAME, csid->id); - ret = devm_request_irq(dev, csid->irq, csid_isr, - IRQF_TRIGGER_RISING, csid->irq_name, csid); - if (ret < 0) { - dev_err(dev, "request_irq failed: %d\n", ret); - return ret; - } - - disable_irq(csid->irq); - - /* Clocks */ - - csid->nclocks = 0; - while (res->clock[csid->nclocks]) - csid->nclocks++; - - csid->clock = devm_kcalloc(dev, csid->nclocks, sizeof(*csid->clock), - GFP_KERNEL); - if (!csid->clock) - return -ENOMEM; - - for (i = 0; i < csid->nclocks; i++) { - struct camss_clock *clock = &csid->clock[i]; - - clock->clk = devm_clk_get(dev, res->clock[i]); - if (IS_ERR(clock->clk)) - return PTR_ERR(clock->clk); - - clock->name = res->clock[i]; - - clock->nfreqs = 0; - while (res->clock_rate[i][clock->nfreqs]) - clock->nfreqs++; - - if (!clock->nfreqs) { - clock->freq = NULL; - continue; - } - - clock->freq = devm_kcalloc(dev, - clock->nfreqs, - sizeof(*clock->freq), - GFP_KERNEL); - if (!clock->freq) - return -ENOMEM; - - for (j = 0; j < clock->nfreqs; j++) - clock->freq[j] = res->clock_rate[i][j]; - } - - /* Regulator */ - - csid->vdda = devm_regulator_get(dev, res->regulator[0]); - if (IS_ERR(csid->vdda)) { - dev_err(dev, "could not get regulator\n"); - return PTR_ERR(csid->vdda); - } - - init_completion(&csid->reset_complete); - - return 0; -} - -/* - * msm_csid_get_csid_id - Get CSID HW module id - * @entity: Pointer to CSID media entity structure - * @id: Return CSID HW module id here - */ -void msm_csid_get_csid_id(struct media_entity *entity, u8 *id) -{ - struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); - struct csid_device *csid = v4l2_get_subdevdata(sd); - - *id = csid->id; -} - -/* - * csid_get_lane_assign - Calculate CSI2 lane assign configuration parameter - * @lane_cfg - CSI2 lane configuration - * - * Return lane assign - */ -static u32 csid_get_lane_assign(struct csiphy_lanes_cfg *lane_cfg) -{ - u32 lane_assign = 0; - int i; - - for (i = 0; i < lane_cfg->num_data; i++) - lane_assign |= lane_cfg->data[i].pos << (i * 4); - - return lane_assign; -} - -/* - * csid_link_setup - Setup CSID connections - * @entity: Pointer to media entity structure - * @local: Pointer to local pad - * @remote: Pointer to remote pad - * @flags: Link flags - * - * Return 0 on success - */ -static int csid_link_setup(struct media_entity *entity, - const struct media_pad *local, - const struct media_pad *remote, u32 flags) -{ - if (flags & MEDIA_LNK_FL_ENABLED) - if (media_entity_remote_pad(local)) - return -EBUSY; - - if ((local->flags & MEDIA_PAD_FL_SINK) && - (flags & MEDIA_LNK_FL_ENABLED)) { - struct v4l2_subdev *sd; - struct csid_device *csid; - struct csiphy_device *csiphy; - struct csiphy_lanes_cfg *lane_cfg; - struct v4l2_subdev_format format = { 0 }; - - sd = media_entity_to_v4l2_subdev(entity); - csid = v4l2_get_subdevdata(sd); - - /* If test generator is enabled */ - /* do not allow a link from CSIPHY to CSID */ - if (csid->testgen_mode->cur.val != 0) - return -EBUSY; - - sd = media_entity_to_v4l2_subdev(remote->entity); - csiphy = v4l2_get_subdevdata(sd); - - /* If a sensor is not linked to CSIPHY */ - /* do no allow a link from CSIPHY to CSID */ - if (!csiphy->cfg.csi2) - return -EPERM; - - csid->phy.csiphy_id = csiphy->id; - - lane_cfg = &csiphy->cfg.csi2->lane_cfg; - csid->phy.lane_cnt = lane_cfg->num_data; - csid->phy.lane_assign = csid_get_lane_assign(lane_cfg); - - /* Reset format on source pad to sink pad format */ - format.pad = MSM_CSID_PAD_SRC; - format.which = V4L2_SUBDEV_FORMAT_ACTIVE; - csid_set_format(&csid->subdev, NULL, &format); - } - - return 0; -} - -static const struct v4l2_subdev_core_ops csid_core_ops = { - .s_power = csid_set_power, -}; - -static const struct v4l2_subdev_video_ops csid_video_ops = { - .s_stream = csid_set_stream, -}; - -static const struct v4l2_subdev_pad_ops csid_pad_ops = { - .enum_mbus_code = csid_enum_mbus_code, - .enum_frame_size = csid_enum_frame_size, - .get_fmt = csid_get_format, - .set_fmt = csid_set_format, -}; - -static const struct v4l2_subdev_ops csid_v4l2_ops = { - .core = &csid_core_ops, - .video = &csid_video_ops, - .pad = &csid_pad_ops, -}; - -static const struct v4l2_subdev_internal_ops csid_v4l2_internal_ops = { - .open = csid_init_formats, -}; - -static const struct media_entity_operations csid_media_ops = { - .link_setup = csid_link_setup, - .link_validate = v4l2_subdev_link_validate, -}; - -/* - * msm_csid_register_entity - Register subdev node for CSID module - * @csid: CSID device - * @v4l2_dev: V4L2 device - * - * Return 0 on success or a negative error code otherwise - */ -int msm_csid_register_entity(struct csid_device *csid, - struct v4l2_device *v4l2_dev) -{ - struct v4l2_subdev *sd = &csid->subdev; - struct media_pad *pads = csid->pads; - struct device *dev = to_device_index(csid, csid->id); - int ret; - - v4l2_subdev_init(sd, &csid_v4l2_ops); - sd->internal_ops = &csid_v4l2_internal_ops; - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", - MSM_CSID_NAME, csid->id); - v4l2_set_subdevdata(sd, csid); - - ret = v4l2_ctrl_handler_init(&csid->ctrls, 1); - if (ret < 0) { - dev_err(dev, "Failed to init ctrl handler: %d\n", ret); - return ret; - } - - csid->testgen_mode = v4l2_ctrl_new_std_menu_items(&csid->ctrls, - &csid_ctrl_ops, V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(csid_test_pattern_menu) - 1, 0, 0, - csid_test_pattern_menu); - - if (csid->ctrls.error) { - dev_err(dev, "Failed to init ctrl: %d\n", csid->ctrls.error); - ret = csid->ctrls.error; - goto free_ctrl; - } - - csid->subdev.ctrl_handler = &csid->ctrls; - - ret = csid_init_formats(sd, NULL); - if (ret < 0) { - dev_err(dev, "Failed to init format: %d\n", ret); - goto free_ctrl; - } - - pads[MSM_CSID_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[MSM_CSID_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; - - sd->entity.function = MEDIA_ENT_F_IO_V4L; - sd->entity.ops = &csid_media_ops; - ret = media_entity_pads_init(&sd->entity, MSM_CSID_PADS_NUM, pads); - if (ret < 0) { - dev_err(dev, "Failed to init media entity: %d\n", ret); - goto free_ctrl; - } - - ret = v4l2_device_register_subdev(v4l2_dev, sd); - if (ret < 0) { - dev_err(dev, "Failed to register subdev: %d\n", ret); - goto media_cleanup; - } - - return 0; - -media_cleanup: - media_entity_cleanup(&sd->entity); -free_ctrl: - v4l2_ctrl_handler_free(&csid->ctrls); - - return ret; -} - -/* - * msm_csid_unregister_entity - Unregister CSID module subdev node - * @csid: CSID device - */ -void msm_csid_unregister_entity(struct csid_device *csid) -{ - v4l2_device_unregister_subdev(&csid->subdev); - media_entity_cleanup(&csid->subdev.entity); - v4l2_ctrl_handler_free(&csid->ctrls); -} diff --git a/drivers/media/platform/qcom/camss-8x16/camss-csid.h b/drivers/media/platform/qcom/camss-8x16/camss-csid.h deleted file mode 100644 index 8682d3081bc3..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-csid.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * camss-csid.h - * - * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module - * - * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#ifndef QC_MSM_CAMSS_CSID_H -#define QC_MSM_CAMSS_CSID_H - -#include -#include -#include -#include -#include -#include - -#define MSM_CSID_PAD_SINK 0 -#define MSM_CSID_PAD_SRC 1 -#define MSM_CSID_PADS_NUM 2 - -enum csid_payload_mode { - CSID_PAYLOAD_MODE_INCREMENTING = 0, - CSID_PAYLOAD_MODE_ALTERNATING_55_AA = 1, - CSID_PAYLOAD_MODE_ALL_ZEROES = 2, - CSID_PAYLOAD_MODE_ALL_ONES = 3, - CSID_PAYLOAD_MODE_RANDOM = 4, - CSID_PAYLOAD_MODE_USER_SPECIFIED = 5, -}; - -struct csid_testgen_config { - u8 enabled; - enum csid_payload_mode payload_mode; -}; - -struct csid_phy_config { - u8 csiphy_id; - u8 lane_cnt; - u32 lane_assign; -}; - -struct csid_device { - u8 id; - struct v4l2_subdev subdev; - struct media_pad pads[MSM_CSID_PADS_NUM]; - void __iomem *base; - u32 irq; - char irq_name[30]; - struct camss_clock *clock; - int nclocks; - struct regulator *vdda; - struct completion reset_complete; - struct csid_testgen_config testgen; - struct csid_phy_config phy; - struct v4l2_mbus_framefmt fmt[MSM_CSID_PADS_NUM]; - struct v4l2_ctrl_handler ctrls; - struct v4l2_ctrl *testgen_mode; -}; - -struct resources; - -int msm_csid_subdev_init(struct csid_device *csid, - const struct resources *res, u8 id); - -int msm_csid_register_entity(struct csid_device *csid, - struct v4l2_device *v4l2_dev); - -void msm_csid_unregister_entity(struct csid_device *csid); - -void msm_csid_get_csid_id(struct media_entity *entity, u8 *id); - -#endif /* QC_MSM_CAMSS_CSID_H */ diff --git a/drivers/media/platform/qcom/camss-8x16/camss-csiphy.c b/drivers/media/platform/qcom/camss-8x16/camss-csiphy.c deleted file mode 100644 index 7e61caba6a2d..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-csiphy.c +++ /dev/null @@ -1,893 +0,0 @@ -/* - * camss-csiphy.c - * - * Qualcomm MSM Camera Subsystem - CSIPHY Module - * - * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2016-2017 Linaro Ltd. - * - * 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. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "camss-csiphy.h" -#include "camss.h" - -#define MSM_CSIPHY_NAME "msm_csiphy" - -#define CAMSS_CSI_PHY_LNn_CFG2(n) (0x004 + 0x40 * (n)) -#define CAMSS_CSI_PHY_LNn_CFG3(n) (0x008 + 0x40 * (n)) -#define CAMSS_CSI_PHY_GLBL_RESET 0x140 -#define CAMSS_CSI_PHY_GLBL_PWR_CFG 0x144 -#define CAMSS_CSI_PHY_GLBL_IRQ_CMD 0x164 -#define CAMSS_CSI_PHY_HW_VERSION 0x188 -#define CAMSS_CSI_PHY_INTERRUPT_STATUSn(n) (0x18c + 0x4 * (n)) -#define CAMSS_CSI_PHY_INTERRUPT_MASKn(n) (0x1ac + 0x4 * (n)) -#define CAMSS_CSI_PHY_INTERRUPT_CLEARn(n) (0x1cc + 0x4 * (n)) -#define CAMSS_CSI_PHY_GLBL_T_INIT_CFG0 0x1ec -#define CAMSS_CSI_PHY_T_WAKEUP_CFG0 0x1f4 - -static const struct { - u32 code; - u8 bpp; -} csiphy_formats[] = { - { - MEDIA_BUS_FMT_UYVY8_2X8, - 8, - }, - { - MEDIA_BUS_FMT_VYUY8_2X8, - 8, - }, - { - MEDIA_BUS_FMT_YUYV8_2X8, - 8, - }, - { - MEDIA_BUS_FMT_YVYU8_2X8, - 8, - }, - { - MEDIA_BUS_FMT_SBGGR8_1X8, - 8, - }, - { - MEDIA_BUS_FMT_SGBRG8_1X8, - 8, - }, - { - MEDIA_BUS_FMT_SGRBG8_1X8, - 8, - }, - { - MEDIA_BUS_FMT_SRGGB8_1X8, - 8, - }, - { - MEDIA_BUS_FMT_SBGGR10_1X10, - 10, - }, - { - MEDIA_BUS_FMT_SGBRG10_1X10, - 10, - }, - { - MEDIA_BUS_FMT_SGRBG10_1X10, - 10, - }, - { - MEDIA_BUS_FMT_SRGGB10_1X10, - 10, - }, - { - MEDIA_BUS_FMT_SBGGR12_1X12, - 12, - }, - { - MEDIA_BUS_FMT_SGBRG12_1X12, - 12, - }, - { - MEDIA_BUS_FMT_SGRBG12_1X12, - 12, - }, - { - MEDIA_BUS_FMT_SRGGB12_1X12, - 12, - } -}; - -/* - * csiphy_get_bpp - map media bus format to bits per pixel - * @code: media bus format code - * - * Return number of bits per pixel - */ -static u8 csiphy_get_bpp(u32 code) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(csiphy_formats); i++) - if (code == csiphy_formats[i].code) - return csiphy_formats[i].bpp; - - WARN(1, "Unknown format\n"); - - return csiphy_formats[0].bpp; -} - -/* - * csiphy_isr - CSIPHY module interrupt handler - * @irq: Interrupt line - * @dev: CSIPHY device - * - * Return IRQ_HANDLED on success - */ -static irqreturn_t csiphy_isr(int irq, void *dev) -{ - struct csiphy_device *csiphy = dev; - u8 i; - - for (i = 0; i < 8; i++) { - u8 val = readl_relaxed(csiphy->base + - CAMSS_CSI_PHY_INTERRUPT_STATUSn(i)); - writel_relaxed(val, csiphy->base + - CAMSS_CSI_PHY_INTERRUPT_CLEARn(i)); - writel_relaxed(0x1, csiphy->base + CAMSS_CSI_PHY_GLBL_IRQ_CMD); - writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_IRQ_CMD); - writel_relaxed(0x0, csiphy->base + - CAMSS_CSI_PHY_INTERRUPT_CLEARn(i)); - } - - return IRQ_HANDLED; -} - -/* - * csiphy_set_clock_rates - Calculate and set clock rates on CSIPHY module - * @csiphy: CSIPHY device - */ -static int csiphy_set_clock_rates(struct csiphy_device *csiphy) -{ - struct device *dev = to_device_index(csiphy, csiphy->id); - u32 pixel_clock; - int i, j; - int ret; - - ret = camss_get_pixel_clock(&csiphy->subdev.entity, &pixel_clock); - if (ret) - pixel_clock = 0; - - for (i = 0; i < csiphy->nclocks; i++) { - struct camss_clock *clock = &csiphy->clock[i]; - - if (!strcmp(clock->name, "csiphy0_timer") || - !strcmp(clock->name, "csiphy1_timer")) { - u8 bpp = csiphy_get_bpp( - csiphy->fmt[MSM_CSIPHY_PAD_SINK].code); - u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data; - u64 min_rate = pixel_clock * bpp / (2 * num_lanes * 4); - long round_rate; - - camss_add_clock_margin(&min_rate); - - for (j = 0; j < clock->nfreqs; j++) - if (min_rate < clock->freq[j]) - break; - - if (j == clock->nfreqs) { - dev_err(dev, - "Pixel clock is too high for CSIPHY\n"); - return -EINVAL; - } - - /* if sensor pixel clock is not available */ - /* set highest possible CSIPHY clock rate */ - if (min_rate == 0) - j = clock->nfreqs - 1; - - round_rate = clk_round_rate(clock->clk, clock->freq[j]); - if (round_rate < 0) { - dev_err(dev, "clk round rate failed: %ld\n", - round_rate); - return -EINVAL; - } - - csiphy->timer_clk_rate = round_rate; - - ret = clk_set_rate(clock->clk, csiphy->timer_clk_rate); - if (ret < 0) { - dev_err(dev, "clk set rate failed: %d\n", ret); - return ret; - } - } - } - - return 0; -} - -/* - * csiphy_reset - Perform software reset on CSIPHY module - * @csiphy: CSIPHY device - */ -static void csiphy_reset(struct csiphy_device *csiphy) -{ - writel_relaxed(0x1, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); - usleep_range(5000, 8000); - writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); -} - -/* - * csiphy_set_power - Power on/off CSIPHY module - * @sd: CSIPHY V4L2 subdevice - * @on: Requested power state - * - * Return 0 on success or a negative error code otherwise - */ -static int csiphy_set_power(struct v4l2_subdev *sd, int on) -{ - struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); - struct device *dev = to_device_index(csiphy, csiphy->id); - - if (on) { - u8 hw_version; - int ret; - - ret = csiphy_set_clock_rates(csiphy); - if (ret < 0) - return ret; - - ret = camss_enable_clocks(csiphy->nclocks, csiphy->clock, dev); - if (ret < 0) - return ret; - - enable_irq(csiphy->irq); - - csiphy_reset(csiphy); - - hw_version = readl_relaxed(csiphy->base + - CAMSS_CSI_PHY_HW_VERSION); - dev_dbg(dev, "CSIPHY HW Version = 0x%02x\n", hw_version); - } else { - disable_irq(csiphy->irq); - - camss_disable_clocks(csiphy->nclocks, csiphy->clock); - } - - return 0; -} - -/* - * csiphy_get_lane_mask - Calculate CSI2 lane mask configuration parameter - * @lane_cfg - CSI2 lane configuration - * - * Return lane mask - */ -static u8 csiphy_get_lane_mask(struct csiphy_lanes_cfg *lane_cfg) -{ - u8 lane_mask; - int i; - - lane_mask = 1 << lane_cfg->clk.pos; - - for (i = 0; i < lane_cfg->num_data; i++) - lane_mask |= 1 << lane_cfg->data[i].pos; - - return lane_mask; -} - -/* - * csiphy_settle_cnt_calc - Calculate settle count value - * @csiphy: CSIPHY device - * - * Helper function to calculate settle count value. This is - * based on the CSI2 T_hs_settle parameter which in turn - * is calculated based on the CSI2 transmitter pixel clock - * frequency. - * - * Return settle count value or 0 if the CSI2 pixel clock - * frequency is not available - */ -static u8 csiphy_settle_cnt_calc(struct csiphy_device *csiphy) -{ - u8 bpp = csiphy_get_bpp( - csiphy->fmt[MSM_CSIPHY_PAD_SINK].code); - u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data; - u32 pixel_clock; /* Hz */ - u32 mipi_clock; /* Hz */ - u32 ui; /* ps */ - u32 timer_period; /* ps */ - u32 t_hs_prepare_max; /* ps */ - u32 t_hs_prepare_zero_min; /* ps */ - u32 t_hs_settle; /* ps */ - u8 settle_cnt; - int ret; - - ret = camss_get_pixel_clock(&csiphy->subdev.entity, &pixel_clock); - if (ret) { - dev_err(to_device_index(csiphy, csiphy->id), - "Cannot get CSI2 transmitter's pixel clock\n"); - return 0; - } - if (!pixel_clock) { - dev_err(to_device_index(csiphy, csiphy->id), - "Got pixel clock == 0, cannot continue\n"); - return 0; - } - - mipi_clock = pixel_clock * bpp / (2 * num_lanes); - ui = div_u64(1000000000000LL, mipi_clock); - ui /= 2; - t_hs_prepare_max = 85000 + 6 * ui; - t_hs_prepare_zero_min = 145000 + 10 * ui; - t_hs_settle = (t_hs_prepare_max + t_hs_prepare_zero_min) / 2; - - timer_period = div_u64(1000000000000LL, csiphy->timer_clk_rate); - settle_cnt = t_hs_settle / timer_period; - - return settle_cnt; -} - -/* - * csiphy_stream_on - Enable streaming on CSIPHY module - * @csiphy: CSIPHY device - * - * Helper function to enable streaming on CSIPHY module. - * Main configuration of CSIPHY module is also done here. - * - * Return 0 on success or a negative error code otherwise - */ -static int csiphy_stream_on(struct csiphy_device *csiphy) -{ - struct csiphy_config *cfg = &csiphy->cfg; - u8 lane_mask = csiphy_get_lane_mask(&cfg->csi2->lane_cfg); - u8 settle_cnt; - u8 val; - int i = 0; - - settle_cnt = csiphy_settle_cnt_calc(csiphy); - if (!settle_cnt) - return -EINVAL; - - val = readl_relaxed(csiphy->base_clk_mux); - if (cfg->combo_mode && (lane_mask & 0x18) == 0x18) { - val &= ~0xf0; - val |= cfg->csid_id << 4; - } else { - val &= ~0xf; - val |= cfg->csid_id; - } - writel_relaxed(val, csiphy->base_clk_mux); - - writel_relaxed(0x1, csiphy->base + - CAMSS_CSI_PHY_GLBL_T_INIT_CFG0); - writel_relaxed(0x1, csiphy->base + - CAMSS_CSI_PHY_T_WAKEUP_CFG0); - - val = 0x1; - val |= lane_mask << 1; - writel_relaxed(val, csiphy->base + CAMSS_CSI_PHY_GLBL_PWR_CFG); - - val = cfg->combo_mode << 4; - writel_relaxed(val, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); - - while (lane_mask) { - if (lane_mask & 0x1) { - writel_relaxed(0x10, csiphy->base + - CAMSS_CSI_PHY_LNn_CFG2(i)); - writel_relaxed(settle_cnt, csiphy->base + - CAMSS_CSI_PHY_LNn_CFG3(i)); - writel_relaxed(0x3f, csiphy->base + - CAMSS_CSI_PHY_INTERRUPT_MASKn(i)); - writel_relaxed(0x3f, csiphy->base + - CAMSS_CSI_PHY_INTERRUPT_CLEARn(i)); - } - - lane_mask >>= 1; - i++; - } - - return 0; -} - -/* - * csiphy_stream_off - Disable streaming on CSIPHY module - * @csiphy: CSIPHY device - * - * Helper function to disable streaming on CSIPHY module - */ -static void csiphy_stream_off(struct csiphy_device *csiphy) -{ - u8 lane_mask = csiphy_get_lane_mask(&csiphy->cfg.csi2->lane_cfg); - int i = 0; - - while (lane_mask) { - if (lane_mask & 0x1) - writel_relaxed(0x0, csiphy->base + - CAMSS_CSI_PHY_LNn_CFG2(i)); - - lane_mask >>= 1; - i++; - } - - writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_PWR_CFG); -} - - -/* - * csiphy_set_stream - Enable/disable streaming on CSIPHY module - * @sd: CSIPHY V4L2 subdevice - * @enable: Requested streaming state - * - * Return 0 on success or a negative error code otherwise - */ -static int csiphy_set_stream(struct v4l2_subdev *sd, int enable) -{ - struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); - int ret = 0; - - if (enable) - ret = csiphy_stream_on(csiphy); - else - csiphy_stream_off(csiphy); - - return ret; -} - -/* - * __csiphy_get_format - Get pointer to format structure - * @csiphy: CSIPHY device - * @cfg: V4L2 subdev pad configuration - * @pad: pad from which format is requested - * @which: TRY or ACTIVE format - * - * Return pointer to TRY or ACTIVE format structure - */ -static struct v4l2_mbus_framefmt * -__csiphy_get_format(struct csiphy_device *csiphy, - struct v4l2_subdev_pad_config *cfg, - unsigned int pad, - enum v4l2_subdev_format_whence which) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return v4l2_subdev_get_try_format(&csiphy->subdev, cfg, pad); - - return &csiphy->fmt[pad]; -} - -/* - * csiphy_try_format - Handle try format by pad subdev method - * @csiphy: CSIPHY device - * @cfg: V4L2 subdev pad configuration - * @pad: pad on which format is requested - * @fmt: pointer to v4l2 format structure - * @which: wanted subdev format - */ -static void csiphy_try_format(struct csiphy_device *csiphy, - struct v4l2_subdev_pad_config *cfg, - unsigned int pad, - struct v4l2_mbus_framefmt *fmt, - enum v4l2_subdev_format_whence which) -{ - unsigned int i; - - switch (pad) { - case MSM_CSIPHY_PAD_SINK: - /* Set format on sink pad */ - - for (i = 0; i < ARRAY_SIZE(csiphy_formats); i++) - if (fmt->code == csiphy_formats[i].code) - break; - - /* If not found, use UYVY as default */ - if (i >= ARRAY_SIZE(csiphy_formats)) - fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; - - fmt->width = clamp_t(u32, fmt->width, 1, 8191); - fmt->height = clamp_t(u32, fmt->height, 1, 8191); - - fmt->field = V4L2_FIELD_NONE; - fmt->colorspace = V4L2_COLORSPACE_SRGB; - - break; - - case MSM_CSIPHY_PAD_SRC: - /* Set and return a format same as sink pad */ - - *fmt = *__csiphy_get_format(csiphy, cfg, MSM_CSID_PAD_SINK, - which); - - break; - } -} - -/* - * csiphy_enum_mbus_code - Handle pixel format enumeration - * @sd: CSIPHY V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @code: pointer to v4l2_subdev_mbus_code_enum structure - * return -EINVAL or zero on success - */ -static int csiphy_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_mbus_code_enum *code) -{ - struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - if (code->pad == MSM_CSIPHY_PAD_SINK) { - if (code->index >= ARRAY_SIZE(csiphy_formats)) - return -EINVAL; - - code->code = csiphy_formats[code->index].code; - } else { - if (code->index > 0) - return -EINVAL; - - format = __csiphy_get_format(csiphy, cfg, MSM_CSIPHY_PAD_SINK, - code->which); - - code->code = format->code; - } - - return 0; -} - -/* - * csiphy_enum_frame_size - Handle frame size enumeration - * @sd: CSIPHY V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fse: pointer to v4l2_subdev_frame_size_enum structure - * return -EINVAL or zero on success - */ -static int csiphy_enum_frame_size(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_frame_size_enum *fse) -{ - struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt format; - - if (fse->index != 0) - return -EINVAL; - - format.code = fse->code; - format.width = 1; - format.height = 1; - csiphy_try_format(csiphy, cfg, fse->pad, &format, fse->which); - fse->min_width = format.width; - fse->min_height = format.height; - - if (format.code != fse->code) - return -EINVAL; - - format.code = fse->code; - format.width = -1; - format.height = -1; - csiphy_try_format(csiphy, cfg, fse->pad, &format, fse->which); - fse->max_width = format.width; - fse->max_height = format.height; - - return 0; -} - -/* - * csiphy_get_format - Handle get format by pads subdev method - * @sd: CSIPHY V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fmt: pointer to v4l2 subdev format structure - * - * Return -EINVAL or zero on success - */ -static int csiphy_get_format(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - format = __csiphy_get_format(csiphy, cfg, fmt->pad, fmt->which); - if (format == NULL) - return -EINVAL; - - fmt->format = *format; - - return 0; -} - -/* - * csiphy_set_format - Handle set format by pads subdev method - * @sd: CSIPHY V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fmt: pointer to v4l2 subdev format structure - * - * Return -EINVAL or zero on success - */ -static int csiphy_set_format(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - format = __csiphy_get_format(csiphy, cfg, fmt->pad, fmt->which); - if (format == NULL) - return -EINVAL; - - csiphy_try_format(csiphy, cfg, fmt->pad, &fmt->format, fmt->which); - *format = fmt->format; - - /* Propagate the format from sink to source */ - if (fmt->pad == MSM_CSIPHY_PAD_SINK) { - format = __csiphy_get_format(csiphy, cfg, MSM_CSIPHY_PAD_SRC, - fmt->which); - - *format = fmt->format; - csiphy_try_format(csiphy, cfg, MSM_CSIPHY_PAD_SRC, format, - fmt->which); - } - - return 0; -} - -/* - * csiphy_init_formats - Initialize formats on all pads - * @sd: CSIPHY V4L2 subdevice - * @fh: V4L2 subdev file handle - * - * Initialize all pad formats with default values. - * - * Return 0 on success or a negative error code otherwise - */ -static int csiphy_init_formats(struct v4l2_subdev *sd, - struct v4l2_subdev_fh *fh) -{ - struct v4l2_subdev_format format = { - .pad = MSM_CSIPHY_PAD_SINK, - .which = fh ? V4L2_SUBDEV_FORMAT_TRY : - V4L2_SUBDEV_FORMAT_ACTIVE, - .format = { - .code = MEDIA_BUS_FMT_UYVY8_2X8, - .width = 1920, - .height = 1080 - } - }; - - return csiphy_set_format(sd, fh ? fh->pad : NULL, &format); -} - -/* - * msm_csiphy_subdev_init - Initialize CSIPHY device structure and resources - * @csiphy: CSIPHY device - * @res: CSIPHY module resources table - * @id: CSIPHY module id - * - * Return 0 on success or a negative error code otherwise - */ -int msm_csiphy_subdev_init(struct csiphy_device *csiphy, - const struct resources *res, u8 id) -{ - struct device *dev = to_device_index(csiphy, id); - struct platform_device *pdev = to_platform_device(dev); - struct resource *r; - int i, j; - int ret; - - csiphy->id = id; - csiphy->cfg.combo_mode = 0; - - /* Memory */ - - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[0]); - csiphy->base = devm_ioremap_resource(dev, r); - if (IS_ERR(csiphy->base)) { - dev_err(dev, "could not map memory\n"); - return PTR_ERR(csiphy->base); - } - - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[1]); - csiphy->base_clk_mux = devm_ioremap_resource(dev, r); - if (IS_ERR(csiphy->base_clk_mux)) { - dev_err(dev, "could not map memory\n"); - return PTR_ERR(csiphy->base_clk_mux); - } - - /* Interrupt */ - - r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, - res->interrupt[0]); - if (!r) { - dev_err(dev, "missing IRQ\n"); - return -EINVAL; - } - - csiphy->irq = r->start; - snprintf(csiphy->irq_name, sizeof(csiphy->irq_name), "%s_%s%d", - dev_name(dev), MSM_CSIPHY_NAME, csiphy->id); - ret = devm_request_irq(dev, csiphy->irq, csiphy_isr, - IRQF_TRIGGER_RISING, csiphy->irq_name, csiphy); - if (ret < 0) { - dev_err(dev, "request_irq failed: %d\n", ret); - return ret; - } - - disable_irq(csiphy->irq); - - /* Clocks */ - - csiphy->nclocks = 0; - while (res->clock[csiphy->nclocks]) - csiphy->nclocks++; - - csiphy->clock = devm_kcalloc(dev, - csiphy->nclocks, sizeof(*csiphy->clock), - GFP_KERNEL); - if (!csiphy->clock) - return -ENOMEM; - - for (i = 0; i < csiphy->nclocks; i++) { - struct camss_clock *clock = &csiphy->clock[i]; - - clock->clk = devm_clk_get(dev, res->clock[i]); - if (IS_ERR(clock->clk)) - return PTR_ERR(clock->clk); - - clock->name = res->clock[i]; - - clock->nfreqs = 0; - while (res->clock_rate[i][clock->nfreqs]) - clock->nfreqs++; - - if (!clock->nfreqs) { - clock->freq = NULL; - continue; - } - - clock->freq = devm_kcalloc(dev, - clock->nfreqs, - sizeof(*clock->freq), - GFP_KERNEL); - if (!clock->freq) - return -ENOMEM; - - for (j = 0; j < clock->nfreqs; j++) - clock->freq[j] = res->clock_rate[i][j]; - } - - return 0; -} - -/* - * csiphy_link_setup - Setup CSIPHY connections - * @entity: Pointer to media entity structure - * @local: Pointer to local pad - * @remote: Pointer to remote pad - * @flags: Link flags - * - * Rreturn 0 on success - */ -static int csiphy_link_setup(struct media_entity *entity, - const struct media_pad *local, - const struct media_pad *remote, u32 flags) -{ - if ((local->flags & MEDIA_PAD_FL_SOURCE) && - (flags & MEDIA_LNK_FL_ENABLED)) { - struct v4l2_subdev *sd; - struct csiphy_device *csiphy; - struct csid_device *csid; - - if (media_entity_remote_pad(local)) - return -EBUSY; - - sd = media_entity_to_v4l2_subdev(entity); - csiphy = v4l2_get_subdevdata(sd); - - sd = media_entity_to_v4l2_subdev(remote->entity); - csid = v4l2_get_subdevdata(sd); - - csiphy->cfg.csid_id = csid->id; - } - - return 0; -} - -static const struct v4l2_subdev_core_ops csiphy_core_ops = { - .s_power = csiphy_set_power, -}; - -static const struct v4l2_subdev_video_ops csiphy_video_ops = { - .s_stream = csiphy_set_stream, -}; - -static const struct v4l2_subdev_pad_ops csiphy_pad_ops = { - .enum_mbus_code = csiphy_enum_mbus_code, - .enum_frame_size = csiphy_enum_frame_size, - .get_fmt = csiphy_get_format, - .set_fmt = csiphy_set_format, -}; - -static const struct v4l2_subdev_ops csiphy_v4l2_ops = { - .core = &csiphy_core_ops, - .video = &csiphy_video_ops, - .pad = &csiphy_pad_ops, -}; - -static const struct v4l2_subdev_internal_ops csiphy_v4l2_internal_ops = { - .open = csiphy_init_formats, -}; - -static const struct media_entity_operations csiphy_media_ops = { - .link_setup = csiphy_link_setup, - .link_validate = v4l2_subdev_link_validate, -}; - -/* - * msm_csiphy_register_entity - Register subdev node for CSIPHY module - * @csiphy: CSIPHY device - * @v4l2_dev: V4L2 device - * - * Return 0 on success or a negative error code otherwise - */ -int msm_csiphy_register_entity(struct csiphy_device *csiphy, - struct v4l2_device *v4l2_dev) -{ - struct v4l2_subdev *sd = &csiphy->subdev; - struct media_pad *pads = csiphy->pads; - struct device *dev = to_device_index(csiphy, csiphy->id); - int ret; - - v4l2_subdev_init(sd, &csiphy_v4l2_ops); - sd->internal_ops = &csiphy_v4l2_internal_ops; - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", - MSM_CSIPHY_NAME, csiphy->id); - v4l2_set_subdevdata(sd, csiphy); - - ret = csiphy_init_formats(sd, NULL); - if (ret < 0) { - dev_err(dev, "Failed to init format: %d\n", ret); - return ret; - } - - pads[MSM_CSIPHY_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[MSM_CSIPHY_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; - - sd->entity.function = MEDIA_ENT_F_IO_V4L; - sd->entity.ops = &csiphy_media_ops; - ret = media_entity_pads_init(&sd->entity, MSM_CSIPHY_PADS_NUM, pads); - if (ret < 0) { - dev_err(dev, "Failed to init media entity: %d\n", ret); - return ret; - } - - ret = v4l2_device_register_subdev(v4l2_dev, sd); - if (ret < 0) { - dev_err(dev, "Failed to register subdev: %d\n", ret); - media_entity_cleanup(&sd->entity); - } - - return ret; -} - -/* - * msm_csiphy_unregister_entity - Unregister CSIPHY module subdev node - * @csiphy: CSIPHY device - */ -void msm_csiphy_unregister_entity(struct csiphy_device *csiphy) -{ - v4l2_device_unregister_subdev(&csiphy->subdev); - media_entity_cleanup(&csiphy->subdev.entity); -} diff --git a/drivers/media/platform/qcom/camss-8x16/camss-csiphy.h b/drivers/media/platform/qcom/camss-8x16/camss-csiphy.h deleted file mode 100644 index ba8781122065..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-csiphy.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * camss-csiphy.h - * - * Qualcomm MSM Camera Subsystem - CSIPHY Module - * - * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2016-2017 Linaro Ltd. - * - * 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. - */ -#ifndef QC_MSM_CAMSS_CSIPHY_H -#define QC_MSM_CAMSS_CSIPHY_H - -#include -#include -#include -#include -#include - -#define MSM_CSIPHY_PAD_SINK 0 -#define MSM_CSIPHY_PAD_SRC 1 -#define MSM_CSIPHY_PADS_NUM 2 - -struct csiphy_lane { - u8 pos; - u8 pol; -}; - -struct csiphy_lanes_cfg { - int num_data; - struct csiphy_lane *data; - struct csiphy_lane clk; -}; - -struct csiphy_csi2_cfg { - struct csiphy_lanes_cfg lane_cfg; -}; - -struct csiphy_config { - u8 combo_mode; - u8 csid_id; - struct csiphy_csi2_cfg *csi2; -}; - -struct csiphy_device { - u8 id; - struct v4l2_subdev subdev; - struct media_pad pads[MSM_CSIPHY_PADS_NUM]; - void __iomem *base; - void __iomem *base_clk_mux; - u32 irq; - char irq_name[30]; - struct camss_clock *clock; - int nclocks; - u32 timer_clk_rate; - struct csiphy_config cfg; - struct v4l2_mbus_framefmt fmt[MSM_CSIPHY_PADS_NUM]; -}; - -struct resources; - -int msm_csiphy_subdev_init(struct csiphy_device *csiphy, - const struct resources *res, u8 id); - -int msm_csiphy_register_entity(struct csiphy_device *csiphy, - struct v4l2_device *v4l2_dev); - -void msm_csiphy_unregister_entity(struct csiphy_device *csiphy); - -#endif /* QC_MSM_CAMSS_CSIPHY_H */ diff --git a/drivers/media/platform/qcom/camss-8x16/camss-ispif.c b/drivers/media/platform/qcom/camss-8x16/camss-ispif.c deleted file mode 100644 index 9d1af9353c1d..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-ispif.c +++ /dev/null @@ -1,1178 +0,0 @@ -/* - * camss-ispif.c - * - * Qualcomm MSM Camera Subsystem - ISPIF (ISP Interface) Module - * - * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "camss-ispif.h" -#include "camss.h" - -#define MSM_ISPIF_NAME "msm_ispif" - -#define ispif_line_array(ptr_line) \ - ((const struct ispif_line (*)[]) &(ptr_line[-(ptr_line->id)])) - -#define to_ispif(ptr_line) \ - container_of(ispif_line_array(ptr_line), struct ispif_device, ptr_line) - -#define ISPIF_RST_CMD_0 0x008 -#define ISPIF_RST_CMD_0_STROBED_RST_EN (1 << 0) -#define ISPIF_RST_CMD_0_MISC_LOGIC_RST (1 << 1) -#define ISPIF_RST_CMD_0_SW_REG_RST (1 << 2) -#define ISPIF_RST_CMD_0_PIX_INTF_0_CSID_RST (1 << 3) -#define ISPIF_RST_CMD_0_PIX_INTF_0_VFE_RST (1 << 4) -#define ISPIF_RST_CMD_0_PIX_INTF_1_CSID_RST (1 << 5) -#define ISPIF_RST_CMD_0_PIX_INTF_1_VFE_RST (1 << 6) -#define ISPIF_RST_CMD_0_RDI_INTF_0_CSID_RST (1 << 7) -#define ISPIF_RST_CMD_0_RDI_INTF_0_VFE_RST (1 << 8) -#define ISPIF_RST_CMD_0_RDI_INTF_1_CSID_RST (1 << 9) -#define ISPIF_RST_CMD_0_RDI_INTF_1_VFE_RST (1 << 10) -#define ISPIF_RST_CMD_0_RDI_INTF_2_CSID_RST (1 << 11) -#define ISPIF_RST_CMD_0_RDI_INTF_2_VFE_RST (1 << 12) -#define ISPIF_RST_CMD_0_PIX_OUTPUT_0_MISR_RST (1 << 16) -#define ISPIF_RST_CMD_0_RDI_OUTPUT_0_MISR_RST (1 << 17) -#define ISPIF_RST_CMD_0_RDI_OUTPUT_1_MISR_RST (1 << 18) -#define ISPIF_RST_CMD_0_RDI_OUTPUT_2_MISR_RST (1 << 19) -#define ISPIF_IRQ_GLOBAL_CLEAR_CMD 0x01c -#define ISPIF_VFE_m_CTRL_0(m) (0x200 + 0x200 * (m)) -#define ISPIF_VFE_m_CTRL_0_PIX0_LINE_BUF_EN (1 << 6) -#define ISPIF_VFE_m_IRQ_MASK_0(m) (0x208 + 0x200 * (m)) -#define ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE 0x00001249 -#define ISPIF_VFE_m_IRQ_MASK_0_PIX0_MASK 0x00001fff -#define ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE 0x02492000 -#define ISPIF_VFE_m_IRQ_MASK_0_RDI0_MASK 0x03ffe000 -#define ISPIF_VFE_m_IRQ_MASK_1(m) (0x20c + 0x200 * (m)) -#define ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE 0x00001249 -#define ISPIF_VFE_m_IRQ_MASK_1_PIX1_MASK 0x00001fff -#define ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE 0x02492000 -#define ISPIF_VFE_m_IRQ_MASK_1_RDI1_MASK 0x03ffe000 -#define ISPIF_VFE_m_IRQ_MASK_2(m) (0x210 + 0x200 * (m)) -#define ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE 0x00001249 -#define ISPIF_VFE_m_IRQ_MASK_2_RDI2_MASK 0x00001fff -#define ISPIF_VFE_m_IRQ_STATUS_0(m) (0x21c + 0x200 * (m)) -#define ISPIF_VFE_m_IRQ_STATUS_0_PIX0_OVERFLOW (1 << 12) -#define ISPIF_VFE_m_IRQ_STATUS_0_RDI0_OVERFLOW (1 << 25) -#define ISPIF_VFE_m_IRQ_STATUS_1(m) (0x220 + 0x200 * (m)) -#define ISPIF_VFE_m_IRQ_STATUS_1_PIX1_OVERFLOW (1 << 12) -#define ISPIF_VFE_m_IRQ_STATUS_1_RDI1_OVERFLOW (1 << 25) -#define ISPIF_VFE_m_IRQ_STATUS_2(m) (0x224 + 0x200 * (m)) -#define ISPIF_VFE_m_IRQ_STATUS_2_RDI2_OVERFLOW (1 << 12) -#define ISPIF_VFE_m_IRQ_CLEAR_0(m) (0x230 + 0x200 * (m)) -#define ISPIF_VFE_m_IRQ_CLEAR_1(m) (0x234 + 0x200 * (m)) -#define ISPIF_VFE_m_IRQ_CLEAR_2(m) (0x238 + 0x200 * (m)) -#define ISPIF_VFE_m_INTF_INPUT_SEL(m) (0x244 + 0x200 * (m)) -#define ISPIF_VFE_m_INTF_CMD_0(m) (0x248 + 0x200 * (m)) -#define ISPIF_VFE_m_INTF_CMD_1(m) (0x24c + 0x200 * (m)) -#define ISPIF_VFE_m_PIX_INTF_n_CID_MASK(m, n) \ - (0x254 + 0x200 * (m) + 0x4 * (n)) -#define ISPIF_VFE_m_RDI_INTF_n_CID_MASK(m, n) \ - (0x264 + 0x200 * (m) + 0x4 * (n)) -#define ISPIF_VFE_m_PIX_INTF_n_STATUS(m, n) \ - (0x2c0 + 0x200 * (m) + 0x4 * (n)) -#define ISPIF_VFE_m_RDI_INTF_n_STATUS(m, n) \ - (0x2d0 + 0x200 * (m) + 0x4 * (n)) - -#define CSI_PIX_CLK_MUX_SEL 0x000 -#define CSI_RDI_CLK_MUX_SEL 0x008 - -#define ISPIF_TIMEOUT_SLEEP_US 1000 -#define ISPIF_TIMEOUT_ALL_US 1000000 -#define ISPIF_RESET_TIMEOUT_MS 500 - -enum ispif_intf_cmd { - CMD_DISABLE_FRAME_BOUNDARY = 0x0, - CMD_ENABLE_FRAME_BOUNDARY = 0x1, - CMD_DISABLE_IMMEDIATELY = 0x2, - CMD_ALL_DISABLE_IMMEDIATELY = 0xaaaaaaaa, - CMD_ALL_NO_CHANGE = 0xffffffff, -}; - -static const u32 ispif_formats[] = { - MEDIA_BUS_FMT_UYVY8_2X8, - MEDIA_BUS_FMT_VYUY8_2X8, - MEDIA_BUS_FMT_YUYV8_2X8, - MEDIA_BUS_FMT_YVYU8_2X8, - MEDIA_BUS_FMT_SBGGR8_1X8, - MEDIA_BUS_FMT_SGBRG8_1X8, - MEDIA_BUS_FMT_SGRBG8_1X8, - MEDIA_BUS_FMT_SRGGB8_1X8, - MEDIA_BUS_FMT_SBGGR10_1X10, - MEDIA_BUS_FMT_SGBRG10_1X10, - MEDIA_BUS_FMT_SGRBG10_1X10, - MEDIA_BUS_FMT_SRGGB10_1X10, - MEDIA_BUS_FMT_SBGGR12_1X12, - MEDIA_BUS_FMT_SGBRG12_1X12, - MEDIA_BUS_FMT_SGRBG12_1X12, - MEDIA_BUS_FMT_SRGGB12_1X12, -}; - -/* - * ispif_isr - ISPIF module interrupt handler - * @irq: Interrupt line - * @dev: ISPIF device - * - * Return IRQ_HANDLED on success - */ -static irqreturn_t ispif_isr(int irq, void *dev) -{ - struct ispif_device *ispif = dev; - u32 value0, value1, value2; - - value0 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_0(0)); - value1 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_1(0)); - value2 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_2(0)); - - writel_relaxed(value0, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(0)); - writel_relaxed(value1, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(0)); - writel_relaxed(value2, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_2(0)); - - writel(0x1, ispif->base + ISPIF_IRQ_GLOBAL_CLEAR_CMD); - - if ((value0 >> 27) & 0x1) - complete(&ispif->reset_complete); - - if (unlikely(value0 & ISPIF_VFE_m_IRQ_STATUS_0_PIX0_OVERFLOW)) - dev_err_ratelimited(to_device(ispif), "VFE0 pix0 overflow\n"); - - if (unlikely(value0 & ISPIF_VFE_m_IRQ_STATUS_0_RDI0_OVERFLOW)) - dev_err_ratelimited(to_device(ispif), "VFE0 rdi0 overflow\n"); - - if (unlikely(value1 & ISPIF_VFE_m_IRQ_STATUS_1_PIX1_OVERFLOW)) - dev_err_ratelimited(to_device(ispif), "VFE0 pix1 overflow\n"); - - if (unlikely(value1 & ISPIF_VFE_m_IRQ_STATUS_1_RDI1_OVERFLOW)) - dev_err_ratelimited(to_device(ispif), "VFE0 rdi1 overflow\n"); - - if (unlikely(value2 & ISPIF_VFE_m_IRQ_STATUS_2_RDI2_OVERFLOW)) - dev_err_ratelimited(to_device(ispif), "VFE0 rdi2 overflow\n"); - - return IRQ_HANDLED; -} - -/* - * ispif_reset - Trigger reset on ISPIF module and wait to complete - * @ispif: ISPIF device - * - * Return 0 on success or a negative error code otherwise - */ -static int ispif_reset(struct ispif_device *ispif) -{ - unsigned long time; - u32 val; - int ret; - - ret = camss_enable_clocks(ispif->nclocks_for_reset, - ispif->clock_for_reset, - to_device(ispif)); - if (ret < 0) - return ret; - - reinit_completion(&ispif->reset_complete); - - val = ISPIF_RST_CMD_0_STROBED_RST_EN | - ISPIF_RST_CMD_0_MISC_LOGIC_RST | - ISPIF_RST_CMD_0_SW_REG_RST | - ISPIF_RST_CMD_0_PIX_INTF_0_CSID_RST | - ISPIF_RST_CMD_0_PIX_INTF_0_VFE_RST | - ISPIF_RST_CMD_0_PIX_INTF_1_CSID_RST | - ISPIF_RST_CMD_0_PIX_INTF_1_VFE_RST | - ISPIF_RST_CMD_0_RDI_INTF_0_CSID_RST | - ISPIF_RST_CMD_0_RDI_INTF_0_VFE_RST | - ISPIF_RST_CMD_0_RDI_INTF_1_CSID_RST | - ISPIF_RST_CMD_0_RDI_INTF_1_VFE_RST | - ISPIF_RST_CMD_0_RDI_INTF_2_CSID_RST | - ISPIF_RST_CMD_0_RDI_INTF_2_VFE_RST | - ISPIF_RST_CMD_0_PIX_OUTPUT_0_MISR_RST | - ISPIF_RST_CMD_0_RDI_OUTPUT_0_MISR_RST | - ISPIF_RST_CMD_0_RDI_OUTPUT_1_MISR_RST | - ISPIF_RST_CMD_0_RDI_OUTPUT_2_MISR_RST; - - writel_relaxed(val, ispif->base + ISPIF_RST_CMD_0); - - time = wait_for_completion_timeout(&ispif->reset_complete, - msecs_to_jiffies(ISPIF_RESET_TIMEOUT_MS)); - if (!time) { - dev_err(to_device(ispif), "ISPIF reset timeout\n"); - return -EIO; - } - - camss_disable_clocks(ispif->nclocks_for_reset, ispif->clock_for_reset); - - return 0; -} - -/* - * ispif_set_power - Power on/off ISPIF module - * @sd: ISPIF V4L2 subdevice - * @on: Requested power state - * - * Return 0 on success or a negative error code otherwise - */ -static int ispif_set_power(struct v4l2_subdev *sd, int on) -{ - struct ispif_line *line = v4l2_get_subdevdata(sd); - struct ispif_device *ispif = to_ispif(line); - struct device *dev = to_device(ispif); - int ret = 0; - - mutex_lock(&ispif->power_lock); - - if (on) { - if (ispif->power_count) { - /* Power is already on */ - ispif->power_count++; - goto exit; - } - - ret = camss_enable_clocks(ispif->nclocks, ispif->clock, dev); - if (ret < 0) - goto exit; - - ret = ispif_reset(ispif); - if (ret < 0) { - camss_disable_clocks(ispif->nclocks, ispif->clock); - goto exit; - } - - ispif->intf_cmd[line->vfe_id].cmd_0 = CMD_ALL_NO_CHANGE; - ispif->intf_cmd[line->vfe_id].cmd_1 = CMD_ALL_NO_CHANGE; - - ispif->power_count++; - } else { - if (ispif->power_count == 0) { - dev_err(dev, "ispif power off on power_count == 0\n"); - goto exit; - } else if (ispif->power_count == 1) { - camss_disable_clocks(ispif->nclocks, ispif->clock); - } - - ispif->power_count--; - } - -exit: - mutex_unlock(&ispif->power_lock); - - return ret; -} - -/* - * ispif_select_clk_mux - Select clock for PIX/RDI interface - * @ispif: ISPIF device - * @intf: VFE interface - * @csid: CSID HW module id - * @vfe: VFE HW module id - * @enable: enable or disable the selected clock - */ -static void ispif_select_clk_mux(struct ispif_device *ispif, - enum ispif_intf intf, u8 csid, - u8 vfe, u8 enable) -{ - u32 val; - - switch (intf) { - case PIX0: - val = readl_relaxed(ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); - val &= ~(0xf << (vfe * 8)); - if (enable) - val |= (csid << (vfe * 8)); - writel_relaxed(val, ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); - break; - - case RDI0: - val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); - val &= ~(0xf << (vfe * 12)); - if (enable) - val |= (csid << (vfe * 12)); - writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); - break; - - case PIX1: - val = readl_relaxed(ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); - val &= ~(0xf << (4 + (vfe * 8))); - if (enable) - val |= (csid << (4 + (vfe * 8))); - writel_relaxed(val, ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); - break; - - case RDI1: - val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); - val &= ~(0xf << (4 + (vfe * 12))); - if (enable) - val |= (csid << (4 + (vfe * 12))); - writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); - break; - - case RDI2: - val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); - val &= ~(0xf << (8 + (vfe * 12))); - if (enable) - val |= (csid << (8 + (vfe * 12))); - writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); - break; - } - - mb(); -} - -/* - * ispif_validate_intf_status - Validate current status of PIX/RDI interface - * @ispif: ISPIF device - * @intf: VFE interface - * @vfe: VFE HW module id - * - * Return 0 when interface is idle or -EBUSY otherwise - */ -static int ispif_validate_intf_status(struct ispif_device *ispif, - enum ispif_intf intf, u8 vfe) -{ - int ret = 0; - u32 val = 0; - - switch (intf) { - case PIX0: - val = readl_relaxed(ispif->base + - ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 0)); - break; - case RDI0: - val = readl_relaxed(ispif->base + - ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 0)); - break; - case PIX1: - val = readl_relaxed(ispif->base + - ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 1)); - break; - case RDI1: - val = readl_relaxed(ispif->base + - ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 1)); - break; - case RDI2: - val = readl_relaxed(ispif->base + - ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 2)); - break; - } - - if ((val & 0xf) != 0xf) { - dev_err(to_device(ispif), "%s: ispif is busy: 0x%x\n", - __func__, val); - ret = -EBUSY; - } - - return ret; -} - -/* - * ispif_wait_for_stop - Wait for PIX/RDI interface to stop - * @ispif: ISPIF device - * @intf: VFE interface - * @vfe: VFE HW module id - * - * Return 0 on success or a negative error code otherwise - */ -static int ispif_wait_for_stop(struct ispif_device *ispif, - enum ispif_intf intf, u8 vfe) -{ - u32 addr = 0; - u32 stop_flag = 0; - int ret; - - switch (intf) { - case PIX0: - addr = ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 0); - break; - case RDI0: - addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 0); - break; - case PIX1: - addr = ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 1); - break; - case RDI1: - addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 1); - break; - case RDI2: - addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 2); - break; - } - - ret = readl_poll_timeout(ispif->base + addr, - stop_flag, - (stop_flag & 0xf) == 0xf, - ISPIF_TIMEOUT_SLEEP_US, - ISPIF_TIMEOUT_ALL_US); - if (ret < 0) - dev_err(to_device(ispif), "%s: ispif stop timeout\n", - __func__); - - return ret; -} - -/* - * ispif_select_csid - Select CSID HW module for input from - * @ispif: ISPIF device - * @intf: VFE interface - * @csid: CSID HW module id - * @vfe: VFE HW module id - * @enable: enable or disable the selected input - */ -static void ispif_select_csid(struct ispif_device *ispif, enum ispif_intf intf, - u8 csid, u8 vfe, u8 enable) -{ - u32 val; - - val = readl_relaxed(ispif->base + ISPIF_VFE_m_INTF_INPUT_SEL(vfe)); - switch (intf) { - case PIX0: - val &= ~(BIT(1) | BIT(0)); - if (enable) - val |= csid; - break; - case RDI0: - val &= ~(BIT(5) | BIT(4)); - if (enable) - val |= (csid << 4); - break; - case PIX1: - val &= ~(BIT(9) | BIT(8)); - if (enable) - val |= (csid << 8); - break; - case RDI1: - val &= ~(BIT(13) | BIT(12)); - if (enable) - val |= (csid << 12); - break; - case RDI2: - val &= ~(BIT(21) | BIT(20)); - if (enable) - val |= (csid << 20); - break; - } - - writel(val, ispif->base + ISPIF_VFE_m_INTF_INPUT_SEL(vfe)); -} - -/* - * ispif_select_cid - Enable/disable desired CID - * @ispif: ISPIF device - * @intf: VFE interface - * @cid: desired CID to enable/disable - * @vfe: VFE HW module id - * @enable: enable or disable the desired CID - */ -static void ispif_select_cid(struct ispif_device *ispif, enum ispif_intf intf, - u8 cid, u8 vfe, u8 enable) -{ - u32 cid_mask = 1 << cid; - u32 addr = 0; - u32 val; - - switch (intf) { - case PIX0: - addr = ISPIF_VFE_m_PIX_INTF_n_CID_MASK(vfe, 0); - break; - case RDI0: - addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 0); - break; - case PIX1: - addr = ISPIF_VFE_m_PIX_INTF_n_CID_MASK(vfe, 1); - break; - case RDI1: - addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 1); - break; - case RDI2: - addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 2); - break; - } - - val = readl_relaxed(ispif->base + addr); - if (enable) - val |= cid_mask; - else - val &= ~cid_mask; - - writel(val, ispif->base + addr); -} - -/* - * ispif_config_irq - Enable/disable interrupts for PIX/RDI interface - * @ispif: ISPIF device - * @intf: VFE interface - * @vfe: VFE HW module id - * @enable: enable or disable - */ -static void ispif_config_irq(struct ispif_device *ispif, enum ispif_intf intf, - u8 vfe, u8 enable) -{ - u32 val; - - switch (intf) { - case PIX0: - val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); - val &= ~ISPIF_VFE_m_IRQ_MASK_0_PIX0_MASK; - if (enable) - val |= ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE; - writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); - writel_relaxed(ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE, - ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(vfe)); - break; - case RDI0: - val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); - val &= ~ISPIF_VFE_m_IRQ_MASK_0_RDI0_MASK; - if (enable) - val |= ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE; - writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); - writel_relaxed(ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE, - ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(vfe)); - break; - case PIX1: - val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); - val &= ~ISPIF_VFE_m_IRQ_MASK_1_PIX1_MASK; - if (enable) - val |= ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE; - writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); - writel_relaxed(ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE, - ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(vfe)); - break; - case RDI1: - val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); - val &= ~ISPIF_VFE_m_IRQ_MASK_1_RDI1_MASK; - if (enable) - val |= ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE; - writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); - writel_relaxed(ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE, - ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(vfe)); - break; - case RDI2: - val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_2(vfe)); - val &= ~ISPIF_VFE_m_IRQ_MASK_2_RDI2_MASK; - if (enable) - val |= ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE; - writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_2(vfe)); - writel_relaxed(ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE, - ispif->base + ISPIF_VFE_m_IRQ_CLEAR_2(vfe)); - break; - } - - writel(0x1, ispif->base + ISPIF_IRQ_GLOBAL_CLEAR_CMD); -} - -/* - * ispif_set_intf_cmd - Set command to enable/disable interface - * @ispif: ISPIF device - * @cmd: interface command - * @intf: VFE interface - * @vfe: VFE HW module id - * @vc: virtual channel - */ -static void ispif_set_intf_cmd(struct ispif_device *ispif, u8 cmd, - enum ispif_intf intf, u8 vfe, u8 vc) -{ - u32 *val; - - if (intf == RDI2) { - val = &ispif->intf_cmd[vfe].cmd_1; - *val &= ~(0x3 << (vc * 2 + 8)); - *val |= (cmd << (vc * 2 + 8)); - wmb(); - writel_relaxed(*val, ispif->base + ISPIF_VFE_m_INTF_CMD_1(vfe)); - wmb(); - } else { - val = &ispif->intf_cmd[vfe].cmd_0; - *val &= ~(0x3 << (vc * 2 + intf * 8)); - *val |= (cmd << (vc * 2 + intf * 8)); - wmb(); - writel_relaxed(*val, ispif->base + ISPIF_VFE_m_INTF_CMD_0(vfe)); - wmb(); - } -} - -/* - * ispif_set_stream - Enable/disable streaming on ISPIF module - * @sd: ISPIF V4L2 subdevice - * @enable: Requested streaming state - * - * Main configuration of ISPIF module is also done here. - * - * Return 0 on success or a negative error code otherwise - */ -static int ispif_set_stream(struct v4l2_subdev *sd, int enable) -{ - struct ispif_line *line = v4l2_get_subdevdata(sd); - struct ispif_device *ispif = to_ispif(line); - enum ispif_intf intf = line->interface; - u8 csid = line->csid_id; - u8 vfe = line->vfe_id; - u8 vc = 0; /* Virtual Channel 0 */ - u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */ - int ret; - - if (enable) { - if (!media_entity_remote_pad(&line->pads[MSM_ISPIF_PAD_SINK])) - return -ENOLINK; - - /* Config */ - - mutex_lock(&ispif->config_lock); - ispif_select_clk_mux(ispif, intf, csid, vfe, 1); - - ret = ispif_validate_intf_status(ispif, intf, vfe); - if (ret < 0) { - mutex_unlock(&ispif->config_lock); - return ret; - } - - ispif_select_csid(ispif, intf, csid, vfe, 1); - ispif_select_cid(ispif, intf, cid, vfe, 1); - ispif_config_irq(ispif, intf, vfe, 1); - ispif_set_intf_cmd(ispif, CMD_ENABLE_FRAME_BOUNDARY, - intf, vfe, vc); - } else { - mutex_lock(&ispif->config_lock); - ispif_set_intf_cmd(ispif, CMD_DISABLE_FRAME_BOUNDARY, - intf, vfe, vc); - mutex_unlock(&ispif->config_lock); - - ret = ispif_wait_for_stop(ispif, intf, vfe); - if (ret < 0) - return ret; - - mutex_lock(&ispif->config_lock); - ispif_config_irq(ispif, intf, vfe, 0); - ispif_select_cid(ispif, intf, cid, vfe, 0); - ispif_select_csid(ispif, intf, csid, vfe, 0); - ispif_select_clk_mux(ispif, intf, csid, vfe, 0); - } - - mutex_unlock(&ispif->config_lock); - - return 0; -} - -/* - * __ispif_get_format - Get pointer to format structure - * @ispif: ISPIF line - * @cfg: V4L2 subdev pad configuration - * @pad: pad from which format is requested - * @which: TRY or ACTIVE format - * - * Return pointer to TRY or ACTIVE format structure - */ -static struct v4l2_mbus_framefmt * -__ispif_get_format(struct ispif_line *line, - struct v4l2_subdev_pad_config *cfg, - unsigned int pad, - enum v4l2_subdev_format_whence which) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return v4l2_subdev_get_try_format(&line->subdev, cfg, pad); - - return &line->fmt[pad]; -} - -/* - * ispif_try_format - Handle try format by pad subdev method - * @ispif: ISPIF line - * @cfg: V4L2 subdev pad configuration - * @pad: pad on which format is requested - * @fmt: pointer to v4l2 format structure - * @which: wanted subdev format - */ -static void ispif_try_format(struct ispif_line *line, - struct v4l2_subdev_pad_config *cfg, - unsigned int pad, - struct v4l2_mbus_framefmt *fmt, - enum v4l2_subdev_format_whence which) -{ - unsigned int i; - - switch (pad) { - case MSM_ISPIF_PAD_SINK: - /* Set format on sink pad */ - - for (i = 0; i < ARRAY_SIZE(ispif_formats); i++) - if (fmt->code == ispif_formats[i]) - break; - - /* If not found, use UYVY as default */ - if (i >= ARRAY_SIZE(ispif_formats)) - fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; - - fmt->width = clamp_t(u32, fmt->width, 1, 8191); - fmt->height = clamp_t(u32, fmt->height, 1, 8191); - - fmt->field = V4L2_FIELD_NONE; - fmt->colorspace = V4L2_COLORSPACE_SRGB; - - break; - - case MSM_ISPIF_PAD_SRC: - /* Set and return a format same as sink pad */ - - *fmt = *__ispif_get_format(line, cfg, MSM_ISPIF_PAD_SINK, - which); - - break; - } - - fmt->colorspace = V4L2_COLORSPACE_SRGB; -} - -/* - * ispif_enum_mbus_code - Handle pixel format enumeration - * @sd: ISPIF V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @code: pointer to v4l2_subdev_mbus_code_enum structure - * return -EINVAL or zero on success - */ -static int ispif_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_mbus_code_enum *code) -{ - struct ispif_line *line = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - if (code->pad == MSM_ISPIF_PAD_SINK) { - if (code->index >= ARRAY_SIZE(ispif_formats)) - return -EINVAL; - - code->code = ispif_formats[code->index]; - } else { - if (code->index > 0) - return -EINVAL; - - format = __ispif_get_format(line, cfg, MSM_ISPIF_PAD_SINK, - code->which); - - code->code = format->code; - } - - return 0; -} - -/* - * ispif_enum_frame_size - Handle frame size enumeration - * @sd: ISPIF V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fse: pointer to v4l2_subdev_frame_size_enum structure - * return -EINVAL or zero on success - */ -static int ispif_enum_frame_size(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_frame_size_enum *fse) -{ - struct ispif_line *line = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt format; - - if (fse->index != 0) - return -EINVAL; - - format.code = fse->code; - format.width = 1; - format.height = 1; - ispif_try_format(line, cfg, fse->pad, &format, fse->which); - fse->min_width = format.width; - fse->min_height = format.height; - - if (format.code != fse->code) - return -EINVAL; - - format.code = fse->code; - format.width = -1; - format.height = -1; - ispif_try_format(line, cfg, fse->pad, &format, fse->which); - fse->max_width = format.width; - fse->max_height = format.height; - - return 0; -} - -/* - * ispif_get_format - Handle get format by pads subdev method - * @sd: ISPIF V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fmt: pointer to v4l2 subdev format structure - * - * Return -EINVAL or zero on success - */ -static int ispif_get_format(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct ispif_line *line = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - format = __ispif_get_format(line, cfg, fmt->pad, fmt->which); - if (format == NULL) - return -EINVAL; - - fmt->format = *format; - - return 0; -} - -/* - * ispif_set_format - Handle set format by pads subdev method - * @sd: ISPIF V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fmt: pointer to v4l2 subdev format structure - * - * Return -EINVAL or zero on success - */ -static int ispif_set_format(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct ispif_line *line = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - format = __ispif_get_format(line, cfg, fmt->pad, fmt->which); - if (format == NULL) - return -EINVAL; - - ispif_try_format(line, cfg, fmt->pad, &fmt->format, fmt->which); - *format = fmt->format; - - /* Propagate the format from sink to source */ - if (fmt->pad == MSM_ISPIF_PAD_SINK) { - format = __ispif_get_format(line, cfg, MSM_ISPIF_PAD_SRC, - fmt->which); - - *format = fmt->format; - ispif_try_format(line, cfg, MSM_ISPIF_PAD_SRC, format, - fmt->which); - } - - return 0; -} - -/* - * ispif_init_formats - Initialize formats on all pads - * @sd: ISPIF V4L2 subdevice - * @fh: V4L2 subdev file handle - * - * Initialize all pad formats with default values. - * - * Return 0 on success or a negative error code otherwise - */ -static int ispif_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct v4l2_subdev_format format = { - .pad = MSM_ISPIF_PAD_SINK, - .which = fh ? V4L2_SUBDEV_FORMAT_TRY : - V4L2_SUBDEV_FORMAT_ACTIVE, - .format = { - .code = MEDIA_BUS_FMT_UYVY8_2X8, - .width = 1920, - .height = 1080 - } - }; - - return ispif_set_format(sd, fh ? fh->pad : NULL, &format); -} - -/* - * msm_ispif_subdev_init - Initialize ISPIF device structure and resources - * @ispif: ISPIF device - * @res: ISPIF module resources table - * - * Return 0 on success or a negative error code otherwise - */ -int msm_ispif_subdev_init(struct ispif_device *ispif, - const struct resources_ispif *res) -{ - struct device *dev = to_device(ispif); - struct platform_device *pdev = to_platform_device(dev); - struct resource *r; - int i; - int ret; - - /* Memory */ - - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[0]); - ispif->base = devm_ioremap_resource(dev, r); - if (IS_ERR(ispif->base)) { - dev_err(dev, "could not map memory\n"); - return PTR_ERR(ispif->base); - } - - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[1]); - ispif->base_clk_mux = devm_ioremap_resource(dev, r); - if (IS_ERR(ispif->base_clk_mux)) { - dev_err(dev, "could not map memory\n"); - return PTR_ERR(ispif->base_clk_mux); - } - - /* Interrupt */ - - r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, res->interrupt); - - if (!r) { - dev_err(dev, "missing IRQ\n"); - return -EINVAL; - } - - ispif->irq = r->start; - snprintf(ispif->irq_name, sizeof(ispif->irq_name), "%s_%s", - dev_name(dev), MSM_ISPIF_NAME); - ret = devm_request_irq(dev, ispif->irq, ispif_isr, - IRQF_TRIGGER_RISING, ispif->irq_name, ispif); - if (ret < 0) { - dev_err(dev, "request_irq failed: %d\n", ret); - return ret; - } - - /* Clocks */ - - ispif->nclocks = 0; - while (res->clock[ispif->nclocks]) - ispif->nclocks++; - - ispif->clock = devm_kcalloc(dev, - ispif->nclocks, sizeof(*ispif->clock), - GFP_KERNEL); - if (!ispif->clock) - return -ENOMEM; - - for (i = 0; i < ispif->nclocks; i++) { - struct camss_clock *clock = &ispif->clock[i]; - - clock->clk = devm_clk_get(dev, res->clock[i]); - if (IS_ERR(clock->clk)) - return PTR_ERR(clock->clk); - - clock->freq = NULL; - clock->nfreqs = 0; - } - - ispif->nclocks_for_reset = 0; - while (res->clock_for_reset[ispif->nclocks_for_reset]) - ispif->nclocks_for_reset++; - - ispif->clock_for_reset = devm_kcalloc(dev, - ispif->nclocks_for_reset, - sizeof(*ispif->clock_for_reset), - GFP_KERNEL); - if (!ispif->clock_for_reset) - return -ENOMEM; - - for (i = 0; i < ispif->nclocks_for_reset; i++) { - struct camss_clock *clock = &ispif->clock_for_reset[i]; - - clock->clk = devm_clk_get(dev, res->clock_for_reset[i]); - if (IS_ERR(clock->clk)) - return PTR_ERR(clock->clk); - - clock->freq = NULL; - clock->nfreqs = 0; - } - - for (i = 0; i < ARRAY_SIZE(ispif->line); i++) - ispif->line[i].id = i; - - mutex_init(&ispif->power_lock); - ispif->power_count = 0; - - mutex_init(&ispif->config_lock); - - init_completion(&ispif->reset_complete); - - return 0; -} - -/* - * ispif_get_intf - Get ISPIF interface to use by VFE line id - * @line_id: VFE line id that the ISPIF line is connected to - * - * Return ISPIF interface to use - */ -static enum ispif_intf ispif_get_intf(enum vfe_line_id line_id) -{ - switch (line_id) { - case (VFE_LINE_RDI0): - return RDI0; - case (VFE_LINE_RDI1): - return RDI1; - case (VFE_LINE_RDI2): - return RDI2; - case (VFE_LINE_PIX): - return PIX0; - default: - return RDI0; - } -} - -/* - * ispif_link_setup - Setup ISPIF connections - * @entity: Pointer to media entity structure - * @local: Pointer to local pad - * @remote: Pointer to remote pad - * @flags: Link flags - * - * Return 0 on success - */ -static int ispif_link_setup(struct media_entity *entity, - const struct media_pad *local, - const struct media_pad *remote, u32 flags) -{ - if (flags & MEDIA_LNK_FL_ENABLED) { - if (media_entity_remote_pad(local)) - return -EBUSY; - - if (local->flags & MEDIA_PAD_FL_SINK) { - struct v4l2_subdev *sd; - struct ispif_line *line; - - sd = media_entity_to_v4l2_subdev(entity); - line = v4l2_get_subdevdata(sd); - - msm_csid_get_csid_id(remote->entity, &line->csid_id); - } else { /* MEDIA_PAD_FL_SOURCE */ - struct v4l2_subdev *sd; - struct ispif_line *line; - enum vfe_line_id id; - - sd = media_entity_to_v4l2_subdev(entity); - line = v4l2_get_subdevdata(sd); - - msm_vfe_get_vfe_id(remote->entity, &line->vfe_id); - msm_vfe_get_vfe_line_id(remote->entity, &id); - line->interface = ispif_get_intf(id); - } - } - - return 0; -} - -static const struct v4l2_subdev_core_ops ispif_core_ops = { - .s_power = ispif_set_power, -}; - -static const struct v4l2_subdev_video_ops ispif_video_ops = { - .s_stream = ispif_set_stream, -}; - -static const struct v4l2_subdev_pad_ops ispif_pad_ops = { - .enum_mbus_code = ispif_enum_mbus_code, - .enum_frame_size = ispif_enum_frame_size, - .get_fmt = ispif_get_format, - .set_fmt = ispif_set_format, -}; - -static const struct v4l2_subdev_ops ispif_v4l2_ops = { - .core = &ispif_core_ops, - .video = &ispif_video_ops, - .pad = &ispif_pad_ops, -}; - -static const struct v4l2_subdev_internal_ops ispif_v4l2_internal_ops = { - .open = ispif_init_formats, -}; - -static const struct media_entity_operations ispif_media_ops = { - .link_setup = ispif_link_setup, - .link_validate = v4l2_subdev_link_validate, -}; - -/* - * msm_ispif_register_entities - Register subdev node for ISPIF module - * @ispif: ISPIF device - * @v4l2_dev: V4L2 device - * - * Return 0 on success or a negative error code otherwise - */ -int msm_ispif_register_entities(struct ispif_device *ispif, - struct v4l2_device *v4l2_dev) -{ - struct device *dev = to_device(ispif); - int ret; - int i; - - for (i = 0; i < ARRAY_SIZE(ispif->line); i++) { - struct v4l2_subdev *sd = &ispif->line[i].subdev; - struct media_pad *pads = ispif->line[i].pads; - - v4l2_subdev_init(sd, &ispif_v4l2_ops); - sd->internal_ops = &ispif_v4l2_internal_ops; - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", - MSM_ISPIF_NAME, i); - v4l2_set_subdevdata(sd, &ispif->line[i]); - - ret = ispif_init_formats(sd, NULL); - if (ret < 0) { - dev_err(dev, "Failed to init format: %d\n", ret); - goto error; - } - - pads[MSM_ISPIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[MSM_ISPIF_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; - - sd->entity.function = MEDIA_ENT_F_IO_V4L; - sd->entity.ops = &ispif_media_ops; - ret = media_entity_pads_init(&sd->entity, MSM_ISPIF_PADS_NUM, - pads); - if (ret < 0) { - dev_err(dev, "Failed to init media entity: %d\n", ret); - goto error; - } - - ret = v4l2_device_register_subdev(v4l2_dev, sd); - if (ret < 0) { - dev_err(dev, "Failed to register subdev: %d\n", ret); - media_entity_cleanup(&sd->entity); - goto error; - } - } - - return 0; - -error: - for (i--; i >= 0; i--) { - struct v4l2_subdev *sd = &ispif->line[i].subdev; - - v4l2_device_unregister_subdev(sd); - media_entity_cleanup(&sd->entity); - } - - return ret; -} - -/* - * msm_ispif_unregister_entities - Unregister ISPIF module subdev node - * @ispif: ISPIF device - */ -void msm_ispif_unregister_entities(struct ispif_device *ispif) -{ - int i; - - mutex_destroy(&ispif->power_lock); - mutex_destroy(&ispif->config_lock); - - for (i = 0; i < ARRAY_SIZE(ispif->line); i++) { - struct v4l2_subdev *sd = &ispif->line[i].subdev; - - v4l2_device_unregister_subdev(sd); - media_entity_cleanup(&sd->entity); - } -} diff --git a/drivers/media/platform/qcom/camss-8x16/camss-ispif.h b/drivers/media/platform/qcom/camss-8x16/camss-ispif.h deleted file mode 100644 index f668306020c3..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-ispif.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * camss-ispif.h - * - * Qualcomm MSM Camera Subsystem - ISPIF (ISP Interface) Module - * - * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#ifndef QC_MSM_CAMSS_ISPIF_H -#define QC_MSM_CAMSS_ISPIF_H - -#include -#include -#include -#include - -/* Number of ISPIF lines - same as number of CSID hardware modules */ -#define MSM_ISPIF_LINE_NUM 2 - -#define MSM_ISPIF_PAD_SINK 0 -#define MSM_ISPIF_PAD_SRC 1 -#define MSM_ISPIF_PADS_NUM 2 - -#define MSM_ISPIF_VFE_NUM 1 - -enum ispif_intf { - PIX0, - RDI0, - PIX1, - RDI1, - RDI2 -}; - -struct ispif_intf_cmd_reg { - u32 cmd_0; - u32 cmd_1; -}; - -struct ispif_line { - u8 id; - u8 csid_id; - u8 vfe_id; - enum ispif_intf interface; - struct v4l2_subdev subdev; - struct media_pad pads[MSM_ISPIF_PADS_NUM]; - struct v4l2_mbus_framefmt fmt[MSM_ISPIF_PADS_NUM]; -}; - -struct ispif_device { - void __iomem *base; - void __iomem *base_clk_mux; - u32 irq; - char irq_name[30]; - struct camss_clock *clock; - int nclocks; - struct camss_clock *clock_for_reset; - int nclocks_for_reset; - struct completion reset_complete; - int power_count; - struct mutex power_lock; - struct ispif_intf_cmd_reg intf_cmd[MSM_ISPIF_VFE_NUM]; - struct mutex config_lock; - struct ispif_line line[MSM_ISPIF_LINE_NUM]; -}; - -struct resources_ispif; - -int msm_ispif_subdev_init(struct ispif_device *ispif, - const struct resources_ispif *res); - -int msm_ispif_register_entities(struct ispif_device *ispif, - struct v4l2_device *v4l2_dev); - -void msm_ispif_unregister_entities(struct ispif_device *ispif); - -#endif /* QC_MSM_CAMSS_ISPIF_H */ diff --git a/drivers/media/platform/qcom/camss-8x16/camss-vfe.c b/drivers/media/platform/qcom/camss-8x16/camss-vfe.c deleted file mode 100644 index a6329a8a7c4a..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-vfe.c +++ /dev/null @@ -1,3093 +0,0 @@ -/* - * camss-vfe.c - * - * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module - * - * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "camss-vfe.h" -#include "camss.h" - -#define MSM_VFE_NAME "msm_vfe" - -#define vfe_line_array(ptr_line) \ - ((const struct vfe_line (*)[]) &(ptr_line[-(ptr_line->id)])) - -#define to_vfe(ptr_line) \ - container_of(vfe_line_array(ptr_line), struct vfe_device, ptr_line) - -#define VFE_0_HW_VERSION 0x000 - -#define VFE_0_GLOBAL_RESET_CMD 0x00c -#define VFE_0_GLOBAL_RESET_CMD_CORE (1 << 0) -#define VFE_0_GLOBAL_RESET_CMD_CAMIF (1 << 1) -#define VFE_0_GLOBAL_RESET_CMD_BUS (1 << 2) -#define VFE_0_GLOBAL_RESET_CMD_BUS_BDG (1 << 3) -#define VFE_0_GLOBAL_RESET_CMD_REGISTER (1 << 4) -#define VFE_0_GLOBAL_RESET_CMD_TIMER (1 << 5) -#define VFE_0_GLOBAL_RESET_CMD_PM (1 << 6) -#define VFE_0_GLOBAL_RESET_CMD_BUS_MISR (1 << 7) -#define VFE_0_GLOBAL_RESET_CMD_TESTGEN (1 << 8) - -#define VFE_0_MODULE_CFG 0x018 -#define VFE_0_MODULE_CFG_DEMUX (1 << 2) -#define VFE_0_MODULE_CFG_CHROMA_UPSAMPLE (1 << 3) -#define VFE_0_MODULE_CFG_SCALE_ENC (1 << 23) -#define VFE_0_MODULE_CFG_CROP_ENC (1 << 27) - -#define VFE_0_CORE_CFG 0x01c -#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR 0x4 -#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB 0x5 -#define VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY 0x6 -#define VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY 0x7 - -#define VFE_0_IRQ_CMD 0x024 -#define VFE_0_IRQ_CMD_GLOBAL_CLEAR (1 << 0) - -#define VFE_0_IRQ_MASK_0 0x028 -#define VFE_0_IRQ_MASK_0_CAMIF_SOF (1 << 0) -#define VFE_0_IRQ_MASK_0_CAMIF_EOF (1 << 1) -#define VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n) (1 << ((n) + 5)) -#define VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(n) \ - ((n) == VFE_LINE_PIX ? (1 << 4) : VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n)) -#define VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(n) (1 << ((n) + 8)) -#define VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(n) (1 << ((n) + 25)) -#define VFE_0_IRQ_MASK_0_RESET_ACK (1 << 31) -#define VFE_0_IRQ_MASK_1 0x02c -#define VFE_0_IRQ_MASK_1_CAMIF_ERROR (1 << 0) -#define VFE_0_IRQ_MASK_1_VIOLATION (1 << 7) -#define VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK (1 << 8) -#define VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(n) (1 << ((n) + 9)) -#define VFE_0_IRQ_MASK_1_RDIn_SOF(n) (1 << ((n) + 29)) - -#define VFE_0_IRQ_CLEAR_0 0x030 -#define VFE_0_IRQ_CLEAR_1 0x034 - -#define VFE_0_IRQ_STATUS_0 0x038 -#define VFE_0_IRQ_STATUS_0_CAMIF_SOF (1 << 0) -#define VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n) (1 << ((n) + 5)) -#define VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(n) \ - ((n) == VFE_LINE_PIX ? (1 << 4) : VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n)) -#define VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(n) (1 << ((n) + 8)) -#define VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(n) (1 << ((n) + 25)) -#define VFE_0_IRQ_STATUS_0_RESET_ACK (1 << 31) -#define VFE_0_IRQ_STATUS_1 0x03c -#define VFE_0_IRQ_STATUS_1_VIOLATION (1 << 7) -#define VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK (1 << 8) -#define VFE_0_IRQ_STATUS_1_RDIn_SOF(n) (1 << ((n) + 29)) - -#define VFE_0_IRQ_COMPOSITE_MASK_0 0x40 -#define VFE_0_VIOLATION_STATUS 0x48 - -#define VFE_0_BUS_CMD 0x4c -#define VFE_0_BUS_CMD_Mx_RLD_CMD(x) (1 << (x)) - -#define VFE_0_BUS_CFG 0x050 - -#define VFE_0_BUS_XBAR_CFG_x(x) (0x58 + 0x4 * ((x) / 2)) -#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN (1 << 1) -#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA (0x3 << 4) -#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT 8 -#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA 0 -#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 5 -#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 6 -#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 7 - -#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(n) (0x06c + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT 0 -#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT 1 -#define VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(n) (0x070 + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(n) (0x074 + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(n) (0x078 + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT 2 -#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK (0x1F << 2) - -#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(n) (0x07c + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT 16 -#define VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(n) (0x080 + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(n) (0x084 + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(n) \ - (0x088 + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(n) \ - (0x08c + 0x24 * (n)) -#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF 0xffffffff - -#define VFE_0_BUS_PING_PONG_STATUS 0x268 - -#define VFE_0_BUS_BDG_CMD 0x2c0 -#define VFE_0_BUS_BDG_CMD_HALT_REQ 1 - -#define VFE_0_BUS_BDG_QOS_CFG_0 0x2c4 -#define VFE_0_BUS_BDG_QOS_CFG_0_CFG 0xaaa5aaa5 -#define VFE_0_BUS_BDG_QOS_CFG_1 0x2c8 -#define VFE_0_BUS_BDG_QOS_CFG_2 0x2cc -#define VFE_0_BUS_BDG_QOS_CFG_3 0x2d0 -#define VFE_0_BUS_BDG_QOS_CFG_4 0x2d4 -#define VFE_0_BUS_BDG_QOS_CFG_5 0x2d8 -#define VFE_0_BUS_BDG_QOS_CFG_6 0x2dc -#define VFE_0_BUS_BDG_QOS_CFG_7 0x2e0 -#define VFE_0_BUS_BDG_QOS_CFG_7_CFG 0x0001aaa5 - -#define VFE_0_RDI_CFG_x(x) (0x2e8 + (0x4 * (x))) -#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT 28 -#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK (0xf << 28) -#define VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT 4 -#define VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK (0xf << 4) -#define VFE_0_RDI_CFG_x_RDI_EN_BIT (1 << 2) -#define VFE_0_RDI_CFG_x_MIPI_EN_BITS 0x3 -#define VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(r) (1 << (16 + (r))) - -#define VFE_0_CAMIF_CMD 0x2f4 -#define VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY 0 -#define VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY 1 -#define VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS (1 << 2) -#define VFE_0_CAMIF_CFG 0x2f8 -#define VFE_0_CAMIF_CFG_VFE_OUTPUT_EN (1 << 6) -#define VFE_0_CAMIF_FRAME_CFG 0x300 -#define VFE_0_CAMIF_WINDOW_WIDTH_CFG 0x304 -#define VFE_0_CAMIF_WINDOW_HEIGHT_CFG 0x308 -#define VFE_0_CAMIF_SUBSAMPLE_CFG_0 0x30c -#define VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN 0x314 -#define VFE_0_CAMIF_STATUS 0x31c -#define VFE_0_CAMIF_STATUS_HALT (1 << 31) - -#define VFE_0_REG_UPDATE 0x378 -#define VFE_0_REG_UPDATE_RDIn(n) (1 << (1 + (n))) -#define VFE_0_REG_UPDATE_line_n(n) \ - ((n) == VFE_LINE_PIX ? 1 : VFE_0_REG_UPDATE_RDIn(n)) - -#define VFE_0_DEMUX_CFG 0x424 -#define VFE_0_DEMUX_CFG_PERIOD 0x3 -#define VFE_0_DEMUX_GAIN_0 0x428 -#define VFE_0_DEMUX_GAIN_0_CH0_EVEN (0x80 << 0) -#define VFE_0_DEMUX_GAIN_0_CH0_ODD (0x80 << 16) -#define VFE_0_DEMUX_GAIN_1 0x42c -#define VFE_0_DEMUX_GAIN_1_CH1 (0x80 << 0) -#define VFE_0_DEMUX_GAIN_1_CH2 (0x80 << 16) -#define VFE_0_DEMUX_EVEN_CFG 0x438 -#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV 0x9cac -#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU 0xac9c -#define VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY 0xc9ca -#define VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY 0xcac9 -#define VFE_0_DEMUX_ODD_CFG 0x43c -#define VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV 0x9cac -#define VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU 0xac9c -#define VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY 0xc9ca -#define VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY 0xcac9 - -#define VFE_0_SCALE_ENC_Y_CFG 0x75c -#define VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE 0x760 -#define VFE_0_SCALE_ENC_Y_H_PHASE 0x764 -#define VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE 0x76c -#define VFE_0_SCALE_ENC_Y_V_PHASE 0x770 -#define VFE_0_SCALE_ENC_CBCR_CFG 0x778 -#define VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE 0x77c -#define VFE_0_SCALE_ENC_CBCR_H_PHASE 0x780 -#define VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE 0x790 -#define VFE_0_SCALE_ENC_CBCR_V_PHASE 0x794 - -#define VFE_0_CROP_ENC_Y_WIDTH 0x854 -#define VFE_0_CROP_ENC_Y_HEIGHT 0x858 -#define VFE_0_CROP_ENC_CBCR_WIDTH 0x85c -#define VFE_0_CROP_ENC_CBCR_HEIGHT 0x860 - -#define VFE_0_CLAMP_ENC_MAX_CFG 0x874 -#define VFE_0_CLAMP_ENC_MAX_CFG_CH0 (0xff << 0) -#define VFE_0_CLAMP_ENC_MAX_CFG_CH1 (0xff << 8) -#define VFE_0_CLAMP_ENC_MAX_CFG_CH2 (0xff << 16) -#define VFE_0_CLAMP_ENC_MIN_CFG 0x878 -#define VFE_0_CLAMP_ENC_MIN_CFG_CH0 (0x0 << 0) -#define VFE_0_CLAMP_ENC_MIN_CFG_CH1 (0x0 << 8) -#define VFE_0_CLAMP_ENC_MIN_CFG_CH2 (0x0 << 16) - -#define VFE_0_CGC_OVERRIDE_1 0x974 -#define VFE_0_CGC_OVERRIDE_1_IMAGE_Mx_CGC_OVERRIDE(x) (1 << (x)) - -/* VFE reset timeout */ -#define VFE_RESET_TIMEOUT_MS 50 -/* VFE halt timeout */ -#define VFE_HALT_TIMEOUT_MS 100 -/* Max number of frame drop updates per frame */ -#define VFE_FRAME_DROP_UPDATES 5 -/* Frame drop value. NOTE: VAL + UPDATES should not exceed 31 */ -#define VFE_FRAME_DROP_VAL 20 - -#define VFE_NEXT_SOF_MS 500 - -#define CAMIF_TIMEOUT_SLEEP_US 1000 -#define CAMIF_TIMEOUT_ALL_US 1000000 - -#define SCALER_RATIO_MAX 16 - -static const struct { - u32 code; - u8 bpp; -} vfe_formats[] = { - { - MEDIA_BUS_FMT_UYVY8_2X8, - 8, - }, - { - MEDIA_BUS_FMT_VYUY8_2X8, - 8, - }, - { - MEDIA_BUS_FMT_YUYV8_2X8, - 8, - }, - { - MEDIA_BUS_FMT_YVYU8_2X8, - 8, - }, - { - MEDIA_BUS_FMT_SBGGR8_1X8, - 8, - }, - { - MEDIA_BUS_FMT_SGBRG8_1X8, - 8, - }, - { - MEDIA_BUS_FMT_SGRBG8_1X8, - 8, - }, - { - MEDIA_BUS_FMT_SRGGB8_1X8, - 8, - }, - { - MEDIA_BUS_FMT_SBGGR10_1X10, - 10, - }, - { - MEDIA_BUS_FMT_SGBRG10_1X10, - 10, - }, - { - MEDIA_BUS_FMT_SGRBG10_1X10, - 10, - }, - { - MEDIA_BUS_FMT_SRGGB10_1X10, - 10, - }, - { - MEDIA_BUS_FMT_SBGGR12_1X12, - 12, - }, - { - MEDIA_BUS_FMT_SGBRG12_1X12, - 12, - }, - { - MEDIA_BUS_FMT_SGRBG12_1X12, - 12, - }, - { - MEDIA_BUS_FMT_SRGGB12_1X12, - 12, - } -}; - -/* - * vfe_get_bpp - map media bus format to bits per pixel - * @code: media bus format code - * - * Return number of bits per pixel - */ -static u8 vfe_get_bpp(u32 code) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(vfe_formats); i++) - if (code == vfe_formats[i].code) - return vfe_formats[i].bpp; - - WARN(1, "Unknown format\n"); - - return vfe_formats[0].bpp; -} - -static inline void vfe_reg_clr(struct vfe_device *vfe, u32 reg, u32 clr_bits) -{ - u32 bits = readl_relaxed(vfe->base + reg); - - writel_relaxed(bits & ~clr_bits, vfe->base + reg); -} - -static inline void vfe_reg_set(struct vfe_device *vfe, u32 reg, u32 set_bits) -{ - u32 bits = readl_relaxed(vfe->base + reg); - - writel_relaxed(bits | set_bits, vfe->base + reg); -} - -static void vfe_global_reset(struct vfe_device *vfe) -{ - u32 reset_bits = VFE_0_GLOBAL_RESET_CMD_TESTGEN | - VFE_0_GLOBAL_RESET_CMD_BUS_MISR | - VFE_0_GLOBAL_RESET_CMD_PM | - VFE_0_GLOBAL_RESET_CMD_TIMER | - VFE_0_GLOBAL_RESET_CMD_REGISTER | - VFE_0_GLOBAL_RESET_CMD_BUS_BDG | - VFE_0_GLOBAL_RESET_CMD_BUS | - VFE_0_GLOBAL_RESET_CMD_CAMIF | - VFE_0_GLOBAL_RESET_CMD_CORE; - - writel_relaxed(reset_bits, vfe->base + VFE_0_GLOBAL_RESET_CMD); -} - -static void vfe_wm_enable(struct vfe_device *vfe, u8 wm, u8 enable) -{ - if (enable) - vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), - 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT); - else - vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), - 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT); -} - -static void vfe_wm_frame_based(struct vfe_device *vfe, u8 wm, u8 enable) -{ - if (enable) - vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), - 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT); - else - vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), - 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT); -} - -#define CALC_WORD(width, M, N) (((width) * (M) + (N) - 1) / (N)) - -static int vfe_word_per_line(uint32_t format, uint32_t pixel_per_line) -{ - int val = 0; - - switch (format) { - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - val = CALC_WORD(pixel_per_line, 1, 8); - break; - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_VYUY: - val = CALC_WORD(pixel_per_line, 2, 8); - break; - } - - return val; -} - -static void vfe_get_wm_sizes(struct v4l2_pix_format_mplane *pix, u8 plane, - u16 *width, u16 *height, u16 *bytesperline) -{ - switch (pix->pixelformat) { - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - *width = pix->width; - *height = pix->height; - *bytesperline = pix->plane_fmt[0].bytesperline; - if (plane == 1) - *height /= 2; - break; - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - *width = pix->width; - *height = pix->height; - *bytesperline = pix->plane_fmt[0].bytesperline; - break; - } -} - -static void vfe_wm_line_based(struct vfe_device *vfe, u32 wm, - struct v4l2_pix_format_mplane *pix, - u8 plane, u32 enable) -{ - u32 reg; - - if (enable) { - u16 width = 0, height = 0, bytesperline = 0, wpl; - - vfe_get_wm_sizes(pix, plane, &width, &height, &bytesperline); - - wpl = vfe_word_per_line(pix->pixelformat, width); - - reg = height - 1; - reg |= ((wpl + 1) / 2 - 1) << 16; - - writel_relaxed(reg, vfe->base + - VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); - - wpl = vfe_word_per_line(pix->pixelformat, bytesperline); - - reg = 0x3; - reg |= (height - 1) << 4; - reg |= wpl << 16; - - writel_relaxed(reg, vfe->base + - VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); - } else { - writel_relaxed(0, vfe->base + - VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); - writel_relaxed(0, vfe->base + - VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); - } -} - -static void vfe_wm_set_framedrop_period(struct vfe_device *vfe, u8 wm, u8 per) -{ - u32 reg; - - reg = readl_relaxed(vfe->base + - VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); - - reg &= ~(VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK); - - reg |= (per << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT) - & VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK; - - writel_relaxed(reg, - vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); -} - -static void vfe_wm_set_framedrop_pattern(struct vfe_device *vfe, u8 wm, - u32 pattern) -{ - writel_relaxed(pattern, - vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(wm)); -} - -static void vfe_wm_set_ub_cfg(struct vfe_device *vfe, u8 wm, u16 offset, - u16 depth) -{ - u32 reg; - - reg = (offset << VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT) | - depth; - writel_relaxed(reg, vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(wm)); -} - -static void vfe_bus_reload_wm(struct vfe_device *vfe, u8 wm) -{ - wmb(); - writel_relaxed(VFE_0_BUS_CMD_Mx_RLD_CMD(wm), vfe->base + VFE_0_BUS_CMD); - wmb(); -} - -static void vfe_wm_set_ping_addr(struct vfe_device *vfe, u8 wm, u32 addr) -{ - writel_relaxed(addr, - vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(wm)); -} - -static void vfe_wm_set_pong_addr(struct vfe_device *vfe, u8 wm, u32 addr) -{ - writel_relaxed(addr, - vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(wm)); -} - -static int vfe_wm_get_ping_pong_status(struct vfe_device *vfe, u8 wm) -{ - u32 reg; - - reg = readl_relaxed(vfe->base + VFE_0_BUS_PING_PONG_STATUS); - - return (reg >> wm) & 0x1; -} - -static void vfe_bus_enable_wr_if(struct vfe_device *vfe, u8 enable) -{ - if (enable) - writel_relaxed(0x10000009, vfe->base + VFE_0_BUS_CFG); - else - writel_relaxed(0, vfe->base + VFE_0_BUS_CFG); -} - -static void vfe_bus_connect_wm_to_rdi(struct vfe_device *vfe, u8 wm, - enum vfe_line_id id) -{ - u32 reg; - - reg = VFE_0_RDI_CFG_x_MIPI_EN_BITS; - reg |= VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(id); - vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), reg); - - reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; - reg |= ((3 * id) << VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT) & - VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK; - vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), reg); - - switch (id) { - case VFE_LINE_RDI0: - default: - reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << - VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; - break; - case VFE_LINE_RDI1: - reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << - VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; - break; - case VFE_LINE_RDI2: - reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << - VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; - break; - } - - if (wm % 2 == 1) - reg <<= 16; - - vfe_reg_set(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); -} - -static void vfe_wm_set_subsample(struct vfe_device *vfe, u8 wm) -{ - writel_relaxed(VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF, - vfe->base + - VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(wm)); -} - -static void vfe_bus_disconnect_wm_from_rdi(struct vfe_device *vfe, u8 wm, - enum vfe_line_id id) -{ - u32 reg; - - reg = VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(id); - vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(0), reg); - - reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; - vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), reg); - - switch (id) { - case VFE_LINE_RDI0: - default: - reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << - VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; - break; - case VFE_LINE_RDI1: - reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << - VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; - break; - case VFE_LINE_RDI2: - reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << - VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; - break; - } - - if (wm % 2 == 1) - reg <<= 16; - - vfe_reg_clr(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); -} - -static void vfe_set_xbar_cfg(struct vfe_device *vfe, struct vfe_output *output, - u8 enable) -{ - struct vfe_line *line = container_of(output, struct vfe_line, output); - u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; - u32 reg; - unsigned int i; - - for (i = 0; i < output->wm_num; i++) { - if (i == 0) { - reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA << - VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; - } else if (i == 1) { - reg = VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN; - if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV16) - reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA; - } else { - /* On current devices output->wm_num is always <= 2 */ - break; - } - - if (output->wm_idx[i] % 2 == 1) - reg <<= 16; - - if (enable) - vfe_reg_set(vfe, - VFE_0_BUS_XBAR_CFG_x(output->wm_idx[i]), - reg); - else - vfe_reg_clr(vfe, - VFE_0_BUS_XBAR_CFG_x(output->wm_idx[i]), - reg); - } -} - -static void vfe_set_rdi_cid(struct vfe_device *vfe, enum vfe_line_id id, u8 cid) -{ - vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), - VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK); - - vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), - cid << VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT); -} - -static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) -{ - vfe->reg_update |= VFE_0_REG_UPDATE_line_n(line_id); - wmb(); - writel_relaxed(vfe->reg_update, vfe->base + VFE_0_REG_UPDATE); - wmb(); -} - -static void vfe_enable_irq_wm_line(struct vfe_device *vfe, u8 wm, - enum vfe_line_id line_id, u8 enable) -{ - u32 irq_en0 = VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(wm) | - VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); - u32 irq_en1 = VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(wm) | - VFE_0_IRQ_MASK_1_RDIn_SOF(line_id); - - if (enable) { - vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); - vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); - } else { - vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); - vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); - } -} - -static void vfe_enable_irq_pix_line(struct vfe_device *vfe, u8 comp, - enum vfe_line_id line_id, u8 enable) -{ - struct vfe_output *output = &vfe->line[line_id].output; - unsigned int i; - u32 irq_en0; - u32 irq_en1; - u32 comp_mask = 0; - - irq_en0 = VFE_0_IRQ_MASK_0_CAMIF_SOF; - irq_en0 |= VFE_0_IRQ_MASK_0_CAMIF_EOF; - irq_en0 |= VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(comp); - irq_en0 |= VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); - irq_en1 = VFE_0_IRQ_MASK_1_CAMIF_ERROR; - for (i = 0; i < output->wm_num; i++) { - irq_en1 |= VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW( - output->wm_idx[i]); - comp_mask |= (1 << output->wm_idx[i]) << comp * 8; - } - - if (enable) { - vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); - vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); - vfe_reg_set(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); - } else { - vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); - vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); - vfe_reg_clr(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); - } -} - -static void vfe_enable_irq_common(struct vfe_device *vfe) -{ - u32 irq_en0 = VFE_0_IRQ_MASK_0_RESET_ACK; - u32 irq_en1 = VFE_0_IRQ_MASK_1_VIOLATION | - VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK; - - vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); - vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); -} - -static void vfe_set_demux_cfg(struct vfe_device *vfe, struct vfe_line *line) -{ - u32 val, even_cfg, odd_cfg; - - writel_relaxed(VFE_0_DEMUX_CFG_PERIOD, vfe->base + VFE_0_DEMUX_CFG); - - val = VFE_0_DEMUX_GAIN_0_CH0_EVEN | VFE_0_DEMUX_GAIN_0_CH0_ODD; - writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_0); - - val = VFE_0_DEMUX_GAIN_1_CH1 | VFE_0_DEMUX_GAIN_1_CH2; - writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_1); - - switch (line->fmt[MSM_VFE_PAD_SINK].code) { - case MEDIA_BUS_FMT_YUYV8_2X8: - even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV; - odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV; - break; - case MEDIA_BUS_FMT_YVYU8_2X8: - even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU; - odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU; - break; - case MEDIA_BUS_FMT_UYVY8_2X8: - default: - even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY; - odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY; - break; - case MEDIA_BUS_FMT_VYUY8_2X8: - even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY; - odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY; - break; - } - - writel_relaxed(even_cfg, vfe->base + VFE_0_DEMUX_EVEN_CFG); - writel_relaxed(odd_cfg, vfe->base + VFE_0_DEMUX_ODD_CFG); -} - -static inline u8 vfe_calc_interp_reso(u16 input, u16 output) -{ - if (input / output >= 16) - return 0; - - if (input / output >= 8) - return 1; - - if (input / output >= 4) - return 2; - - return 3; -} - -static void vfe_set_scale_cfg(struct vfe_device *vfe, struct vfe_line *line) -{ - u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; - u32 reg; - u16 input, output; - u8 interp_reso; - u32 phase_mult; - - writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_Y_CFG); - - input = line->fmt[MSM_VFE_PAD_SINK].width; - output = line->compose.width; - reg = (output << 16) | input; - writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE); - - interp_reso = vfe_calc_interp_reso(input, output); - phase_mult = input * (1 << (13 + interp_reso)) / output; - reg = (interp_reso << 20) | phase_mult; - writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_PHASE); - - input = line->fmt[MSM_VFE_PAD_SINK].height; - output = line->compose.height; - reg = (output << 16) | input; - writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE); - - interp_reso = vfe_calc_interp_reso(input, output); - phase_mult = input * (1 << (13 + interp_reso)) / output; - reg = (interp_reso << 20) | phase_mult; - writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_PHASE); - - writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_CBCR_CFG); - - input = line->fmt[MSM_VFE_PAD_SINK].width; - output = line->compose.width / 2; - reg = (output << 16) | input; - writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE); - - interp_reso = vfe_calc_interp_reso(input, output); - phase_mult = input * (1 << (13 + interp_reso)) / output; - reg = (interp_reso << 20) | phase_mult; - writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_PHASE); - - input = line->fmt[MSM_VFE_PAD_SINK].height; - output = line->compose.height; - if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) - output = line->compose.height / 2; - reg = (output << 16) | input; - writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE); - - interp_reso = vfe_calc_interp_reso(input, output); - phase_mult = input * (1 << (13 + interp_reso)) / output; - reg = (interp_reso << 20) | phase_mult; - writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_PHASE); -} - -static void vfe_set_crop_cfg(struct vfe_device *vfe, struct vfe_line *line) -{ - u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; - u32 reg; - u16 first, last; - - first = line->crop.left; - last = line->crop.left + line->crop.width - 1; - reg = (first << 16) | last; - writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_WIDTH); - - first = line->crop.top; - last = line->crop.top + line->crop.height - 1; - reg = (first << 16) | last; - writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_HEIGHT); - - first = line->crop.left / 2; - last = line->crop.left / 2 + line->crop.width / 2 - 1; - reg = (first << 16) | last; - writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_WIDTH); - - first = line->crop.top; - last = line->crop.top + line->crop.height - 1; - if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) { - first = line->crop.top / 2; - last = line->crop.top / 2 + line->crop.height / 2 - 1; - } - reg = (first << 16) | last; - writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_HEIGHT); -} - -static void vfe_set_clamp_cfg(struct vfe_device *vfe) -{ - u32 val = VFE_0_CLAMP_ENC_MAX_CFG_CH0 | - VFE_0_CLAMP_ENC_MAX_CFG_CH1 | - VFE_0_CLAMP_ENC_MAX_CFG_CH2; - - writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MAX_CFG); - - val = VFE_0_CLAMP_ENC_MIN_CFG_CH0 | - VFE_0_CLAMP_ENC_MIN_CFG_CH1 | - VFE_0_CLAMP_ENC_MIN_CFG_CH2; - - writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MIN_CFG); -} - -/* - * vfe_reset - Trigger reset on VFE module and wait to complete - * @vfe: VFE device - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_reset(struct vfe_device *vfe) -{ - unsigned long time; - - reinit_completion(&vfe->reset_complete); - - vfe_global_reset(vfe); - - time = wait_for_completion_timeout(&vfe->reset_complete, - msecs_to_jiffies(VFE_RESET_TIMEOUT_MS)); - if (!time) { - dev_err(to_device(vfe), "VFE reset timeout\n"); - return -EIO; - } - - return 0; -} - -/* - * vfe_halt - Trigger halt on VFE module and wait to complete - * @vfe: VFE device - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_halt(struct vfe_device *vfe) -{ - unsigned long time; - - reinit_completion(&vfe->halt_complete); - - writel_relaxed(VFE_0_BUS_BDG_CMD_HALT_REQ, - vfe->base + VFE_0_BUS_BDG_CMD); - - time = wait_for_completion_timeout(&vfe->halt_complete, - msecs_to_jiffies(VFE_HALT_TIMEOUT_MS)); - if (!time) { - dev_err(to_device(vfe), "VFE halt timeout\n"); - return -EIO; - } - - return 0; -} - -static void vfe_init_outputs(struct vfe_device *vfe) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(vfe->line); i++) { - struct vfe_output *output = &vfe->line[i].output; - - output->state = VFE_OUTPUT_OFF; - output->buf[0] = NULL; - output->buf[1] = NULL; - INIT_LIST_HEAD(&output->pending_bufs); - - output->wm_num = 1; - if (vfe->line[i].id == VFE_LINE_PIX) - output->wm_num = 2; - } -} - -static void vfe_reset_output_maps(struct vfe_device *vfe) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) - vfe->wm_output_map[i] = VFE_LINE_NONE; -} - -static void vfe_set_qos(struct vfe_device *vfe) -{ - u32 val = VFE_0_BUS_BDG_QOS_CFG_0_CFG; - u32 val7 = VFE_0_BUS_BDG_QOS_CFG_7_CFG; - - writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_0); - writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_1); - writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_2); - writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_3); - writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_4); - writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_5); - writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_6); - writel_relaxed(val7, vfe->base + VFE_0_BUS_BDG_QOS_CFG_7); -} - -static void vfe_set_cgc_override(struct vfe_device *vfe, u8 wm, u8 enable) -{ - u32 val = VFE_0_CGC_OVERRIDE_1_IMAGE_Mx_CGC_OVERRIDE(wm); - - if (enable) - vfe_reg_set(vfe, VFE_0_CGC_OVERRIDE_1, val); - else - vfe_reg_clr(vfe, VFE_0_CGC_OVERRIDE_1, val); - - wmb(); -} - -static void vfe_set_module_cfg(struct vfe_device *vfe, u8 enable) -{ - u32 val = VFE_0_MODULE_CFG_DEMUX | - VFE_0_MODULE_CFG_CHROMA_UPSAMPLE | - VFE_0_MODULE_CFG_SCALE_ENC | - VFE_0_MODULE_CFG_CROP_ENC; - - if (enable) - writel_relaxed(val, vfe->base + VFE_0_MODULE_CFG); - else - writel_relaxed(0x0, vfe->base + VFE_0_MODULE_CFG); -} - -static void vfe_set_camif_cfg(struct vfe_device *vfe, struct vfe_line *line) -{ - u32 val; - - switch (line->fmt[MSM_VFE_PAD_SINK].code) { - case MEDIA_BUS_FMT_YUYV8_2X8: - val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR; - break; - case MEDIA_BUS_FMT_YVYU8_2X8: - val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB; - break; - case MEDIA_BUS_FMT_UYVY8_2X8: - default: - val = VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY; - break; - case MEDIA_BUS_FMT_VYUY8_2X8: - val = VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY; - break; - } - - writel_relaxed(val, vfe->base + VFE_0_CORE_CFG); - - val = line->fmt[MSM_VFE_PAD_SINK].width * 2; - val |= line->fmt[MSM_VFE_PAD_SINK].height << 16; - writel_relaxed(val, vfe->base + VFE_0_CAMIF_FRAME_CFG); - - val = line->fmt[MSM_VFE_PAD_SINK].width * 2 - 1; - writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_WIDTH_CFG); - - val = line->fmt[MSM_VFE_PAD_SINK].height - 1; - writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_HEIGHT_CFG); - - val = 0xffffffff; - writel_relaxed(val, vfe->base + VFE_0_CAMIF_SUBSAMPLE_CFG_0); - - val = 0xffffffff; - writel_relaxed(val, vfe->base + VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN); - - val = VFE_0_RDI_CFG_x_MIPI_EN_BITS; - vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), val); - - val = VFE_0_CAMIF_CFG_VFE_OUTPUT_EN; - writel_relaxed(val, vfe->base + VFE_0_CAMIF_CFG); -} - -static void vfe_set_camif_cmd(struct vfe_device *vfe, u32 cmd) -{ - writel_relaxed(VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS, - vfe->base + VFE_0_CAMIF_CMD); - - writel_relaxed(cmd, vfe->base + VFE_0_CAMIF_CMD); -} - -static int vfe_camif_wait_for_stop(struct vfe_device *vfe) -{ - u32 val; - int ret; - - ret = readl_poll_timeout(vfe->base + VFE_0_CAMIF_STATUS, - val, - (val & VFE_0_CAMIF_STATUS_HALT), - CAMIF_TIMEOUT_SLEEP_US, - CAMIF_TIMEOUT_ALL_US); - if (ret < 0) - dev_err(to_device(vfe), "%s: camif stop timeout\n", __func__); - - return ret; -} - -static void vfe_output_init_addrs(struct vfe_device *vfe, - struct vfe_output *output, u8 sync) -{ - u32 ping_addr; - u32 pong_addr; - unsigned int i; - - output->active_buf = 0; - - for (i = 0; i < output->wm_num; i++) { - if (output->buf[0]) - ping_addr = output->buf[0]->addr[i]; - else - ping_addr = 0; - - if (output->buf[1]) - pong_addr = output->buf[1]->addr[i]; - else - pong_addr = ping_addr; - - vfe_wm_set_ping_addr(vfe, output->wm_idx[i], ping_addr); - vfe_wm_set_pong_addr(vfe, output->wm_idx[i], pong_addr); - if (sync) - vfe_bus_reload_wm(vfe, output->wm_idx[i]); - } -} - -static void vfe_output_update_ping_addr(struct vfe_device *vfe, - struct vfe_output *output, u8 sync) -{ - u32 addr; - unsigned int i; - - for (i = 0; i < output->wm_num; i++) { - if (output->buf[0]) - addr = output->buf[0]->addr[i]; - else - addr = 0; - - vfe_wm_set_ping_addr(vfe, output->wm_idx[i], addr); - if (sync) - vfe_bus_reload_wm(vfe, output->wm_idx[i]); - } -} - -static void vfe_output_update_pong_addr(struct vfe_device *vfe, - struct vfe_output *output, u8 sync) -{ - u32 addr; - unsigned int i; - - for (i = 0; i < output->wm_num; i++) { - if (output->buf[1]) - addr = output->buf[1]->addr[i]; - else - addr = 0; - - vfe_wm_set_pong_addr(vfe, output->wm_idx[i], addr); - if (sync) - vfe_bus_reload_wm(vfe, output->wm_idx[i]); - } - -} - -static int vfe_reserve_wm(struct vfe_device *vfe, enum vfe_line_id line_id) -{ - int ret = -EBUSY; - int i; - - for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) { - if (vfe->wm_output_map[i] == VFE_LINE_NONE) { - vfe->wm_output_map[i] = line_id; - ret = i; - break; - } - } - - return ret; -} - -static int vfe_release_wm(struct vfe_device *vfe, u8 wm) -{ - if (wm >= ARRAY_SIZE(vfe->wm_output_map)) - return -EINVAL; - - vfe->wm_output_map[wm] = VFE_LINE_NONE; - - return 0; -} - -static void vfe_output_frame_drop(struct vfe_device *vfe, - struct vfe_output *output, - u32 drop_pattern) -{ - u8 drop_period; - unsigned int i; - - /* We need to toggle update period to be valid on next frame */ - output->drop_update_idx++; - output->drop_update_idx %= VFE_FRAME_DROP_UPDATES; - drop_period = VFE_FRAME_DROP_VAL + output->drop_update_idx; - - for (i = 0; i < output->wm_num; i++) { - vfe_wm_set_framedrop_period(vfe, output->wm_idx[i], - drop_period); - vfe_wm_set_framedrop_pattern(vfe, output->wm_idx[i], - drop_pattern); - } - vfe_reg_update(vfe, container_of(output, struct vfe_line, output)->id); -} - -static struct camss_buffer *vfe_buf_get_pending(struct vfe_output *output) -{ - struct camss_buffer *buffer = NULL; - - if (!list_empty(&output->pending_bufs)) { - buffer = list_first_entry(&output->pending_bufs, - struct camss_buffer, - queue); - list_del(&buffer->queue); - } - - return buffer; -} - -/* - * vfe_buf_add_pending - Add output buffer to list of pending - * @output: VFE output - * @buffer: Video buffer - */ -static void vfe_buf_add_pending(struct vfe_output *output, - struct camss_buffer *buffer) -{ - INIT_LIST_HEAD(&buffer->queue); - list_add_tail(&buffer->queue, &output->pending_bufs); -} - -/* - * vfe_buf_flush_pending - Flush all pending buffers. - * @output: VFE output - * @state: vb2 buffer state - */ -static void vfe_buf_flush_pending(struct vfe_output *output, - enum vb2_buffer_state state) -{ - struct camss_buffer *buf; - struct camss_buffer *t; - - list_for_each_entry_safe(buf, t, &output->pending_bufs, queue) { - vb2_buffer_done(&buf->vb.vb2_buf, state); - list_del(&buf->queue); - } -} - -static void vfe_buf_update_wm_on_next(struct vfe_device *vfe, - struct vfe_output *output) -{ - switch (output->state) { - case VFE_OUTPUT_CONTINUOUS: - vfe_output_frame_drop(vfe, output, 3); - break; - case VFE_OUTPUT_SINGLE: - default: - dev_err_ratelimited(to_device(vfe), - "Next buf in wrong state! %d\n", - output->state); - break; - } -} - -static void vfe_buf_update_wm_on_last(struct vfe_device *vfe, - struct vfe_output *output) -{ - switch (output->state) { - case VFE_OUTPUT_CONTINUOUS: - output->state = VFE_OUTPUT_SINGLE; - vfe_output_frame_drop(vfe, output, 1); - break; - case VFE_OUTPUT_SINGLE: - output->state = VFE_OUTPUT_STOPPING; - vfe_output_frame_drop(vfe, output, 0); - break; - default: - dev_err_ratelimited(to_device(vfe), - "Last buff in wrong state! %d\n", - output->state); - break; - } -} - -static void vfe_buf_update_wm_on_new(struct vfe_device *vfe, - struct vfe_output *output, - struct camss_buffer *new_buf) -{ - int inactive_idx; - - switch (output->state) { - case VFE_OUTPUT_SINGLE: - inactive_idx = !output->active_buf; - - if (!output->buf[inactive_idx]) { - output->buf[inactive_idx] = new_buf; - - if (inactive_idx) - vfe_output_update_pong_addr(vfe, output, 0); - else - vfe_output_update_ping_addr(vfe, output, 0); - - vfe_output_frame_drop(vfe, output, 3); - output->state = VFE_OUTPUT_CONTINUOUS; - } else { - vfe_buf_add_pending(output, new_buf); - dev_err_ratelimited(to_device(vfe), - "Inactive buffer is busy\n"); - } - break; - - case VFE_OUTPUT_IDLE: - if (!output->buf[0]) { - output->buf[0] = new_buf; - - vfe_output_init_addrs(vfe, output, 1); - - vfe_output_frame_drop(vfe, output, 1); - output->state = VFE_OUTPUT_SINGLE; - } else { - vfe_buf_add_pending(output, new_buf); - dev_err_ratelimited(to_device(vfe), - "Output idle with buffer set!\n"); - } - break; - - case VFE_OUTPUT_CONTINUOUS: - default: - vfe_buf_add_pending(output, new_buf); - break; - } -} - -static int vfe_get_output(struct vfe_line *line) -{ - struct vfe_device *vfe = to_vfe(line); - struct vfe_output *output; - unsigned long flags; - int i; - int wm_idx; - - spin_lock_irqsave(&vfe->output_lock, flags); - - output = &line->output; - if (output->state != VFE_OUTPUT_OFF) { - dev_err(to_device(vfe), "Output is running\n"); - goto error; - } - output->state = VFE_OUTPUT_RESERVED; - - output->active_buf = 0; - - for (i = 0; i < output->wm_num; i++) { - wm_idx = vfe_reserve_wm(vfe, line->id); - if (wm_idx < 0) { - dev_err(to_device(vfe), "Can not reserve wm\n"); - goto error_get_wm; - } - output->wm_idx[i] = wm_idx; - } - - output->drop_update_idx = 0; - - spin_unlock_irqrestore(&vfe->output_lock, flags); - - return 0; - -error_get_wm: - for (i--; i >= 0; i--) - vfe_release_wm(vfe, output->wm_idx[i]); - output->state = VFE_OUTPUT_OFF; -error: - spin_unlock_irqrestore(&vfe->output_lock, flags); - - return -EINVAL; -} - -static int vfe_put_output(struct vfe_line *line) -{ - struct vfe_device *vfe = to_vfe(line); - struct vfe_output *output = &line->output; - unsigned long flags; - unsigned int i; - - spin_lock_irqsave(&vfe->output_lock, flags); - - for (i = 0; i < output->wm_num; i++) - vfe_release_wm(vfe, output->wm_idx[i]); - - output->state = VFE_OUTPUT_OFF; - - spin_unlock_irqrestore(&vfe->output_lock, flags); - return 0; -} - -static int vfe_enable_output(struct vfe_line *line) -{ - struct vfe_device *vfe = to_vfe(line); - struct vfe_output *output = &line->output; - unsigned long flags; - unsigned int i; - u16 ub_size; - - switch (vfe->id) { - case 0: - ub_size = MSM_VFE_VFE0_UB_SIZE_RDI; - break; - case 1: - ub_size = MSM_VFE_VFE1_UB_SIZE_RDI; - break; - default: - return -EINVAL; - } - - spin_lock_irqsave(&vfe->output_lock, flags); - - vfe->reg_update &= ~VFE_0_REG_UPDATE_line_n(line->id); - - if (output->state != VFE_OUTPUT_RESERVED) { - dev_err(to_device(vfe), "Output is not in reserved state %d\n", - output->state); - spin_unlock_irqrestore(&vfe->output_lock, flags); - return -EINVAL; - } - output->state = VFE_OUTPUT_IDLE; - - output->buf[0] = vfe_buf_get_pending(output); - output->buf[1] = vfe_buf_get_pending(output); - - if (!output->buf[0] && output->buf[1]) { - output->buf[0] = output->buf[1]; - output->buf[1] = NULL; - } - - if (output->buf[0]) - output->state = VFE_OUTPUT_SINGLE; - - if (output->buf[1]) - output->state = VFE_OUTPUT_CONTINUOUS; - - switch (output->state) { - case VFE_OUTPUT_SINGLE: - vfe_output_frame_drop(vfe, output, 1); - break; - case VFE_OUTPUT_CONTINUOUS: - vfe_output_frame_drop(vfe, output, 3); - break; - default: - vfe_output_frame_drop(vfe, output, 0); - break; - } - - output->sequence = 0; - output->wait_sof = 0; - output->wait_reg_update = 0; - reinit_completion(&output->sof); - reinit_completion(&output->reg_update); - - vfe_output_init_addrs(vfe, output, 0); - - if (line->id != VFE_LINE_PIX) { - vfe_set_cgc_override(vfe, output->wm_idx[0], 1); - vfe_enable_irq_wm_line(vfe, output->wm_idx[0], line->id, 1); - vfe_bus_connect_wm_to_rdi(vfe, output->wm_idx[0], line->id); - vfe_wm_set_subsample(vfe, output->wm_idx[0]); - vfe_set_rdi_cid(vfe, line->id, 0); - vfe_wm_set_ub_cfg(vfe, output->wm_idx[0], - (ub_size + 1) * output->wm_idx[0], ub_size); - vfe_wm_frame_based(vfe, output->wm_idx[0], 1); - vfe_wm_enable(vfe, output->wm_idx[0], 1); - vfe_bus_reload_wm(vfe, output->wm_idx[0]); - } else { - ub_size /= output->wm_num; - for (i = 0; i < output->wm_num; i++) { - vfe_set_cgc_override(vfe, output->wm_idx[i], 1); - vfe_wm_set_subsample(vfe, output->wm_idx[i]); - vfe_wm_set_ub_cfg(vfe, output->wm_idx[i], - (ub_size + 1) * output->wm_idx[i], - ub_size); - vfe_wm_line_based(vfe, output->wm_idx[i], - &line->video_out.active_fmt.fmt.pix_mp, - i, 1); - vfe_wm_enable(vfe, output->wm_idx[i], 1); - vfe_bus_reload_wm(vfe, output->wm_idx[i]); - } - vfe_enable_irq_pix_line(vfe, 0, line->id, 1); - vfe_set_module_cfg(vfe, 1); - vfe_set_camif_cfg(vfe, line); - vfe_set_xbar_cfg(vfe, output, 1); - vfe_set_demux_cfg(vfe, line); - vfe_set_scale_cfg(vfe, line); - vfe_set_crop_cfg(vfe, line); - vfe_set_clamp_cfg(vfe); - vfe_set_camif_cmd(vfe, VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY); - } - - vfe_reg_update(vfe, line->id); - - spin_unlock_irqrestore(&vfe->output_lock, flags); - - return 0; -} - -static int vfe_disable_output(struct vfe_line *line) -{ - struct vfe_device *vfe = to_vfe(line); - struct vfe_output *output = &line->output; - unsigned long flags; - unsigned long time; - unsigned int i; - - spin_lock_irqsave(&vfe->output_lock, flags); - - output->wait_sof = 1; - spin_unlock_irqrestore(&vfe->output_lock, flags); - - time = wait_for_completion_timeout(&output->sof, - msecs_to_jiffies(VFE_NEXT_SOF_MS)); - if (!time) - dev_err(to_device(vfe), "VFE sof timeout\n"); - - spin_lock_irqsave(&vfe->output_lock, flags); - for (i = 0; i < output->wm_num; i++) - vfe_wm_enable(vfe, output->wm_idx[i], 0); - - vfe_reg_update(vfe, line->id); - output->wait_reg_update = 1; - spin_unlock_irqrestore(&vfe->output_lock, flags); - - time = wait_for_completion_timeout(&output->reg_update, - msecs_to_jiffies(VFE_NEXT_SOF_MS)); - if (!time) - dev_err(to_device(vfe), "VFE reg update timeout\n"); - - spin_lock_irqsave(&vfe->output_lock, flags); - - if (line->id != VFE_LINE_PIX) { - vfe_wm_frame_based(vfe, output->wm_idx[0], 0); - vfe_bus_disconnect_wm_from_rdi(vfe, output->wm_idx[0], line->id); - vfe_enable_irq_wm_line(vfe, output->wm_idx[0], line->id, 0); - vfe_set_cgc_override(vfe, output->wm_idx[0], 0); - spin_unlock_irqrestore(&vfe->output_lock, flags); - } else { - for (i = 0; i < output->wm_num; i++) { - vfe_wm_line_based(vfe, output->wm_idx[i], NULL, i, 0); - vfe_set_cgc_override(vfe, output->wm_idx[i], 0); - } - - vfe_enable_irq_pix_line(vfe, 0, line->id, 0); - vfe_set_module_cfg(vfe, 0); - vfe_set_xbar_cfg(vfe, output, 0); - - vfe_set_camif_cmd(vfe, VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY); - spin_unlock_irqrestore(&vfe->output_lock, flags); - - vfe_camif_wait_for_stop(vfe); - } - - return 0; -} - -/* - * vfe_enable - Enable streaming on VFE line - * @line: VFE line - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_enable(struct vfe_line *line) -{ - struct vfe_device *vfe = to_vfe(line); - int ret; - - mutex_lock(&vfe->stream_lock); - - if (!vfe->stream_count) { - vfe_enable_irq_common(vfe); - - vfe_bus_enable_wr_if(vfe, 1); - - vfe_set_qos(vfe); - } - - vfe->stream_count++; - - mutex_unlock(&vfe->stream_lock); - - ret = vfe_get_output(line); - if (ret < 0) - goto error_get_output; - - ret = vfe_enable_output(line); - if (ret < 0) - goto error_enable_output; - - vfe->was_streaming = 1; - - return 0; - - -error_enable_output: - vfe_put_output(line); - -error_get_output: - mutex_lock(&vfe->stream_lock); - - if (vfe->stream_count == 1) - vfe_bus_enable_wr_if(vfe, 0); - - vfe->stream_count--; - - mutex_unlock(&vfe->stream_lock); - - return ret; -} - -/* - * vfe_disable - Disable streaming on VFE line - * @line: VFE line - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_disable(struct vfe_line *line) -{ - struct vfe_device *vfe = to_vfe(line); - - vfe_disable_output(line); - - vfe_put_output(line); - - mutex_lock(&vfe->stream_lock); - - if (vfe->stream_count == 1) - vfe_bus_enable_wr_if(vfe, 0); - - vfe->stream_count--; - - mutex_unlock(&vfe->stream_lock); - - return 0; -} - -/* - * vfe_isr_sof - Process start of frame interrupt - * @vfe: VFE Device - * @line_id: VFE line - */ -static void vfe_isr_sof(struct vfe_device *vfe, enum vfe_line_id line_id) -{ - struct vfe_output *output; - unsigned long flags; - - spin_lock_irqsave(&vfe->output_lock, flags); - output = &vfe->line[line_id].output; - if (output->wait_sof) { - output->wait_sof = 0; - complete(&output->sof); - } - spin_unlock_irqrestore(&vfe->output_lock, flags); -} - -/* - * vfe_isr_reg_update - Process reg update interrupt - * @vfe: VFE Device - * @line_id: VFE line - */ -static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) -{ - struct vfe_output *output; - unsigned long flags; - - spin_lock_irqsave(&vfe->output_lock, flags); - vfe->reg_update &= ~VFE_0_REG_UPDATE_line_n(line_id); - - output = &vfe->line[line_id].output; - - if (output->wait_reg_update) { - output->wait_reg_update = 0; - complete(&output->reg_update); - spin_unlock_irqrestore(&vfe->output_lock, flags); - return; - } - - if (output->state == VFE_OUTPUT_STOPPING) { - /* Release last buffer when hw is idle */ - if (output->last_buffer) { - vb2_buffer_done(&output->last_buffer->vb.vb2_buf, - VB2_BUF_STATE_DONE); - output->last_buffer = NULL; - } - output->state = VFE_OUTPUT_IDLE; - - /* Buffers received in stopping state are queued in */ - /* dma pending queue, start next capture here */ - - output->buf[0] = vfe_buf_get_pending(output); - output->buf[1] = vfe_buf_get_pending(output); - - if (!output->buf[0] && output->buf[1]) { - output->buf[0] = output->buf[1]; - output->buf[1] = NULL; - } - - if (output->buf[0]) - output->state = VFE_OUTPUT_SINGLE; - - if (output->buf[1]) - output->state = VFE_OUTPUT_CONTINUOUS; - - switch (output->state) { - case VFE_OUTPUT_SINGLE: - vfe_output_frame_drop(vfe, output, 2); - break; - case VFE_OUTPUT_CONTINUOUS: - vfe_output_frame_drop(vfe, output, 3); - break; - default: - vfe_output_frame_drop(vfe, output, 0); - break; - } - - vfe_output_init_addrs(vfe, output, 1); - } - - spin_unlock_irqrestore(&vfe->output_lock, flags); -} - -/* - * vfe_isr_wm_done - Process write master done interrupt - * @vfe: VFE Device - * @wm: Write master id - */ -static void vfe_isr_wm_done(struct vfe_device *vfe, u8 wm) -{ - struct camss_buffer *ready_buf; - struct vfe_output *output; - dma_addr_t *new_addr; - unsigned long flags; - u32 active_index; - u64 ts = ktime_get_ns(); - unsigned int i; - - active_index = vfe_wm_get_ping_pong_status(vfe, wm); - - spin_lock_irqsave(&vfe->output_lock, flags); - - if (vfe->wm_output_map[wm] == VFE_LINE_NONE) { - dev_err_ratelimited(to_device(vfe), - "Received wm done for unmapped index\n"); - goto out_unlock; - } - output = &vfe->line[vfe->wm_output_map[wm]].output; - - if (output->active_buf == active_index) { - dev_err_ratelimited(to_device(vfe), - "Active buffer mismatch!\n"); - goto out_unlock; - } - output->active_buf = active_index; - - ready_buf = output->buf[!active_index]; - if (!ready_buf) { - dev_err_ratelimited(to_device(vfe), - "Missing ready buf %d %d!\n", - !active_index, output->state); - goto out_unlock; - } - - ready_buf->vb.vb2_buf.timestamp = ts; - ready_buf->vb.sequence = output->sequence++; - - /* Get next buffer */ - output->buf[!active_index] = vfe_buf_get_pending(output); - if (!output->buf[!active_index]) { - /* No next buffer - set same address */ - new_addr = ready_buf->addr; - vfe_buf_update_wm_on_last(vfe, output); - } else { - new_addr = output->buf[!active_index]->addr; - vfe_buf_update_wm_on_next(vfe, output); - } - - if (active_index) - for (i = 0; i < output->wm_num; i++) - vfe_wm_set_ping_addr(vfe, output->wm_idx[i], - new_addr[i]); - else - for (i = 0; i < output->wm_num; i++) - vfe_wm_set_pong_addr(vfe, output->wm_idx[i], - new_addr[i]); - - spin_unlock_irqrestore(&vfe->output_lock, flags); - - if (output->state == VFE_OUTPUT_STOPPING) - output->last_buffer = ready_buf; - else - vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); - - return; - -out_unlock: - spin_unlock_irqrestore(&vfe->output_lock, flags); -} - -/* - * vfe_isr_wm_done - Process composite image done interrupt - * @vfe: VFE Device - * @comp: Composite image id - */ -static void vfe_isr_comp_done(struct vfe_device *vfe, u8 comp) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) - if (vfe->wm_output_map[i] == VFE_LINE_PIX) { - vfe_isr_wm_done(vfe, i); - break; - } -} - -/* - * vfe_isr - ISPIF module interrupt handler - * @irq: Interrupt line - * @dev: VFE device - * - * Return IRQ_HANDLED on success - */ -static irqreturn_t vfe_isr(int irq, void *dev) -{ - struct vfe_device *vfe = dev; - u32 value0, value1; - u32 violation; - int i, j; - - value0 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_0); - value1 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_1); - - writel_relaxed(value0, vfe->base + VFE_0_IRQ_CLEAR_0); - writel_relaxed(value1, vfe->base + VFE_0_IRQ_CLEAR_1); - - wmb(); - writel_relaxed(VFE_0_IRQ_CMD_GLOBAL_CLEAR, vfe->base + VFE_0_IRQ_CMD); - - if (value0 & VFE_0_IRQ_STATUS_0_RESET_ACK) - complete(&vfe->reset_complete); - - if (value1 & VFE_0_IRQ_STATUS_1_VIOLATION) { - violation = readl_relaxed(vfe->base + VFE_0_VIOLATION_STATUS); - dev_err_ratelimited(to_device(vfe), - "VFE: violation = 0x%08x\n", violation); - } - - if (value1 & VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK) { - complete(&vfe->halt_complete); - writel_relaxed(0x0, vfe->base + VFE_0_BUS_BDG_CMD); - } - - for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) - if (value0 & VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(i)) - vfe_isr_reg_update(vfe, i); - - if (value0 & VFE_0_IRQ_STATUS_0_CAMIF_SOF) - vfe_isr_sof(vfe, VFE_LINE_PIX); - - for (i = VFE_LINE_RDI0; i <= VFE_LINE_RDI2; i++) - if (value1 & VFE_0_IRQ_STATUS_1_RDIn_SOF(i)) - vfe_isr_sof(vfe, i); - - for (i = 0; i < MSM_VFE_COMPOSITE_IRQ_NUM; i++) - if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(i)) { - vfe_isr_comp_done(vfe, i); - for (j = 0; j < ARRAY_SIZE(vfe->wm_output_map); j++) - if (vfe->wm_output_map[j] == VFE_LINE_PIX) - value0 &= ~VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(j); - } - - for (i = 0; i < MSM_VFE_IMAGE_MASTERS_NUM; i++) - if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(i)) - vfe_isr_wm_done(vfe, i); - - return IRQ_HANDLED; -} - -/* - * vfe_set_clock_rates - Calculate and set clock rates on VFE module - * @vfe: VFE device - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_set_clock_rates(struct vfe_device *vfe) -{ - struct device *dev = to_device(vfe); - u32 pixel_clock[MSM_VFE_LINE_NUM]; - int i, j; - int ret; - - for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) { - ret = camss_get_pixel_clock(&vfe->line[i].subdev.entity, - &pixel_clock[i]); - if (ret) - pixel_clock[i] = 0; - } - - for (i = 0; i < vfe->nclocks; i++) { - struct camss_clock *clock = &vfe->clock[i]; - - if (!strcmp(clock->name, "camss_vfe_vfe")) { - u64 min_rate = 0; - long rate; - - for (j = VFE_LINE_RDI0; j <= VFE_LINE_PIX; j++) { - u32 tmp; - u8 bpp; - - if (j == VFE_LINE_PIX) { - tmp = pixel_clock[j]; - } else { - bpp = vfe_get_bpp(vfe->line[j]. - fmt[MSM_VFE_PAD_SINK].code); - tmp = pixel_clock[j] * bpp / 64; - } - - if (min_rate < tmp) - min_rate = tmp; - } - - camss_add_clock_margin(&min_rate); - - for (j = 0; j < clock->nfreqs; j++) - if (min_rate < clock->freq[j]) - break; - - if (j == clock->nfreqs) { - dev_err(dev, - "Pixel clock is too high for VFE"); - return -EINVAL; - } - - /* if sensor pixel clock is not available */ - /* set highest possible VFE clock rate */ - if (min_rate == 0) - j = clock->nfreqs - 1; - - rate = clk_round_rate(clock->clk, clock->freq[j]); - if (rate < 0) { - dev_err(dev, "clk round rate failed: %ld\n", - rate); - return -EINVAL; - } - - ret = clk_set_rate(clock->clk, rate); - if (ret < 0) { - dev_err(dev, "clk set rate failed: %d\n", ret); - return ret; - } - } - } - - return 0; -} - -/* - * vfe_check_clock_rates - Check current clock rates on VFE module - * @vfe: VFE device - * - * Return 0 if current clock rates are suitable for a new pipeline - * or a negative error code otherwise - */ -static int vfe_check_clock_rates(struct vfe_device *vfe) -{ - u32 pixel_clock[MSM_VFE_LINE_NUM]; - int i, j; - int ret; - - for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) { - ret = camss_get_pixel_clock(&vfe->line[i].subdev.entity, - &pixel_clock[i]); - if (ret) - pixel_clock[i] = 0; - } - - for (i = 0; i < vfe->nclocks; i++) { - struct camss_clock *clock = &vfe->clock[i]; - - if (!strcmp(clock->name, "camss_vfe_vfe")) { - u64 min_rate = 0; - unsigned long rate; - - for (j = VFE_LINE_RDI0; j <= VFE_LINE_PIX; j++) { - u32 tmp; - u8 bpp; - - if (j == VFE_LINE_PIX) { - tmp = pixel_clock[j]; - } else { - bpp = vfe_get_bpp(vfe->line[j]. - fmt[MSM_VFE_PAD_SINK].code); - tmp = pixel_clock[j] * bpp / 64; - } - - if (min_rate < tmp) - min_rate = tmp; - } - - camss_add_clock_margin(&min_rate); - - rate = clk_get_rate(clock->clk); - if (rate < min_rate) - return -EBUSY; - } - } - - return 0; -} - -/* - * vfe_get - Power up and reset VFE module - * @vfe: VFE Device - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_get(struct vfe_device *vfe) -{ - int ret; - - mutex_lock(&vfe->power_lock); - - if (vfe->power_count == 0) { - ret = vfe_set_clock_rates(vfe); - if (ret < 0) - goto error_clocks; - - ret = camss_enable_clocks(vfe->nclocks, vfe->clock, - to_device(vfe)); - if (ret < 0) - goto error_clocks; - - ret = vfe_reset(vfe); - if (ret < 0) - goto error_reset; - - vfe_reset_output_maps(vfe); - - vfe_init_outputs(vfe); - } else { - ret = vfe_check_clock_rates(vfe); - if (ret < 0) - goto error_clocks; - } - vfe->power_count++; - - mutex_unlock(&vfe->power_lock); - - return 0; - -error_reset: - camss_disable_clocks(vfe->nclocks, vfe->clock); - -error_clocks: - mutex_unlock(&vfe->power_lock); - - return ret; -} - -/* - * vfe_put - Power down VFE module - * @vfe: VFE Device - */ -static void vfe_put(struct vfe_device *vfe) -{ - mutex_lock(&vfe->power_lock); - - if (vfe->power_count == 0) { - dev_err(to_device(vfe), "vfe power off on power_count == 0\n"); - goto exit; - } else if (vfe->power_count == 1) { - if (vfe->was_streaming) { - vfe->was_streaming = 0; - vfe_halt(vfe); - } - camss_disable_clocks(vfe->nclocks, vfe->clock); - } - - vfe->power_count--; - -exit: - mutex_unlock(&vfe->power_lock); -} - -/* - * vfe_video_pad_to_line - Get pointer to VFE line by media pad - * @pad: Media pad - * - * Return pointer to vfe line structure - */ -static struct vfe_line *vfe_video_pad_to_line(struct media_pad *pad) -{ - struct media_pad *vfe_pad; - struct v4l2_subdev *subdev; - - vfe_pad = media_entity_remote_pad(pad); - if (vfe_pad == NULL) - return NULL; - - subdev = media_entity_to_v4l2_subdev(vfe_pad->entity); - - return container_of(subdev, struct vfe_line, subdev); -} - -/* - * vfe_queue_buffer - Add empty buffer - * @vid: Video device structure - * @buf: Buffer to be enqueued - * - * Add an empty buffer - depending on the current number of buffers it will be - * put in pending buffer queue or directly given to the hardware to be filled. - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_queue_buffer(struct camss_video *vid, - struct camss_buffer *buf) -{ - struct vfe_device *vfe = &vid->camss->vfe; - struct vfe_line *line; - struct vfe_output *output; - unsigned long flags; - - line = vfe_video_pad_to_line(&vid->pad); - if (!line) { - dev_err(to_device(vfe), "Can not queue buffer\n"); - return -1; - } - output = &line->output; - - spin_lock_irqsave(&vfe->output_lock, flags); - - vfe_buf_update_wm_on_new(vfe, output, buf); - - spin_unlock_irqrestore(&vfe->output_lock, flags); - - return 0; -} - -/* - * vfe_flush_buffers - Return all vb2 buffers - * @vid: Video device structure - * @state: vb2 buffer state of the returned buffers - * - * Return all buffers to vb2. This includes queued pending buffers (still - * unused) and any buffers given to the hardware but again still not used. - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_flush_buffers(struct camss_video *vid, - enum vb2_buffer_state state) -{ - struct vfe_device *vfe = &vid->camss->vfe; - struct vfe_line *line; - struct vfe_output *output; - unsigned long flags; - - line = vfe_video_pad_to_line(&vid->pad); - if (!line) { - dev_err(to_device(vfe), "Can not flush buffers\n"); - return -1; - } - output = &line->output; - - spin_lock_irqsave(&vfe->output_lock, flags); - - vfe_buf_flush_pending(output, state); - - if (output->buf[0]) - vb2_buffer_done(&output->buf[0]->vb.vb2_buf, state); - - if (output->buf[1]) - vb2_buffer_done(&output->buf[1]->vb.vb2_buf, state); - - if (output->last_buffer) { - vb2_buffer_done(&output->last_buffer->vb.vb2_buf, state); - output->last_buffer = NULL; - } - - spin_unlock_irqrestore(&vfe->output_lock, flags); - - return 0; -} - -/* - * vfe_set_power - Power on/off VFE module - * @sd: VFE V4L2 subdevice - * @on: Requested power state - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_set_power(struct v4l2_subdev *sd, int on) -{ - struct vfe_line *line = v4l2_get_subdevdata(sd); - struct vfe_device *vfe = to_vfe(line); - int ret; - - if (on) { - u32 hw_version; - - ret = vfe_get(vfe); - if (ret < 0) - return ret; - - hw_version = readl_relaxed(vfe->base + VFE_0_HW_VERSION); - dev_dbg(to_device(vfe), - "VFE HW Version = 0x%08x\n", hw_version); - } else { - vfe_put(vfe); - } - - return 0; -} - -/* - * vfe_set_stream - Enable/disable streaming on VFE module - * @sd: VFE V4L2 subdevice - * @enable: Requested streaming state - * - * Main configuration of VFE module is triggered here. - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_set_stream(struct v4l2_subdev *sd, int enable) -{ - struct vfe_line *line = v4l2_get_subdevdata(sd); - struct vfe_device *vfe = to_vfe(line); - int ret; - - if (enable) { - ret = vfe_enable(line); - if (ret < 0) - dev_err(to_device(vfe), - "Failed to enable vfe outputs\n"); - } else { - ret = vfe_disable(line); - if (ret < 0) - dev_err(to_device(vfe), - "Failed to disable vfe outputs\n"); - } - - return ret; -} - -/* - * __vfe_get_format - Get pointer to format structure - * @line: VFE line - * @cfg: V4L2 subdev pad configuration - * @pad: pad from which format is requested - * @which: TRY or ACTIVE format - * - * Return pointer to TRY or ACTIVE format structure - */ -static struct v4l2_mbus_framefmt * -__vfe_get_format(struct vfe_line *line, - struct v4l2_subdev_pad_config *cfg, - unsigned int pad, - enum v4l2_subdev_format_whence which) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return v4l2_subdev_get_try_format(&line->subdev, cfg, pad); - - return &line->fmt[pad]; -} - -/* - * __vfe_get_compose - Get pointer to compose selection structure - * @line: VFE line - * @cfg: V4L2 subdev pad configuration - * @which: TRY or ACTIVE format - * - * Return pointer to TRY or ACTIVE compose rectangle structure - */ -static struct v4l2_rect * -__vfe_get_compose(struct vfe_line *line, - struct v4l2_subdev_pad_config *cfg, - enum v4l2_subdev_format_whence which) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return v4l2_subdev_get_try_compose(&line->subdev, cfg, - MSM_VFE_PAD_SINK); - - return &line->compose; -} - -/* - * __vfe_get_crop - Get pointer to crop selection structure - * @line: VFE line - * @cfg: V4L2 subdev pad configuration - * @which: TRY or ACTIVE format - * - * Return pointer to TRY or ACTIVE crop rectangle structure - */ -static struct v4l2_rect * -__vfe_get_crop(struct vfe_line *line, - struct v4l2_subdev_pad_config *cfg, - enum v4l2_subdev_format_whence which) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return v4l2_subdev_get_try_crop(&line->subdev, cfg, - MSM_VFE_PAD_SRC); - - return &line->crop; -} - -/* - * vfe_try_format - Handle try format by pad subdev method - * @line: VFE line - * @cfg: V4L2 subdev pad configuration - * @pad: pad on which format is requested - * @fmt: pointer to v4l2 format structure - * @which: wanted subdev format - */ -static void vfe_try_format(struct vfe_line *line, - struct v4l2_subdev_pad_config *cfg, - unsigned int pad, - struct v4l2_mbus_framefmt *fmt, - enum v4l2_subdev_format_whence which) -{ - unsigned int i; - u32 code; - - switch (pad) { - case MSM_VFE_PAD_SINK: - /* Set format on sink pad */ - - for (i = 0; i < ARRAY_SIZE(vfe_formats); i++) - if (fmt->code == vfe_formats[i].code) - break; - - /* If not found, use UYVY as default */ - if (i >= ARRAY_SIZE(vfe_formats)) - fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; - - fmt->width = clamp_t(u32, fmt->width, 1, 8191); - fmt->height = clamp_t(u32, fmt->height, 1, 8191); - - fmt->field = V4L2_FIELD_NONE; - fmt->colorspace = V4L2_COLORSPACE_SRGB; - - break; - - case MSM_VFE_PAD_SRC: - /* Set and return a format same as sink pad */ - - code = fmt->code; - - *fmt = *__vfe_get_format(line, cfg, MSM_VFE_PAD_SINK, - which); - - if (line->id == VFE_LINE_PIX) { - struct v4l2_rect *rect; - - rect = __vfe_get_crop(line, cfg, which); - - fmt->width = rect->width; - fmt->height = rect->height; - - switch (fmt->code) { - case MEDIA_BUS_FMT_YUYV8_2X8: - if (code == MEDIA_BUS_FMT_YUYV8_1_5X8) - fmt->code = MEDIA_BUS_FMT_YUYV8_1_5X8; - else - fmt->code = MEDIA_BUS_FMT_YUYV8_2X8; - break; - case MEDIA_BUS_FMT_YVYU8_2X8: - if (code == MEDIA_BUS_FMT_YVYU8_1_5X8) - fmt->code = MEDIA_BUS_FMT_YVYU8_1_5X8; - else - fmt->code = MEDIA_BUS_FMT_YVYU8_2X8; - break; - case MEDIA_BUS_FMT_UYVY8_2X8: - default: - if (code == MEDIA_BUS_FMT_UYVY8_1_5X8) - fmt->code = MEDIA_BUS_FMT_UYVY8_1_5X8; - else - fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; - break; - case MEDIA_BUS_FMT_VYUY8_2X8: - if (code == MEDIA_BUS_FMT_VYUY8_1_5X8) - fmt->code = MEDIA_BUS_FMT_VYUY8_1_5X8; - else - fmt->code = MEDIA_BUS_FMT_VYUY8_2X8; - break; - } - } - - break; - } - - fmt->colorspace = V4L2_COLORSPACE_SRGB; -} - -/* - * vfe_try_compose - Handle try compose selection by pad subdev method - * @line: VFE line - * @cfg: V4L2 subdev pad configuration - * @rect: pointer to v4l2 rect structure - * @which: wanted subdev format - */ -static void vfe_try_compose(struct vfe_line *line, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_rect *rect, - enum v4l2_subdev_format_whence which) -{ - struct v4l2_mbus_framefmt *fmt; - - fmt = __vfe_get_format(line, cfg, MSM_VFE_PAD_SINK, which); - - if (rect->width > fmt->width) - rect->width = fmt->width; - - if (rect->height > fmt->height) - rect->height = fmt->height; - - if (fmt->width > rect->width * SCALER_RATIO_MAX) - rect->width = (fmt->width + SCALER_RATIO_MAX - 1) / - SCALER_RATIO_MAX; - - rect->width &= ~0x1; - - if (fmt->height > rect->height * SCALER_RATIO_MAX) - rect->height = (fmt->height + SCALER_RATIO_MAX - 1) / - SCALER_RATIO_MAX; - - if (rect->width < 16) - rect->width = 16; - - if (rect->height < 4) - rect->height = 4; -} - -/* - * vfe_try_crop - Handle try crop selection by pad subdev method - * @line: VFE line - * @cfg: V4L2 subdev pad configuration - * @rect: pointer to v4l2 rect structure - * @which: wanted subdev format - */ -static void vfe_try_crop(struct vfe_line *line, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_rect *rect, - enum v4l2_subdev_format_whence which) -{ - struct v4l2_rect *compose; - - compose = __vfe_get_compose(line, cfg, which); - - if (rect->width > compose->width) - rect->width = compose->width; - - if (rect->width + rect->left > compose->width) - rect->left = compose->width - rect->width; - - if (rect->height > compose->height) - rect->height = compose->height; - - if (rect->height + rect->top > compose->height) - rect->top = compose->height - rect->height; - - /* wm in line based mode writes multiple of 16 horizontally */ - rect->left += (rect->width & 0xf) >> 1; - rect->width &= ~0xf; - - if (rect->width < 16) { - rect->left = 0; - rect->width = 16; - } - - if (rect->height < 4) { - rect->top = 0; - rect->height = 4; - } -} - -/* - * vfe_enum_mbus_code - Handle pixel format enumeration - * @sd: VFE V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @code: pointer to v4l2_subdev_mbus_code_enum structure - * - * return -EINVAL or zero on success - */ -static int vfe_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_mbus_code_enum *code) -{ - struct vfe_line *line = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - if (code->pad == MSM_VFE_PAD_SINK) { - if (code->index >= ARRAY_SIZE(vfe_formats)) - return -EINVAL; - - code->code = vfe_formats[code->index].code; - } else { - if (code->index > 0) - return -EINVAL; - - format = __vfe_get_format(line, cfg, MSM_VFE_PAD_SINK, - code->which); - - code->code = format->code; - } - - return 0; -} - -/* - * vfe_enum_frame_size - Handle frame size enumeration - * @sd: VFE V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fse: pointer to v4l2_subdev_frame_size_enum structure - * - * Return -EINVAL or zero on success - */ -static int vfe_enum_frame_size(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_frame_size_enum *fse) -{ - struct vfe_line *line = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt format; - - if (fse->index != 0) - return -EINVAL; - - format.code = fse->code; - format.width = 1; - format.height = 1; - vfe_try_format(line, cfg, fse->pad, &format, fse->which); - fse->min_width = format.width; - fse->min_height = format.height; - - if (format.code != fse->code) - return -EINVAL; - - format.code = fse->code; - format.width = -1; - format.height = -1; - vfe_try_format(line, cfg, fse->pad, &format, fse->which); - fse->max_width = format.width; - fse->max_height = format.height; - - return 0; -} - -/* - * vfe_get_format - Handle get format by pads subdev method - * @sd: VFE V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fmt: pointer to v4l2 subdev format structure - * - * Return -EINVAL or zero on success - */ -static int vfe_get_format(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct vfe_line *line = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - format = __vfe_get_format(line, cfg, fmt->pad, fmt->which); - if (format == NULL) - return -EINVAL; - - fmt->format = *format; - - return 0; -} - -static int vfe_set_selection(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_selection *sel); - -/* - * vfe_set_format - Handle set format by pads subdev method - * @sd: VFE V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @fmt: pointer to v4l2 subdev format structure - * - * Return -EINVAL or zero on success - */ -static int vfe_set_format(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct vfe_line *line = v4l2_get_subdevdata(sd); - struct v4l2_mbus_framefmt *format; - - format = __vfe_get_format(line, cfg, fmt->pad, fmt->which); - if (format == NULL) - return -EINVAL; - - vfe_try_format(line, cfg, fmt->pad, &fmt->format, fmt->which); - *format = fmt->format; - - if (fmt->pad == MSM_VFE_PAD_SINK) { - struct v4l2_subdev_selection sel = { 0 }; - int ret; - - /* Propagate the format from sink to source */ - format = __vfe_get_format(line, cfg, MSM_VFE_PAD_SRC, - fmt->which); - - *format = fmt->format; - vfe_try_format(line, cfg, MSM_VFE_PAD_SRC, format, - fmt->which); - - if (line->id != VFE_LINE_PIX) - return 0; - - /* Reset sink pad compose selection */ - sel.which = fmt->which; - sel.pad = MSM_VFE_PAD_SINK; - sel.target = V4L2_SEL_TGT_COMPOSE; - sel.r.width = fmt->format.width; - sel.r.height = fmt->format.height; - ret = vfe_set_selection(sd, cfg, &sel); - if (ret < 0) - return ret; - } - - return 0; -} - -/* - * vfe_get_selection - Handle get selection by pads subdev method - * @sd: VFE V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @sel: pointer to v4l2 subdev selection structure - * - * Return -EINVAL or zero on success - */ -static int vfe_get_selection(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_selection *sel) -{ - struct vfe_line *line = v4l2_get_subdevdata(sd); - struct v4l2_subdev_format fmt = { 0 }; - struct v4l2_rect *rect; - int ret; - - if (line->id != VFE_LINE_PIX) - return -EINVAL; - - if (sel->pad == MSM_VFE_PAD_SINK) - switch (sel->target) { - case V4L2_SEL_TGT_COMPOSE_BOUNDS: - fmt.pad = sel->pad; - fmt.which = sel->which; - ret = vfe_get_format(sd, cfg, &fmt); - if (ret < 0) - return ret; - - sel->r.left = 0; - sel->r.top = 0; - sel->r.width = fmt.format.width; - sel->r.height = fmt.format.height; - break; - case V4L2_SEL_TGT_COMPOSE: - rect = __vfe_get_compose(line, cfg, sel->which); - if (rect == NULL) - return -EINVAL; - - sel->r = *rect; - break; - default: - return -EINVAL; - } - else if (sel->pad == MSM_VFE_PAD_SRC) - switch (sel->target) { - case V4L2_SEL_TGT_CROP_BOUNDS: - rect = __vfe_get_compose(line, cfg, sel->which); - if (rect == NULL) - return -EINVAL; - - sel->r.left = rect->left; - sel->r.top = rect->top; - sel->r.width = rect->width; - sel->r.height = rect->height; - break; - case V4L2_SEL_TGT_CROP: - rect = __vfe_get_crop(line, cfg, sel->which); - if (rect == NULL) - return -EINVAL; - - sel->r = *rect; - break; - default: - return -EINVAL; - } - - return 0; -} - -/* - * vfe_set_selection - Handle set selection by pads subdev method - * @sd: VFE V4L2 subdevice - * @cfg: V4L2 subdev pad configuration - * @sel: pointer to v4l2 subdev selection structure - * - * Return -EINVAL or zero on success - */ -static int vfe_set_selection(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_selection *sel) -{ - struct vfe_line *line = v4l2_get_subdevdata(sd); - struct v4l2_rect *rect; - int ret; - - if (line->id != VFE_LINE_PIX) - return -EINVAL; - - if (sel->target == V4L2_SEL_TGT_COMPOSE && - sel->pad == MSM_VFE_PAD_SINK) { - struct v4l2_subdev_selection crop = { 0 }; - - rect = __vfe_get_compose(line, cfg, sel->which); - if (rect == NULL) - return -EINVAL; - - vfe_try_compose(line, cfg, &sel->r, sel->which); - *rect = sel->r; - - /* Reset source crop selection */ - crop.which = sel->which; - crop.pad = MSM_VFE_PAD_SRC; - crop.target = V4L2_SEL_TGT_CROP; - crop.r = *rect; - ret = vfe_set_selection(sd, cfg, &crop); - } else if (sel->target == V4L2_SEL_TGT_CROP && - sel->pad == MSM_VFE_PAD_SRC) { - struct v4l2_subdev_format fmt = { 0 }; - - rect = __vfe_get_crop(line, cfg, sel->which); - if (rect == NULL) - return -EINVAL; - - vfe_try_crop(line, cfg, &sel->r, sel->which); - *rect = sel->r; - - /* Reset source pad format width and height */ - fmt.which = sel->which; - fmt.pad = MSM_VFE_PAD_SRC; - ret = vfe_get_format(sd, cfg, &fmt); - if (ret < 0) - return ret; - - fmt.format.width = rect->width; - fmt.format.height = rect->height; - ret = vfe_set_format(sd, cfg, &fmt); - } else { - ret = -EINVAL; - } - - return ret; -} - -/* - * vfe_init_formats - Initialize formats on all pads - * @sd: VFE V4L2 subdevice - * @fh: V4L2 subdev file handle - * - * Initialize all pad formats with default values. - * - * Return 0 on success or a negative error code otherwise - */ -static int vfe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct v4l2_subdev_format format = { - .pad = MSM_VFE_PAD_SINK, - .which = fh ? V4L2_SUBDEV_FORMAT_TRY : - V4L2_SUBDEV_FORMAT_ACTIVE, - .format = { - .code = MEDIA_BUS_FMT_UYVY8_2X8, - .width = 1920, - .height = 1080 - } - }; - - return vfe_set_format(sd, fh ? fh->pad : NULL, &format); -} - -/* - * msm_vfe_subdev_init - Initialize VFE device structure and resources - * @vfe: VFE device - * @res: VFE module resources table - * - * Return 0 on success or a negative error code otherwise - */ -int msm_vfe_subdev_init(struct vfe_device *vfe, const struct resources *res) -{ - struct device *dev = to_device(vfe); - struct platform_device *pdev = to_platform_device(dev); - struct resource *r; - struct camss *camss = to_camss(vfe); - int i, j; - int ret; - - /* Memory */ - - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[0]); - vfe->base = devm_ioremap_resource(dev, r); - if (IS_ERR(vfe->base)) { - dev_err(dev, "could not map memory\n"); - return PTR_ERR(vfe->base); - } - - /* Interrupt */ - - r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, - res->interrupt[0]); - if (!r) { - dev_err(dev, "missing IRQ\n"); - return -EINVAL; - } - - vfe->irq = r->start; - snprintf(vfe->irq_name, sizeof(vfe->irq_name), "%s_%s%d", - dev_name(dev), MSM_VFE_NAME, vfe->id); - ret = devm_request_irq(dev, vfe->irq, vfe_isr, - IRQF_TRIGGER_RISING, vfe->irq_name, vfe); - if (ret < 0) { - dev_err(dev, "request_irq failed: %d\n", ret); - return ret; - } - - /* Clocks */ - - vfe->nclocks = 0; - while (res->clock[vfe->nclocks]) - vfe->nclocks++; - - vfe->clock = devm_kcalloc(dev, vfe->nclocks, sizeof(*vfe->clock), - GFP_KERNEL); - if (!vfe->clock) - return -ENOMEM; - - for (i = 0; i < vfe->nclocks; i++) { - struct camss_clock *clock = &vfe->clock[i]; - - clock->clk = devm_clk_get(dev, res->clock[i]); - if (IS_ERR(clock->clk)) - return PTR_ERR(clock->clk); - - clock->name = res->clock[i]; - - clock->nfreqs = 0; - while (res->clock_rate[i][clock->nfreqs]) - clock->nfreqs++; - - if (!clock->nfreqs) { - clock->freq = NULL; - continue; - } - - clock->freq = devm_kcalloc(dev, - clock->nfreqs, - sizeof(*clock->freq), - GFP_KERNEL); - if (!clock->freq) - return -ENOMEM; - - for (j = 0; j < clock->nfreqs; j++) - clock->freq[j] = res->clock_rate[i][j]; - } - - mutex_init(&vfe->power_lock); - vfe->power_count = 0; - - mutex_init(&vfe->stream_lock); - vfe->stream_count = 0; - - spin_lock_init(&vfe->output_lock); - - vfe->id = 0; - vfe->reg_update = 0; - - for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) { - vfe->line[i].video_out.type = - V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - vfe->line[i].video_out.camss = camss; - vfe->line[i].id = i; - init_completion(&vfe->line[i].output.sof); - init_completion(&vfe->line[i].output.reg_update); - } - - init_completion(&vfe->reset_complete); - init_completion(&vfe->halt_complete); - - return 0; -} - -/* - * msm_vfe_get_vfe_id - Get VFE HW module id - * @entity: Pointer to VFE media entity structure - * @id: Return CSID HW module id here - */ -void msm_vfe_get_vfe_id(struct media_entity *entity, u8 *id) -{ - struct v4l2_subdev *sd; - struct vfe_line *line; - struct vfe_device *vfe; - - sd = media_entity_to_v4l2_subdev(entity); - line = v4l2_get_subdevdata(sd); - vfe = to_vfe(line); - - *id = vfe->id; -} - -/* - * msm_vfe_get_vfe_line_id - Get VFE line id by media entity - * @entity: Pointer to VFE media entity structure - * @id: Return VFE line id here - */ -void msm_vfe_get_vfe_line_id(struct media_entity *entity, enum vfe_line_id *id) -{ - struct v4l2_subdev *sd; - struct vfe_line *line; - - sd = media_entity_to_v4l2_subdev(entity); - line = v4l2_get_subdevdata(sd); - - *id = line->id; -} - -/* - * vfe_link_setup - Setup VFE connections - * @entity: Pointer to media entity structure - * @local: Pointer to local pad - * @remote: Pointer to remote pad - * @flags: Link flags - * - * Return 0 on success - */ -static int vfe_link_setup(struct media_entity *entity, - const struct media_pad *local, - const struct media_pad *remote, u32 flags) -{ - if (flags & MEDIA_LNK_FL_ENABLED) - if (media_entity_remote_pad(local)) - return -EBUSY; - - return 0; -} - -static const struct v4l2_subdev_core_ops vfe_core_ops = { - .s_power = vfe_set_power, -}; - -static const struct v4l2_subdev_video_ops vfe_video_ops = { - .s_stream = vfe_set_stream, -}; - -static const struct v4l2_subdev_pad_ops vfe_pad_ops = { - .enum_mbus_code = vfe_enum_mbus_code, - .enum_frame_size = vfe_enum_frame_size, - .get_fmt = vfe_get_format, - .set_fmt = vfe_set_format, - .get_selection = vfe_get_selection, - .set_selection = vfe_set_selection, -}; - -static const struct v4l2_subdev_ops vfe_v4l2_ops = { - .core = &vfe_core_ops, - .video = &vfe_video_ops, - .pad = &vfe_pad_ops, -}; - -static const struct v4l2_subdev_internal_ops vfe_v4l2_internal_ops = { - .open = vfe_init_formats, -}; - -static const struct media_entity_operations vfe_media_ops = { - .link_setup = vfe_link_setup, - .link_validate = v4l2_subdev_link_validate, -}; - -static const struct camss_video_ops camss_vfe_video_ops = { - .queue_buffer = vfe_queue_buffer, - .flush_buffers = vfe_flush_buffers, -}; - -void msm_vfe_stop_streaming(struct vfe_device *vfe) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(vfe->line); i++) - msm_video_stop_streaming(&vfe->line[i].video_out); -} - -/* - * msm_vfe_register_entities - Register subdev node for VFE module - * @vfe: VFE device - * @v4l2_dev: V4L2 device - * - * Initialize and register a subdev node for the VFE module. Then - * call msm_video_register() to register the video device node which - * will be connected to this subdev node. Then actually create the - * media link between them. - * - * Return 0 on success or a negative error code otherwise - */ -int msm_vfe_register_entities(struct vfe_device *vfe, - struct v4l2_device *v4l2_dev) -{ - struct device *dev = to_device(vfe); - struct v4l2_subdev *sd; - struct media_pad *pads; - struct camss_video *video_out; - int ret; - int i; - - for (i = 0; i < ARRAY_SIZE(vfe->line); i++) { - char name[32]; - - sd = &vfe->line[i].subdev; - pads = vfe->line[i].pads; - video_out = &vfe->line[i].video_out; - - v4l2_subdev_init(sd, &vfe_v4l2_ops); - sd->internal_ops = &vfe_v4l2_internal_ops; - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - if (i == VFE_LINE_PIX) - snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s", - MSM_VFE_NAME, vfe->id, "pix"); - else - snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s%d", - MSM_VFE_NAME, vfe->id, "rdi", i); - - v4l2_set_subdevdata(sd, &vfe->line[i]); - - ret = vfe_init_formats(sd, NULL); - if (ret < 0) { - dev_err(dev, "Failed to init format: %d\n", ret); - goto error_init; - } - - pads[MSM_VFE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[MSM_VFE_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; - - sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; - sd->entity.ops = &vfe_media_ops; - ret = media_entity_pads_init(&sd->entity, MSM_VFE_PADS_NUM, - pads); - if (ret < 0) { - dev_err(dev, "Failed to init media entity: %d\n", ret); - goto error_init; - } - - ret = v4l2_device_register_subdev(v4l2_dev, sd); - if (ret < 0) { - dev_err(dev, "Failed to register subdev: %d\n", ret); - goto error_reg_subdev; - } - - video_out->ops = &camss_vfe_video_ops; - video_out->bpl_alignment = 8; - video_out->line_based = 0; - if (i == VFE_LINE_PIX) { - video_out->bpl_alignment = 16; - video_out->line_based = 1; - } - snprintf(name, ARRAY_SIZE(name), "%s%d_%s%d", - MSM_VFE_NAME, vfe->id, "video", i); - ret = msm_video_register(video_out, v4l2_dev, name, - i == VFE_LINE_PIX ? 1 : 0); - if (ret < 0) { - dev_err(dev, "Failed to register video node: %d\n", - ret); - goto error_reg_video; - } - - ret = media_create_pad_link( - &sd->entity, MSM_VFE_PAD_SRC, - &video_out->vdev.entity, 0, - MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); - if (ret < 0) { - dev_err(dev, "Failed to link %s->%s entities: %d\n", - sd->entity.name, video_out->vdev.entity.name, - ret); - goto error_link; - } - } - - return 0; - -error_link: - msm_video_unregister(video_out); - -error_reg_video: - v4l2_device_unregister_subdev(sd); - -error_reg_subdev: - media_entity_cleanup(&sd->entity); - -error_init: - for (i--; i >= 0; i--) { - sd = &vfe->line[i].subdev; - video_out = &vfe->line[i].video_out; - - msm_video_unregister(video_out); - v4l2_device_unregister_subdev(sd); - media_entity_cleanup(&sd->entity); - } - - return ret; -} - -/* - * msm_vfe_unregister_entities - Unregister VFE module subdev node - * @vfe: VFE device - */ -void msm_vfe_unregister_entities(struct vfe_device *vfe) -{ - int i; - - mutex_destroy(&vfe->power_lock); - mutex_destroy(&vfe->stream_lock); - - for (i = 0; i < ARRAY_SIZE(vfe->line); i++) { - struct v4l2_subdev *sd = &vfe->line[i].subdev; - struct camss_video *video_out = &vfe->line[i].video_out; - - msm_video_unregister(video_out); - v4l2_device_unregister_subdev(sd); - media_entity_cleanup(&sd->entity); - } -} diff --git a/drivers/media/platform/qcom/camss-8x16/camss-vfe.h b/drivers/media/platform/qcom/camss-8x16/camss-vfe.h deleted file mode 100644 index 53d5b66a9dfb..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-vfe.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * camss-vfe.h - * - * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module - * - * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#ifndef QC_MSM_CAMSS_VFE_H -#define QC_MSM_CAMSS_VFE_H - -#include -#include -#include -#include -#include - -#include "camss-video.h" - -#define MSM_VFE_PAD_SINK 0 -#define MSM_VFE_PAD_SRC 1 -#define MSM_VFE_PADS_NUM 2 - -#define MSM_VFE_LINE_NUM 4 -#define MSM_VFE_IMAGE_MASTERS_NUM 7 -#define MSM_VFE_COMPOSITE_IRQ_NUM 4 - -#define MSM_VFE_VFE0_UB_SIZE 1023 -#define MSM_VFE_VFE0_UB_SIZE_RDI (MSM_VFE_VFE0_UB_SIZE / 3) -#define MSM_VFE_VFE1_UB_SIZE 1535 -#define MSM_VFE_VFE1_UB_SIZE_RDI (MSM_VFE_VFE1_UB_SIZE / 3) - -enum vfe_output_state { - VFE_OUTPUT_OFF, - VFE_OUTPUT_RESERVED, - VFE_OUTPUT_SINGLE, - VFE_OUTPUT_CONTINUOUS, - VFE_OUTPUT_IDLE, - VFE_OUTPUT_STOPPING -}; - -enum vfe_line_id { - VFE_LINE_NONE = -1, - VFE_LINE_RDI0 = 0, - VFE_LINE_RDI1 = 1, - VFE_LINE_RDI2 = 2, - VFE_LINE_PIX = 3 -}; - -struct vfe_output { - u8 wm_num; - u8 wm_idx[3]; - - int active_buf; - struct camss_buffer *buf[2]; - struct camss_buffer *last_buffer; - struct list_head pending_bufs; - - unsigned int drop_update_idx; - - enum vfe_output_state state; - unsigned int sequence; - int wait_sof; - int wait_reg_update; - struct completion sof; - struct completion reg_update; -}; - -struct vfe_line { - enum vfe_line_id id; - struct v4l2_subdev subdev; - struct media_pad pads[MSM_VFE_PADS_NUM]; - struct v4l2_mbus_framefmt fmt[MSM_VFE_PADS_NUM]; - struct v4l2_rect compose; - struct v4l2_rect crop; - struct camss_video video_out; - struct vfe_output output; -}; - -struct vfe_device { - u8 id; - void __iomem *base; - u32 irq; - char irq_name[30]; - struct camss_clock *clock; - int nclocks; - struct completion reset_complete; - struct completion halt_complete; - struct mutex power_lock; - int power_count; - struct mutex stream_lock; - int stream_count; - spinlock_t output_lock; - enum vfe_line_id wm_output_map[MSM_VFE_IMAGE_MASTERS_NUM]; - struct vfe_line line[MSM_VFE_LINE_NUM]; - u32 reg_update; - u8 was_streaming; -}; - -struct resources; - -int msm_vfe_subdev_init(struct vfe_device *vfe, const struct resources *res); - -int msm_vfe_register_entities(struct vfe_device *vfe, - struct v4l2_device *v4l2_dev); - -void msm_vfe_unregister_entities(struct vfe_device *vfe); - -void msm_vfe_get_vfe_id(struct media_entity *entity, u8 *id); -void msm_vfe_get_vfe_line_id(struct media_entity *entity, enum vfe_line_id *id); - -void msm_vfe_stop_streaming(struct vfe_device *vfe); - -#endif /* QC_MSM_CAMSS_VFE_H */ diff --git a/drivers/media/platform/qcom/camss-8x16/camss-video.c b/drivers/media/platform/qcom/camss-8x16/camss-video.c deleted file mode 100644 index ffaa2849e0c1..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-video.c +++ /dev/null @@ -1,859 +0,0 @@ -/* - * camss-video.c - * - * Qualcomm MSM Camera Subsystem - V4L2 device node - * - * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#include -#include -#include -#include -#include -#include -#include - -#include "camss-video.h" -#include "camss.h" - -struct fract { - u8 numerator; - u8 denominator; -}; - -/* - * struct camss_format_info - ISP media bus format information - * @code: V4L2 media bus format code - * @pixelformat: V4L2 pixel format FCC identifier - * @planes: Number of planes - * @hsub: Horizontal subsampling (for each plane) - * @vsub: Vertical subsampling (for each plane) - * @bpp: Bits per pixel when stored in memory (for each plane) - */ -struct camss_format_info { - u32 code; - u32 pixelformat; - u8 planes; - struct fract hsub[3]; - struct fract vsub[3]; - unsigned int bpp[3]; -}; - -static const struct camss_format_info formats_rdi[] = { - { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_UYVY, 1, - { { 1, 1 } }, { { 1, 1 } }, { 16 } }, - { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_VYUY, 1, - { { 1, 1 } }, { { 1, 1 } }, { 16 } }, - { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_YUYV, 1, - { { 1, 1 } }, { { 1, 1 } }, { 16 } }, - { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_YVYU, 1, - { { 1, 1 } }, { { 1, 1 } }, { 16 } }, - { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_PIX_FMT_SBGGR8, 1, - { { 1, 1 } }, { { 1, 1 } }, { 8 } }, - { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_PIX_FMT_SGBRG8, 1, - { { 1, 1 } }, { { 1, 1 } }, { 8 } }, - { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_PIX_FMT_SGRBG8, 1, - { { 1, 1 } }, { { 1, 1 } }, { 8 } }, - { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_PIX_FMT_SRGGB8, 1, - { { 1, 1 } }, { { 1, 1 } }, { 8 } }, - { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_PIX_FMT_SBGGR10P, 1, - { { 1, 1 } }, { { 1, 1 } }, { 10 } }, - { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_PIX_FMT_SGBRG10P, 1, - { { 1, 1 } }, { { 1, 1 } }, { 10 } }, - { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_PIX_FMT_SGRBG10P, 1, - { { 1, 1 } }, { { 1, 1 } }, { 10 } }, - { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_PIX_FMT_SRGGB10P, 1, - { { 1, 1 } }, { { 1, 1 } }, { 10 } }, - { MEDIA_BUS_FMT_SBGGR12_1X12, V4L2_PIX_FMT_SBGGR12P, 1, - { { 1, 1 } }, { { 1, 1 } }, { 12 } }, - { MEDIA_BUS_FMT_SGBRG12_1X12, V4L2_PIX_FMT_SGBRG12P, 1, - { { 1, 1 } }, { { 1, 1 } }, { 12 } }, - { MEDIA_BUS_FMT_SGRBG12_1X12, V4L2_PIX_FMT_SGRBG12P, 1, - { { 1, 1 } }, { { 1, 1 } }, { 12 } }, - { MEDIA_BUS_FMT_SRGGB12_1X12, V4L2_PIX_FMT_SRGGB12P, 1, - { { 1, 1 } }, { { 1, 1 } }, { 12 } }, -}; - -static const struct camss_format_info formats_pix[] = { - { MEDIA_BUS_FMT_YUYV8_1_5X8, V4L2_PIX_FMT_NV12, 1, - { { 1, 1 } }, { { 2, 3 } }, { 8 } }, - { MEDIA_BUS_FMT_YVYU8_1_5X8, V4L2_PIX_FMT_NV12, 1, - { { 1, 1 } }, { { 2, 3 } }, { 8 } }, - { MEDIA_BUS_FMT_UYVY8_1_5X8, V4L2_PIX_FMT_NV12, 1, - { { 1, 1 } }, { { 2, 3 } }, { 8 } }, - { MEDIA_BUS_FMT_VYUY8_1_5X8, V4L2_PIX_FMT_NV12, 1, - { { 1, 1 } }, { { 2, 3 } }, { 8 } }, - { MEDIA_BUS_FMT_YUYV8_1_5X8, V4L2_PIX_FMT_NV21, 1, - { { 1, 1 } }, { { 2, 3 } }, { 8 } }, - { MEDIA_BUS_FMT_YVYU8_1_5X8, V4L2_PIX_FMT_NV21, 1, - { { 1, 1 } }, { { 2, 3 } }, { 8 } }, - { MEDIA_BUS_FMT_UYVY8_1_5X8, V4L2_PIX_FMT_NV21, 1, - { { 1, 1 } }, { { 2, 3 } }, { 8 } }, - { MEDIA_BUS_FMT_VYUY8_1_5X8, V4L2_PIX_FMT_NV21, 1, - { { 1, 1 } }, { { 2, 3 } }, { 8 } }, - { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_NV16, 1, - { { 1, 1 } }, { { 1, 2 } }, { 8 } }, - { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_NV16, 1, - { { 1, 1 } }, { { 1, 2 } }, { 8 } }, - { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_NV16, 1, - { { 1, 1 } }, { { 1, 2 } }, { 8 } }, - { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_NV16, 1, - { { 1, 1 } }, { { 1, 2 } }, { 8 } }, - { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_NV61, 1, - { { 1, 1 } }, { { 1, 2 } }, { 8 } }, - { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_NV61, 1, - { { 1, 1 } }, { { 1, 2 } }, { 8 } }, - { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_NV61, 1, - { { 1, 1 } }, { { 1, 2 } }, { 8 } }, - { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_NV61, 1, - { { 1, 1 } }, { { 1, 2 } }, { 8 } }, -}; - -/* ----------------------------------------------------------------------------- - * Helper functions - */ - -static int video_find_format(u32 code, u32 pixelformat, - const struct camss_format_info *formats, - unsigned int nformats) -{ - int i; - - for (i = 0; i < nformats; i++) { - if (formats[i].code == code && - formats[i].pixelformat == pixelformat) - return i; - } - - for (i = 0; i < nformats; i++) - if (formats[i].code == code) - return i; - - WARN_ON(1); - - return -EINVAL; -} - -/* - * video_mbus_to_pix_mp - Convert v4l2_mbus_framefmt to v4l2_pix_format_mplane - * @mbus: v4l2_mbus_framefmt format (input) - * @pix: v4l2_pix_format_mplane format (output) - * @f: a pointer to formats array element to be used for the conversion - * @alignment: bytesperline alignment value - * - * Fill the output pix structure with information from the input mbus format. - * - * Return 0 on success or a negative error code otherwise - */ -static int video_mbus_to_pix_mp(const struct v4l2_mbus_framefmt *mbus, - struct v4l2_pix_format_mplane *pix, - const struct camss_format_info *f, - unsigned int alignment) -{ - unsigned int i; - u32 bytesperline; - - memset(pix, 0, sizeof(*pix)); - v4l2_fill_pix_format_mplane(pix, mbus); - pix->pixelformat = f->pixelformat; - pix->num_planes = f->planes; - for (i = 0; i < pix->num_planes; i++) { - bytesperline = pix->width / f->hsub[i].numerator * - f->hsub[i].denominator * f->bpp[i] / 8; - bytesperline = ALIGN(bytesperline, alignment); - pix->plane_fmt[i].bytesperline = bytesperline; - pix->plane_fmt[i].sizeimage = pix->height / - f->vsub[i].numerator * f->vsub[i].denominator * - bytesperline; - } - - return 0; -} - -static struct v4l2_subdev *video_remote_subdev(struct camss_video *video, - u32 *pad) -{ - struct media_pad *remote; - - remote = media_entity_remote_pad(&video->pad); - - if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) - return NULL; - - if (pad) - *pad = remote->index; - - return media_entity_to_v4l2_subdev(remote->entity); -} - -static int video_get_subdev_format(struct camss_video *video, - struct v4l2_format *format) -{ - struct v4l2_subdev_format fmt; - struct v4l2_subdev *subdev; - u32 pad; - int ret; - - subdev = video_remote_subdev(video, &pad); - if (subdev == NULL) - return -EPIPE; - - fmt.pad = pad; - fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; - - ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); - if (ret) - return ret; - - ret = video_find_format(fmt.format.code, - format->fmt.pix_mp.pixelformat, - video->formats, video->nformats); - if (ret < 0) - return ret; - - format->type = video->type; - - return video_mbus_to_pix_mp(&fmt.format, &format->fmt.pix_mp, - &video->formats[ret], video->bpl_alignment); -} - -/* ----------------------------------------------------------------------------- - * Video queue operations - */ - -static int video_queue_setup(struct vb2_queue *q, - unsigned int *num_buffers, unsigned int *num_planes, - unsigned int sizes[], struct device *alloc_devs[]) -{ - struct camss_video *video = vb2_get_drv_priv(q); - const struct v4l2_pix_format_mplane *format = - &video->active_fmt.fmt.pix_mp; - unsigned int i; - - if (*num_planes) { - if (*num_planes != format->num_planes) - return -EINVAL; - - for (i = 0; i < *num_planes; i++) - if (sizes[i] < format->plane_fmt[i].sizeimage) - return -EINVAL; - - return 0; - } - - *num_planes = format->num_planes; - - for (i = 0; i < *num_planes; i++) - sizes[i] = format->plane_fmt[i].sizeimage; - - return 0; -} - -static int video_buf_init(struct vb2_buffer *vb) -{ - struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); - struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); - struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer, - vb); - const struct v4l2_pix_format_mplane *format = - &video->active_fmt.fmt.pix_mp; - struct sg_table *sgt; - unsigned int i; - - for (i = 0; i < format->num_planes; i++) { - sgt = vb2_dma_sg_plane_desc(vb, i); - if (!sgt) - return -EFAULT; - - buffer->addr[i] = sg_dma_address(sgt->sgl); - } - - if (format->pixelformat == V4L2_PIX_FMT_NV12 || - format->pixelformat == V4L2_PIX_FMT_NV21 || - format->pixelformat == V4L2_PIX_FMT_NV16 || - format->pixelformat == V4L2_PIX_FMT_NV61) - buffer->addr[1] = buffer->addr[0] + - format->plane_fmt[0].bytesperline * - format->height; - - return 0; -} - -static int video_buf_prepare(struct vb2_buffer *vb) -{ - struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); - struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); - const struct v4l2_pix_format_mplane *format = - &video->active_fmt.fmt.pix_mp; - unsigned int i; - - for (i = 0; i < format->num_planes; i++) { - if (format->plane_fmt[i].sizeimage > vb2_plane_size(vb, i)) - return -EINVAL; - - vb2_set_plane_payload(vb, i, format->plane_fmt[i].sizeimage); - } - - vbuf->field = V4L2_FIELD_NONE; - - return 0; -} - -static void video_buf_queue(struct vb2_buffer *vb) -{ - struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); - struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); - struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer, - vb); - - video->ops->queue_buffer(video, buffer); -} - -static int video_check_format(struct camss_video *video) -{ - struct v4l2_pix_format_mplane *pix = &video->active_fmt.fmt.pix_mp; - struct v4l2_format format; - struct v4l2_pix_format_mplane *sd_pix = &format.fmt.pix_mp; - int ret; - - sd_pix->pixelformat = pix->pixelformat; - ret = video_get_subdev_format(video, &format); - if (ret < 0) - return ret; - - if (pix->pixelformat != sd_pix->pixelformat || - pix->height != sd_pix->height || - pix->width != sd_pix->width || - pix->num_planes != sd_pix->num_planes || - pix->field != format.fmt.pix_mp.field) - return -EPIPE; - - return 0; -} - -static int video_start_streaming(struct vb2_queue *q, unsigned int count) -{ - struct camss_video *video = vb2_get_drv_priv(q); - struct video_device *vdev = &video->vdev; - struct media_entity *entity; - struct media_pad *pad; - struct v4l2_subdev *subdev; - int ret; - - ret = media_pipeline_start(&vdev->entity, &video->pipe); - if (ret < 0) - return ret; - - ret = video_check_format(video); - if (ret < 0) - goto error; - - entity = &vdev->entity; - while (1) { - pad = &entity->pads[0]; - if (!(pad->flags & MEDIA_PAD_FL_SINK)) - break; - - pad = media_entity_remote_pad(pad); - if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) - break; - - entity = pad->entity; - subdev = media_entity_to_v4l2_subdev(entity); - - ret = v4l2_subdev_call(subdev, video, s_stream, 1); - if (ret < 0 && ret != -ENOIOCTLCMD) - goto error; - } - - return 0; - -error: - media_pipeline_stop(&vdev->entity); - - video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED); - - return ret; -} - -static void video_stop_streaming(struct vb2_queue *q) -{ - struct camss_video *video = vb2_get_drv_priv(q); - struct video_device *vdev = &video->vdev; - struct media_entity *entity; - struct media_pad *pad; - struct v4l2_subdev *subdev; - - entity = &vdev->entity; - while (1) { - pad = &entity->pads[0]; - if (!(pad->flags & MEDIA_PAD_FL_SINK)) - break; - - pad = media_entity_remote_pad(pad); - if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) - break; - - entity = pad->entity; - subdev = media_entity_to_v4l2_subdev(entity); - - v4l2_subdev_call(subdev, video, s_stream, 0); - } - - media_pipeline_stop(&vdev->entity); - - video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR); -} - -static const struct vb2_ops msm_video_vb2_q_ops = { - .queue_setup = video_queue_setup, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, - .buf_init = video_buf_init, - .buf_prepare = video_buf_prepare, - .buf_queue = video_buf_queue, - .start_streaming = video_start_streaming, - .stop_streaming = video_stop_streaming, -}; - -/* ----------------------------------------------------------------------------- - * V4L2 ioctls - */ - -static int video_querycap(struct file *file, void *fh, - struct v4l2_capability *cap) -{ - struct camss_video *video = video_drvdata(file); - - strlcpy(cap->driver, "qcom-camss", sizeof(cap->driver)); - strlcpy(cap->card, "Qualcomm Camera Subsystem", sizeof(cap->card)); - snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", - dev_name(video->camss->dev)); - - return 0; -} - -static int video_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) -{ - struct camss_video *video = video_drvdata(file); - int i, j, k; - - if (f->type != video->type) - return -EINVAL; - - if (f->index >= video->nformats) - return -EINVAL; - - /* find index "i" of "k"th unique pixelformat in formats array */ - k = -1; - for (i = 0; i < video->nformats; i++) { - for (j = 0; j < i; j++) { - if (video->formats[i].pixelformat == - video->formats[j].pixelformat) - break; - } - - if (j == i) - k++; - - if (k == f->index) - break; - } - - if (k < f->index) - return -EINVAL; - - f->pixelformat = video->formats[i].pixelformat; - - return 0; -} - -static int video_g_fmt(struct file *file, void *fh, struct v4l2_format *f) -{ - struct camss_video *video = video_drvdata(file); - - *f = video->active_fmt; - - return 0; -} - -static int __video_try_fmt(struct camss_video *video, struct v4l2_format *f) -{ - struct v4l2_pix_format_mplane *pix_mp; - const struct camss_format_info *fi; - struct v4l2_plane_pix_format *p; - u32 bytesperline[3] = { 0 }; - u32 sizeimage[3] = { 0 }; - u32 width, height; - u32 bpl, lines; - int i, j; - - pix_mp = &f->fmt.pix_mp; - - if (video->line_based) - for (i = 0; i < pix_mp->num_planes && i < 3; i++) { - p = &pix_mp->plane_fmt[i]; - bytesperline[i] = clamp_t(u32, p->bytesperline, - 1, 65528); - sizeimage[i] = clamp_t(u32, p->sizeimage, - bytesperline[i], - bytesperline[i] * 4096); - } - - for (j = 0; j < video->nformats; j++) - if (pix_mp->pixelformat == video->formats[j].pixelformat) - break; - - if (j == video->nformats) - j = 0; /* default format */ - - fi = &video->formats[j]; - width = pix_mp->width; - height = pix_mp->height; - - memset(pix_mp, 0, sizeof(*pix_mp)); - - pix_mp->pixelformat = fi->pixelformat; - pix_mp->width = clamp_t(u32, width, 1, 8191); - pix_mp->height = clamp_t(u32, height, 1, 8191); - pix_mp->num_planes = fi->planes; - for (i = 0; i < pix_mp->num_planes; i++) { - bpl = pix_mp->width / fi->hsub[i].numerator * - fi->hsub[i].denominator * fi->bpp[i] / 8; - bpl = ALIGN(bpl, video->bpl_alignment); - pix_mp->plane_fmt[i].bytesperline = bpl; - pix_mp->plane_fmt[i].sizeimage = pix_mp->height / - fi->vsub[i].numerator * fi->vsub[i].denominator * bpl; - } - - pix_mp->field = V4L2_FIELD_NONE; - pix_mp->colorspace = V4L2_COLORSPACE_SRGB; - pix_mp->flags = 0; - pix_mp->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace); - pix_mp->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, - pix_mp->colorspace, pix_mp->ycbcr_enc); - pix_mp->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix_mp->colorspace); - - if (video->line_based) - for (i = 0; i < pix_mp->num_planes; i++) { - p = &pix_mp->plane_fmt[i]; - p->bytesperline = clamp_t(u32, p->bytesperline, - 1, 65528); - p->sizeimage = clamp_t(u32, p->sizeimage, - p->bytesperline, - p->bytesperline * 4096); - lines = p->sizeimage / p->bytesperline; - - if (p->bytesperline < bytesperline[i]) - p->bytesperline = ALIGN(bytesperline[i], 8); - - if (p->sizeimage < p->bytesperline * lines) - p->sizeimage = p->bytesperline * lines; - - if (p->sizeimage < sizeimage[i]) - p->sizeimage = sizeimage[i]; - } - - return 0; -} - -static int video_try_fmt(struct file *file, void *fh, struct v4l2_format *f) -{ - struct camss_video *video = video_drvdata(file); - - return __video_try_fmt(video, f); -} - -static int video_s_fmt(struct file *file, void *fh, struct v4l2_format *f) -{ - struct camss_video *video = video_drvdata(file); - int ret; - - if (vb2_is_busy(&video->vb2_q)) - return -EBUSY; - - ret = __video_try_fmt(video, f); - if (ret < 0) - return ret; - - video->active_fmt = *f; - - return 0; -} - -static int video_enum_input(struct file *file, void *fh, - struct v4l2_input *input) -{ - if (input->index > 0) - return -EINVAL; - - strlcpy(input->name, "camera", sizeof(input->name)); - input->type = V4L2_INPUT_TYPE_CAMERA; - - return 0; -} - -static int video_g_input(struct file *file, void *fh, unsigned int *input) -{ - *input = 0; - - return 0; -} - -static int video_s_input(struct file *file, void *fh, unsigned int input) -{ - return input == 0 ? 0 : -EINVAL; -} - -static const struct v4l2_ioctl_ops msm_vid_ioctl_ops = { - .vidioc_querycap = video_querycap, - .vidioc_enum_fmt_vid_cap_mplane = video_enum_fmt, - .vidioc_g_fmt_vid_cap_mplane = video_g_fmt, - .vidioc_s_fmt_vid_cap_mplane = video_s_fmt, - .vidioc_try_fmt_vid_cap_mplane = video_try_fmt, - .vidioc_reqbufs = vb2_ioctl_reqbufs, - .vidioc_querybuf = vb2_ioctl_querybuf, - .vidioc_qbuf = vb2_ioctl_qbuf, - .vidioc_expbuf = vb2_ioctl_expbuf, - .vidioc_dqbuf = vb2_ioctl_dqbuf, - .vidioc_create_bufs = vb2_ioctl_create_bufs, - .vidioc_prepare_buf = vb2_ioctl_prepare_buf, - .vidioc_streamon = vb2_ioctl_streamon, - .vidioc_streamoff = vb2_ioctl_streamoff, - .vidioc_enum_input = video_enum_input, - .vidioc_g_input = video_g_input, - .vidioc_s_input = video_s_input, -}; - -/* ----------------------------------------------------------------------------- - * V4L2 file operations - */ - -static int video_open(struct file *file) -{ - struct video_device *vdev = video_devdata(file); - struct camss_video *video = video_drvdata(file); - struct v4l2_fh *vfh; - int ret; - - mutex_lock(&video->lock); - - vfh = kzalloc(sizeof(*vfh), GFP_KERNEL); - if (vfh == NULL) { - ret = -ENOMEM; - goto error_alloc; - } - - v4l2_fh_init(vfh, vdev); - v4l2_fh_add(vfh); - - file->private_data = vfh; - - ret = v4l2_pipeline_pm_use(&vdev->entity, 1); - if (ret < 0) { - dev_err(video->camss->dev, "Failed to power up pipeline: %d\n", - ret); - goto error_pm_use; - } - - mutex_unlock(&video->lock); - - return 0; - -error_pm_use: - v4l2_fh_release(file); - -error_alloc: - mutex_unlock(&video->lock); - - return ret; -} - -static int video_release(struct file *file) -{ - struct video_device *vdev = video_devdata(file); - - vb2_fop_release(file); - - v4l2_pipeline_pm_use(&vdev->entity, 0); - - file->private_data = NULL; - - return 0; -} - -static const struct v4l2_file_operations msm_vid_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, - .open = video_open, - .release = video_release, - .poll = vb2_fop_poll, - .mmap = vb2_fop_mmap, - .read = vb2_fop_read, -}; - -/* ----------------------------------------------------------------------------- - * CAMSS video core - */ - -static void msm_video_release(struct video_device *vdev) -{ - struct camss_video *video = video_get_drvdata(vdev); - - media_entity_cleanup(&vdev->entity); - - mutex_destroy(&video->q_lock); - mutex_destroy(&video->lock); - - if (atomic_dec_and_test(&video->camss->ref_count)) - camss_delete(video->camss); -} - -/* - * msm_video_init_format - Helper function to initialize format - * @video: struct camss_video - * - * Initialize pad format with default value. - * - * Return 0 on success or a negative error code otherwise - */ -static int msm_video_init_format(struct camss_video *video) -{ - int ret; - struct v4l2_format format = { - .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, - .fmt.pix_mp = { - .width = 1920, - .height = 1080, - .pixelformat = video->formats[0].pixelformat, - }, - }; - - ret = __video_try_fmt(video, &format); - if (ret < 0) - return ret; - - video->active_fmt = format; - - return 0; -} - -/* - * msm_video_register - Register a video device node - * @video: struct camss_video - * @v4l2_dev: V4L2 device - * @name: name to be used for the video device node - * - * Initialize and register a video device node to a V4L2 device. Also - * initialize the vb2 queue. - * - * Return 0 on success or a negative error code otherwise - */ - -int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev, - const char *name, int is_pix) -{ - struct media_pad *pad = &video->pad; - struct video_device *vdev; - struct vb2_queue *q; - int ret; - - vdev = &video->vdev; - - mutex_init(&video->q_lock); - - q = &video->vb2_q; - q->drv_priv = video; - q->mem_ops = &vb2_dma_sg_memops; - q->ops = &msm_video_vb2_q_ops; - q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - q->io_modes = VB2_DMABUF | VB2_MMAP | VB2_READ; - q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - q->buf_struct_size = sizeof(struct camss_buffer); - q->dev = video->camss->dev; - q->lock = &video->q_lock; - ret = vb2_queue_init(q); - if (ret < 0) { - dev_err(v4l2_dev->dev, "Failed to init vb2 queue: %d\n", ret); - goto error_vb2_init; - } - - pad->flags = MEDIA_PAD_FL_SINK; - ret = media_entity_pads_init(&vdev->entity, 1, pad); - if (ret < 0) { - dev_err(v4l2_dev->dev, "Failed to init video entity: %d\n", - ret); - goto error_media_init; - } - - mutex_init(&video->lock); - - video->formats = formats_rdi; - video->nformats = ARRAY_SIZE(formats_rdi); - if (is_pix) { - video->formats = formats_pix; - video->nformats = ARRAY_SIZE(formats_pix); - } - - ret = msm_video_init_format(video); - if (ret < 0) { - dev_err(v4l2_dev->dev, "Failed to init format: %d\n", ret); - goto error_video_register; - } - - vdev->fops = &msm_vid_fops; - vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING | - V4L2_CAP_READWRITE; - vdev->ioctl_ops = &msm_vid_ioctl_ops; - vdev->release = msm_video_release; - vdev->v4l2_dev = v4l2_dev; - vdev->vfl_dir = VFL_DIR_RX; - vdev->queue = &video->vb2_q; - vdev->lock = &video->lock; - strlcpy(vdev->name, name, sizeof(vdev->name)); - - ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); - if (ret < 0) { - dev_err(v4l2_dev->dev, "Failed to register video device: %d\n", - ret); - goto error_video_register; - } - - video_set_drvdata(vdev, video); - atomic_inc(&video->camss->ref_count); - - return 0; - -error_video_register: - media_entity_cleanup(&vdev->entity); - mutex_destroy(&video->lock); -error_media_init: - vb2_queue_release(&video->vb2_q); -error_vb2_init: - mutex_destroy(&video->q_lock); - - return ret; -} - -void msm_video_stop_streaming(struct camss_video *video) -{ - if (vb2_is_streaming(&video->vb2_q)) - vb2_queue_release(&video->vb2_q); -} - -void msm_video_unregister(struct camss_video *video) -{ - atomic_inc(&video->camss->ref_count); - video_unregister_device(&video->vdev); - atomic_dec(&video->camss->ref_count); -} diff --git a/drivers/media/platform/qcom/camss-8x16/camss-video.h b/drivers/media/platform/qcom/camss-8x16/camss-video.h deleted file mode 100644 index 38bd1f2eec54..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss-video.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * camss-video.h - * - * Qualcomm MSM Camera Subsystem - V4L2 device node - * - * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#ifndef QC_MSM_CAMSS_VIDEO_H -#define QC_MSM_CAMSS_VIDEO_H - -#include -#include -#include -#include -#include -#include -#include -#include - -struct camss_buffer { - struct vb2_v4l2_buffer vb; - dma_addr_t addr[3]; - struct list_head queue; -}; - -struct camss_video; - -struct camss_video_ops { - int (*queue_buffer)(struct camss_video *vid, struct camss_buffer *buf); - int (*flush_buffers)(struct camss_video *vid, - enum vb2_buffer_state state); -}; - -struct camss_format_info; - -struct camss_video { - struct camss *camss; - struct vb2_queue vb2_q; - struct video_device vdev; - struct media_pad pad; - struct v4l2_format active_fmt; - enum v4l2_buf_type type; - struct media_pipeline pipe; - const struct camss_video_ops *ops; - struct mutex lock; - struct mutex q_lock; - unsigned int bpl_alignment; - unsigned int line_based; - const struct camss_format_info *formats; - unsigned int nformats; -}; - -void msm_video_stop_streaming(struct camss_video *video); - -int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev, - const char *name, int is_pix); - -void msm_video_unregister(struct camss_video *video); - -#endif /* QC_MSM_CAMSS_VIDEO_H */ diff --git a/drivers/media/platform/qcom/camss-8x16/camss.c b/drivers/media/platform/qcom/camss-8x16/camss.c deleted file mode 100644 index 23fda6207a23..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss.c +++ /dev/null @@ -1,751 +0,0 @@ -/* - * camss.c - * - * Qualcomm MSM Camera Subsystem - Core - * - * Copyright (c) 2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "camss.h" - -#define CAMSS_CLOCK_MARGIN_NUMERATOR 105 -#define CAMSS_CLOCK_MARGIN_DENOMINATOR 100 - -static const struct resources csiphy_res[] = { - /* CSIPHY0 */ - { - .regulator = { NULL }, - .clock = { "camss_top_ahb", "ispif_ahb", - "camss_ahb", "csiphy0_timer" }, - .clock_rate = { { 0 }, - { 0 }, - { 0 }, - { 100000000, 200000000 } }, - .reg = { "csiphy0", "csiphy0_clk_mux" }, - .interrupt = { "csiphy0" } - }, - - /* CSIPHY1 */ - { - .regulator = { NULL }, - .clock = { "camss_top_ahb", "ispif_ahb", - "camss_ahb", "csiphy1_timer" }, - .clock_rate = { { 0 }, - { 0 }, - { 0 }, - { 100000000, 200000000 } }, - .reg = { "csiphy1", "csiphy1_clk_mux" }, - .interrupt = { "csiphy1" } - } -}; - -static const struct resources csid_res[] = { - /* CSID0 */ - { - .regulator = { "vdda" }, - .clock = { "camss_top_ahb", "ispif_ahb", - "csi0_ahb", "camss_ahb", - "csi0", "csi0_phy", "csi0_pix", "csi0_rdi" }, - .clock_rate = { { 0 }, - { 0 }, - { 0 }, - { 0 }, - { 100000000, 200000000 }, - { 0 }, - { 0 }, - { 0 } }, - .reg = { "csid0" }, - .interrupt = { "csid0" } - }, - - /* CSID1 */ - { - .regulator = { "vdda" }, - .clock = { "camss_top_ahb", "ispif_ahb", - "csi1_ahb", "camss_ahb", - "csi1", "csi1_phy", "csi1_pix", "csi1_rdi" }, - .clock_rate = { { 0 }, - { 0 }, - { 0 }, - { 0 }, - { 100000000, 200000000 }, - { 0 }, - { 0 }, - { 0 } }, - .reg = { "csid1" }, - .interrupt = { "csid1" } - }, -}; - -static const struct resources_ispif ispif_res = { - /* ISPIF */ - .clock = { "camss_top_ahb", "camss_ahb", "ispif_ahb", - "csi0", "csi0_pix", "csi0_rdi", - "csi1", "csi1_pix", "csi1_rdi" }, - .clock_for_reset = { "camss_vfe_vfe", "camss_csi_vfe" }, - .reg = { "ispif", "csi_clk_mux" }, - .interrupt = "ispif" - -}; - -static const struct resources vfe_res = { - /* VFE0 */ - .regulator = { NULL }, - .clock = { "camss_top_ahb", "camss_vfe_vfe", "camss_csi_vfe", - "iface", "bus", "camss_ahb" }, - .clock_rate = { { 0 }, - { 50000000, 80000000, 100000000, 160000000, - 177780000, 200000000, 266670000, 320000000, - 400000000, 465000000 }, - { 0 }, - { 0 }, - { 0 }, - { 0 }, - { 0 }, - { 0 }, - { 0 } }, - .reg = { "vfe0" }, - .interrupt = { "vfe0" } -}; - -/* - * camss_add_clock_margin - Add margin to clock frequency rate - * @rate: Clock frequency rate - * - * When making calculations with physical clock frequency values - * some safety margin must be added. Add it. - */ -inline void camss_add_clock_margin(u64 *rate) -{ - *rate *= CAMSS_CLOCK_MARGIN_NUMERATOR; - *rate = div_u64(*rate, CAMSS_CLOCK_MARGIN_DENOMINATOR); -} - -/* - * camss_enable_clocks - Enable multiple clocks - * @nclocks: Number of clocks in clock array - * @clock: Clock array - * @dev: Device - * - * Return 0 on success or a negative error code otherwise - */ -int camss_enable_clocks(int nclocks, struct camss_clock *clock, - struct device *dev) -{ - int ret; - int i; - - for (i = 0; i < nclocks; i++) { - ret = clk_prepare_enable(clock[i].clk); - if (ret) { - dev_err(dev, "clock enable failed: %d\n", ret); - goto error; - } - } - - return 0; - -error: - for (i--; i >= 0; i--) - clk_disable_unprepare(clock[i].clk); - - return ret; -} - -/* - * camss_disable_clocks - Disable multiple clocks - * @nclocks: Number of clocks in clock array - * @clock: Clock array - */ -void camss_disable_clocks(int nclocks, struct camss_clock *clock) -{ - int i; - - for (i = nclocks - 1; i >= 0; i--) - clk_disable_unprepare(clock[i].clk); -} - -/* - * camss_find_sensor - Find a linked media entity which represents a sensor - * @entity: Media entity to start searching from - * - * Return a pointer to sensor media entity or NULL if not found - */ -static struct media_entity *camss_find_sensor(struct media_entity *entity) -{ - struct media_pad *pad; - - while (1) { - pad = &entity->pads[0]; - if (!(pad->flags & MEDIA_PAD_FL_SINK)) - return NULL; - - pad = media_entity_remote_pad(pad); - if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) - return NULL; - - entity = pad->entity; - - if (entity->function == MEDIA_ENT_F_CAM_SENSOR) - return entity; - } -} - -/* - * camss_get_pixel_clock - Get pixel clock rate from sensor - * @entity: Media entity in the current pipeline - * @pixel_clock: Received pixel clock value - * - * Return 0 on success or a negative error code otherwise - */ -int camss_get_pixel_clock(struct media_entity *entity, u32 *pixel_clock) -{ - struct media_entity *sensor; - struct v4l2_subdev *subdev; - struct v4l2_ctrl *ctrl; - - sensor = camss_find_sensor(entity); - if (!sensor) - return -ENODEV; - - subdev = media_entity_to_v4l2_subdev(sensor); - - ctrl = v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_PIXEL_RATE); - - if (!ctrl) - return -EINVAL; - - *pixel_clock = v4l2_ctrl_g_ctrl_int64(ctrl); - - return 0; -} - -/* - * camss_of_parse_endpoint_node - Parse port endpoint node - * @dev: Device - * @node: Device node to be parsed - * @csd: Parsed data from port endpoint node - * - * Return 0 on success or a negative error code on failure - */ -static int camss_of_parse_endpoint_node(struct device *dev, - struct device_node *node, - struct camss_async_subdev *csd) -{ - struct csiphy_lanes_cfg *lncfg = &csd->interface.csi2.lane_cfg; - struct v4l2_fwnode_bus_mipi_csi2 *mipi_csi2; - struct v4l2_fwnode_endpoint vep = { { 0 } }; - unsigned int i; - - v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); - - csd->interface.csiphy_id = vep.base.port; - - mipi_csi2 = &vep.bus.mipi_csi2; - lncfg->clk.pos = mipi_csi2->clock_lane; - lncfg->clk.pol = mipi_csi2->lane_polarities[0]; - lncfg->num_data = mipi_csi2->num_data_lanes; - - lncfg->data = devm_kcalloc(dev, - lncfg->num_data, sizeof(*lncfg->data), - GFP_KERNEL); - if (!lncfg->data) - return -ENOMEM; - - for (i = 0; i < lncfg->num_data; i++) { - lncfg->data[i].pos = mipi_csi2->data_lanes[i]; - lncfg->data[i].pol = mipi_csi2->lane_polarities[i + 1]; - } - - return 0; -} - -/* - * camss_of_parse_ports - Parse ports node - * @dev: Device - * @notifier: v4l2_device notifier data - * - * Return number of "port" nodes found in "ports" node - */ -static int camss_of_parse_ports(struct device *dev, - struct v4l2_async_notifier *notifier) -{ - struct device_node *node = NULL; - struct device_node *remote = NULL; - unsigned int size, i; - int ret; - - while ((node = of_graph_get_next_endpoint(dev->of_node, node))) - if (of_device_is_available(node)) - notifier->num_subdevs++; - - size = sizeof(*notifier->subdevs) * notifier->num_subdevs; - notifier->subdevs = devm_kzalloc(dev, size, GFP_KERNEL); - if (!notifier->subdevs) { - dev_err(dev, "Failed to allocate memory\n"); - return -ENOMEM; - } - - i = 0; - while ((node = of_graph_get_next_endpoint(dev->of_node, node))) { - struct camss_async_subdev *csd; - - if (!of_device_is_available(node)) - continue; - - csd = devm_kzalloc(dev, sizeof(*csd), GFP_KERNEL); - if (!csd) { - of_node_put(node); - dev_err(dev, "Failed to allocate memory\n"); - return -ENOMEM; - } - - notifier->subdevs[i++] = &csd->asd; - - ret = camss_of_parse_endpoint_node(dev, node, csd); - if (ret < 0) { - of_node_put(node); - return ret; - } - - remote = of_graph_get_remote_port_parent(node); - of_node_put(node); - - if (!remote) { - dev_err(dev, "Cannot get remote parent\n"); - return -EINVAL; - } - - csd->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; - csd->asd.match.fwnode = of_fwnode_handle(remote); - } - - return notifier->num_subdevs; -} - -/* - * camss_init_subdevices - Initialize subdev structures and resources - * @camss: CAMSS device - * - * Return 0 on success or a negative error code on failure - */ -static int camss_init_subdevices(struct camss *camss) -{ - unsigned int i; - int ret; - - for (i = 0; i < ARRAY_SIZE(camss->csiphy); i++) { - ret = msm_csiphy_subdev_init(&camss->csiphy[i], - &csiphy_res[i], i); - if (ret < 0) { - dev_err(camss->dev, - "Failed to init csiphy%d sub-device: %d\n", - i, ret); - return ret; - } - } - - for (i = 0; i < ARRAY_SIZE(camss->csid); i++) { - ret = msm_csid_subdev_init(&camss->csid[i], - &csid_res[i], i); - if (ret < 0) { - dev_err(camss->dev, - "Failed to init csid%d sub-device: %d\n", - i, ret); - return ret; - } - } - - ret = msm_ispif_subdev_init(&camss->ispif, &ispif_res); - if (ret < 0) { - dev_err(camss->dev, "Failed to init ispif sub-device: %d\n", - ret); - return ret; - } - - ret = msm_vfe_subdev_init(&camss->vfe, &vfe_res); - if (ret < 0) { - dev_err(camss->dev, "Fail to init vfe sub-device: %d\n", ret); - return ret; - } - - return 0; -} - -/* - * camss_register_entities - Register subdev nodes and create links - * @camss: CAMSS device - * - * Return 0 on success or a negative error code on failure - */ -static int camss_register_entities(struct camss *camss) -{ - int i, j; - int ret; - - for (i = 0; i < ARRAY_SIZE(camss->csiphy); i++) { - ret = msm_csiphy_register_entity(&camss->csiphy[i], - &camss->v4l2_dev); - if (ret < 0) { - dev_err(camss->dev, - "Failed to register csiphy%d entity: %d\n", - i, ret); - goto err_reg_csiphy; - } - } - - for (i = 0; i < ARRAY_SIZE(camss->csid); i++) { - ret = msm_csid_register_entity(&camss->csid[i], - &camss->v4l2_dev); - if (ret < 0) { - dev_err(camss->dev, - "Failed to register csid%d entity: %d\n", - i, ret); - goto err_reg_csid; - } - } - - ret = msm_ispif_register_entities(&camss->ispif, &camss->v4l2_dev); - if (ret < 0) { - dev_err(camss->dev, "Failed to register ispif entities: %d\n", - ret); - goto err_reg_ispif; - } - - ret = msm_vfe_register_entities(&camss->vfe, &camss->v4l2_dev); - if (ret < 0) { - dev_err(camss->dev, "Failed to register vfe entities: %d\n", - ret); - goto err_reg_vfe; - } - - for (i = 0; i < ARRAY_SIZE(camss->csiphy); i++) { - for (j = 0; j < ARRAY_SIZE(camss->csid); j++) { - ret = media_create_pad_link( - &camss->csiphy[i].subdev.entity, - MSM_CSIPHY_PAD_SRC, - &camss->csid[j].subdev.entity, - MSM_CSID_PAD_SINK, - 0); - if (ret < 0) { - dev_err(camss->dev, - "Failed to link %s->%s entities: %d\n", - camss->csiphy[i].subdev.entity.name, - camss->csid[j].subdev.entity.name, - ret); - goto err_link; - } - } - } - - for (i = 0; i < ARRAY_SIZE(camss->csid); i++) { - for (j = 0; j < ARRAY_SIZE(camss->ispif.line); j++) { - ret = media_create_pad_link( - &camss->csid[i].subdev.entity, - MSM_CSID_PAD_SRC, - &camss->ispif.line[j].subdev.entity, - MSM_ISPIF_PAD_SINK, - 0); - if (ret < 0) { - dev_err(camss->dev, - "Failed to link %s->%s entities: %d\n", - camss->csid[i].subdev.entity.name, - camss->ispif.line[j].subdev.entity.name, - ret); - goto err_link; - } - } - } - - for (i = 0; i < ARRAY_SIZE(camss->ispif.line); i++) { - for (j = 0; j < ARRAY_SIZE(camss->vfe.line); j++) { - ret = media_create_pad_link( - &camss->ispif.line[i].subdev.entity, - MSM_ISPIF_PAD_SRC, - &camss->vfe.line[j].subdev.entity, - MSM_VFE_PAD_SINK, - 0); - if (ret < 0) { - dev_err(camss->dev, - "Failed to link %s->%s entities: %d\n", - camss->ispif.line[i].subdev.entity.name, - camss->vfe.line[j].subdev.entity.name, - ret); - goto err_link; - } - } - } - - return 0; - -err_link: - msm_vfe_unregister_entities(&camss->vfe); -err_reg_vfe: - msm_ispif_unregister_entities(&camss->ispif); -err_reg_ispif: - - i = ARRAY_SIZE(camss->csid); -err_reg_csid: - for (i--; i >= 0; i--) - msm_csid_unregister_entity(&camss->csid[i]); - - i = ARRAY_SIZE(camss->csiphy); -err_reg_csiphy: - for (i--; i >= 0; i--) - msm_csiphy_unregister_entity(&camss->csiphy[i]); - - return ret; -} - -/* - * camss_unregister_entities - Unregister subdev nodes - * @camss: CAMSS device - * - * Return 0 on success or a negative error code on failure - */ -static void camss_unregister_entities(struct camss *camss) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(camss->csiphy); i++) - msm_csiphy_unregister_entity(&camss->csiphy[i]); - - for (i = 0; i < ARRAY_SIZE(camss->csid); i++) - msm_csid_unregister_entity(&camss->csid[i]); - - msm_ispif_unregister_entities(&camss->ispif); - msm_vfe_unregister_entities(&camss->vfe); -} - -static int camss_subdev_notifier_bound(struct v4l2_async_notifier *async, - struct v4l2_subdev *subdev, - struct v4l2_async_subdev *asd) -{ - struct camss *camss = container_of(async, struct camss, notifier); - struct camss_async_subdev *csd = - container_of(asd, struct camss_async_subdev, asd); - u8 id = csd->interface.csiphy_id; - struct csiphy_device *csiphy = &camss->csiphy[id]; - - csiphy->cfg.csi2 = &csd->interface.csi2; - subdev->host_priv = csiphy; - - return 0; -} - -static int camss_subdev_notifier_complete(struct v4l2_async_notifier *async) -{ - struct camss *camss = container_of(async, struct camss, notifier); - struct v4l2_device *v4l2_dev = &camss->v4l2_dev; - struct v4l2_subdev *sd; - int ret; - - list_for_each_entry(sd, &v4l2_dev->subdevs, list) { - if (sd->host_priv) { - struct media_entity *sensor = &sd->entity; - struct csiphy_device *csiphy = - (struct csiphy_device *) sd->host_priv; - struct media_entity *input = &csiphy->subdev.entity; - unsigned int i; - - for (i = 0; i < sensor->num_pads; i++) { - if (sensor->pads[i].flags & MEDIA_PAD_FL_SOURCE) - break; - } - if (i == sensor->num_pads) { - dev_err(camss->dev, - "No source pad in external entity\n"); - return -EINVAL; - } - - ret = media_create_pad_link(sensor, i, - input, MSM_CSIPHY_PAD_SINK, - MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); - if (ret < 0) { - dev_err(camss->dev, - "Failed to link %s->%s entities: %d\n", - sensor->name, input->name, ret); - return ret; - } - } - } - - ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev); - if (ret < 0) - return ret; - - return media_device_register(&camss->media_dev); -} - -static const struct v4l2_async_notifier_operations camss_subdev_notifier_ops = { - .bound = camss_subdev_notifier_bound, - .complete = camss_subdev_notifier_complete, -}; - -static const struct media_device_ops camss_media_ops = { - .link_notify = v4l2_pipeline_link_notify, -}; - -/* - * camss_probe - Probe CAMSS platform device - * @pdev: Pointer to CAMSS platform device - * - * Return 0 on success or a negative error code on failure - */ -static int camss_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct camss *camss; - int ret; - - camss = kzalloc(sizeof(*camss), GFP_KERNEL); - if (!camss) - return -ENOMEM; - - atomic_set(&camss->ref_count, 0); - camss->dev = dev; - platform_set_drvdata(pdev, camss); - - ret = camss_of_parse_ports(dev, &camss->notifier); - if (ret < 0) - return ret; - - ret = camss_init_subdevices(camss); - if (ret < 0) - return ret; - - ret = dma_set_mask_and_coherent(dev, 0xffffffff); - if (ret) - return ret; - - camss->media_dev.dev = camss->dev; - strlcpy(camss->media_dev.model, "Qualcomm Camera Subsystem", - sizeof(camss->media_dev.model)); - camss->media_dev.ops = &camss_media_ops; - media_device_init(&camss->media_dev); - - camss->v4l2_dev.mdev = &camss->media_dev; - ret = v4l2_device_register(camss->dev, &camss->v4l2_dev); - if (ret < 0) { - dev_err(dev, "Failed to register V4L2 device: %d\n", ret); - return ret; - } - - ret = camss_register_entities(camss); - if (ret < 0) - goto err_register_entities; - - if (camss->notifier.num_subdevs) { - camss->notifier.ops = &camss_subdev_notifier_ops; - - ret = v4l2_async_notifier_register(&camss->v4l2_dev, - &camss->notifier); - if (ret) { - dev_err(dev, - "Failed to register async subdev nodes: %d\n", - ret); - goto err_register_subdevs; - } - } else { - ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev); - if (ret < 0) { - dev_err(dev, "Failed to register subdev nodes: %d\n", - ret); - goto err_register_subdevs; - } - - ret = media_device_register(&camss->media_dev); - if (ret < 0) { - dev_err(dev, "Failed to register media device: %d\n", - ret); - goto err_register_subdevs; - } - } - - return 0; - -err_register_subdevs: - camss_unregister_entities(camss); -err_register_entities: - v4l2_device_unregister(&camss->v4l2_dev); - - return ret; -} - -void camss_delete(struct camss *camss) -{ - v4l2_device_unregister(&camss->v4l2_dev); - media_device_unregister(&camss->media_dev); - media_device_cleanup(&camss->media_dev); - - kfree(camss); -} - -/* - * camss_remove - Remove CAMSS platform device - * @pdev: Pointer to CAMSS platform device - * - * Always returns 0. - */ -static int camss_remove(struct platform_device *pdev) -{ - struct camss *camss = platform_get_drvdata(pdev); - - msm_vfe_stop_streaming(&camss->vfe); - - v4l2_async_notifier_unregister(&camss->notifier); - camss_unregister_entities(camss); - - if (atomic_read(&camss->ref_count) == 0) - camss_delete(camss); - - return 0; -} - -static const struct of_device_id camss_dt_match[] = { - { .compatible = "qcom,msm8916-camss" }, - { } -}; - -MODULE_DEVICE_TABLE(of, camss_dt_match); - -static struct platform_driver qcom_camss_driver = { - .probe = camss_probe, - .remove = camss_remove, - .driver = { - .name = "qcom-camss", - .of_match_table = camss_dt_match, - }, -}; - -module_platform_driver(qcom_camss_driver); - -MODULE_ALIAS("platform:qcom-camss"); -MODULE_DESCRIPTION("Qualcomm Camera Subsystem driver"); -MODULE_AUTHOR("Todor Tomov "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/camss-8x16/camss.h b/drivers/media/platform/qcom/camss-8x16/camss.h deleted file mode 100644 index 4ad223443e4b..000000000000 --- a/drivers/media/platform/qcom/camss-8x16/camss.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * camss.h - * - * Qualcomm MSM Camera Subsystem - Core - * - * Copyright (c) 2015, The Linux Foundation. All rights reserved. - * Copyright (C) 2015-2017 Linaro Ltd. - * - * 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. - */ -#ifndef QC_MSM_CAMSS_H -#define QC_MSM_CAMSS_H - -#include -#include -#include -#include -#include -#include -#include - -#include "camss-csid.h" -#include "camss-csiphy.h" -#include "camss-ispif.h" -#include "camss-vfe.h" - -#define CAMSS_CSID_NUM 2 -#define CAMSS_CSIPHY_NUM 2 - -#define to_camss(ptr_module) \ - container_of(ptr_module, struct camss, ptr_module) - -#define to_device(ptr_module) \ - (to_camss(ptr_module)->dev) - -#define module_pointer(ptr_module, index) \ - ((const struct ptr_module##_device (*)[]) &(ptr_module[-(index)])) - -#define to_camss_index(ptr_module, index) \ - container_of(module_pointer(ptr_module, index), \ - struct camss, ptr_module) - -#define to_device_index(ptr_module, index) \ - (to_camss_index(ptr_module, index)->dev) - -#define CAMSS_RES_MAX 15 - -struct resources { - char *regulator[CAMSS_RES_MAX]; - char *clock[CAMSS_RES_MAX]; - u32 clock_rate[CAMSS_RES_MAX][CAMSS_RES_MAX]; - char *reg[CAMSS_RES_MAX]; - char *interrupt[CAMSS_RES_MAX]; -}; - -struct resources_ispif { - char *clock[CAMSS_RES_MAX]; - char *clock_for_reset[CAMSS_RES_MAX]; - char *reg[CAMSS_RES_MAX]; - char *interrupt; -}; - -struct camss { - struct v4l2_device v4l2_dev; - struct v4l2_async_notifier notifier; - struct media_device media_dev; - struct device *dev; - struct csiphy_device csiphy[CAMSS_CSIPHY_NUM]; - struct csid_device csid[CAMSS_CSID_NUM]; - struct ispif_device ispif; - struct vfe_device vfe; - atomic_t ref_count; -}; - -struct camss_camera_interface { - u8 csiphy_id; - struct csiphy_csi2_cfg csi2; -}; - -struct camss_async_subdev { - struct camss_camera_interface interface; - struct v4l2_async_subdev asd; -}; - -struct camss_clock { - struct clk *clk; - const char *name; - u32 *freq; - u32 nfreqs; -}; - -void camss_add_clock_margin(u64 *rate); -int camss_enable_clocks(int nclocks, struct camss_clock *clock, - struct device *dev); -void camss_disable_clocks(int nclocks, struct camss_clock *clock); -int camss_get_pixel_clock(struct media_entity *entity, u32 *pixel_clock); -void camss_delete(struct camss *camss); - -#endif /* QC_MSM_CAMSS_H */ diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile new file mode 100644 index 000000000000..3c4024fbb768 --- /dev/null +++ b/drivers/media/platform/qcom/camss/Makefile @@ -0,0 +1,11 @@ +# Makefile for Qualcomm CAMSS driver + +qcom-camss-objs += \ + camss.o \ + camss-csid.o \ + camss-csiphy.o \ + camss-ispif.o \ + camss-vfe.o \ + camss-video.o \ + +obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c new file mode 100644 index 000000000000..39ea27b9da3b --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid.c @@ -0,0 +1,1094 @@ +/* + * camss-csid.c + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camss-csid.h" +#include "camss.h" + +#define MSM_CSID_NAME "msm_csid" + +#define CAMSS_CSID_HW_VERSION 0x0 +#define CAMSS_CSID_CORE_CTRL_0 0x004 +#define CAMSS_CSID_CORE_CTRL_1 0x008 +#define CAMSS_CSID_RST_CMD 0x00c +#define CAMSS_CSID_CID_LUT_VC_n(n) (0x010 + 0x4 * (n)) +#define CAMSS_CSID_CID_n_CFG(n) (0x020 + 0x4 * (n)) +#define CAMSS_CSID_IRQ_CLEAR_CMD 0x060 +#define CAMSS_CSID_IRQ_MASK 0x064 +#define CAMSS_CSID_IRQ_STATUS 0x068 +#define CAMSS_CSID_TG_CTRL 0x0a0 +#define CAMSS_CSID_TG_CTRL_DISABLE 0xa06436 +#define CAMSS_CSID_TG_CTRL_ENABLE 0xa06437 +#define CAMSS_CSID_TG_VC_CFG 0x0a4 +#define CAMSS_CSID_TG_VC_CFG_H_BLANKING 0x3ff +#define CAMSS_CSID_TG_VC_CFG_V_BLANKING 0x7f +#define CAMSS_CSID_TG_DT_n_CGG_0(n) (0x0ac + 0xc * (n)) +#define CAMSS_CSID_TG_DT_n_CGG_1(n) (0x0b0 + 0xc * (n)) +#define CAMSS_CSID_TG_DT_n_CGG_2(n) (0x0b4 + 0xc * (n)) + +#define DATA_TYPE_EMBEDDED_DATA_8BIT 0x12 +#define DATA_TYPE_YUV422_8BIT 0x1e +#define DATA_TYPE_RAW_6BIT 0x28 +#define DATA_TYPE_RAW_8BIT 0x2a +#define DATA_TYPE_RAW_10BIT 0x2b +#define DATA_TYPE_RAW_12BIT 0x2c + +#define DECODE_FORMAT_UNCOMPRESSED_6_BIT 0x0 +#define DECODE_FORMAT_UNCOMPRESSED_8_BIT 0x1 +#define DECODE_FORMAT_UNCOMPRESSED_10_BIT 0x2 +#define DECODE_FORMAT_UNCOMPRESSED_12_BIT 0x3 + +#define CSID_RESET_TIMEOUT_MS 500 + +struct csid_fmts { + u32 code; + u8 data_type; + u8 decode_format; + u8 bpp; + u8 spp; /* bus samples per pixel */ +}; + +static const struct csid_fmts csid_input_fmts[] = { + { + MEDIA_BUS_FMT_UYVY8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_VYUY8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_YUYV8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_YVYU8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_SBGGR8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + } +}; + +static const struct csid_fmts *csid_get_fmt_entry(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(csid_input_fmts); i++) + if (code == csid_input_fmts[i].code) + return &csid_input_fmts[i]; + + WARN(1, "Unknown format\n"); + + return &csid_input_fmts[0]; +} + +/* + * csid_isr - CSID module interrupt handler + * @irq: Interrupt line + * @dev: CSID device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t csid_isr(int irq, void *dev) +{ + struct csid_device *csid = dev; + u32 value; + + value = readl_relaxed(csid->base + CAMSS_CSID_IRQ_STATUS); + writel_relaxed(value, csid->base + CAMSS_CSID_IRQ_CLEAR_CMD); + + if ((value >> 11) & 0x1) + complete(&csid->reset_complete); + + return IRQ_HANDLED; +} + +/* + * csid_set_clock_rates - Calculate and set clock rates on CSID module + * @csiphy: CSID device + */ +static int csid_set_clock_rates(struct csid_device *csid) +{ + struct device *dev = to_device_index(csid, csid->id); + u32 pixel_clock; + int i, j; + int ret; + + ret = camss_get_pixel_clock(&csid->subdev.entity, &pixel_clock); + if (ret) + pixel_clock = 0; + + for (i = 0; i < csid->nclocks; i++) { + struct camss_clock *clock = &csid->clock[i]; + + if (!strcmp(clock->name, "csi0") || + !strcmp(clock->name, "csi1")) { + u8 bpp = csid_get_fmt_entry( + csid->fmt[MSM_CSIPHY_PAD_SINK].code)->bpp; + u8 num_lanes = csid->phy.lane_cnt; + u64 min_rate = pixel_clock * bpp / (2 * num_lanes * 4); + long rate; + + camss_add_clock_margin(&min_rate); + + for (j = 0; j < clock->nfreqs; j++) + if (min_rate < clock->freq[j]) + break; + + if (j == clock->nfreqs) { + dev_err(dev, + "Pixel clock is too high for CSID\n"); + return -EINVAL; + } + + /* if sensor pixel clock is not available */ + /* set highest possible CSID clock rate */ + if (min_rate == 0) + j = clock->nfreqs - 1; + + rate = clk_round_rate(clock->clk, clock->freq[j]); + if (rate < 0) { + dev_err(dev, "clk round rate failed: %ld\n", + rate); + return -EINVAL; + } + + ret = clk_set_rate(clock->clk, rate); + if (ret < 0) { + dev_err(dev, "clk set rate failed: %d\n", ret); + return ret; + } + } + } + + return 0; +} + +/* + * csid_reset - Trigger reset on CSID module and wait to complete + * @csid: CSID device + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_reset(struct csid_device *csid) +{ + unsigned long time; + + reinit_completion(&csid->reset_complete); + + writel_relaxed(0x7fff, csid->base + CAMSS_CSID_RST_CMD); + + time = wait_for_completion_timeout(&csid->reset_complete, + msecs_to_jiffies(CSID_RESET_TIMEOUT_MS)); + if (!time) { + dev_err(to_device_index(csid, csid->id), + "CSID reset timeout\n"); + return -EIO; + } + + return 0; +} + +/* + * csid_set_power - Power on/off CSID module + * @sd: CSID V4L2 subdevice + * @on: Requested power state + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_set_power(struct v4l2_subdev *sd, int on) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct device *dev = to_device_index(csid, csid->id); + int ret; + + if (on) { + u32 hw_version; + + ret = regulator_enable(csid->vdda); + if (ret < 0) + return ret; + + ret = csid_set_clock_rates(csid); + if (ret < 0) { + regulator_disable(csid->vdda); + return ret; + } + + ret = camss_enable_clocks(csid->nclocks, csid->clock, dev); + if (ret < 0) { + regulator_disable(csid->vdda); + return ret; + } + + enable_irq(csid->irq); + + ret = csid_reset(csid); + if (ret < 0) { + disable_irq(csid->irq); + camss_disable_clocks(csid->nclocks, csid->clock); + regulator_disable(csid->vdda); + return ret; + } + + hw_version = readl_relaxed(csid->base + CAMSS_CSID_HW_VERSION); + dev_dbg(dev, "CSID HW Version = 0x%08x\n", hw_version); + } else { + disable_irq(csid->irq); + camss_disable_clocks(csid->nclocks, csid->clock); + ret = regulator_disable(csid->vdda); + } + + return ret; +} + +/* + * csid_set_stream - Enable/disable streaming on CSID module + * @sd: CSID V4L2 subdevice + * @enable: Requested streaming state + * + * Main configuration of CSID module is also done here. + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct csid_testgen_config *tg = &csid->testgen; + u32 val; + + if (enable) { + u8 vc = 0; /* Virtual Channel 0 */ + u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */ + u8 dt, dt_shift, df; + int ret; + + ret = v4l2_ctrl_handler_setup(&csid->ctrls); + if (ret < 0) { + dev_err(to_device_index(csid, csid->id), + "could not sync v4l2 controls: %d\n", ret); + return ret; + } + + if (!tg->enabled && + !media_entity_remote_pad(&csid->pads[MSM_CSID_PAD_SINK])) + return -ENOLINK; + + dt = csid_get_fmt_entry(csid->fmt[MSM_CSID_PAD_SRC].code)-> + data_type; + + if (tg->enabled) { + /* Config Test Generator */ + struct v4l2_mbus_framefmt *f = + &csid->fmt[MSM_CSID_PAD_SRC]; + u8 bpp = csid_get_fmt_entry(f->code)->bpp; + u8 spp = csid_get_fmt_entry(f->code)->spp; + u32 num_bytes_per_line = f->width * bpp * spp / 8; + u32 num_lines = f->height; + + /* 31:24 V blank, 23:13 H blank, 3:2 num of active DT */ + /* 1:0 VC */ + val = ((CAMSS_CSID_TG_VC_CFG_V_BLANKING & 0xff) << 24) | + ((CAMSS_CSID_TG_VC_CFG_H_BLANKING & 0x7ff) << 13); + writel_relaxed(val, csid->base + CAMSS_CSID_TG_VC_CFG); + + /* 28:16 bytes per lines, 12:0 num of lines */ + val = ((num_bytes_per_line & 0x1fff) << 16) | + (num_lines & 0x1fff); + writel_relaxed(val, csid->base + + CAMSS_CSID_TG_DT_n_CGG_0(0)); + + /* 5:0 data type */ + val = dt; + writel_relaxed(val, csid->base + + CAMSS_CSID_TG_DT_n_CGG_1(0)); + + /* 2:0 output test pattern */ + val = tg->payload_mode; + writel_relaxed(val, csid->base + + CAMSS_CSID_TG_DT_n_CGG_2(0)); + } else { + struct csid_phy_config *phy = &csid->phy; + + val = phy->lane_cnt - 1; + val |= phy->lane_assign << 4; + + writel_relaxed(val, + csid->base + CAMSS_CSID_CORE_CTRL_0); + + val = phy->csiphy_id << 17; + val |= 0x9; + + writel_relaxed(val, + csid->base + CAMSS_CSID_CORE_CTRL_1); + } + + /* Config LUT */ + + dt_shift = (cid % 4) * 8; + df = csid_get_fmt_entry(csid->fmt[MSM_CSID_PAD_SINK].code)-> + decode_format; + + val = readl_relaxed(csid->base + CAMSS_CSID_CID_LUT_VC_n(vc)); + val &= ~(0xff << dt_shift); + val |= dt << dt_shift; + writel_relaxed(val, csid->base + CAMSS_CSID_CID_LUT_VC_n(vc)); + + val = (df << 4) | 0x3; + writel_relaxed(val, csid->base + CAMSS_CSID_CID_n_CFG(cid)); + + if (tg->enabled) { + val = CAMSS_CSID_TG_CTRL_ENABLE; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL); + } + } else { + if (tg->enabled) { + val = CAMSS_CSID_TG_CTRL_DISABLE; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL); + } + } + + return 0; +} + +/* + * __csid_get_format - Get pointer to format structure + * @csid: CSID device + * @cfg: V4L2 subdev pad configuration + * @pad: pad from which format is requested + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE format structure + */ +static struct v4l2_mbus_framefmt * +__csid_get_format(struct csid_device *csid, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&csid->subdev, cfg, pad); + + return &csid->fmt[pad]; +} + +/* + * csid_try_format - Handle try format by pad subdev method + * @csid: CSID device + * @cfg: V4L2 subdev pad configuration + * @pad: pad on which format is requested + * @fmt: pointer to v4l2 format structure + * @which: wanted subdev format + */ +static void csid_try_format(struct csid_device *csid, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + + switch (pad) { + case MSM_CSID_PAD_SINK: + /* Set format on sink pad */ + + for (i = 0; i < ARRAY_SIZE(csid_input_fmts); i++) + if (fmt->code == csid_input_fmts[i].code) + break; + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(csid_input_fmts)) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + break; + + case MSM_CSID_PAD_SRC: + if (csid->testgen_mode->cur.val == 0) { + /* Test generator is disabled, keep pad formats */ + /* in sync - set and return a format same as sink pad */ + struct v4l2_mbus_framefmt format; + + format = *__csid_get_format(csid, cfg, + MSM_CSID_PAD_SINK, which); + *fmt = format; + } else { + /* Test generator is enabled, set format on source*/ + /* pad to allow test generator usage */ + + for (i = 0; i < ARRAY_SIZE(csid_input_fmts); i++) + if (csid_input_fmts[i].code == fmt->code) + break; + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(csid_input_fmts)) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + } + break; + } + + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +/* + * csid_enum_mbus_code - Handle pixel format enumeration + * @sd: CSID V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int csid_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + if (code->pad == MSM_CSID_PAD_SINK) { + if (code->index >= ARRAY_SIZE(csid_input_fmts)) + return -EINVAL; + + code->code = csid_input_fmts[code->index].code; + } else { + if (csid->testgen_mode->cur.val == 0) { + if (code->index > 0) + return -EINVAL; + + format = __csid_get_format(csid, cfg, MSM_CSID_PAD_SINK, + code->which); + + code->code = format->code; + } else { + if (code->index >= ARRAY_SIZE(csid_input_fmts)) + return -EINVAL; + + code->code = csid_input_fmts[code->index].code; + } + } + + return 0; +} + +/* + * csid_enum_frame_size - Handle frame size enumeration + * @sd: CSID V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: pointer to v4l2_subdev_frame_size_enum structure + * return -EINVAL or zero on success + */ +static int csid_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + csid_try_format(csid, cfg, fse->pad, &format, fse->which); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + csid_try_format(csid, cfg, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * csid_get_format - Handle get format by pads subdev method + * @sd: CSID V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int csid_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csid_get_format(csid, cfg, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +/* + * csid_set_format - Handle set format by pads subdev method + * @sd: CSID V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int csid_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csid_get_format(csid, cfg, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + csid_try_format(csid, cfg, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == MSM_CSID_PAD_SINK) { + format = __csid_get_format(csid, cfg, MSM_CSID_PAD_SRC, + fmt->which); + + *format = fmt->format; + csid_try_format(csid, cfg, MSM_CSID_PAD_SRC, format, + fmt->which); + } + + return 0; +} + +/* + * csid_init_formats - Initialize formats on all pads + * @sd: CSID V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format = { + .pad = MSM_CSID_PAD_SINK, + .which = fh ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080 + } + }; + + return csid_set_format(sd, fh ? fh->pad : NULL, &format); +} + +static const char * const csid_test_pattern_menu[] = { + "Disabled", + "Incrementing", + "Alternating 0x55/0xAA", + "All Zeros 0x00", + "All Ones 0xFF", + "Pseudo-random Data", +}; + +/* + * csid_set_test_pattern - Set test generator's pattern mode + * @csid: CSID device + * @value: desired test pattern mode + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_set_test_pattern(struct csid_device *csid, s32 value) +{ + struct csid_testgen_config *tg = &csid->testgen; + + /* If CSID is linked to CSIPHY, do not allow to enable test generator */ + if (value && media_entity_remote_pad(&csid->pads[MSM_CSID_PAD_SINK])) + return -EBUSY; + + tg->enabled = !!value; + + switch (value) { + case 1: + tg->payload_mode = CSID_PAYLOAD_MODE_INCREMENTING; + break; + case 2: + tg->payload_mode = CSID_PAYLOAD_MODE_ALTERNATING_55_AA; + break; + case 3: + tg->payload_mode = CSID_PAYLOAD_MODE_ALL_ZEROES; + break; + case 4: + tg->payload_mode = CSID_PAYLOAD_MODE_ALL_ONES; + break; + case 5: + tg->payload_mode = CSID_PAYLOAD_MODE_RANDOM; + break; + } + + return 0; +} + +/* + * csid_s_ctrl - Handle set control subdev method + * @ctrl: pointer to v4l2 control structure + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct csid_device *csid = container_of(ctrl->handler, + struct csid_device, ctrls); + int ret = -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + ret = csid_set_test_pattern(csid, ctrl->val); + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops csid_ctrl_ops = { + .s_ctrl = csid_s_ctrl, +}; + +/* + * msm_csid_subdev_init - Initialize CSID device structure and resources + * @csid: CSID device + * @res: CSID module resources table + * @id: CSID module id + * + * Return 0 on success or a negative error code otherwise + */ +int msm_csid_subdev_init(struct csid_device *csid, + const struct resources *res, u8 id) +{ + struct device *dev = to_device_index(csid, id); + struct platform_device *pdev = to_platform_device(dev); + struct resource *r; + int i, j; + int ret; + + csid->id = id; + + /* Memory */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[0]); + csid->base = devm_ioremap_resource(dev, r); + if (IS_ERR(csid->base)) { + dev_err(dev, "could not map memory\n"); + return PTR_ERR(csid->base); + } + + /* Interrupt */ + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + res->interrupt[0]); + if (!r) { + dev_err(dev, "missing IRQ\n"); + return -EINVAL; + } + + csid->irq = r->start; + snprintf(csid->irq_name, sizeof(csid->irq_name), "%s_%s%d", + dev_name(dev), MSM_CSID_NAME, csid->id); + ret = devm_request_irq(dev, csid->irq, csid_isr, + IRQF_TRIGGER_RISING, csid->irq_name, csid); + if (ret < 0) { + dev_err(dev, "request_irq failed: %d\n", ret); + return ret; + } + + disable_irq(csid->irq); + + /* Clocks */ + + csid->nclocks = 0; + while (res->clock[csid->nclocks]) + csid->nclocks++; + + csid->clock = devm_kcalloc(dev, csid->nclocks, sizeof(*csid->clock), + GFP_KERNEL); + if (!csid->clock) + return -ENOMEM; + + for (i = 0; i < csid->nclocks; i++) { + struct camss_clock *clock = &csid->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->name = res->clock[i]; + + clock->nfreqs = 0; + while (res->clock_rate[i][clock->nfreqs]) + clock->nfreqs++; + + if (!clock->nfreqs) { + clock->freq = NULL; + continue; + } + + clock->freq = devm_kcalloc(dev, + clock->nfreqs, + sizeof(*clock->freq), + GFP_KERNEL); + if (!clock->freq) + return -ENOMEM; + + for (j = 0; j < clock->nfreqs; j++) + clock->freq[j] = res->clock_rate[i][j]; + } + + /* Regulator */ + + csid->vdda = devm_regulator_get(dev, res->regulator[0]); + if (IS_ERR(csid->vdda)) { + dev_err(dev, "could not get regulator\n"); + return PTR_ERR(csid->vdda); + } + + init_completion(&csid->reset_complete); + + return 0; +} + +/* + * msm_csid_get_csid_id - Get CSID HW module id + * @entity: Pointer to CSID media entity structure + * @id: Return CSID HW module id here + */ +void msm_csid_get_csid_id(struct media_entity *entity, u8 *id) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct csid_device *csid = v4l2_get_subdevdata(sd); + + *id = csid->id; +} + +/* + * csid_get_lane_assign - Calculate CSI2 lane assign configuration parameter + * @lane_cfg - CSI2 lane configuration + * + * Return lane assign + */ +static u32 csid_get_lane_assign(struct csiphy_lanes_cfg *lane_cfg) +{ + u32 lane_assign = 0; + int i; + + for (i = 0; i < lane_cfg->num_data; i++) + lane_assign |= lane_cfg->data[i].pos << (i * 4); + + return lane_assign; +} + +/* + * csid_link_setup - Setup CSID connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad + * @remote: Pointer to remote pad + * @flags: Link flags + * + * Return 0 on success + */ +static int csid_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (flags & MEDIA_LNK_FL_ENABLED) + if (media_entity_remote_pad(local)) + return -EBUSY; + + if ((local->flags & MEDIA_PAD_FL_SINK) && + (flags & MEDIA_LNK_FL_ENABLED)) { + struct v4l2_subdev *sd; + struct csid_device *csid; + struct csiphy_device *csiphy; + struct csiphy_lanes_cfg *lane_cfg; + struct v4l2_subdev_format format = { 0 }; + + sd = media_entity_to_v4l2_subdev(entity); + csid = v4l2_get_subdevdata(sd); + + /* If test generator is enabled */ + /* do not allow a link from CSIPHY to CSID */ + if (csid->testgen_mode->cur.val != 0) + return -EBUSY; + + sd = media_entity_to_v4l2_subdev(remote->entity); + csiphy = v4l2_get_subdevdata(sd); + + /* If a sensor is not linked to CSIPHY */ + /* do no allow a link from CSIPHY to CSID */ + if (!csiphy->cfg.csi2) + return -EPERM; + + csid->phy.csiphy_id = csiphy->id; + + lane_cfg = &csiphy->cfg.csi2->lane_cfg; + csid->phy.lane_cnt = lane_cfg->num_data; + csid->phy.lane_assign = csid_get_lane_assign(lane_cfg); + + /* Reset format on source pad to sink pad format */ + format.pad = MSM_CSID_PAD_SRC; + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + csid_set_format(&csid->subdev, NULL, &format); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops csid_core_ops = { + .s_power = csid_set_power, +}; + +static const struct v4l2_subdev_video_ops csid_video_ops = { + .s_stream = csid_set_stream, +}; + +static const struct v4l2_subdev_pad_ops csid_pad_ops = { + .enum_mbus_code = csid_enum_mbus_code, + .enum_frame_size = csid_enum_frame_size, + .get_fmt = csid_get_format, + .set_fmt = csid_set_format, +}; + +static const struct v4l2_subdev_ops csid_v4l2_ops = { + .core = &csid_core_ops, + .video = &csid_video_ops, + .pad = &csid_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops csid_v4l2_internal_ops = { + .open = csid_init_formats, +}; + +static const struct media_entity_operations csid_media_ops = { + .link_setup = csid_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * msm_csid_register_entity - Register subdev node for CSID module + * @csid: CSID device + * @v4l2_dev: V4L2 device + * + * Return 0 on success or a negative error code otherwise + */ +int msm_csid_register_entity(struct csid_device *csid, + struct v4l2_device *v4l2_dev) +{ + struct v4l2_subdev *sd = &csid->subdev; + struct media_pad *pads = csid->pads; + struct device *dev = to_device_index(csid, csid->id); + int ret; + + v4l2_subdev_init(sd, &csid_v4l2_ops); + sd->internal_ops = &csid_v4l2_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", + MSM_CSID_NAME, csid->id); + v4l2_set_subdevdata(sd, csid); + + ret = v4l2_ctrl_handler_init(&csid->ctrls, 1); + if (ret < 0) { + dev_err(dev, "Failed to init ctrl handler: %d\n", ret); + return ret; + } + + csid->testgen_mode = v4l2_ctrl_new_std_menu_items(&csid->ctrls, + &csid_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(csid_test_pattern_menu) - 1, 0, 0, + csid_test_pattern_menu); + + if (csid->ctrls.error) { + dev_err(dev, "Failed to init ctrl: %d\n", csid->ctrls.error); + ret = csid->ctrls.error; + goto free_ctrl; + } + + csid->subdev.ctrl_handler = &csid->ctrls; + + ret = csid_init_formats(sd, NULL); + if (ret < 0) { + dev_err(dev, "Failed to init format: %d\n", ret); + goto free_ctrl; + } + + pads[MSM_CSID_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[MSM_CSID_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + + sd->entity.function = MEDIA_ENT_F_IO_V4L; + sd->entity.ops = &csid_media_ops; + ret = media_entity_pads_init(&sd->entity, MSM_CSID_PADS_NUM, pads); + if (ret < 0) { + dev_err(dev, "Failed to init media entity: %d\n", ret); + goto free_ctrl; + } + + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(dev, "Failed to register subdev: %d\n", ret); + goto media_cleanup; + } + + return 0; + +media_cleanup: + media_entity_cleanup(&sd->entity); +free_ctrl: + v4l2_ctrl_handler_free(&csid->ctrls); + + return ret; +} + +/* + * msm_csid_unregister_entity - Unregister CSID module subdev node + * @csid: CSID device + */ +void msm_csid_unregister_entity(struct csid_device *csid) +{ + v4l2_device_unregister_subdev(&csid->subdev); + media_entity_cleanup(&csid->subdev.entity); + v4l2_ctrl_handler_free(&csid->ctrls); +} diff --git a/drivers/media/platform/qcom/camss/camss-csid.h b/drivers/media/platform/qcom/camss/camss-csid.h new file mode 100644 index 000000000000..8012222b81ad --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid.h @@ -0,0 +1,82 @@ +/* + * camss-csid.h + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#ifndef QC_MSM_CAMSS_CSID_H +#define QC_MSM_CAMSS_CSID_H + +#include +#include +#include +#include +#include +#include + +#define MSM_CSID_PAD_SINK 0 +#define MSM_CSID_PAD_SRC 1 +#define MSM_CSID_PADS_NUM 2 + +enum csid_payload_mode { + CSID_PAYLOAD_MODE_INCREMENTING = 0, + CSID_PAYLOAD_MODE_ALTERNATING_55_AA = 1, + CSID_PAYLOAD_MODE_ALL_ZEROES = 2, + CSID_PAYLOAD_MODE_ALL_ONES = 3, + CSID_PAYLOAD_MODE_RANDOM = 4, + CSID_PAYLOAD_MODE_USER_SPECIFIED = 5, +}; + +struct csid_testgen_config { + u8 enabled; + enum csid_payload_mode payload_mode; +}; + +struct csid_phy_config { + u8 csiphy_id; + u8 lane_cnt; + u32 lane_assign; +}; + +struct csid_device { + u8 id; + struct v4l2_subdev subdev; + struct media_pad pads[MSM_CSID_PADS_NUM]; + void __iomem *base; + u32 irq; + char irq_name[30]; + struct camss_clock *clock; + int nclocks; + struct regulator *vdda; + struct completion reset_complete; + struct csid_testgen_config testgen; + struct csid_phy_config phy; + struct v4l2_mbus_framefmt fmt[MSM_CSID_PADS_NUM]; + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *testgen_mode; +}; + +struct resources; + +int msm_csid_subdev_init(struct csid_device *csid, + const struct resources *res, u8 id); + +int msm_csid_register_entity(struct csid_device *csid, + struct v4l2_device *v4l2_dev); + +void msm_csid_unregister_entity(struct csid_device *csid); + +void msm_csid_get_csid_id(struct media_entity *entity, u8 *id); + +#endif /* QC_MSM_CAMSS_CSID_H */ diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c new file mode 100644 index 000000000000..642de259dc6b --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c @@ -0,0 +1,893 @@ +/* + * camss-csiphy.c + * + * Qualcomm MSM Camera Subsystem - CSIPHY Module + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2016-2018 Linaro Ltd. + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camss-csiphy.h" +#include "camss.h" + +#define MSM_CSIPHY_NAME "msm_csiphy" + +#define CAMSS_CSI_PHY_LNn_CFG2(n) (0x004 + 0x40 * (n)) +#define CAMSS_CSI_PHY_LNn_CFG3(n) (0x008 + 0x40 * (n)) +#define CAMSS_CSI_PHY_GLBL_RESET 0x140 +#define CAMSS_CSI_PHY_GLBL_PWR_CFG 0x144 +#define CAMSS_CSI_PHY_GLBL_IRQ_CMD 0x164 +#define CAMSS_CSI_PHY_HW_VERSION 0x188 +#define CAMSS_CSI_PHY_INTERRUPT_STATUSn(n) (0x18c + 0x4 * (n)) +#define CAMSS_CSI_PHY_INTERRUPT_MASKn(n) (0x1ac + 0x4 * (n)) +#define CAMSS_CSI_PHY_INTERRUPT_CLEARn(n) (0x1cc + 0x4 * (n)) +#define CAMSS_CSI_PHY_GLBL_T_INIT_CFG0 0x1ec +#define CAMSS_CSI_PHY_T_WAKEUP_CFG0 0x1f4 + +static const struct { + u32 code; + u8 bpp; +} csiphy_formats[] = { + { + MEDIA_BUS_FMT_UYVY8_2X8, + 8, + }, + { + MEDIA_BUS_FMT_VYUY8_2X8, + 8, + }, + { + MEDIA_BUS_FMT_YUYV8_2X8, + 8, + }, + { + MEDIA_BUS_FMT_YVYU8_2X8, + 8, + }, + { + MEDIA_BUS_FMT_SBGGR8_1X8, + 8, + }, + { + MEDIA_BUS_FMT_SGBRG8_1X8, + 8, + }, + { + MEDIA_BUS_FMT_SGRBG8_1X8, + 8, + }, + { + MEDIA_BUS_FMT_SRGGB8_1X8, + 8, + }, + { + MEDIA_BUS_FMT_SBGGR10_1X10, + 10, + }, + { + MEDIA_BUS_FMT_SGBRG10_1X10, + 10, + }, + { + MEDIA_BUS_FMT_SGRBG10_1X10, + 10, + }, + { + MEDIA_BUS_FMT_SRGGB10_1X10, + 10, + }, + { + MEDIA_BUS_FMT_SBGGR12_1X12, + 12, + }, + { + MEDIA_BUS_FMT_SGBRG12_1X12, + 12, + }, + { + MEDIA_BUS_FMT_SGRBG12_1X12, + 12, + }, + { + MEDIA_BUS_FMT_SRGGB12_1X12, + 12, + } +}; + +/* + * csiphy_get_bpp - map media bus format to bits per pixel + * @code: media bus format code + * + * Return number of bits per pixel + */ +static u8 csiphy_get_bpp(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(csiphy_formats); i++) + if (code == csiphy_formats[i].code) + return csiphy_formats[i].bpp; + + WARN(1, "Unknown format\n"); + + return csiphy_formats[0].bpp; +} + +/* + * csiphy_isr - CSIPHY module interrupt handler + * @irq: Interrupt line + * @dev: CSIPHY device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t csiphy_isr(int irq, void *dev) +{ + struct csiphy_device *csiphy = dev; + u8 i; + + for (i = 0; i < 8; i++) { + u8 val = readl_relaxed(csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_STATUSn(i)); + writel_relaxed(val, csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_CLEARn(i)); + writel_relaxed(0x1, csiphy->base + CAMSS_CSI_PHY_GLBL_IRQ_CMD); + writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_IRQ_CMD); + writel_relaxed(0x0, csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_CLEARn(i)); + } + + return IRQ_HANDLED; +} + +/* + * csiphy_set_clock_rates - Calculate and set clock rates on CSIPHY module + * @csiphy: CSIPHY device + */ +static int csiphy_set_clock_rates(struct csiphy_device *csiphy) +{ + struct device *dev = to_device_index(csiphy, csiphy->id); + u32 pixel_clock; + int i, j; + int ret; + + ret = camss_get_pixel_clock(&csiphy->subdev.entity, &pixel_clock); + if (ret) + pixel_clock = 0; + + for (i = 0; i < csiphy->nclocks; i++) { + struct camss_clock *clock = &csiphy->clock[i]; + + if (!strcmp(clock->name, "csiphy0_timer") || + !strcmp(clock->name, "csiphy1_timer")) { + u8 bpp = csiphy_get_bpp( + csiphy->fmt[MSM_CSIPHY_PAD_SINK].code); + u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data; + u64 min_rate = pixel_clock * bpp / (2 * num_lanes * 4); + long round_rate; + + camss_add_clock_margin(&min_rate); + + for (j = 0; j < clock->nfreqs; j++) + if (min_rate < clock->freq[j]) + break; + + if (j == clock->nfreqs) { + dev_err(dev, + "Pixel clock is too high for CSIPHY\n"); + return -EINVAL; + } + + /* if sensor pixel clock is not available */ + /* set highest possible CSIPHY clock rate */ + if (min_rate == 0) + j = clock->nfreqs - 1; + + round_rate = clk_round_rate(clock->clk, clock->freq[j]); + if (round_rate < 0) { + dev_err(dev, "clk round rate failed: %ld\n", + round_rate); + return -EINVAL; + } + + csiphy->timer_clk_rate = round_rate; + + ret = clk_set_rate(clock->clk, csiphy->timer_clk_rate); + if (ret < 0) { + dev_err(dev, "clk set rate failed: %d\n", ret); + return ret; + } + } + } + + return 0; +} + +/* + * csiphy_reset - Perform software reset on CSIPHY module + * @csiphy: CSIPHY device + */ +static void csiphy_reset(struct csiphy_device *csiphy) +{ + writel_relaxed(0x1, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); + usleep_range(5000, 8000); + writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); +} + +/* + * csiphy_set_power - Power on/off CSIPHY module + * @sd: CSIPHY V4L2 subdevice + * @on: Requested power state + * + * Return 0 on success or a negative error code otherwise + */ +static int csiphy_set_power(struct v4l2_subdev *sd, int on) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct device *dev = to_device_index(csiphy, csiphy->id); + + if (on) { + u8 hw_version; + int ret; + + ret = csiphy_set_clock_rates(csiphy); + if (ret < 0) + return ret; + + ret = camss_enable_clocks(csiphy->nclocks, csiphy->clock, dev); + if (ret < 0) + return ret; + + enable_irq(csiphy->irq); + + csiphy_reset(csiphy); + + hw_version = readl_relaxed(csiphy->base + + CAMSS_CSI_PHY_HW_VERSION); + dev_dbg(dev, "CSIPHY HW Version = 0x%02x\n", hw_version); + } else { + disable_irq(csiphy->irq); + + camss_disable_clocks(csiphy->nclocks, csiphy->clock); + } + + return 0; +} + +/* + * csiphy_get_lane_mask - Calculate CSI2 lane mask configuration parameter + * @lane_cfg - CSI2 lane configuration + * + * Return lane mask + */ +static u8 csiphy_get_lane_mask(struct csiphy_lanes_cfg *lane_cfg) +{ + u8 lane_mask; + int i; + + lane_mask = 1 << lane_cfg->clk.pos; + + for (i = 0; i < lane_cfg->num_data; i++) + lane_mask |= 1 << lane_cfg->data[i].pos; + + return lane_mask; +} + +/* + * csiphy_settle_cnt_calc - Calculate settle count value + * @csiphy: CSIPHY device + * + * Helper function to calculate settle count value. This is + * based on the CSI2 T_hs_settle parameter which in turn + * is calculated based on the CSI2 transmitter pixel clock + * frequency. + * + * Return settle count value or 0 if the CSI2 pixel clock + * frequency is not available + */ +static u8 csiphy_settle_cnt_calc(struct csiphy_device *csiphy) +{ + u8 bpp = csiphy_get_bpp( + csiphy->fmt[MSM_CSIPHY_PAD_SINK].code); + u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data; + u32 pixel_clock; /* Hz */ + u32 mipi_clock; /* Hz */ + u32 ui; /* ps */ + u32 timer_period; /* ps */ + u32 t_hs_prepare_max; /* ps */ + u32 t_hs_prepare_zero_min; /* ps */ + u32 t_hs_settle; /* ps */ + u8 settle_cnt; + int ret; + + ret = camss_get_pixel_clock(&csiphy->subdev.entity, &pixel_clock); + if (ret) { + dev_err(to_device_index(csiphy, csiphy->id), + "Cannot get CSI2 transmitter's pixel clock\n"); + return 0; + } + if (!pixel_clock) { + dev_err(to_device_index(csiphy, csiphy->id), + "Got pixel clock == 0, cannot continue\n"); + return 0; + } + + mipi_clock = pixel_clock * bpp / (2 * num_lanes); + ui = div_u64(1000000000000LL, mipi_clock); + ui /= 2; + t_hs_prepare_max = 85000 + 6 * ui; + t_hs_prepare_zero_min = 145000 + 10 * ui; + t_hs_settle = (t_hs_prepare_max + t_hs_prepare_zero_min) / 2; + + timer_period = div_u64(1000000000000LL, csiphy->timer_clk_rate); + settle_cnt = t_hs_settle / timer_period; + + return settle_cnt; +} + +/* + * csiphy_stream_on - Enable streaming on CSIPHY module + * @csiphy: CSIPHY device + * + * Helper function to enable streaming on CSIPHY module. + * Main configuration of CSIPHY module is also done here. + * + * Return 0 on success or a negative error code otherwise + */ +static int csiphy_stream_on(struct csiphy_device *csiphy) +{ + struct csiphy_config *cfg = &csiphy->cfg; + u8 lane_mask = csiphy_get_lane_mask(&cfg->csi2->lane_cfg); + u8 settle_cnt; + u8 val; + int i = 0; + + settle_cnt = csiphy_settle_cnt_calc(csiphy); + if (!settle_cnt) + return -EINVAL; + + val = readl_relaxed(csiphy->base_clk_mux); + if (cfg->combo_mode && (lane_mask & 0x18) == 0x18) { + val &= ~0xf0; + val |= cfg->csid_id << 4; + } else { + val &= ~0xf; + val |= cfg->csid_id; + } + writel_relaxed(val, csiphy->base_clk_mux); + + writel_relaxed(0x1, csiphy->base + + CAMSS_CSI_PHY_GLBL_T_INIT_CFG0); + writel_relaxed(0x1, csiphy->base + + CAMSS_CSI_PHY_T_WAKEUP_CFG0); + + val = 0x1; + val |= lane_mask << 1; + writel_relaxed(val, csiphy->base + CAMSS_CSI_PHY_GLBL_PWR_CFG); + + val = cfg->combo_mode << 4; + writel_relaxed(val, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); + + while (lane_mask) { + if (lane_mask & 0x1) { + writel_relaxed(0x10, csiphy->base + + CAMSS_CSI_PHY_LNn_CFG2(i)); + writel_relaxed(settle_cnt, csiphy->base + + CAMSS_CSI_PHY_LNn_CFG3(i)); + writel_relaxed(0x3f, csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_MASKn(i)); + writel_relaxed(0x3f, csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_CLEARn(i)); + } + + lane_mask >>= 1; + i++; + } + + return 0; +} + +/* + * csiphy_stream_off - Disable streaming on CSIPHY module + * @csiphy: CSIPHY device + * + * Helper function to disable streaming on CSIPHY module + */ +static void csiphy_stream_off(struct csiphy_device *csiphy) +{ + u8 lane_mask = csiphy_get_lane_mask(&csiphy->cfg.csi2->lane_cfg); + int i = 0; + + while (lane_mask) { + if (lane_mask & 0x1) + writel_relaxed(0x0, csiphy->base + + CAMSS_CSI_PHY_LNn_CFG2(i)); + + lane_mask >>= 1; + i++; + } + + writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_PWR_CFG); +} + + +/* + * csiphy_set_stream - Enable/disable streaming on CSIPHY module + * @sd: CSIPHY V4L2 subdevice + * @enable: Requested streaming state + * + * Return 0 on success or a negative error code otherwise + */ +static int csiphy_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + int ret = 0; + + if (enable) + ret = csiphy_stream_on(csiphy); + else + csiphy_stream_off(csiphy); + + return ret; +} + +/* + * __csiphy_get_format - Get pointer to format structure + * @csiphy: CSIPHY device + * @cfg: V4L2 subdev pad configuration + * @pad: pad from which format is requested + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE format structure + */ +static struct v4l2_mbus_framefmt * +__csiphy_get_format(struct csiphy_device *csiphy, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&csiphy->subdev, cfg, pad); + + return &csiphy->fmt[pad]; +} + +/* + * csiphy_try_format - Handle try format by pad subdev method + * @csiphy: CSIPHY device + * @cfg: V4L2 subdev pad configuration + * @pad: pad on which format is requested + * @fmt: pointer to v4l2 format structure + * @which: wanted subdev format + */ +static void csiphy_try_format(struct csiphy_device *csiphy, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + + switch (pad) { + case MSM_CSIPHY_PAD_SINK: + /* Set format on sink pad */ + + for (i = 0; i < ARRAY_SIZE(csiphy_formats); i++) + if (fmt->code == csiphy_formats[i].code) + break; + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(csiphy_formats)) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + break; + + case MSM_CSIPHY_PAD_SRC: + /* Set and return a format same as sink pad */ + + *fmt = *__csiphy_get_format(csiphy, cfg, MSM_CSID_PAD_SINK, + which); + + break; + } +} + +/* + * csiphy_enum_mbus_code - Handle pixel format enumeration + * @sd: CSIPHY V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int csiphy_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + if (code->pad == MSM_CSIPHY_PAD_SINK) { + if (code->index >= ARRAY_SIZE(csiphy_formats)) + return -EINVAL; + + code->code = csiphy_formats[code->index].code; + } else { + if (code->index > 0) + return -EINVAL; + + format = __csiphy_get_format(csiphy, cfg, MSM_CSIPHY_PAD_SINK, + code->which); + + code->code = format->code; + } + + return 0; +} + +/* + * csiphy_enum_frame_size - Handle frame size enumeration + * @sd: CSIPHY V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: pointer to v4l2_subdev_frame_size_enum structure + * return -EINVAL or zero on success + */ +static int csiphy_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + csiphy_try_format(csiphy, cfg, fse->pad, &format, fse->which); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + csiphy_try_format(csiphy, cfg, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * csiphy_get_format - Handle get format by pads subdev method + * @sd: CSIPHY V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int csiphy_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csiphy_get_format(csiphy, cfg, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +/* + * csiphy_set_format - Handle set format by pads subdev method + * @sd: CSIPHY V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int csiphy_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csiphy_get_format(csiphy, cfg, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + csiphy_try_format(csiphy, cfg, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == MSM_CSIPHY_PAD_SINK) { + format = __csiphy_get_format(csiphy, cfg, MSM_CSIPHY_PAD_SRC, + fmt->which); + + *format = fmt->format; + csiphy_try_format(csiphy, cfg, MSM_CSIPHY_PAD_SRC, format, + fmt->which); + } + + return 0; +} + +/* + * csiphy_init_formats - Initialize formats on all pads + * @sd: CSIPHY V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. + * + * Return 0 on success or a negative error code otherwise + */ +static int csiphy_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format = { + .pad = MSM_CSIPHY_PAD_SINK, + .which = fh ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080 + } + }; + + return csiphy_set_format(sd, fh ? fh->pad : NULL, &format); +} + +/* + * msm_csiphy_subdev_init - Initialize CSIPHY device structure and resources + * @csiphy: CSIPHY device + * @res: CSIPHY module resources table + * @id: CSIPHY module id + * + * Return 0 on success or a negative error code otherwise + */ +int msm_csiphy_subdev_init(struct csiphy_device *csiphy, + const struct resources *res, u8 id) +{ + struct device *dev = to_device_index(csiphy, id); + struct platform_device *pdev = to_platform_device(dev); + struct resource *r; + int i, j; + int ret; + + csiphy->id = id; + csiphy->cfg.combo_mode = 0; + + /* Memory */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[0]); + csiphy->base = devm_ioremap_resource(dev, r); + if (IS_ERR(csiphy->base)) { + dev_err(dev, "could not map memory\n"); + return PTR_ERR(csiphy->base); + } + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[1]); + csiphy->base_clk_mux = devm_ioremap_resource(dev, r); + if (IS_ERR(csiphy->base_clk_mux)) { + dev_err(dev, "could not map memory\n"); + return PTR_ERR(csiphy->base_clk_mux); + } + + /* Interrupt */ + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + res->interrupt[0]); + if (!r) { + dev_err(dev, "missing IRQ\n"); + return -EINVAL; + } + + csiphy->irq = r->start; + snprintf(csiphy->irq_name, sizeof(csiphy->irq_name), "%s_%s%d", + dev_name(dev), MSM_CSIPHY_NAME, csiphy->id); + ret = devm_request_irq(dev, csiphy->irq, csiphy_isr, + IRQF_TRIGGER_RISING, csiphy->irq_name, csiphy); + if (ret < 0) { + dev_err(dev, "request_irq failed: %d\n", ret); + return ret; + } + + disable_irq(csiphy->irq); + + /* Clocks */ + + csiphy->nclocks = 0; + while (res->clock[csiphy->nclocks]) + csiphy->nclocks++; + + csiphy->clock = devm_kcalloc(dev, + csiphy->nclocks, sizeof(*csiphy->clock), + GFP_KERNEL); + if (!csiphy->clock) + return -ENOMEM; + + for (i = 0; i < csiphy->nclocks; i++) { + struct camss_clock *clock = &csiphy->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->name = res->clock[i]; + + clock->nfreqs = 0; + while (res->clock_rate[i][clock->nfreqs]) + clock->nfreqs++; + + if (!clock->nfreqs) { + clock->freq = NULL; + continue; + } + + clock->freq = devm_kcalloc(dev, + clock->nfreqs, + sizeof(*clock->freq), + GFP_KERNEL); + if (!clock->freq) + return -ENOMEM; + + for (j = 0; j < clock->nfreqs; j++) + clock->freq[j] = res->clock_rate[i][j]; + } + + return 0; +} + +/* + * csiphy_link_setup - Setup CSIPHY connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad + * @remote: Pointer to remote pad + * @flags: Link flags + * + * Rreturn 0 on success + */ +static int csiphy_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if ((local->flags & MEDIA_PAD_FL_SOURCE) && + (flags & MEDIA_LNK_FL_ENABLED)) { + struct v4l2_subdev *sd; + struct csiphy_device *csiphy; + struct csid_device *csid; + + if (media_entity_remote_pad(local)) + return -EBUSY; + + sd = media_entity_to_v4l2_subdev(entity); + csiphy = v4l2_get_subdevdata(sd); + + sd = media_entity_to_v4l2_subdev(remote->entity); + csid = v4l2_get_subdevdata(sd); + + csiphy->cfg.csid_id = csid->id; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops csiphy_core_ops = { + .s_power = csiphy_set_power, +}; + +static const struct v4l2_subdev_video_ops csiphy_video_ops = { + .s_stream = csiphy_set_stream, +}; + +static const struct v4l2_subdev_pad_ops csiphy_pad_ops = { + .enum_mbus_code = csiphy_enum_mbus_code, + .enum_frame_size = csiphy_enum_frame_size, + .get_fmt = csiphy_get_format, + .set_fmt = csiphy_set_format, +}; + +static const struct v4l2_subdev_ops csiphy_v4l2_ops = { + .core = &csiphy_core_ops, + .video = &csiphy_video_ops, + .pad = &csiphy_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops csiphy_v4l2_internal_ops = { + .open = csiphy_init_formats, +}; + +static const struct media_entity_operations csiphy_media_ops = { + .link_setup = csiphy_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * msm_csiphy_register_entity - Register subdev node for CSIPHY module + * @csiphy: CSIPHY device + * @v4l2_dev: V4L2 device + * + * Return 0 on success or a negative error code otherwise + */ +int msm_csiphy_register_entity(struct csiphy_device *csiphy, + struct v4l2_device *v4l2_dev) +{ + struct v4l2_subdev *sd = &csiphy->subdev; + struct media_pad *pads = csiphy->pads; + struct device *dev = to_device_index(csiphy, csiphy->id); + int ret; + + v4l2_subdev_init(sd, &csiphy_v4l2_ops); + sd->internal_ops = &csiphy_v4l2_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", + MSM_CSIPHY_NAME, csiphy->id); + v4l2_set_subdevdata(sd, csiphy); + + ret = csiphy_init_formats(sd, NULL); + if (ret < 0) { + dev_err(dev, "Failed to init format: %d\n", ret); + return ret; + } + + pads[MSM_CSIPHY_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[MSM_CSIPHY_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + + sd->entity.function = MEDIA_ENT_F_IO_V4L; + sd->entity.ops = &csiphy_media_ops; + ret = media_entity_pads_init(&sd->entity, MSM_CSIPHY_PADS_NUM, pads); + if (ret < 0) { + dev_err(dev, "Failed to init media entity: %d\n", ret); + return ret; + } + + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(dev, "Failed to register subdev: %d\n", ret); + media_entity_cleanup(&sd->entity); + } + + return ret; +} + +/* + * msm_csiphy_unregister_entity - Unregister CSIPHY module subdev node + * @csiphy: CSIPHY device + */ +void msm_csiphy_unregister_entity(struct csiphy_device *csiphy) +{ + v4l2_device_unregister_subdev(&csiphy->subdev); + media_entity_cleanup(&csiphy->subdev.entity); +} diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.h b/drivers/media/platform/qcom/camss/camss-csiphy.h new file mode 100644 index 000000000000..9a422095b773 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csiphy.h @@ -0,0 +1,77 @@ +/* + * camss-csiphy.h + * + * Qualcomm MSM Camera Subsystem - CSIPHY Module + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2016-2018 Linaro Ltd. + * + * 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. + */ +#ifndef QC_MSM_CAMSS_CSIPHY_H +#define QC_MSM_CAMSS_CSIPHY_H + +#include +#include +#include +#include +#include + +#define MSM_CSIPHY_PAD_SINK 0 +#define MSM_CSIPHY_PAD_SRC 1 +#define MSM_CSIPHY_PADS_NUM 2 + +struct csiphy_lane { + u8 pos; + u8 pol; +}; + +struct csiphy_lanes_cfg { + int num_data; + struct csiphy_lane *data; + struct csiphy_lane clk; +}; + +struct csiphy_csi2_cfg { + struct csiphy_lanes_cfg lane_cfg; +}; + +struct csiphy_config { + u8 combo_mode; + u8 csid_id; + struct csiphy_csi2_cfg *csi2; +}; + +struct csiphy_device { + u8 id; + struct v4l2_subdev subdev; + struct media_pad pads[MSM_CSIPHY_PADS_NUM]; + void __iomem *base; + void __iomem *base_clk_mux; + u32 irq; + char irq_name[30]; + struct camss_clock *clock; + int nclocks; + u32 timer_clk_rate; + struct csiphy_config cfg; + struct v4l2_mbus_framefmt fmt[MSM_CSIPHY_PADS_NUM]; +}; + +struct resources; + +int msm_csiphy_subdev_init(struct csiphy_device *csiphy, + const struct resources *res, u8 id); + +int msm_csiphy_register_entity(struct csiphy_device *csiphy, + struct v4l2_device *v4l2_dev); + +void msm_csiphy_unregister_entity(struct csiphy_device *csiphy); + +#endif /* QC_MSM_CAMSS_CSIPHY_H */ diff --git a/drivers/media/platform/qcom/camss/camss-ispif.c b/drivers/media/platform/qcom/camss/camss-ispif.c new file mode 100644 index 000000000000..636d5e712037 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-ispif.c @@ -0,0 +1,1178 @@ +/* + * camss-ispif.c + * + * Qualcomm MSM Camera Subsystem - ISPIF (ISP Interface) Module + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camss-ispif.h" +#include "camss.h" + +#define MSM_ISPIF_NAME "msm_ispif" + +#define ispif_line_array(ptr_line) \ + ((const struct ispif_line (*)[]) &(ptr_line[-(ptr_line->id)])) + +#define to_ispif(ptr_line) \ + container_of(ispif_line_array(ptr_line), struct ispif_device, ptr_line) + +#define ISPIF_RST_CMD_0 0x008 +#define ISPIF_RST_CMD_0_STROBED_RST_EN (1 << 0) +#define ISPIF_RST_CMD_0_MISC_LOGIC_RST (1 << 1) +#define ISPIF_RST_CMD_0_SW_REG_RST (1 << 2) +#define ISPIF_RST_CMD_0_PIX_INTF_0_CSID_RST (1 << 3) +#define ISPIF_RST_CMD_0_PIX_INTF_0_VFE_RST (1 << 4) +#define ISPIF_RST_CMD_0_PIX_INTF_1_CSID_RST (1 << 5) +#define ISPIF_RST_CMD_0_PIX_INTF_1_VFE_RST (1 << 6) +#define ISPIF_RST_CMD_0_RDI_INTF_0_CSID_RST (1 << 7) +#define ISPIF_RST_CMD_0_RDI_INTF_0_VFE_RST (1 << 8) +#define ISPIF_RST_CMD_0_RDI_INTF_1_CSID_RST (1 << 9) +#define ISPIF_RST_CMD_0_RDI_INTF_1_VFE_RST (1 << 10) +#define ISPIF_RST_CMD_0_RDI_INTF_2_CSID_RST (1 << 11) +#define ISPIF_RST_CMD_0_RDI_INTF_2_VFE_RST (1 << 12) +#define ISPIF_RST_CMD_0_PIX_OUTPUT_0_MISR_RST (1 << 16) +#define ISPIF_RST_CMD_0_RDI_OUTPUT_0_MISR_RST (1 << 17) +#define ISPIF_RST_CMD_0_RDI_OUTPUT_1_MISR_RST (1 << 18) +#define ISPIF_RST_CMD_0_RDI_OUTPUT_2_MISR_RST (1 << 19) +#define ISPIF_IRQ_GLOBAL_CLEAR_CMD 0x01c +#define ISPIF_VFE_m_CTRL_0(m) (0x200 + 0x200 * (m)) +#define ISPIF_VFE_m_CTRL_0_PIX0_LINE_BUF_EN (1 << 6) +#define ISPIF_VFE_m_IRQ_MASK_0(m) (0x208 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE 0x00001249 +#define ISPIF_VFE_m_IRQ_MASK_0_PIX0_MASK 0x00001fff +#define ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE 0x02492000 +#define ISPIF_VFE_m_IRQ_MASK_0_RDI0_MASK 0x03ffe000 +#define ISPIF_VFE_m_IRQ_MASK_1(m) (0x20c + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE 0x00001249 +#define ISPIF_VFE_m_IRQ_MASK_1_PIX1_MASK 0x00001fff +#define ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE 0x02492000 +#define ISPIF_VFE_m_IRQ_MASK_1_RDI1_MASK 0x03ffe000 +#define ISPIF_VFE_m_IRQ_MASK_2(m) (0x210 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE 0x00001249 +#define ISPIF_VFE_m_IRQ_MASK_2_RDI2_MASK 0x00001fff +#define ISPIF_VFE_m_IRQ_STATUS_0(m) (0x21c + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_STATUS_0_PIX0_OVERFLOW (1 << 12) +#define ISPIF_VFE_m_IRQ_STATUS_0_RDI0_OVERFLOW (1 << 25) +#define ISPIF_VFE_m_IRQ_STATUS_1(m) (0x220 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_STATUS_1_PIX1_OVERFLOW (1 << 12) +#define ISPIF_VFE_m_IRQ_STATUS_1_RDI1_OVERFLOW (1 << 25) +#define ISPIF_VFE_m_IRQ_STATUS_2(m) (0x224 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_STATUS_2_RDI2_OVERFLOW (1 << 12) +#define ISPIF_VFE_m_IRQ_CLEAR_0(m) (0x230 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_CLEAR_1(m) (0x234 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_CLEAR_2(m) (0x238 + 0x200 * (m)) +#define ISPIF_VFE_m_INTF_INPUT_SEL(m) (0x244 + 0x200 * (m)) +#define ISPIF_VFE_m_INTF_CMD_0(m) (0x248 + 0x200 * (m)) +#define ISPIF_VFE_m_INTF_CMD_1(m) (0x24c + 0x200 * (m)) +#define ISPIF_VFE_m_PIX_INTF_n_CID_MASK(m, n) \ + (0x254 + 0x200 * (m) + 0x4 * (n)) +#define ISPIF_VFE_m_RDI_INTF_n_CID_MASK(m, n) \ + (0x264 + 0x200 * (m) + 0x4 * (n)) +#define ISPIF_VFE_m_PIX_INTF_n_STATUS(m, n) \ + (0x2c0 + 0x200 * (m) + 0x4 * (n)) +#define ISPIF_VFE_m_RDI_INTF_n_STATUS(m, n) \ + (0x2d0 + 0x200 * (m) + 0x4 * (n)) + +#define CSI_PIX_CLK_MUX_SEL 0x000 +#define CSI_RDI_CLK_MUX_SEL 0x008 + +#define ISPIF_TIMEOUT_SLEEP_US 1000 +#define ISPIF_TIMEOUT_ALL_US 1000000 +#define ISPIF_RESET_TIMEOUT_MS 500 + +enum ispif_intf_cmd { + CMD_DISABLE_FRAME_BOUNDARY = 0x0, + CMD_ENABLE_FRAME_BOUNDARY = 0x1, + CMD_DISABLE_IMMEDIATELY = 0x2, + CMD_ALL_DISABLE_IMMEDIATELY = 0xaaaaaaaa, + CMD_ALL_NO_CHANGE = 0xffffffff, +}; + +static const u32 ispif_formats[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, +}; + +/* + * ispif_isr - ISPIF module interrupt handler + * @irq: Interrupt line + * @dev: ISPIF device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t ispif_isr(int irq, void *dev) +{ + struct ispif_device *ispif = dev; + u32 value0, value1, value2; + + value0 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_0(0)); + value1 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_1(0)); + value2 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_2(0)); + + writel_relaxed(value0, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(0)); + writel_relaxed(value1, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(0)); + writel_relaxed(value2, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_2(0)); + + writel(0x1, ispif->base + ISPIF_IRQ_GLOBAL_CLEAR_CMD); + + if ((value0 >> 27) & 0x1) + complete(&ispif->reset_complete); + + if (unlikely(value0 & ISPIF_VFE_m_IRQ_STATUS_0_PIX0_OVERFLOW)) + dev_err_ratelimited(to_device(ispif), "VFE0 pix0 overflow\n"); + + if (unlikely(value0 & ISPIF_VFE_m_IRQ_STATUS_0_RDI0_OVERFLOW)) + dev_err_ratelimited(to_device(ispif), "VFE0 rdi0 overflow\n"); + + if (unlikely(value1 & ISPIF_VFE_m_IRQ_STATUS_1_PIX1_OVERFLOW)) + dev_err_ratelimited(to_device(ispif), "VFE0 pix1 overflow\n"); + + if (unlikely(value1 & ISPIF_VFE_m_IRQ_STATUS_1_RDI1_OVERFLOW)) + dev_err_ratelimited(to_device(ispif), "VFE0 rdi1 overflow\n"); + + if (unlikely(value2 & ISPIF_VFE_m_IRQ_STATUS_2_RDI2_OVERFLOW)) + dev_err_ratelimited(to_device(ispif), "VFE0 rdi2 overflow\n"); + + return IRQ_HANDLED; +} + +/* + * ispif_reset - Trigger reset on ISPIF module and wait to complete + * @ispif: ISPIF device + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_reset(struct ispif_device *ispif) +{ + unsigned long time; + u32 val; + int ret; + + ret = camss_enable_clocks(ispif->nclocks_for_reset, + ispif->clock_for_reset, + to_device(ispif)); + if (ret < 0) + return ret; + + reinit_completion(&ispif->reset_complete); + + val = ISPIF_RST_CMD_0_STROBED_RST_EN | + ISPIF_RST_CMD_0_MISC_LOGIC_RST | + ISPIF_RST_CMD_0_SW_REG_RST | + ISPIF_RST_CMD_0_PIX_INTF_0_CSID_RST | + ISPIF_RST_CMD_0_PIX_INTF_0_VFE_RST | + ISPIF_RST_CMD_0_PIX_INTF_1_CSID_RST | + ISPIF_RST_CMD_0_PIX_INTF_1_VFE_RST | + ISPIF_RST_CMD_0_RDI_INTF_0_CSID_RST | + ISPIF_RST_CMD_0_RDI_INTF_0_VFE_RST | + ISPIF_RST_CMD_0_RDI_INTF_1_CSID_RST | + ISPIF_RST_CMD_0_RDI_INTF_1_VFE_RST | + ISPIF_RST_CMD_0_RDI_INTF_2_CSID_RST | + ISPIF_RST_CMD_0_RDI_INTF_2_VFE_RST | + ISPIF_RST_CMD_0_PIX_OUTPUT_0_MISR_RST | + ISPIF_RST_CMD_0_RDI_OUTPUT_0_MISR_RST | + ISPIF_RST_CMD_0_RDI_OUTPUT_1_MISR_RST | + ISPIF_RST_CMD_0_RDI_OUTPUT_2_MISR_RST; + + writel_relaxed(val, ispif->base + ISPIF_RST_CMD_0); + + time = wait_for_completion_timeout(&ispif->reset_complete, + msecs_to_jiffies(ISPIF_RESET_TIMEOUT_MS)); + if (!time) { + dev_err(to_device(ispif), "ISPIF reset timeout\n"); + return -EIO; + } + + camss_disable_clocks(ispif->nclocks_for_reset, ispif->clock_for_reset); + + return 0; +} + +/* + * ispif_set_power - Power on/off ISPIF module + * @sd: ISPIF V4L2 subdevice + * @on: Requested power state + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_set_power(struct v4l2_subdev *sd, int on) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct ispif_device *ispif = to_ispif(line); + struct device *dev = to_device(ispif); + int ret = 0; + + mutex_lock(&ispif->power_lock); + + if (on) { + if (ispif->power_count) { + /* Power is already on */ + ispif->power_count++; + goto exit; + } + + ret = camss_enable_clocks(ispif->nclocks, ispif->clock, dev); + if (ret < 0) + goto exit; + + ret = ispif_reset(ispif); + if (ret < 0) { + camss_disable_clocks(ispif->nclocks, ispif->clock); + goto exit; + } + + ispif->intf_cmd[line->vfe_id].cmd_0 = CMD_ALL_NO_CHANGE; + ispif->intf_cmd[line->vfe_id].cmd_1 = CMD_ALL_NO_CHANGE; + + ispif->power_count++; + } else { + if (ispif->power_count == 0) { + dev_err(dev, "ispif power off on power_count == 0\n"); + goto exit; + } else if (ispif->power_count == 1) { + camss_disable_clocks(ispif->nclocks, ispif->clock); + } + + ispif->power_count--; + } + +exit: + mutex_unlock(&ispif->power_lock); + + return ret; +} + +/* + * ispif_select_clk_mux - Select clock for PIX/RDI interface + * @ispif: ISPIF device + * @intf: VFE interface + * @csid: CSID HW module id + * @vfe: VFE HW module id + * @enable: enable or disable the selected clock + */ +static void ispif_select_clk_mux(struct ispif_device *ispif, + enum ispif_intf intf, u8 csid, + u8 vfe, u8 enable) +{ + u32 val; + + switch (intf) { + case PIX0: + val = readl_relaxed(ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); + val &= ~(0xf << (vfe * 8)); + if (enable) + val |= (csid << (vfe * 8)); + writel_relaxed(val, ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); + break; + + case RDI0: + val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + val &= ~(0xf << (vfe * 12)); + if (enable) + val |= (csid << (vfe * 12)); + writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + break; + + case PIX1: + val = readl_relaxed(ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); + val &= ~(0xf << (4 + (vfe * 8))); + if (enable) + val |= (csid << (4 + (vfe * 8))); + writel_relaxed(val, ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); + break; + + case RDI1: + val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + val &= ~(0xf << (4 + (vfe * 12))); + if (enable) + val |= (csid << (4 + (vfe * 12))); + writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + break; + + case RDI2: + val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + val &= ~(0xf << (8 + (vfe * 12))); + if (enable) + val |= (csid << (8 + (vfe * 12))); + writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + break; + } + + mb(); +} + +/* + * ispif_validate_intf_status - Validate current status of PIX/RDI interface + * @ispif: ISPIF device + * @intf: VFE interface + * @vfe: VFE HW module id + * + * Return 0 when interface is idle or -EBUSY otherwise + */ +static int ispif_validate_intf_status(struct ispif_device *ispif, + enum ispif_intf intf, u8 vfe) +{ + int ret = 0; + u32 val = 0; + + switch (intf) { + case PIX0: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 0)); + break; + case RDI0: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 0)); + break; + case PIX1: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 1)); + break; + case RDI1: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 1)); + break; + case RDI2: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 2)); + break; + } + + if ((val & 0xf) != 0xf) { + dev_err(to_device(ispif), "%s: ispif is busy: 0x%x\n", + __func__, val); + ret = -EBUSY; + } + + return ret; +} + +/* + * ispif_wait_for_stop - Wait for PIX/RDI interface to stop + * @ispif: ISPIF device + * @intf: VFE interface + * @vfe: VFE HW module id + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_wait_for_stop(struct ispif_device *ispif, + enum ispif_intf intf, u8 vfe) +{ + u32 addr = 0; + u32 stop_flag = 0; + int ret; + + switch (intf) { + case PIX0: + addr = ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 0); + break; + case RDI0: + addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 0); + break; + case PIX1: + addr = ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 1); + break; + case RDI1: + addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 1); + break; + case RDI2: + addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 2); + break; + } + + ret = readl_poll_timeout(ispif->base + addr, + stop_flag, + (stop_flag & 0xf) == 0xf, + ISPIF_TIMEOUT_SLEEP_US, + ISPIF_TIMEOUT_ALL_US); + if (ret < 0) + dev_err(to_device(ispif), "%s: ispif stop timeout\n", + __func__); + + return ret; +} + +/* + * ispif_select_csid - Select CSID HW module for input from + * @ispif: ISPIF device + * @intf: VFE interface + * @csid: CSID HW module id + * @vfe: VFE HW module id + * @enable: enable or disable the selected input + */ +static void ispif_select_csid(struct ispif_device *ispif, enum ispif_intf intf, + u8 csid, u8 vfe, u8 enable) +{ + u32 val; + + val = readl_relaxed(ispif->base + ISPIF_VFE_m_INTF_INPUT_SEL(vfe)); + switch (intf) { + case PIX0: + val &= ~(BIT(1) | BIT(0)); + if (enable) + val |= csid; + break; + case RDI0: + val &= ~(BIT(5) | BIT(4)); + if (enable) + val |= (csid << 4); + break; + case PIX1: + val &= ~(BIT(9) | BIT(8)); + if (enable) + val |= (csid << 8); + break; + case RDI1: + val &= ~(BIT(13) | BIT(12)); + if (enable) + val |= (csid << 12); + break; + case RDI2: + val &= ~(BIT(21) | BIT(20)); + if (enable) + val |= (csid << 20); + break; + } + + writel(val, ispif->base + ISPIF_VFE_m_INTF_INPUT_SEL(vfe)); +} + +/* + * ispif_select_cid - Enable/disable desired CID + * @ispif: ISPIF device + * @intf: VFE interface + * @cid: desired CID to enable/disable + * @vfe: VFE HW module id + * @enable: enable or disable the desired CID + */ +static void ispif_select_cid(struct ispif_device *ispif, enum ispif_intf intf, + u8 cid, u8 vfe, u8 enable) +{ + u32 cid_mask = 1 << cid; + u32 addr = 0; + u32 val; + + switch (intf) { + case PIX0: + addr = ISPIF_VFE_m_PIX_INTF_n_CID_MASK(vfe, 0); + break; + case RDI0: + addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 0); + break; + case PIX1: + addr = ISPIF_VFE_m_PIX_INTF_n_CID_MASK(vfe, 1); + break; + case RDI1: + addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 1); + break; + case RDI2: + addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 2); + break; + } + + val = readl_relaxed(ispif->base + addr); + if (enable) + val |= cid_mask; + else + val &= ~cid_mask; + + writel(val, ispif->base + addr); +} + +/* + * ispif_config_irq - Enable/disable interrupts for PIX/RDI interface + * @ispif: ISPIF device + * @intf: VFE interface + * @vfe: VFE HW module id + * @enable: enable or disable + */ +static void ispif_config_irq(struct ispif_device *ispif, enum ispif_intf intf, + u8 vfe, u8 enable) +{ + u32 val; + + switch (intf) { + case PIX0: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_0_PIX0_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(vfe)); + break; + case RDI0: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_0_RDI0_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(vfe)); + break; + case PIX1: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_1_PIX1_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(vfe)); + break; + case RDI1: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_1_RDI1_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(vfe)); + break; + case RDI2: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_2(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_2_RDI2_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_2(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_2(vfe)); + break; + } + + writel(0x1, ispif->base + ISPIF_IRQ_GLOBAL_CLEAR_CMD); +} + +/* + * ispif_set_intf_cmd - Set command to enable/disable interface + * @ispif: ISPIF device + * @cmd: interface command + * @intf: VFE interface + * @vfe: VFE HW module id + * @vc: virtual channel + */ +static void ispif_set_intf_cmd(struct ispif_device *ispif, u8 cmd, + enum ispif_intf intf, u8 vfe, u8 vc) +{ + u32 *val; + + if (intf == RDI2) { + val = &ispif->intf_cmd[vfe].cmd_1; + *val &= ~(0x3 << (vc * 2 + 8)); + *val |= (cmd << (vc * 2 + 8)); + wmb(); + writel_relaxed(*val, ispif->base + ISPIF_VFE_m_INTF_CMD_1(vfe)); + wmb(); + } else { + val = &ispif->intf_cmd[vfe].cmd_0; + *val &= ~(0x3 << (vc * 2 + intf * 8)); + *val |= (cmd << (vc * 2 + intf * 8)); + wmb(); + writel_relaxed(*val, ispif->base + ISPIF_VFE_m_INTF_CMD_0(vfe)); + wmb(); + } +} + +/* + * ispif_set_stream - Enable/disable streaming on ISPIF module + * @sd: ISPIF V4L2 subdevice + * @enable: Requested streaming state + * + * Main configuration of ISPIF module is also done here. + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct ispif_device *ispif = to_ispif(line); + enum ispif_intf intf = line->interface; + u8 csid = line->csid_id; + u8 vfe = line->vfe_id; + u8 vc = 0; /* Virtual Channel 0 */ + u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */ + int ret; + + if (enable) { + if (!media_entity_remote_pad(&line->pads[MSM_ISPIF_PAD_SINK])) + return -ENOLINK; + + /* Config */ + + mutex_lock(&ispif->config_lock); + ispif_select_clk_mux(ispif, intf, csid, vfe, 1); + + ret = ispif_validate_intf_status(ispif, intf, vfe); + if (ret < 0) { + mutex_unlock(&ispif->config_lock); + return ret; + } + + ispif_select_csid(ispif, intf, csid, vfe, 1); + ispif_select_cid(ispif, intf, cid, vfe, 1); + ispif_config_irq(ispif, intf, vfe, 1); + ispif_set_intf_cmd(ispif, CMD_ENABLE_FRAME_BOUNDARY, + intf, vfe, vc); + } else { + mutex_lock(&ispif->config_lock); + ispif_set_intf_cmd(ispif, CMD_DISABLE_FRAME_BOUNDARY, + intf, vfe, vc); + mutex_unlock(&ispif->config_lock); + + ret = ispif_wait_for_stop(ispif, intf, vfe); + if (ret < 0) + return ret; + + mutex_lock(&ispif->config_lock); + ispif_config_irq(ispif, intf, vfe, 0); + ispif_select_cid(ispif, intf, cid, vfe, 0); + ispif_select_csid(ispif, intf, csid, vfe, 0); + ispif_select_clk_mux(ispif, intf, csid, vfe, 0); + } + + mutex_unlock(&ispif->config_lock); + + return 0; +} + +/* + * __ispif_get_format - Get pointer to format structure + * @ispif: ISPIF line + * @cfg: V4L2 subdev pad configuration + * @pad: pad from which format is requested + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE format structure + */ +static struct v4l2_mbus_framefmt * +__ispif_get_format(struct ispif_line *line, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&line->subdev, cfg, pad); + + return &line->fmt[pad]; +} + +/* + * ispif_try_format - Handle try format by pad subdev method + * @ispif: ISPIF line + * @cfg: V4L2 subdev pad configuration + * @pad: pad on which format is requested + * @fmt: pointer to v4l2 format structure + * @which: wanted subdev format + */ +static void ispif_try_format(struct ispif_line *line, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + + switch (pad) { + case MSM_ISPIF_PAD_SINK: + /* Set format on sink pad */ + + for (i = 0; i < ARRAY_SIZE(ispif_formats); i++) + if (fmt->code == ispif_formats[i]) + break; + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(ispif_formats)) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + break; + + case MSM_ISPIF_PAD_SRC: + /* Set and return a format same as sink pad */ + + *fmt = *__ispif_get_format(line, cfg, MSM_ISPIF_PAD_SINK, + which); + + break; + } + + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +/* + * ispif_enum_mbus_code - Handle pixel format enumeration + * @sd: ISPIF V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ispif_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + if (code->pad == MSM_ISPIF_PAD_SINK) { + if (code->index >= ARRAY_SIZE(ispif_formats)) + return -EINVAL; + + code->code = ispif_formats[code->index]; + } else { + if (code->index > 0) + return -EINVAL; + + format = __ispif_get_format(line, cfg, MSM_ISPIF_PAD_SINK, + code->which); + + code->code = format->code; + } + + return 0; +} + +/* + * ispif_enum_frame_size - Handle frame size enumeration + * @sd: ISPIF V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: pointer to v4l2_subdev_frame_size_enum structure + * return -EINVAL or zero on success + */ +static int ispif_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + ispif_try_format(line, cfg, fse->pad, &format, fse->which); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + ispif_try_format(line, cfg, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * ispif_get_format - Handle get format by pads subdev method + * @sd: ISPIF V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int ispif_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ispif_get_format(line, cfg, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +/* + * ispif_set_format - Handle set format by pads subdev method + * @sd: ISPIF V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int ispif_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ispif_get_format(line, cfg, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ispif_try_format(line, cfg, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == MSM_ISPIF_PAD_SINK) { + format = __ispif_get_format(line, cfg, MSM_ISPIF_PAD_SRC, + fmt->which); + + *format = fmt->format; + ispif_try_format(line, cfg, MSM_ISPIF_PAD_SRC, format, + fmt->which); + } + + return 0; +} + +/* + * ispif_init_formats - Initialize formats on all pads + * @sd: ISPIF V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format = { + .pad = MSM_ISPIF_PAD_SINK, + .which = fh ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080 + } + }; + + return ispif_set_format(sd, fh ? fh->pad : NULL, &format); +} + +/* + * msm_ispif_subdev_init - Initialize ISPIF device structure and resources + * @ispif: ISPIF device + * @res: ISPIF module resources table + * + * Return 0 on success or a negative error code otherwise + */ +int msm_ispif_subdev_init(struct ispif_device *ispif, + const struct resources_ispif *res) +{ + struct device *dev = to_device(ispif); + struct platform_device *pdev = to_platform_device(dev); + struct resource *r; + int i; + int ret; + + /* Memory */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[0]); + ispif->base = devm_ioremap_resource(dev, r); + if (IS_ERR(ispif->base)) { + dev_err(dev, "could not map memory\n"); + return PTR_ERR(ispif->base); + } + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[1]); + ispif->base_clk_mux = devm_ioremap_resource(dev, r); + if (IS_ERR(ispif->base_clk_mux)) { + dev_err(dev, "could not map memory\n"); + return PTR_ERR(ispif->base_clk_mux); + } + + /* Interrupt */ + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, res->interrupt); + + if (!r) { + dev_err(dev, "missing IRQ\n"); + return -EINVAL; + } + + ispif->irq = r->start; + snprintf(ispif->irq_name, sizeof(ispif->irq_name), "%s_%s", + dev_name(dev), MSM_ISPIF_NAME); + ret = devm_request_irq(dev, ispif->irq, ispif_isr, + IRQF_TRIGGER_RISING, ispif->irq_name, ispif); + if (ret < 0) { + dev_err(dev, "request_irq failed: %d\n", ret); + return ret; + } + + /* Clocks */ + + ispif->nclocks = 0; + while (res->clock[ispif->nclocks]) + ispif->nclocks++; + + ispif->clock = devm_kcalloc(dev, + ispif->nclocks, sizeof(*ispif->clock), + GFP_KERNEL); + if (!ispif->clock) + return -ENOMEM; + + for (i = 0; i < ispif->nclocks; i++) { + struct camss_clock *clock = &ispif->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->freq = NULL; + clock->nfreqs = 0; + } + + ispif->nclocks_for_reset = 0; + while (res->clock_for_reset[ispif->nclocks_for_reset]) + ispif->nclocks_for_reset++; + + ispif->clock_for_reset = devm_kcalloc(dev, + ispif->nclocks_for_reset, + sizeof(*ispif->clock_for_reset), + GFP_KERNEL); + if (!ispif->clock_for_reset) + return -ENOMEM; + + for (i = 0; i < ispif->nclocks_for_reset; i++) { + struct camss_clock *clock = &ispif->clock_for_reset[i]; + + clock->clk = devm_clk_get(dev, res->clock_for_reset[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->freq = NULL; + clock->nfreqs = 0; + } + + for (i = 0; i < ARRAY_SIZE(ispif->line); i++) + ispif->line[i].id = i; + + mutex_init(&ispif->power_lock); + ispif->power_count = 0; + + mutex_init(&ispif->config_lock); + + init_completion(&ispif->reset_complete); + + return 0; +} + +/* + * ispif_get_intf - Get ISPIF interface to use by VFE line id + * @line_id: VFE line id that the ISPIF line is connected to + * + * Return ISPIF interface to use + */ +static enum ispif_intf ispif_get_intf(enum vfe_line_id line_id) +{ + switch (line_id) { + case (VFE_LINE_RDI0): + return RDI0; + case (VFE_LINE_RDI1): + return RDI1; + case (VFE_LINE_RDI2): + return RDI2; + case (VFE_LINE_PIX): + return PIX0; + default: + return RDI0; + } +} + +/* + * ispif_link_setup - Setup ISPIF connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad + * @remote: Pointer to remote pad + * @flags: Link flags + * + * Return 0 on success + */ +static int ispif_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (media_entity_remote_pad(local)) + return -EBUSY; + + if (local->flags & MEDIA_PAD_FL_SINK) { + struct v4l2_subdev *sd; + struct ispif_line *line; + + sd = media_entity_to_v4l2_subdev(entity); + line = v4l2_get_subdevdata(sd); + + msm_csid_get_csid_id(remote->entity, &line->csid_id); + } else { /* MEDIA_PAD_FL_SOURCE */ + struct v4l2_subdev *sd; + struct ispif_line *line; + enum vfe_line_id id; + + sd = media_entity_to_v4l2_subdev(entity); + line = v4l2_get_subdevdata(sd); + + msm_vfe_get_vfe_id(remote->entity, &line->vfe_id); + msm_vfe_get_vfe_line_id(remote->entity, &id); + line->interface = ispif_get_intf(id); + } + } + + return 0; +} + +static const struct v4l2_subdev_core_ops ispif_core_ops = { + .s_power = ispif_set_power, +}; + +static const struct v4l2_subdev_video_ops ispif_video_ops = { + .s_stream = ispif_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ispif_pad_ops = { + .enum_mbus_code = ispif_enum_mbus_code, + .enum_frame_size = ispif_enum_frame_size, + .get_fmt = ispif_get_format, + .set_fmt = ispif_set_format, +}; + +static const struct v4l2_subdev_ops ispif_v4l2_ops = { + .core = &ispif_core_ops, + .video = &ispif_video_ops, + .pad = &ispif_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops ispif_v4l2_internal_ops = { + .open = ispif_init_formats, +}; + +static const struct media_entity_operations ispif_media_ops = { + .link_setup = ispif_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * msm_ispif_register_entities - Register subdev node for ISPIF module + * @ispif: ISPIF device + * @v4l2_dev: V4L2 device + * + * Return 0 on success or a negative error code otherwise + */ +int msm_ispif_register_entities(struct ispif_device *ispif, + struct v4l2_device *v4l2_dev) +{ + struct device *dev = to_device(ispif); + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(ispif->line); i++) { + struct v4l2_subdev *sd = &ispif->line[i].subdev; + struct media_pad *pads = ispif->line[i].pads; + + v4l2_subdev_init(sd, &ispif_v4l2_ops); + sd->internal_ops = &ispif_v4l2_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", + MSM_ISPIF_NAME, i); + v4l2_set_subdevdata(sd, &ispif->line[i]); + + ret = ispif_init_formats(sd, NULL); + if (ret < 0) { + dev_err(dev, "Failed to init format: %d\n", ret); + goto error; + } + + pads[MSM_ISPIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[MSM_ISPIF_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + + sd->entity.function = MEDIA_ENT_F_IO_V4L; + sd->entity.ops = &ispif_media_ops; + ret = media_entity_pads_init(&sd->entity, MSM_ISPIF_PADS_NUM, + pads); + if (ret < 0) { + dev_err(dev, "Failed to init media entity: %d\n", ret); + goto error; + } + + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(dev, "Failed to register subdev: %d\n", ret); + media_entity_cleanup(&sd->entity); + goto error; + } + } + + return 0; + +error: + for (i--; i >= 0; i--) { + struct v4l2_subdev *sd = &ispif->line[i].subdev; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } + + return ret; +} + +/* + * msm_ispif_unregister_entities - Unregister ISPIF module subdev node + * @ispif: ISPIF device + */ +void msm_ispif_unregister_entities(struct ispif_device *ispif) +{ + int i; + + mutex_destroy(&ispif->power_lock); + mutex_destroy(&ispif->config_lock); + + for (i = 0; i < ARRAY_SIZE(ispif->line); i++) { + struct v4l2_subdev *sd = &ispif->line[i].subdev; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } +} diff --git a/drivers/media/platform/qcom/camss/camss-ispif.h b/drivers/media/platform/qcom/camss/camss-ispif.h new file mode 100644 index 000000000000..c90e1598943a --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-ispif.h @@ -0,0 +1,85 @@ +/* + * camss-ispif.h + * + * Qualcomm MSM Camera Subsystem - ISPIF (ISP Interface) Module + * + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#ifndef QC_MSM_CAMSS_ISPIF_H +#define QC_MSM_CAMSS_ISPIF_H + +#include +#include +#include +#include + +/* Number of ISPIF lines - same as number of CSID hardware modules */ +#define MSM_ISPIF_LINE_NUM 2 + +#define MSM_ISPIF_PAD_SINK 0 +#define MSM_ISPIF_PAD_SRC 1 +#define MSM_ISPIF_PADS_NUM 2 + +#define MSM_ISPIF_VFE_NUM 1 + +enum ispif_intf { + PIX0, + RDI0, + PIX1, + RDI1, + RDI2 +}; + +struct ispif_intf_cmd_reg { + u32 cmd_0; + u32 cmd_1; +}; + +struct ispif_line { + u8 id; + u8 csid_id; + u8 vfe_id; + enum ispif_intf interface; + struct v4l2_subdev subdev; + struct media_pad pads[MSM_ISPIF_PADS_NUM]; + struct v4l2_mbus_framefmt fmt[MSM_ISPIF_PADS_NUM]; +}; + +struct ispif_device { + void __iomem *base; + void __iomem *base_clk_mux; + u32 irq; + char irq_name[30]; + struct camss_clock *clock; + int nclocks; + struct camss_clock *clock_for_reset; + int nclocks_for_reset; + struct completion reset_complete; + int power_count; + struct mutex power_lock; + struct ispif_intf_cmd_reg intf_cmd[MSM_ISPIF_VFE_NUM]; + struct mutex config_lock; + struct ispif_line line[MSM_ISPIF_LINE_NUM]; +}; + +struct resources_ispif; + +int msm_ispif_subdev_init(struct ispif_device *ispif, + const struct resources_ispif *res); + +int msm_ispif_register_entities(struct ispif_device *ispif, + struct v4l2_device *v4l2_dev); + +void msm_ispif_unregister_entities(struct ispif_device *ispif); + +#endif /* QC_MSM_CAMSS_ISPIF_H */ diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c new file mode 100644 index 000000000000..380b90bdd720 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -0,0 +1,3093 @@ +/* + * camss-vfe.c + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camss-vfe.h" +#include "camss.h" + +#define MSM_VFE_NAME "msm_vfe" + +#define vfe_line_array(ptr_line) \ + ((const struct vfe_line (*)[]) &(ptr_line[-(ptr_line->id)])) + +#define to_vfe(ptr_line) \ + container_of(vfe_line_array(ptr_line), struct vfe_device, ptr_line) + +#define VFE_0_HW_VERSION 0x000 + +#define VFE_0_GLOBAL_RESET_CMD 0x00c +#define VFE_0_GLOBAL_RESET_CMD_CORE (1 << 0) +#define VFE_0_GLOBAL_RESET_CMD_CAMIF (1 << 1) +#define VFE_0_GLOBAL_RESET_CMD_BUS (1 << 2) +#define VFE_0_GLOBAL_RESET_CMD_BUS_BDG (1 << 3) +#define VFE_0_GLOBAL_RESET_CMD_REGISTER (1 << 4) +#define VFE_0_GLOBAL_RESET_CMD_TIMER (1 << 5) +#define VFE_0_GLOBAL_RESET_CMD_PM (1 << 6) +#define VFE_0_GLOBAL_RESET_CMD_BUS_MISR (1 << 7) +#define VFE_0_GLOBAL_RESET_CMD_TESTGEN (1 << 8) + +#define VFE_0_MODULE_CFG 0x018 +#define VFE_0_MODULE_CFG_DEMUX (1 << 2) +#define VFE_0_MODULE_CFG_CHROMA_UPSAMPLE (1 << 3) +#define VFE_0_MODULE_CFG_SCALE_ENC (1 << 23) +#define VFE_0_MODULE_CFG_CROP_ENC (1 << 27) + +#define VFE_0_CORE_CFG 0x01c +#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR 0x4 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB 0x5 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY 0x6 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY 0x7 + +#define VFE_0_IRQ_CMD 0x024 +#define VFE_0_IRQ_CMD_GLOBAL_CLEAR (1 << 0) + +#define VFE_0_IRQ_MASK_0 0x028 +#define VFE_0_IRQ_MASK_0_CAMIF_SOF (1 << 0) +#define VFE_0_IRQ_MASK_0_CAMIF_EOF (1 << 1) +#define VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n) (1 << ((n) + 5)) +#define VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(n) \ + ((n) == VFE_LINE_PIX ? (1 << 4) : VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n)) +#define VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(n) (1 << ((n) + 8)) +#define VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(n) (1 << ((n) + 25)) +#define VFE_0_IRQ_MASK_0_RESET_ACK (1 << 31) +#define VFE_0_IRQ_MASK_1 0x02c +#define VFE_0_IRQ_MASK_1_CAMIF_ERROR (1 << 0) +#define VFE_0_IRQ_MASK_1_VIOLATION (1 << 7) +#define VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK (1 << 8) +#define VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(n) (1 << ((n) + 9)) +#define VFE_0_IRQ_MASK_1_RDIn_SOF(n) (1 << ((n) + 29)) + +#define VFE_0_IRQ_CLEAR_0 0x030 +#define VFE_0_IRQ_CLEAR_1 0x034 + +#define VFE_0_IRQ_STATUS_0 0x038 +#define VFE_0_IRQ_STATUS_0_CAMIF_SOF (1 << 0) +#define VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n) (1 << ((n) + 5)) +#define VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(n) \ + ((n) == VFE_LINE_PIX ? (1 << 4) : VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n)) +#define VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(n) (1 << ((n) + 8)) +#define VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(n) (1 << ((n) + 25)) +#define VFE_0_IRQ_STATUS_0_RESET_ACK (1 << 31) +#define VFE_0_IRQ_STATUS_1 0x03c +#define VFE_0_IRQ_STATUS_1_VIOLATION (1 << 7) +#define VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK (1 << 8) +#define VFE_0_IRQ_STATUS_1_RDIn_SOF(n) (1 << ((n) + 29)) + +#define VFE_0_IRQ_COMPOSITE_MASK_0 0x40 +#define VFE_0_VIOLATION_STATUS 0x48 + +#define VFE_0_BUS_CMD 0x4c +#define VFE_0_BUS_CMD_Mx_RLD_CMD(x) (1 << (x)) + +#define VFE_0_BUS_CFG 0x050 + +#define VFE_0_BUS_XBAR_CFG_x(x) (0x58 + 0x4 * ((x) / 2)) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN (1 << 1) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA (0x3 << 4) +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT 8 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA 0 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 5 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 6 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 7 + +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(n) (0x06c + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT 0 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT 1 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(n) (0x070 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(n) (0x074 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(n) (0x078 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT 2 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK (0x1F << 2) + +#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(n) (0x07c + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT 16 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(n) (0x080 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(n) (0x084 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(n) \ + (0x088 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(n) \ + (0x08c + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF 0xffffffff + +#define VFE_0_BUS_PING_PONG_STATUS 0x268 + +#define VFE_0_BUS_BDG_CMD 0x2c0 +#define VFE_0_BUS_BDG_CMD_HALT_REQ 1 + +#define VFE_0_BUS_BDG_QOS_CFG_0 0x2c4 +#define VFE_0_BUS_BDG_QOS_CFG_0_CFG 0xaaa5aaa5 +#define VFE_0_BUS_BDG_QOS_CFG_1 0x2c8 +#define VFE_0_BUS_BDG_QOS_CFG_2 0x2cc +#define VFE_0_BUS_BDG_QOS_CFG_3 0x2d0 +#define VFE_0_BUS_BDG_QOS_CFG_4 0x2d4 +#define VFE_0_BUS_BDG_QOS_CFG_5 0x2d8 +#define VFE_0_BUS_BDG_QOS_CFG_6 0x2dc +#define VFE_0_BUS_BDG_QOS_CFG_7 0x2e0 +#define VFE_0_BUS_BDG_QOS_CFG_7_CFG 0x0001aaa5 + +#define VFE_0_RDI_CFG_x(x) (0x2e8 + (0x4 * (x))) +#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT 28 +#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK (0xf << 28) +#define VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT 4 +#define VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK (0xf << 4) +#define VFE_0_RDI_CFG_x_RDI_EN_BIT (1 << 2) +#define VFE_0_RDI_CFG_x_MIPI_EN_BITS 0x3 +#define VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(r) (1 << (16 + (r))) + +#define VFE_0_CAMIF_CMD 0x2f4 +#define VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY 0 +#define VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY 1 +#define VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS (1 << 2) +#define VFE_0_CAMIF_CFG 0x2f8 +#define VFE_0_CAMIF_CFG_VFE_OUTPUT_EN (1 << 6) +#define VFE_0_CAMIF_FRAME_CFG 0x300 +#define VFE_0_CAMIF_WINDOW_WIDTH_CFG 0x304 +#define VFE_0_CAMIF_WINDOW_HEIGHT_CFG 0x308 +#define VFE_0_CAMIF_SUBSAMPLE_CFG_0 0x30c +#define VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN 0x314 +#define VFE_0_CAMIF_STATUS 0x31c +#define VFE_0_CAMIF_STATUS_HALT (1 << 31) + +#define VFE_0_REG_UPDATE 0x378 +#define VFE_0_REG_UPDATE_RDIn(n) (1 << (1 + (n))) +#define VFE_0_REG_UPDATE_line_n(n) \ + ((n) == VFE_LINE_PIX ? 1 : VFE_0_REG_UPDATE_RDIn(n)) + +#define VFE_0_DEMUX_CFG 0x424 +#define VFE_0_DEMUX_CFG_PERIOD 0x3 +#define VFE_0_DEMUX_GAIN_0 0x428 +#define VFE_0_DEMUX_GAIN_0_CH0_EVEN (0x80 << 0) +#define VFE_0_DEMUX_GAIN_0_CH0_ODD (0x80 << 16) +#define VFE_0_DEMUX_GAIN_1 0x42c +#define VFE_0_DEMUX_GAIN_1_CH1 (0x80 << 0) +#define VFE_0_DEMUX_GAIN_1_CH2 (0x80 << 16) +#define VFE_0_DEMUX_EVEN_CFG 0x438 +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV 0x9cac +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU 0xac9c +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY 0xc9ca +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY 0xcac9 +#define VFE_0_DEMUX_ODD_CFG 0x43c +#define VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV 0x9cac +#define VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU 0xac9c +#define VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY 0xc9ca +#define VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY 0xcac9 + +#define VFE_0_SCALE_ENC_Y_CFG 0x75c +#define VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE 0x760 +#define VFE_0_SCALE_ENC_Y_H_PHASE 0x764 +#define VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE 0x76c +#define VFE_0_SCALE_ENC_Y_V_PHASE 0x770 +#define VFE_0_SCALE_ENC_CBCR_CFG 0x778 +#define VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE 0x77c +#define VFE_0_SCALE_ENC_CBCR_H_PHASE 0x780 +#define VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE 0x790 +#define VFE_0_SCALE_ENC_CBCR_V_PHASE 0x794 + +#define VFE_0_CROP_ENC_Y_WIDTH 0x854 +#define VFE_0_CROP_ENC_Y_HEIGHT 0x858 +#define VFE_0_CROP_ENC_CBCR_WIDTH 0x85c +#define VFE_0_CROP_ENC_CBCR_HEIGHT 0x860 + +#define VFE_0_CLAMP_ENC_MAX_CFG 0x874 +#define VFE_0_CLAMP_ENC_MAX_CFG_CH0 (0xff << 0) +#define VFE_0_CLAMP_ENC_MAX_CFG_CH1 (0xff << 8) +#define VFE_0_CLAMP_ENC_MAX_CFG_CH2 (0xff << 16) +#define VFE_0_CLAMP_ENC_MIN_CFG 0x878 +#define VFE_0_CLAMP_ENC_MIN_CFG_CH0 (0x0 << 0) +#define VFE_0_CLAMP_ENC_MIN_CFG_CH1 (0x0 << 8) +#define VFE_0_CLAMP_ENC_MIN_CFG_CH2 (0x0 << 16) + +#define VFE_0_CGC_OVERRIDE_1 0x974 +#define VFE_0_CGC_OVERRIDE_1_IMAGE_Mx_CGC_OVERRIDE(x) (1 << (x)) + +/* VFE reset timeout */ +#define VFE_RESET_TIMEOUT_MS 50 +/* VFE halt timeout */ +#define VFE_HALT_TIMEOUT_MS 100 +/* Max number of frame drop updates per frame */ +#define VFE_FRAME_DROP_UPDATES 5 +/* Frame drop value. NOTE: VAL + UPDATES should not exceed 31 */ +#define VFE_FRAME_DROP_VAL 20 + +#define VFE_NEXT_SOF_MS 500 + +#define CAMIF_TIMEOUT_SLEEP_US 1000 +#define CAMIF_TIMEOUT_ALL_US 1000000 + +#define SCALER_RATIO_MAX 16 + +static const struct { + u32 code; + u8 bpp; +} vfe_formats[] = { + { + MEDIA_BUS_FMT_UYVY8_2X8, + 8, + }, + { + MEDIA_BUS_FMT_VYUY8_2X8, + 8, + }, + { + MEDIA_BUS_FMT_YUYV8_2X8, + 8, + }, + { + MEDIA_BUS_FMT_YVYU8_2X8, + 8, + }, + { + MEDIA_BUS_FMT_SBGGR8_1X8, + 8, + }, + { + MEDIA_BUS_FMT_SGBRG8_1X8, + 8, + }, + { + MEDIA_BUS_FMT_SGRBG8_1X8, + 8, + }, + { + MEDIA_BUS_FMT_SRGGB8_1X8, + 8, + }, + { + MEDIA_BUS_FMT_SBGGR10_1X10, + 10, + }, + { + MEDIA_BUS_FMT_SGBRG10_1X10, + 10, + }, + { + MEDIA_BUS_FMT_SGRBG10_1X10, + 10, + }, + { + MEDIA_BUS_FMT_SRGGB10_1X10, + 10, + }, + { + MEDIA_BUS_FMT_SBGGR12_1X12, + 12, + }, + { + MEDIA_BUS_FMT_SGBRG12_1X12, + 12, + }, + { + MEDIA_BUS_FMT_SGRBG12_1X12, + 12, + }, + { + MEDIA_BUS_FMT_SRGGB12_1X12, + 12, + } +}; + +/* + * vfe_get_bpp - map media bus format to bits per pixel + * @code: media bus format code + * + * Return number of bits per pixel + */ +static u8 vfe_get_bpp(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vfe_formats); i++) + if (code == vfe_formats[i].code) + return vfe_formats[i].bpp; + + WARN(1, "Unknown format\n"); + + return vfe_formats[0].bpp; +} + +static inline void vfe_reg_clr(struct vfe_device *vfe, u32 reg, u32 clr_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits & ~clr_bits, vfe->base + reg); +} + +static inline void vfe_reg_set(struct vfe_device *vfe, u32 reg, u32 set_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits | set_bits, vfe->base + reg); +} + +static void vfe_global_reset(struct vfe_device *vfe) +{ + u32 reset_bits = VFE_0_GLOBAL_RESET_CMD_TESTGEN | + VFE_0_GLOBAL_RESET_CMD_BUS_MISR | + VFE_0_GLOBAL_RESET_CMD_PM | + VFE_0_GLOBAL_RESET_CMD_TIMER | + VFE_0_GLOBAL_RESET_CMD_REGISTER | + VFE_0_GLOBAL_RESET_CMD_BUS_BDG | + VFE_0_GLOBAL_RESET_CMD_BUS | + VFE_0_GLOBAL_RESET_CMD_CAMIF | + VFE_0_GLOBAL_RESET_CMD_CORE; + + writel_relaxed(reset_bits, vfe->base + VFE_0_GLOBAL_RESET_CMD); +} + +static void vfe_wm_enable(struct vfe_device *vfe, u8 wm, u8 enable) +{ + if (enable) + vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT); + else + vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT); +} + +static void vfe_wm_frame_based(struct vfe_device *vfe, u8 wm, u8 enable) +{ + if (enable) + vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT); + else + vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT); +} + +#define CALC_WORD(width, M, N) (((width) * (M) + (N) - 1) / (N)) + +static int vfe_word_per_line(uint32_t format, uint32_t pixel_per_line) +{ + int val = 0; + + switch (format) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + val = CALC_WORD(pixel_per_line, 1, 8); + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + val = CALC_WORD(pixel_per_line, 2, 8); + break; + } + + return val; +} + +static void vfe_get_wm_sizes(struct v4l2_pix_format_mplane *pix, u8 plane, + u16 *width, u16 *height, u16 *bytesperline) +{ + switch (pix->pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + *width = pix->width; + *height = pix->height; + *bytesperline = pix->plane_fmt[0].bytesperline; + if (plane == 1) + *height /= 2; + break; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + *width = pix->width; + *height = pix->height; + *bytesperline = pix->plane_fmt[0].bytesperline; + break; + } +} + +static void vfe_wm_line_based(struct vfe_device *vfe, u32 wm, + struct v4l2_pix_format_mplane *pix, + u8 plane, u32 enable) +{ + u32 reg; + + if (enable) { + u16 width = 0, height = 0, bytesperline = 0, wpl; + + vfe_get_wm_sizes(pix, plane, &width, &height, &bytesperline); + + wpl = vfe_word_per_line(pix->pixelformat, width); + + reg = height - 1; + reg |= ((wpl + 1) / 2 - 1) << 16; + + writel_relaxed(reg, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); + + wpl = vfe_word_per_line(pix->pixelformat, bytesperline); + + reg = 0x3; + reg |= (height - 1) << 4; + reg |= wpl << 16; + + writel_relaxed(reg, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); + } else { + writel_relaxed(0, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); + writel_relaxed(0, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); + } +} + +static void vfe_wm_set_framedrop_period(struct vfe_device *vfe, u8 wm, u8 per) +{ + u32 reg; + + reg = readl_relaxed(vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); + + reg &= ~(VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK); + + reg |= (per << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT) + & VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK; + + writel_relaxed(reg, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); +} + +static void vfe_wm_set_framedrop_pattern(struct vfe_device *vfe, u8 wm, + u32 pattern) +{ + writel_relaxed(pattern, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(wm)); +} + +static void vfe_wm_set_ub_cfg(struct vfe_device *vfe, u8 wm, u16 offset, + u16 depth) +{ + u32 reg; + + reg = (offset << VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT) | + depth; + writel_relaxed(reg, vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(wm)); +} + +static void vfe_bus_reload_wm(struct vfe_device *vfe, u8 wm) +{ + wmb(); + writel_relaxed(VFE_0_BUS_CMD_Mx_RLD_CMD(wm), vfe->base + VFE_0_BUS_CMD); + wmb(); +} + +static void vfe_wm_set_ping_addr(struct vfe_device *vfe, u8 wm, u32 addr) +{ + writel_relaxed(addr, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(wm)); +} + +static void vfe_wm_set_pong_addr(struct vfe_device *vfe, u8 wm, u32 addr) +{ + writel_relaxed(addr, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(wm)); +} + +static int vfe_wm_get_ping_pong_status(struct vfe_device *vfe, u8 wm) +{ + u32 reg; + + reg = readl_relaxed(vfe->base + VFE_0_BUS_PING_PONG_STATUS); + + return (reg >> wm) & 0x1; +} + +static void vfe_bus_enable_wr_if(struct vfe_device *vfe, u8 enable) +{ + if (enable) + writel_relaxed(0x10000009, vfe->base + VFE_0_BUS_CFG); + else + writel_relaxed(0, vfe->base + VFE_0_BUS_CFG); +} + +static void vfe_bus_connect_wm_to_rdi(struct vfe_device *vfe, u8 wm, + enum vfe_line_id id) +{ + u32 reg; + + reg = VFE_0_RDI_CFG_x_MIPI_EN_BITS; + reg |= VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(id); + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), reg); + + reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; + reg |= ((3 * id) << VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT) & + VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), reg); + + switch (id) { + case VFE_LINE_RDI0: + default: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI1: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI2: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + } + + if (wm % 2 == 1) + reg <<= 16; + + vfe_reg_set(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); +} + +static void vfe_wm_set_subsample(struct vfe_device *vfe, u8 wm) +{ + writel_relaxed(VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF, + vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(wm)); +} + +static void vfe_bus_disconnect_wm_from_rdi(struct vfe_device *vfe, u8 wm, + enum vfe_line_id id) +{ + u32 reg; + + reg = VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(id); + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(0), reg); + + reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), reg); + + switch (id) { + case VFE_LINE_RDI0: + default: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI1: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI2: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + } + + if (wm % 2 == 1) + reg <<= 16; + + vfe_reg_clr(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); +} + +static void vfe_set_xbar_cfg(struct vfe_device *vfe, struct vfe_output *output, + u8 enable) +{ + struct vfe_line *line = container_of(output, struct vfe_line, output); + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + unsigned int i; + + for (i = 0; i < output->wm_num; i++) { + if (i == 0) { + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + } else if (i == 1) { + reg = VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV16) + reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA; + } else { + /* On current devices output->wm_num is always <= 2 */ + break; + } + + if (output->wm_idx[i] % 2 == 1) + reg <<= 16; + + if (enable) + vfe_reg_set(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[i]), + reg); + else + vfe_reg_clr(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[i]), + reg); + } +} + +static void vfe_set_rdi_cid(struct vfe_device *vfe, enum vfe_line_id id, u8 cid) +{ + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), + VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK); + + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), + cid << VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT); +} + +static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + vfe->reg_update |= VFE_0_REG_UPDATE_line_n(line_id); + wmb(); + writel_relaxed(vfe->reg_update, vfe->base + VFE_0_REG_UPDATE); + wmb(); +} + +static void vfe_enable_irq_wm_line(struct vfe_device *vfe, u8 wm, + enum vfe_line_id line_id, u8 enable) +{ + u32 irq_en0 = VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(wm) | + VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); + u32 irq_en1 = VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(wm) | + VFE_0_IRQ_MASK_1_RDIn_SOF(line_id); + + if (enable) { + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); + } else { + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); + } +} + +static void vfe_enable_irq_pix_line(struct vfe_device *vfe, u8 comp, + enum vfe_line_id line_id, u8 enable) +{ + struct vfe_output *output = &vfe->line[line_id].output; + unsigned int i; + u32 irq_en0; + u32 irq_en1; + u32 comp_mask = 0; + + irq_en0 = VFE_0_IRQ_MASK_0_CAMIF_SOF; + irq_en0 |= VFE_0_IRQ_MASK_0_CAMIF_EOF; + irq_en0 |= VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(comp); + irq_en0 |= VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); + irq_en1 = VFE_0_IRQ_MASK_1_CAMIF_ERROR; + for (i = 0; i < output->wm_num; i++) { + irq_en1 |= VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW( + output->wm_idx[i]); + comp_mask |= (1 << output->wm_idx[i]) << comp * 8; + } + + if (enable) { + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); + vfe_reg_set(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); + } else { + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); + vfe_reg_clr(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); + } +} + +static void vfe_enable_irq_common(struct vfe_device *vfe) +{ + u32 irq_en0 = VFE_0_IRQ_MASK_0_RESET_ACK; + u32 irq_en1 = VFE_0_IRQ_MASK_1_VIOLATION | + VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK; + + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); +} + +static void vfe_set_demux_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 val, even_cfg, odd_cfg; + + writel_relaxed(VFE_0_DEMUX_CFG_PERIOD, vfe->base + VFE_0_DEMUX_CFG); + + val = VFE_0_DEMUX_GAIN_0_CH0_EVEN | VFE_0_DEMUX_GAIN_0_CH0_ODD; + writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_0); + + val = VFE_0_DEMUX_GAIN_1_CH1 | VFE_0_DEMUX_GAIN_1_CH2; + writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_1); + + switch (line->fmt[MSM_VFE_PAD_SINK].code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY; + break; + } + + writel_relaxed(even_cfg, vfe->base + VFE_0_DEMUX_EVEN_CFG); + writel_relaxed(odd_cfg, vfe->base + VFE_0_DEMUX_ODD_CFG); +} + +static inline u8 vfe_calc_interp_reso(u16 input, u16 output) +{ + if (input / output >= 16) + return 0; + + if (input / output >= 8) + return 1; + + if (input / output >= 4) + return 2; + + return 3; +} + +static void vfe_set_scale_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + u16 input, output; + u8 interp_reso; + u32 phase_mult; + + writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_Y_CFG); + + input = line->fmt[MSM_VFE_PAD_SINK].width; + output = line->compose.width; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (13 + interp_reso)) / output; + reg = (interp_reso << 20) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_PHASE); + + input = line->fmt[MSM_VFE_PAD_SINK].height; + output = line->compose.height; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (13 + interp_reso)) / output; + reg = (interp_reso << 20) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_PHASE); + + writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_CBCR_CFG); + + input = line->fmt[MSM_VFE_PAD_SINK].width; + output = line->compose.width / 2; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (13 + interp_reso)) / output; + reg = (interp_reso << 20) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_PHASE); + + input = line->fmt[MSM_VFE_PAD_SINK].height; + output = line->compose.height; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) + output = line->compose.height / 2; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (13 + interp_reso)) / output; + reg = (interp_reso << 20) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_PHASE); +} + +static void vfe_set_crop_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + u16 first, last; + + first = line->crop.left; + last = line->crop.left + line->crop.width - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_WIDTH); + + first = line->crop.top; + last = line->crop.top + line->crop.height - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_HEIGHT); + + first = line->crop.left / 2; + last = line->crop.left / 2 + line->crop.width / 2 - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_WIDTH); + + first = line->crop.top; + last = line->crop.top + line->crop.height - 1; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) { + first = line->crop.top / 2; + last = line->crop.top / 2 + line->crop.height / 2 - 1; + } + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_HEIGHT); +} + +static void vfe_set_clamp_cfg(struct vfe_device *vfe) +{ + u32 val = VFE_0_CLAMP_ENC_MAX_CFG_CH0 | + VFE_0_CLAMP_ENC_MAX_CFG_CH1 | + VFE_0_CLAMP_ENC_MAX_CFG_CH2; + + writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MAX_CFG); + + val = VFE_0_CLAMP_ENC_MIN_CFG_CH0 | + VFE_0_CLAMP_ENC_MIN_CFG_CH1 | + VFE_0_CLAMP_ENC_MIN_CFG_CH2; + + writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MIN_CFG); +} + +/* + * vfe_reset - Trigger reset on VFE module and wait to complete + * @vfe: VFE device + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_reset(struct vfe_device *vfe) +{ + unsigned long time; + + reinit_completion(&vfe->reset_complete); + + vfe_global_reset(vfe); + + time = wait_for_completion_timeout(&vfe->reset_complete, + msecs_to_jiffies(VFE_RESET_TIMEOUT_MS)); + if (!time) { + dev_err(to_device(vfe), "VFE reset timeout\n"); + return -EIO; + } + + return 0; +} + +/* + * vfe_halt - Trigger halt on VFE module and wait to complete + * @vfe: VFE device + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_halt(struct vfe_device *vfe) +{ + unsigned long time; + + reinit_completion(&vfe->halt_complete); + + writel_relaxed(VFE_0_BUS_BDG_CMD_HALT_REQ, + vfe->base + VFE_0_BUS_BDG_CMD); + + time = wait_for_completion_timeout(&vfe->halt_complete, + msecs_to_jiffies(VFE_HALT_TIMEOUT_MS)); + if (!time) { + dev_err(to_device(vfe), "VFE halt timeout\n"); + return -EIO; + } + + return 0; +} + +static void vfe_init_outputs(struct vfe_device *vfe) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vfe->line); i++) { + struct vfe_output *output = &vfe->line[i].output; + + output->state = VFE_OUTPUT_OFF; + output->buf[0] = NULL; + output->buf[1] = NULL; + INIT_LIST_HEAD(&output->pending_bufs); + + output->wm_num = 1; + if (vfe->line[i].id == VFE_LINE_PIX) + output->wm_num = 2; + } +} + +static void vfe_reset_output_maps(struct vfe_device *vfe) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) + vfe->wm_output_map[i] = VFE_LINE_NONE; +} + +static void vfe_set_qos(struct vfe_device *vfe) +{ + u32 val = VFE_0_BUS_BDG_QOS_CFG_0_CFG; + u32 val7 = VFE_0_BUS_BDG_QOS_CFG_7_CFG; + + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_0); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_1); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_2); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_3); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_4); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_5); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_6); + writel_relaxed(val7, vfe->base + VFE_0_BUS_BDG_QOS_CFG_7); +} + +static void vfe_set_cgc_override(struct vfe_device *vfe, u8 wm, u8 enable) +{ + u32 val = VFE_0_CGC_OVERRIDE_1_IMAGE_Mx_CGC_OVERRIDE(wm); + + if (enable) + vfe_reg_set(vfe, VFE_0_CGC_OVERRIDE_1, val); + else + vfe_reg_clr(vfe, VFE_0_CGC_OVERRIDE_1, val); + + wmb(); +} + +static void vfe_set_module_cfg(struct vfe_device *vfe, u8 enable) +{ + u32 val = VFE_0_MODULE_CFG_DEMUX | + VFE_0_MODULE_CFG_CHROMA_UPSAMPLE | + VFE_0_MODULE_CFG_SCALE_ENC | + VFE_0_MODULE_CFG_CROP_ENC; + + if (enable) + writel_relaxed(val, vfe->base + VFE_0_MODULE_CFG); + else + writel_relaxed(0x0, vfe->base + VFE_0_MODULE_CFG); +} + +static void vfe_set_camif_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 val; + + switch (line->fmt[MSM_VFE_PAD_SINK].code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY; + break; + } + + writel_relaxed(val, vfe->base + VFE_0_CORE_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].width * 2; + val |= line->fmt[MSM_VFE_PAD_SINK].height << 16; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_FRAME_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].width * 2 - 1; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_WIDTH_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].height - 1; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_HEIGHT_CFG); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_SUBSAMPLE_CFG_0); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN); + + val = VFE_0_RDI_CFG_x_MIPI_EN_BITS; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), val); + + val = VFE_0_CAMIF_CFG_VFE_OUTPUT_EN; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_CFG); +} + +static void vfe_set_camif_cmd(struct vfe_device *vfe, u32 cmd) +{ + writel_relaxed(VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS, + vfe->base + VFE_0_CAMIF_CMD); + + writel_relaxed(cmd, vfe->base + VFE_0_CAMIF_CMD); +} + +static int vfe_camif_wait_for_stop(struct vfe_device *vfe) +{ + u32 val; + int ret; + + ret = readl_poll_timeout(vfe->base + VFE_0_CAMIF_STATUS, + val, + (val & VFE_0_CAMIF_STATUS_HALT), + CAMIF_TIMEOUT_SLEEP_US, + CAMIF_TIMEOUT_ALL_US); + if (ret < 0) + dev_err(to_device(vfe), "%s: camif stop timeout\n", __func__); + + return ret; +} + +static void vfe_output_init_addrs(struct vfe_device *vfe, + struct vfe_output *output, u8 sync) +{ + u32 ping_addr; + u32 pong_addr; + unsigned int i; + + output->active_buf = 0; + + for (i = 0; i < output->wm_num; i++) { + if (output->buf[0]) + ping_addr = output->buf[0]->addr[i]; + else + ping_addr = 0; + + if (output->buf[1]) + pong_addr = output->buf[1]->addr[i]; + else + pong_addr = ping_addr; + + vfe_wm_set_ping_addr(vfe, output->wm_idx[i], ping_addr); + vfe_wm_set_pong_addr(vfe, output->wm_idx[i], pong_addr); + if (sync) + vfe_bus_reload_wm(vfe, output->wm_idx[i]); + } +} + +static void vfe_output_update_ping_addr(struct vfe_device *vfe, + struct vfe_output *output, u8 sync) +{ + u32 addr; + unsigned int i; + + for (i = 0; i < output->wm_num; i++) { + if (output->buf[0]) + addr = output->buf[0]->addr[i]; + else + addr = 0; + + vfe_wm_set_ping_addr(vfe, output->wm_idx[i], addr); + if (sync) + vfe_bus_reload_wm(vfe, output->wm_idx[i]); + } +} + +static void vfe_output_update_pong_addr(struct vfe_device *vfe, + struct vfe_output *output, u8 sync) +{ + u32 addr; + unsigned int i; + + for (i = 0; i < output->wm_num; i++) { + if (output->buf[1]) + addr = output->buf[1]->addr[i]; + else + addr = 0; + + vfe_wm_set_pong_addr(vfe, output->wm_idx[i], addr); + if (sync) + vfe_bus_reload_wm(vfe, output->wm_idx[i]); + } + +} + +static int vfe_reserve_wm(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + int ret = -EBUSY; + int i; + + for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) { + if (vfe->wm_output_map[i] == VFE_LINE_NONE) { + vfe->wm_output_map[i] = line_id; + ret = i; + break; + } + } + + return ret; +} + +static int vfe_release_wm(struct vfe_device *vfe, u8 wm) +{ + if (wm >= ARRAY_SIZE(vfe->wm_output_map)) + return -EINVAL; + + vfe->wm_output_map[wm] = VFE_LINE_NONE; + + return 0; +} + +static void vfe_output_frame_drop(struct vfe_device *vfe, + struct vfe_output *output, + u32 drop_pattern) +{ + u8 drop_period; + unsigned int i; + + /* We need to toggle update period to be valid on next frame */ + output->drop_update_idx++; + output->drop_update_idx %= VFE_FRAME_DROP_UPDATES; + drop_period = VFE_FRAME_DROP_VAL + output->drop_update_idx; + + for (i = 0; i < output->wm_num; i++) { + vfe_wm_set_framedrop_period(vfe, output->wm_idx[i], + drop_period); + vfe_wm_set_framedrop_pattern(vfe, output->wm_idx[i], + drop_pattern); + } + vfe_reg_update(vfe, container_of(output, struct vfe_line, output)->id); +} + +static struct camss_buffer *vfe_buf_get_pending(struct vfe_output *output) +{ + struct camss_buffer *buffer = NULL; + + if (!list_empty(&output->pending_bufs)) { + buffer = list_first_entry(&output->pending_bufs, + struct camss_buffer, + queue); + list_del(&buffer->queue); + } + + return buffer; +} + +/* + * vfe_buf_add_pending - Add output buffer to list of pending + * @output: VFE output + * @buffer: Video buffer + */ +static void vfe_buf_add_pending(struct vfe_output *output, + struct camss_buffer *buffer) +{ + INIT_LIST_HEAD(&buffer->queue); + list_add_tail(&buffer->queue, &output->pending_bufs); +} + +/* + * vfe_buf_flush_pending - Flush all pending buffers. + * @output: VFE output + * @state: vb2 buffer state + */ +static void vfe_buf_flush_pending(struct vfe_output *output, + enum vb2_buffer_state state) +{ + struct camss_buffer *buf; + struct camss_buffer *t; + + list_for_each_entry_safe(buf, t, &output->pending_bufs, queue) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->queue); + } +} + +static void vfe_buf_update_wm_on_next(struct vfe_device *vfe, + struct vfe_output *output) +{ + switch (output->state) { + case VFE_OUTPUT_CONTINUOUS: + vfe_output_frame_drop(vfe, output, 3); + break; + case VFE_OUTPUT_SINGLE: + default: + dev_err_ratelimited(to_device(vfe), + "Next buf in wrong state! %d\n", + output->state); + break; + } +} + +static void vfe_buf_update_wm_on_last(struct vfe_device *vfe, + struct vfe_output *output) +{ + switch (output->state) { + case VFE_OUTPUT_CONTINUOUS: + output->state = VFE_OUTPUT_SINGLE; + vfe_output_frame_drop(vfe, output, 1); + break; + case VFE_OUTPUT_SINGLE: + output->state = VFE_OUTPUT_STOPPING; + vfe_output_frame_drop(vfe, output, 0); + break; + default: + dev_err_ratelimited(to_device(vfe), + "Last buff in wrong state! %d\n", + output->state); + break; + } +} + +static void vfe_buf_update_wm_on_new(struct vfe_device *vfe, + struct vfe_output *output, + struct camss_buffer *new_buf) +{ + int inactive_idx; + + switch (output->state) { + case VFE_OUTPUT_SINGLE: + inactive_idx = !output->active_buf; + + if (!output->buf[inactive_idx]) { + output->buf[inactive_idx] = new_buf; + + if (inactive_idx) + vfe_output_update_pong_addr(vfe, output, 0); + else + vfe_output_update_ping_addr(vfe, output, 0); + + vfe_output_frame_drop(vfe, output, 3); + output->state = VFE_OUTPUT_CONTINUOUS; + } else { + vfe_buf_add_pending(output, new_buf); + dev_err_ratelimited(to_device(vfe), + "Inactive buffer is busy\n"); + } + break; + + case VFE_OUTPUT_IDLE: + if (!output->buf[0]) { + output->buf[0] = new_buf; + + vfe_output_init_addrs(vfe, output, 1); + + vfe_output_frame_drop(vfe, output, 1); + output->state = VFE_OUTPUT_SINGLE; + } else { + vfe_buf_add_pending(output, new_buf); + dev_err_ratelimited(to_device(vfe), + "Output idle with buffer set!\n"); + } + break; + + case VFE_OUTPUT_CONTINUOUS: + default: + vfe_buf_add_pending(output, new_buf); + break; + } +} + +static int vfe_get_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output; + unsigned long flags; + int i; + int wm_idx; + + spin_lock_irqsave(&vfe->output_lock, flags); + + output = &line->output; + if (output->state != VFE_OUTPUT_OFF) { + dev_err(to_device(vfe), "Output is running\n"); + goto error; + } + output->state = VFE_OUTPUT_RESERVED; + + output->active_buf = 0; + + for (i = 0; i < output->wm_num; i++) { + wm_idx = vfe_reserve_wm(vfe, line->id); + if (wm_idx < 0) { + dev_err(to_device(vfe), "Can not reserve wm\n"); + goto error_get_wm; + } + output->wm_idx[i] = wm_idx; + } + + output->drop_update_idx = 0; + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; + +error_get_wm: + for (i--; i >= 0; i--) + vfe_release_wm(vfe, output->wm_idx[i]); + output->state = VFE_OUTPUT_OFF; +error: + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return -EINVAL; +} + +static int vfe_put_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&vfe->output_lock, flags); + + for (i = 0; i < output->wm_num; i++) + vfe_release_wm(vfe, output->wm_idx[i]); + + output->state = VFE_OUTPUT_OFF; + + spin_unlock_irqrestore(&vfe->output_lock, flags); + return 0; +} + +static int vfe_enable_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + unsigned long flags; + unsigned int i; + u16 ub_size; + + switch (vfe->id) { + case 0: + ub_size = MSM_VFE_VFE0_UB_SIZE_RDI; + break; + case 1: + ub_size = MSM_VFE_VFE1_UB_SIZE_RDI; + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&vfe->output_lock, flags); + + vfe->reg_update &= ~VFE_0_REG_UPDATE_line_n(line->id); + + if (output->state != VFE_OUTPUT_RESERVED) { + dev_err(to_device(vfe), "Output is not in reserved state %d\n", + output->state); + spin_unlock_irqrestore(&vfe->output_lock, flags); + return -EINVAL; + } + output->state = VFE_OUTPUT_IDLE; + + output->buf[0] = vfe_buf_get_pending(output); + output->buf[1] = vfe_buf_get_pending(output); + + if (!output->buf[0] && output->buf[1]) { + output->buf[0] = output->buf[1]; + output->buf[1] = NULL; + } + + if (output->buf[0]) + output->state = VFE_OUTPUT_SINGLE; + + if (output->buf[1]) + output->state = VFE_OUTPUT_CONTINUOUS; + + switch (output->state) { + case VFE_OUTPUT_SINGLE: + vfe_output_frame_drop(vfe, output, 1); + break; + case VFE_OUTPUT_CONTINUOUS: + vfe_output_frame_drop(vfe, output, 3); + break; + default: + vfe_output_frame_drop(vfe, output, 0); + break; + } + + output->sequence = 0; + output->wait_sof = 0; + output->wait_reg_update = 0; + reinit_completion(&output->sof); + reinit_completion(&output->reg_update); + + vfe_output_init_addrs(vfe, output, 0); + + if (line->id != VFE_LINE_PIX) { + vfe_set_cgc_override(vfe, output->wm_idx[0], 1); + vfe_enable_irq_wm_line(vfe, output->wm_idx[0], line->id, 1); + vfe_bus_connect_wm_to_rdi(vfe, output->wm_idx[0], line->id); + vfe_wm_set_subsample(vfe, output->wm_idx[0]); + vfe_set_rdi_cid(vfe, line->id, 0); + vfe_wm_set_ub_cfg(vfe, output->wm_idx[0], + (ub_size + 1) * output->wm_idx[0], ub_size); + vfe_wm_frame_based(vfe, output->wm_idx[0], 1); + vfe_wm_enable(vfe, output->wm_idx[0], 1); + vfe_bus_reload_wm(vfe, output->wm_idx[0]); + } else { + ub_size /= output->wm_num; + for (i = 0; i < output->wm_num; i++) { + vfe_set_cgc_override(vfe, output->wm_idx[i], 1); + vfe_wm_set_subsample(vfe, output->wm_idx[i]); + vfe_wm_set_ub_cfg(vfe, output->wm_idx[i], + (ub_size + 1) * output->wm_idx[i], + ub_size); + vfe_wm_line_based(vfe, output->wm_idx[i], + &line->video_out.active_fmt.fmt.pix_mp, + i, 1); + vfe_wm_enable(vfe, output->wm_idx[i], 1); + vfe_bus_reload_wm(vfe, output->wm_idx[i]); + } + vfe_enable_irq_pix_line(vfe, 0, line->id, 1); + vfe_set_module_cfg(vfe, 1); + vfe_set_camif_cfg(vfe, line); + vfe_set_xbar_cfg(vfe, output, 1); + vfe_set_demux_cfg(vfe, line); + vfe_set_scale_cfg(vfe, line); + vfe_set_crop_cfg(vfe, line); + vfe_set_clamp_cfg(vfe); + vfe_set_camif_cmd(vfe, VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY); + } + + vfe_reg_update(vfe, line->id); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +static int vfe_disable_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + unsigned long flags; + unsigned long time; + unsigned int i; + + spin_lock_irqsave(&vfe->output_lock, flags); + + output->wait_sof = 1; + spin_unlock_irqrestore(&vfe->output_lock, flags); + + time = wait_for_completion_timeout(&output->sof, + msecs_to_jiffies(VFE_NEXT_SOF_MS)); + if (!time) + dev_err(to_device(vfe), "VFE sof timeout\n"); + + spin_lock_irqsave(&vfe->output_lock, flags); + for (i = 0; i < output->wm_num; i++) + vfe_wm_enable(vfe, output->wm_idx[i], 0); + + vfe_reg_update(vfe, line->id); + output->wait_reg_update = 1; + spin_unlock_irqrestore(&vfe->output_lock, flags); + + time = wait_for_completion_timeout(&output->reg_update, + msecs_to_jiffies(VFE_NEXT_SOF_MS)); + if (!time) + dev_err(to_device(vfe), "VFE reg update timeout\n"); + + spin_lock_irqsave(&vfe->output_lock, flags); + + if (line->id != VFE_LINE_PIX) { + vfe_wm_frame_based(vfe, output->wm_idx[0], 0); + vfe_bus_disconnect_wm_from_rdi(vfe, output->wm_idx[0], line->id); + vfe_enable_irq_wm_line(vfe, output->wm_idx[0], line->id, 0); + vfe_set_cgc_override(vfe, output->wm_idx[0], 0); + spin_unlock_irqrestore(&vfe->output_lock, flags); + } else { + for (i = 0; i < output->wm_num; i++) { + vfe_wm_line_based(vfe, output->wm_idx[i], NULL, i, 0); + vfe_set_cgc_override(vfe, output->wm_idx[i], 0); + } + + vfe_enable_irq_pix_line(vfe, 0, line->id, 0); + vfe_set_module_cfg(vfe, 0); + vfe_set_xbar_cfg(vfe, output, 0); + + vfe_set_camif_cmd(vfe, VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY); + spin_unlock_irqrestore(&vfe->output_lock, flags); + + vfe_camif_wait_for_stop(vfe); + } + + return 0; +} + +/* + * vfe_enable - Enable streaming on VFE line + * @line: VFE line + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_enable(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + int ret; + + mutex_lock(&vfe->stream_lock); + + if (!vfe->stream_count) { + vfe_enable_irq_common(vfe); + + vfe_bus_enable_wr_if(vfe, 1); + + vfe_set_qos(vfe); + } + + vfe->stream_count++; + + mutex_unlock(&vfe->stream_lock); + + ret = vfe_get_output(line); + if (ret < 0) + goto error_get_output; + + ret = vfe_enable_output(line); + if (ret < 0) + goto error_enable_output; + + vfe->was_streaming = 1; + + return 0; + + +error_enable_output: + vfe_put_output(line); + +error_get_output: + mutex_lock(&vfe->stream_lock); + + if (vfe->stream_count == 1) + vfe_bus_enable_wr_if(vfe, 0); + + vfe->stream_count--; + + mutex_unlock(&vfe->stream_lock); + + return ret; +} + +/* + * vfe_disable - Disable streaming on VFE line + * @line: VFE line + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_disable(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + + vfe_disable_output(line); + + vfe_put_output(line); + + mutex_lock(&vfe->stream_lock); + + if (vfe->stream_count == 1) + vfe_bus_enable_wr_if(vfe, 0); + + vfe->stream_count--; + + mutex_unlock(&vfe->stream_lock); + + return 0; +} + +/* + * vfe_isr_sof - Process start of frame interrupt + * @vfe: VFE Device + * @line_id: VFE line + */ +static void vfe_isr_sof(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + struct vfe_output *output; + unsigned long flags; + + spin_lock_irqsave(&vfe->output_lock, flags); + output = &vfe->line[line_id].output; + if (output->wait_sof) { + output->wait_sof = 0; + complete(&output->sof); + } + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_isr_reg_update - Process reg update interrupt + * @vfe: VFE Device + * @line_id: VFE line + */ +static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + struct vfe_output *output; + unsigned long flags; + + spin_lock_irqsave(&vfe->output_lock, flags); + vfe->reg_update &= ~VFE_0_REG_UPDATE_line_n(line_id); + + output = &vfe->line[line_id].output; + + if (output->wait_reg_update) { + output->wait_reg_update = 0; + complete(&output->reg_update); + spin_unlock_irqrestore(&vfe->output_lock, flags); + return; + } + + if (output->state == VFE_OUTPUT_STOPPING) { + /* Release last buffer when hw is idle */ + if (output->last_buffer) { + vb2_buffer_done(&output->last_buffer->vb.vb2_buf, + VB2_BUF_STATE_DONE); + output->last_buffer = NULL; + } + output->state = VFE_OUTPUT_IDLE; + + /* Buffers received in stopping state are queued in */ + /* dma pending queue, start next capture here */ + + output->buf[0] = vfe_buf_get_pending(output); + output->buf[1] = vfe_buf_get_pending(output); + + if (!output->buf[0] && output->buf[1]) { + output->buf[0] = output->buf[1]; + output->buf[1] = NULL; + } + + if (output->buf[0]) + output->state = VFE_OUTPUT_SINGLE; + + if (output->buf[1]) + output->state = VFE_OUTPUT_CONTINUOUS; + + switch (output->state) { + case VFE_OUTPUT_SINGLE: + vfe_output_frame_drop(vfe, output, 2); + break; + case VFE_OUTPUT_CONTINUOUS: + vfe_output_frame_drop(vfe, output, 3); + break; + default: + vfe_output_frame_drop(vfe, output, 0); + break; + } + + vfe_output_init_addrs(vfe, output, 1); + } + + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_isr_wm_done - Process write master done interrupt + * @vfe: VFE Device + * @wm: Write master id + */ +static void vfe_isr_wm_done(struct vfe_device *vfe, u8 wm) +{ + struct camss_buffer *ready_buf; + struct vfe_output *output; + dma_addr_t *new_addr; + unsigned long flags; + u32 active_index; + u64 ts = ktime_get_ns(); + unsigned int i; + + active_index = vfe_wm_get_ping_pong_status(vfe, wm); + + spin_lock_irqsave(&vfe->output_lock, flags); + + if (vfe->wm_output_map[wm] == VFE_LINE_NONE) { + dev_err_ratelimited(to_device(vfe), + "Received wm done for unmapped index\n"); + goto out_unlock; + } + output = &vfe->line[vfe->wm_output_map[wm]].output; + + if (output->active_buf == active_index) { + dev_err_ratelimited(to_device(vfe), + "Active buffer mismatch!\n"); + goto out_unlock; + } + output->active_buf = active_index; + + ready_buf = output->buf[!active_index]; + if (!ready_buf) { + dev_err_ratelimited(to_device(vfe), + "Missing ready buf %d %d!\n", + !active_index, output->state); + goto out_unlock; + } + + ready_buf->vb.vb2_buf.timestamp = ts; + ready_buf->vb.sequence = output->sequence++; + + /* Get next buffer */ + output->buf[!active_index] = vfe_buf_get_pending(output); + if (!output->buf[!active_index]) { + /* No next buffer - set same address */ + new_addr = ready_buf->addr; + vfe_buf_update_wm_on_last(vfe, output); + } else { + new_addr = output->buf[!active_index]->addr; + vfe_buf_update_wm_on_next(vfe, output); + } + + if (active_index) + for (i = 0; i < output->wm_num; i++) + vfe_wm_set_ping_addr(vfe, output->wm_idx[i], + new_addr[i]); + else + for (i = 0; i < output->wm_num; i++) + vfe_wm_set_pong_addr(vfe, output->wm_idx[i], + new_addr[i]); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + if (output->state == VFE_OUTPUT_STOPPING) + output->last_buffer = ready_buf; + else + vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + + return; + +out_unlock: + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_isr_wm_done - Process composite image done interrupt + * @vfe: VFE Device + * @comp: Composite image id + */ +static void vfe_isr_comp_done(struct vfe_device *vfe, u8 comp) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) + if (vfe->wm_output_map[i] == VFE_LINE_PIX) { + vfe_isr_wm_done(vfe, i); + break; + } +} + +/* + * vfe_isr - ISPIF module interrupt handler + * @irq: Interrupt line + * @dev: VFE device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t vfe_isr(int irq, void *dev) +{ + struct vfe_device *vfe = dev; + u32 value0, value1; + u32 violation; + int i, j; + + value0 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_0); + value1 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_1); + + writel_relaxed(value0, vfe->base + VFE_0_IRQ_CLEAR_0); + writel_relaxed(value1, vfe->base + VFE_0_IRQ_CLEAR_1); + + wmb(); + writel_relaxed(VFE_0_IRQ_CMD_GLOBAL_CLEAR, vfe->base + VFE_0_IRQ_CMD); + + if (value0 & VFE_0_IRQ_STATUS_0_RESET_ACK) + complete(&vfe->reset_complete); + + if (value1 & VFE_0_IRQ_STATUS_1_VIOLATION) { + violation = readl_relaxed(vfe->base + VFE_0_VIOLATION_STATUS); + dev_err_ratelimited(to_device(vfe), + "VFE: violation = 0x%08x\n", violation); + } + + if (value1 & VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK) { + complete(&vfe->halt_complete); + writel_relaxed(0x0, vfe->base + VFE_0_BUS_BDG_CMD); + } + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) + if (value0 & VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(i)) + vfe_isr_reg_update(vfe, i); + + if (value0 & VFE_0_IRQ_STATUS_0_CAMIF_SOF) + vfe_isr_sof(vfe, VFE_LINE_PIX); + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_RDI2; i++) + if (value1 & VFE_0_IRQ_STATUS_1_RDIn_SOF(i)) + vfe_isr_sof(vfe, i); + + for (i = 0; i < MSM_VFE_COMPOSITE_IRQ_NUM; i++) + if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(i)) { + vfe_isr_comp_done(vfe, i); + for (j = 0; j < ARRAY_SIZE(vfe->wm_output_map); j++) + if (vfe->wm_output_map[j] == VFE_LINE_PIX) + value0 &= ~VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(j); + } + + for (i = 0; i < MSM_VFE_IMAGE_MASTERS_NUM; i++) + if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(i)) + vfe_isr_wm_done(vfe, i); + + return IRQ_HANDLED; +} + +/* + * vfe_set_clock_rates - Calculate and set clock rates on VFE module + * @vfe: VFE device + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_set_clock_rates(struct vfe_device *vfe) +{ + struct device *dev = to_device(vfe); + u32 pixel_clock[MSM_VFE_LINE_NUM]; + int i, j; + int ret; + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) { + ret = camss_get_pixel_clock(&vfe->line[i].subdev.entity, + &pixel_clock[i]); + if (ret) + pixel_clock[i] = 0; + } + + for (i = 0; i < vfe->nclocks; i++) { + struct camss_clock *clock = &vfe->clock[i]; + + if (!strcmp(clock->name, "camss_vfe_vfe")) { + u64 min_rate = 0; + long rate; + + for (j = VFE_LINE_RDI0; j <= VFE_LINE_PIX; j++) { + u32 tmp; + u8 bpp; + + if (j == VFE_LINE_PIX) { + tmp = pixel_clock[j]; + } else { + bpp = vfe_get_bpp(vfe->line[j]. + fmt[MSM_VFE_PAD_SINK].code); + tmp = pixel_clock[j] * bpp / 64; + } + + if (min_rate < tmp) + min_rate = tmp; + } + + camss_add_clock_margin(&min_rate); + + for (j = 0; j < clock->nfreqs; j++) + if (min_rate < clock->freq[j]) + break; + + if (j == clock->nfreqs) { + dev_err(dev, + "Pixel clock is too high for VFE"); + return -EINVAL; + } + + /* if sensor pixel clock is not available */ + /* set highest possible VFE clock rate */ + if (min_rate == 0) + j = clock->nfreqs - 1; + + rate = clk_round_rate(clock->clk, clock->freq[j]); + if (rate < 0) { + dev_err(dev, "clk round rate failed: %ld\n", + rate); + return -EINVAL; + } + + ret = clk_set_rate(clock->clk, rate); + if (ret < 0) { + dev_err(dev, "clk set rate failed: %d\n", ret); + return ret; + } + } + } + + return 0; +} + +/* + * vfe_check_clock_rates - Check current clock rates on VFE module + * @vfe: VFE device + * + * Return 0 if current clock rates are suitable for a new pipeline + * or a negative error code otherwise + */ +static int vfe_check_clock_rates(struct vfe_device *vfe) +{ + u32 pixel_clock[MSM_VFE_LINE_NUM]; + int i, j; + int ret; + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) { + ret = camss_get_pixel_clock(&vfe->line[i].subdev.entity, + &pixel_clock[i]); + if (ret) + pixel_clock[i] = 0; + } + + for (i = 0; i < vfe->nclocks; i++) { + struct camss_clock *clock = &vfe->clock[i]; + + if (!strcmp(clock->name, "camss_vfe_vfe")) { + u64 min_rate = 0; + unsigned long rate; + + for (j = VFE_LINE_RDI0; j <= VFE_LINE_PIX; j++) { + u32 tmp; + u8 bpp; + + if (j == VFE_LINE_PIX) { + tmp = pixel_clock[j]; + } else { + bpp = vfe_get_bpp(vfe->line[j]. + fmt[MSM_VFE_PAD_SINK].code); + tmp = pixel_clock[j] * bpp / 64; + } + + if (min_rate < tmp) + min_rate = tmp; + } + + camss_add_clock_margin(&min_rate); + + rate = clk_get_rate(clock->clk); + if (rate < min_rate) + return -EBUSY; + } + } + + return 0; +} + +/* + * vfe_get - Power up and reset VFE module + * @vfe: VFE Device + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_get(struct vfe_device *vfe) +{ + int ret; + + mutex_lock(&vfe->power_lock); + + if (vfe->power_count == 0) { + ret = vfe_set_clock_rates(vfe); + if (ret < 0) + goto error_clocks; + + ret = camss_enable_clocks(vfe->nclocks, vfe->clock, + to_device(vfe)); + if (ret < 0) + goto error_clocks; + + ret = vfe_reset(vfe); + if (ret < 0) + goto error_reset; + + vfe_reset_output_maps(vfe); + + vfe_init_outputs(vfe); + } else { + ret = vfe_check_clock_rates(vfe); + if (ret < 0) + goto error_clocks; + } + vfe->power_count++; + + mutex_unlock(&vfe->power_lock); + + return 0; + +error_reset: + camss_disable_clocks(vfe->nclocks, vfe->clock); + +error_clocks: + mutex_unlock(&vfe->power_lock); + + return ret; +} + +/* + * vfe_put - Power down VFE module + * @vfe: VFE Device + */ +static void vfe_put(struct vfe_device *vfe) +{ + mutex_lock(&vfe->power_lock); + + if (vfe->power_count == 0) { + dev_err(to_device(vfe), "vfe power off on power_count == 0\n"); + goto exit; + } else if (vfe->power_count == 1) { + if (vfe->was_streaming) { + vfe->was_streaming = 0; + vfe_halt(vfe); + } + camss_disable_clocks(vfe->nclocks, vfe->clock); + } + + vfe->power_count--; + +exit: + mutex_unlock(&vfe->power_lock); +} + +/* + * vfe_video_pad_to_line - Get pointer to VFE line by media pad + * @pad: Media pad + * + * Return pointer to vfe line structure + */ +static struct vfe_line *vfe_video_pad_to_line(struct media_pad *pad) +{ + struct media_pad *vfe_pad; + struct v4l2_subdev *subdev; + + vfe_pad = media_entity_remote_pad(pad); + if (vfe_pad == NULL) + return NULL; + + subdev = media_entity_to_v4l2_subdev(vfe_pad->entity); + + return container_of(subdev, struct vfe_line, subdev); +} + +/* + * vfe_queue_buffer - Add empty buffer + * @vid: Video device structure + * @buf: Buffer to be enqueued + * + * Add an empty buffer - depending on the current number of buffers it will be + * put in pending buffer queue or directly given to the hardware to be filled. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_queue_buffer(struct camss_video *vid, + struct camss_buffer *buf) +{ + struct vfe_device *vfe = &vid->camss->vfe; + struct vfe_line *line; + struct vfe_output *output; + unsigned long flags; + + line = vfe_video_pad_to_line(&vid->pad); + if (!line) { + dev_err(to_device(vfe), "Can not queue buffer\n"); + return -1; + } + output = &line->output; + + spin_lock_irqsave(&vfe->output_lock, flags); + + vfe_buf_update_wm_on_new(vfe, output, buf); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +/* + * vfe_flush_buffers - Return all vb2 buffers + * @vid: Video device structure + * @state: vb2 buffer state of the returned buffers + * + * Return all buffers to vb2. This includes queued pending buffers (still + * unused) and any buffers given to the hardware but again still not used. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_flush_buffers(struct camss_video *vid, + enum vb2_buffer_state state) +{ + struct vfe_device *vfe = &vid->camss->vfe; + struct vfe_line *line; + struct vfe_output *output; + unsigned long flags; + + line = vfe_video_pad_to_line(&vid->pad); + if (!line) { + dev_err(to_device(vfe), "Can not flush buffers\n"); + return -1; + } + output = &line->output; + + spin_lock_irqsave(&vfe->output_lock, flags); + + vfe_buf_flush_pending(output, state); + + if (output->buf[0]) + vb2_buffer_done(&output->buf[0]->vb.vb2_buf, state); + + if (output->buf[1]) + vb2_buffer_done(&output->buf[1]->vb.vb2_buf, state); + + if (output->last_buffer) { + vb2_buffer_done(&output->last_buffer->vb.vb2_buf, state); + output->last_buffer = NULL; + } + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +/* + * vfe_set_power - Power on/off VFE module + * @sd: VFE V4L2 subdevice + * @on: Requested power state + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_set_power(struct v4l2_subdev *sd, int on) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct vfe_device *vfe = to_vfe(line); + int ret; + + if (on) { + u32 hw_version; + + ret = vfe_get(vfe); + if (ret < 0) + return ret; + + hw_version = readl_relaxed(vfe->base + VFE_0_HW_VERSION); + dev_dbg(to_device(vfe), + "VFE HW Version = 0x%08x\n", hw_version); + } else { + vfe_put(vfe); + } + + return 0; +} + +/* + * vfe_set_stream - Enable/disable streaming on VFE module + * @sd: VFE V4L2 subdevice + * @enable: Requested streaming state + * + * Main configuration of VFE module is triggered here. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct vfe_device *vfe = to_vfe(line); + int ret; + + if (enable) { + ret = vfe_enable(line); + if (ret < 0) + dev_err(to_device(vfe), + "Failed to enable vfe outputs\n"); + } else { + ret = vfe_disable(line); + if (ret < 0) + dev_err(to_device(vfe), + "Failed to disable vfe outputs\n"); + } + + return ret; +} + +/* + * __vfe_get_format - Get pointer to format structure + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @pad: pad from which format is requested + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE format structure + */ +static struct v4l2_mbus_framefmt * +__vfe_get_format(struct vfe_line *line, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&line->subdev, cfg, pad); + + return &line->fmt[pad]; +} + +/* + * __vfe_get_compose - Get pointer to compose selection structure + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE compose rectangle structure + */ +static struct v4l2_rect * +__vfe_get_compose(struct vfe_line *line, + struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_compose(&line->subdev, cfg, + MSM_VFE_PAD_SINK); + + return &line->compose; +} + +/* + * __vfe_get_crop - Get pointer to crop selection structure + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE crop rectangle structure + */ +static struct v4l2_rect * +__vfe_get_crop(struct vfe_line *line, + struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&line->subdev, cfg, + MSM_VFE_PAD_SRC); + + return &line->crop; +} + +/* + * vfe_try_format - Handle try format by pad subdev method + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @pad: pad on which format is requested + * @fmt: pointer to v4l2 format structure + * @which: wanted subdev format + */ +static void vfe_try_format(struct vfe_line *line, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + u32 code; + + switch (pad) { + case MSM_VFE_PAD_SINK: + /* Set format on sink pad */ + + for (i = 0; i < ARRAY_SIZE(vfe_formats); i++) + if (fmt->code == vfe_formats[i].code) + break; + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(vfe_formats)) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + break; + + case MSM_VFE_PAD_SRC: + /* Set and return a format same as sink pad */ + + code = fmt->code; + + *fmt = *__vfe_get_format(line, cfg, MSM_VFE_PAD_SINK, + which); + + if (line->id == VFE_LINE_PIX) { + struct v4l2_rect *rect; + + rect = __vfe_get_crop(line, cfg, which); + + fmt->width = rect->width; + fmt->height = rect->height; + + switch (fmt->code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + if (code == MEDIA_BUS_FMT_YUYV8_1_5X8) + fmt->code = MEDIA_BUS_FMT_YUYV8_1_5X8; + else + fmt->code = MEDIA_BUS_FMT_YUYV8_2X8; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + if (code == MEDIA_BUS_FMT_YVYU8_1_5X8) + fmt->code = MEDIA_BUS_FMT_YVYU8_1_5X8; + else + fmt->code = MEDIA_BUS_FMT_YVYU8_2X8; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + if (code == MEDIA_BUS_FMT_UYVY8_1_5X8) + fmt->code = MEDIA_BUS_FMT_UYVY8_1_5X8; + else + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + if (code == MEDIA_BUS_FMT_VYUY8_1_5X8) + fmt->code = MEDIA_BUS_FMT_VYUY8_1_5X8; + else + fmt->code = MEDIA_BUS_FMT_VYUY8_2X8; + break; + } + } + + break; + } + + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +/* + * vfe_try_compose - Handle try compose selection by pad subdev method + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @rect: pointer to v4l2 rect structure + * @which: wanted subdev format + */ +static void vfe_try_compose(struct vfe_line *line, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_rect *rect, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = __vfe_get_format(line, cfg, MSM_VFE_PAD_SINK, which); + + if (rect->width > fmt->width) + rect->width = fmt->width; + + if (rect->height > fmt->height) + rect->height = fmt->height; + + if (fmt->width > rect->width * SCALER_RATIO_MAX) + rect->width = (fmt->width + SCALER_RATIO_MAX - 1) / + SCALER_RATIO_MAX; + + rect->width &= ~0x1; + + if (fmt->height > rect->height * SCALER_RATIO_MAX) + rect->height = (fmt->height + SCALER_RATIO_MAX - 1) / + SCALER_RATIO_MAX; + + if (rect->width < 16) + rect->width = 16; + + if (rect->height < 4) + rect->height = 4; +} + +/* + * vfe_try_crop - Handle try crop selection by pad subdev method + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @rect: pointer to v4l2 rect structure + * @which: wanted subdev format + */ +static void vfe_try_crop(struct vfe_line *line, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_rect *rect, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_rect *compose; + + compose = __vfe_get_compose(line, cfg, which); + + if (rect->width > compose->width) + rect->width = compose->width; + + if (rect->width + rect->left > compose->width) + rect->left = compose->width - rect->width; + + if (rect->height > compose->height) + rect->height = compose->height; + + if (rect->height + rect->top > compose->height) + rect->top = compose->height - rect->height; + + /* wm in line based mode writes multiple of 16 horizontally */ + rect->left += (rect->width & 0xf) >> 1; + rect->width &= ~0xf; + + if (rect->width < 16) { + rect->left = 0; + rect->width = 16; + } + + if (rect->height < 4) { + rect->top = 0; + rect->height = 4; + } +} + +/* + * vfe_enum_mbus_code - Handle pixel format enumeration + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * + * return -EINVAL or zero on success + */ +static int vfe_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + if (code->pad == MSM_VFE_PAD_SINK) { + if (code->index >= ARRAY_SIZE(vfe_formats)) + return -EINVAL; + + code->code = vfe_formats[code->index].code; + } else { + if (code->index > 0) + return -EINVAL; + + format = __vfe_get_format(line, cfg, MSM_VFE_PAD_SINK, + code->which); + + code->code = format->code; + } + + return 0; +} + +/* + * vfe_enum_frame_size - Handle frame size enumeration + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: pointer to v4l2_subdev_frame_size_enum structure + * + * Return -EINVAL or zero on success + */ +static int vfe_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + vfe_try_format(line, cfg, fse->pad, &format, fse->which); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + vfe_try_format(line, cfg, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * vfe_get_format - Handle get format by pads subdev method + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int vfe_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __vfe_get_format(line, cfg, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +static int vfe_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel); + +/* + * vfe_set_format - Handle set format by pads subdev method + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int vfe_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __vfe_get_format(line, cfg, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + vfe_try_format(line, cfg, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + if (fmt->pad == MSM_VFE_PAD_SINK) { + struct v4l2_subdev_selection sel = { 0 }; + int ret; + + /* Propagate the format from sink to source */ + format = __vfe_get_format(line, cfg, MSM_VFE_PAD_SRC, + fmt->which); + + *format = fmt->format; + vfe_try_format(line, cfg, MSM_VFE_PAD_SRC, format, + fmt->which); + + if (line->id != VFE_LINE_PIX) + return 0; + + /* Reset sink pad compose selection */ + sel.which = fmt->which; + sel.pad = MSM_VFE_PAD_SINK; + sel.target = V4L2_SEL_TGT_COMPOSE; + sel.r.width = fmt->format.width; + sel.r.height = fmt->format.height; + ret = vfe_set_selection(sd, cfg, &sel); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * vfe_get_selection - Handle get selection by pads subdev method + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @sel: pointer to v4l2 subdev selection structure + * + * Return -EINVAL or zero on success + */ +static int vfe_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_subdev_format fmt = { 0 }; + struct v4l2_rect *rect; + int ret; + + if (line->id != VFE_LINE_PIX) + return -EINVAL; + + if (sel->pad == MSM_VFE_PAD_SINK) + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + fmt.pad = sel->pad; + fmt.which = sel->which; + ret = vfe_get_format(sd, cfg, &fmt); + if (ret < 0) + return ret; + + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = fmt.format.width; + sel->r.height = fmt.format.height; + break; + case V4L2_SEL_TGT_COMPOSE: + rect = __vfe_get_compose(line, cfg, sel->which); + if (rect == NULL) + return -EINVAL; + + sel->r = *rect; + break; + default: + return -EINVAL; + } + else if (sel->pad == MSM_VFE_PAD_SRC) + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + rect = __vfe_get_compose(line, cfg, sel->which); + if (rect == NULL) + return -EINVAL; + + sel->r.left = rect->left; + sel->r.top = rect->top; + sel->r.width = rect->width; + sel->r.height = rect->height; + break; + case V4L2_SEL_TGT_CROP: + rect = __vfe_get_crop(line, cfg, sel->which); + if (rect == NULL) + return -EINVAL; + + sel->r = *rect; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * vfe_set_selection - Handle set selection by pads subdev method + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @sel: pointer to v4l2 subdev selection structure + * + * Return -EINVAL or zero on success + */ +static int vfe_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_rect *rect; + int ret; + + if (line->id != VFE_LINE_PIX) + return -EINVAL; + + if (sel->target == V4L2_SEL_TGT_COMPOSE && + sel->pad == MSM_VFE_PAD_SINK) { + struct v4l2_subdev_selection crop = { 0 }; + + rect = __vfe_get_compose(line, cfg, sel->which); + if (rect == NULL) + return -EINVAL; + + vfe_try_compose(line, cfg, &sel->r, sel->which); + *rect = sel->r; + + /* Reset source crop selection */ + crop.which = sel->which; + crop.pad = MSM_VFE_PAD_SRC; + crop.target = V4L2_SEL_TGT_CROP; + crop.r = *rect; + ret = vfe_set_selection(sd, cfg, &crop); + } else if (sel->target == V4L2_SEL_TGT_CROP && + sel->pad == MSM_VFE_PAD_SRC) { + struct v4l2_subdev_format fmt = { 0 }; + + rect = __vfe_get_crop(line, cfg, sel->which); + if (rect == NULL) + return -EINVAL; + + vfe_try_crop(line, cfg, &sel->r, sel->which); + *rect = sel->r; + + /* Reset source pad format width and height */ + fmt.which = sel->which; + fmt.pad = MSM_VFE_PAD_SRC; + ret = vfe_get_format(sd, cfg, &fmt); + if (ret < 0) + return ret; + + fmt.format.width = rect->width; + fmt.format.height = rect->height; + ret = vfe_set_format(sd, cfg, &fmt); + } else { + ret = -EINVAL; + } + + return ret; +} + +/* + * vfe_init_formats - Initialize formats on all pads + * @sd: VFE V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format = { + .pad = MSM_VFE_PAD_SINK, + .which = fh ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080 + } + }; + + return vfe_set_format(sd, fh ? fh->pad : NULL, &format); +} + +/* + * msm_vfe_subdev_init - Initialize VFE device structure and resources + * @vfe: VFE device + * @res: VFE module resources table + * + * Return 0 on success or a negative error code otherwise + */ +int msm_vfe_subdev_init(struct vfe_device *vfe, const struct resources *res) +{ + struct device *dev = to_device(vfe); + struct platform_device *pdev = to_platform_device(dev); + struct resource *r; + struct camss *camss = to_camss(vfe); + int i, j; + int ret; + + /* Memory */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg[0]); + vfe->base = devm_ioremap_resource(dev, r); + if (IS_ERR(vfe->base)) { + dev_err(dev, "could not map memory\n"); + return PTR_ERR(vfe->base); + } + + /* Interrupt */ + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + res->interrupt[0]); + if (!r) { + dev_err(dev, "missing IRQ\n"); + return -EINVAL; + } + + vfe->irq = r->start; + snprintf(vfe->irq_name, sizeof(vfe->irq_name), "%s_%s%d", + dev_name(dev), MSM_VFE_NAME, vfe->id); + ret = devm_request_irq(dev, vfe->irq, vfe_isr, + IRQF_TRIGGER_RISING, vfe->irq_name, vfe); + if (ret < 0) { + dev_err(dev, "request_irq failed: %d\n", ret); + return ret; + } + + /* Clocks */ + + vfe->nclocks = 0; + while (res->clock[vfe->nclocks]) + vfe->nclocks++; + + vfe->clock = devm_kcalloc(dev, vfe->nclocks, sizeof(*vfe->clock), + GFP_KERNEL); + if (!vfe->clock) + return -ENOMEM; + + for (i = 0; i < vfe->nclocks; i++) { + struct camss_clock *clock = &vfe->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->name = res->clock[i]; + + clock->nfreqs = 0; + while (res->clock_rate[i][clock->nfreqs]) + clock->nfreqs++; + + if (!clock->nfreqs) { + clock->freq = NULL; + continue; + } + + clock->freq = devm_kcalloc(dev, + clock->nfreqs, + sizeof(*clock->freq), + GFP_KERNEL); + if (!clock->freq) + return -ENOMEM; + + for (j = 0; j < clock->nfreqs; j++) + clock->freq[j] = res->clock_rate[i][j]; + } + + mutex_init(&vfe->power_lock); + vfe->power_count = 0; + + mutex_init(&vfe->stream_lock); + vfe->stream_count = 0; + + spin_lock_init(&vfe->output_lock); + + vfe->id = 0; + vfe->reg_update = 0; + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) { + vfe->line[i].video_out.type = + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + vfe->line[i].video_out.camss = camss; + vfe->line[i].id = i; + init_completion(&vfe->line[i].output.sof); + init_completion(&vfe->line[i].output.reg_update); + } + + init_completion(&vfe->reset_complete); + init_completion(&vfe->halt_complete); + + return 0; +} + +/* + * msm_vfe_get_vfe_id - Get VFE HW module id + * @entity: Pointer to VFE media entity structure + * @id: Return CSID HW module id here + */ +void msm_vfe_get_vfe_id(struct media_entity *entity, u8 *id) +{ + struct v4l2_subdev *sd; + struct vfe_line *line; + struct vfe_device *vfe; + + sd = media_entity_to_v4l2_subdev(entity); + line = v4l2_get_subdevdata(sd); + vfe = to_vfe(line); + + *id = vfe->id; +} + +/* + * msm_vfe_get_vfe_line_id - Get VFE line id by media entity + * @entity: Pointer to VFE media entity structure + * @id: Return VFE line id here + */ +void msm_vfe_get_vfe_line_id(struct media_entity *entity, enum vfe_line_id *id) +{ + struct v4l2_subdev *sd; + struct vfe_line *line; + + sd = media_entity_to_v4l2_subdev(entity); + line = v4l2_get_subdevdata(sd); + + *id = line->id; +} + +/* + * vfe_link_setup - Setup VFE connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad + * @remote: Pointer to remote pad + * @flags: Link flags + * + * Return 0 on success + */ +static int vfe_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (flags & MEDIA_LNK_FL_ENABLED) + if (media_entity_remote_pad(local)) + return -EBUSY; + + return 0; +} + +static const struct v4l2_subdev_core_ops vfe_core_ops = { + .s_power = vfe_set_power, +}; + +static const struct v4l2_subdev_video_ops vfe_video_ops = { + .s_stream = vfe_set_stream, +}; + +static const struct v4l2_subdev_pad_ops vfe_pad_ops = { + .enum_mbus_code = vfe_enum_mbus_code, + .enum_frame_size = vfe_enum_frame_size, + .get_fmt = vfe_get_format, + .set_fmt = vfe_set_format, + .get_selection = vfe_get_selection, + .set_selection = vfe_set_selection, +}; + +static const struct v4l2_subdev_ops vfe_v4l2_ops = { + .core = &vfe_core_ops, + .video = &vfe_video_ops, + .pad = &vfe_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops vfe_v4l2_internal_ops = { + .open = vfe_init_formats, +}; + +static const struct media_entity_operations vfe_media_ops = { + .link_setup = vfe_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct camss_video_ops camss_vfe_video_ops = { + .queue_buffer = vfe_queue_buffer, + .flush_buffers = vfe_flush_buffers, +}; + +void msm_vfe_stop_streaming(struct vfe_device *vfe) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vfe->line); i++) + msm_video_stop_streaming(&vfe->line[i].video_out); +} + +/* + * msm_vfe_register_entities - Register subdev node for VFE module + * @vfe: VFE device + * @v4l2_dev: V4L2 device + * + * Initialize and register a subdev node for the VFE module. Then + * call msm_video_register() to register the video device node which + * will be connected to this subdev node. Then actually create the + * media link between them. + * + * Return 0 on success or a negative error code otherwise + */ +int msm_vfe_register_entities(struct vfe_device *vfe, + struct v4l2_device *v4l2_dev) +{ + struct device *dev = to_device(vfe); + struct v4l2_subdev *sd; + struct media_pad *pads; + struct camss_video *video_out; + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(vfe->line); i++) { + char name[32]; + + sd = &vfe->line[i].subdev; + pads = vfe->line[i].pads; + video_out = &vfe->line[i].video_out; + + v4l2_subdev_init(sd, &vfe_v4l2_ops); + sd->internal_ops = &vfe_v4l2_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + if (i == VFE_LINE_PIX) + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s", + MSM_VFE_NAME, vfe->id, "pix"); + else + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s%d", + MSM_VFE_NAME, vfe->id, "rdi", i); + + v4l2_set_subdevdata(sd, &vfe->line[i]); + + ret = vfe_init_formats(sd, NULL); + if (ret < 0) { + dev_err(dev, "Failed to init format: %d\n", ret); + goto error_init; + } + + pads[MSM_VFE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[MSM_VFE_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &vfe_media_ops; + ret = media_entity_pads_init(&sd->entity, MSM_VFE_PADS_NUM, + pads); + if (ret < 0) { + dev_err(dev, "Failed to init media entity: %d\n", ret); + goto error_init; + } + + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(dev, "Failed to register subdev: %d\n", ret); + goto error_reg_subdev; + } + + video_out->ops = &camss_vfe_video_ops; + video_out->bpl_alignment = 8; + video_out->line_based = 0; + if (i == VFE_LINE_PIX) { + video_out->bpl_alignment = 16; + video_out->line_based = 1; + } + snprintf(name, ARRAY_SIZE(name), "%s%d_%s%d", + MSM_VFE_NAME, vfe->id, "video", i); + ret = msm_video_register(video_out, v4l2_dev, name, + i == VFE_LINE_PIX ? 1 : 0); + if (ret < 0) { + dev_err(dev, "Failed to register video node: %d\n", + ret); + goto error_reg_video; + } + + ret = media_create_pad_link( + &sd->entity, MSM_VFE_PAD_SRC, + &video_out->vdev.entity, 0, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret < 0) { + dev_err(dev, "Failed to link %s->%s entities: %d\n", + sd->entity.name, video_out->vdev.entity.name, + ret); + goto error_link; + } + } + + return 0; + +error_link: + msm_video_unregister(video_out); + +error_reg_video: + v4l2_device_unregister_subdev(sd); + +error_reg_subdev: + media_entity_cleanup(&sd->entity); + +error_init: + for (i--; i >= 0; i--) { + sd = &vfe->line[i].subdev; + video_out = &vfe->line[i].video_out; + + msm_video_unregister(video_out); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } + + return ret; +} + +/* + * msm_vfe_unregister_entities - Unregister VFE module subdev node + * @vfe: VFE device + */ +void msm_vfe_unregister_entities(struct vfe_device *vfe) +{ + int i; + + mutex_destroy(&vfe->power_lock); + mutex_destroy(&vfe->stream_lock); + + for (i = 0; i < ARRAY_SIZE(vfe->line); i++) { + struct v4l2_subdev *sd = &vfe->line[i].subdev; + struct camss_video *video_out = &vfe->line[i].video_out; + + msm_video_unregister(video_out); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } +} diff --git a/drivers/media/platform/qcom/camss/camss-vfe.h b/drivers/media/platform/qcom/camss/camss-vfe.h new file mode 100644 index 000000000000..5aa740741de8 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe.h @@ -0,0 +1,123 @@ +/* + * camss-vfe.h + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#ifndef QC_MSM_CAMSS_VFE_H +#define QC_MSM_CAMSS_VFE_H + +#include +#include +#include +#include +#include + +#include "camss-video.h" + +#define MSM_VFE_PAD_SINK 0 +#define MSM_VFE_PAD_SRC 1 +#define MSM_VFE_PADS_NUM 2 + +#define MSM_VFE_LINE_NUM 4 +#define MSM_VFE_IMAGE_MASTERS_NUM 7 +#define MSM_VFE_COMPOSITE_IRQ_NUM 4 + +#define MSM_VFE_VFE0_UB_SIZE 1023 +#define MSM_VFE_VFE0_UB_SIZE_RDI (MSM_VFE_VFE0_UB_SIZE / 3) +#define MSM_VFE_VFE1_UB_SIZE 1535 +#define MSM_VFE_VFE1_UB_SIZE_RDI (MSM_VFE_VFE1_UB_SIZE / 3) + +enum vfe_output_state { + VFE_OUTPUT_OFF, + VFE_OUTPUT_RESERVED, + VFE_OUTPUT_SINGLE, + VFE_OUTPUT_CONTINUOUS, + VFE_OUTPUT_IDLE, + VFE_OUTPUT_STOPPING +}; + +enum vfe_line_id { + VFE_LINE_NONE = -1, + VFE_LINE_RDI0 = 0, + VFE_LINE_RDI1 = 1, + VFE_LINE_RDI2 = 2, + VFE_LINE_PIX = 3 +}; + +struct vfe_output { + u8 wm_num; + u8 wm_idx[3]; + + int active_buf; + struct camss_buffer *buf[2]; + struct camss_buffer *last_buffer; + struct list_head pending_bufs; + + unsigned int drop_update_idx; + + enum vfe_output_state state; + unsigned int sequence; + int wait_sof; + int wait_reg_update; + struct completion sof; + struct completion reg_update; +}; + +struct vfe_line { + enum vfe_line_id id; + struct v4l2_subdev subdev; + struct media_pad pads[MSM_VFE_PADS_NUM]; + struct v4l2_mbus_framefmt fmt[MSM_VFE_PADS_NUM]; + struct v4l2_rect compose; + struct v4l2_rect crop; + struct camss_video video_out; + struct vfe_output output; +}; + +struct vfe_device { + u8 id; + void __iomem *base; + u32 irq; + char irq_name[30]; + struct camss_clock *clock; + int nclocks; + struct completion reset_complete; + struct completion halt_complete; + struct mutex power_lock; + int power_count; + struct mutex stream_lock; + int stream_count; + spinlock_t output_lock; + enum vfe_line_id wm_output_map[MSM_VFE_IMAGE_MASTERS_NUM]; + struct vfe_line line[MSM_VFE_LINE_NUM]; + u32 reg_update; + u8 was_streaming; +}; + +struct resources; + +int msm_vfe_subdev_init(struct vfe_device *vfe, const struct resources *res); + +int msm_vfe_register_entities(struct vfe_device *vfe, + struct v4l2_device *v4l2_dev); + +void msm_vfe_unregister_entities(struct vfe_device *vfe); + +void msm_vfe_get_vfe_id(struct media_entity *entity, u8 *id); +void msm_vfe_get_vfe_line_id(struct media_entity *entity, enum vfe_line_id *id); + +void msm_vfe_stop_streaming(struct vfe_device *vfe); + +#endif /* QC_MSM_CAMSS_VFE_H */ diff --git a/drivers/media/platform/qcom/camss/camss-video.c b/drivers/media/platform/qcom/camss/camss-video.c new file mode 100644 index 000000000000..0e7b842854ec --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-video.c @@ -0,0 +1,859 @@ +/* + * camss-video.c + * + * Qualcomm MSM Camera Subsystem - V4L2 device node + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "camss-video.h" +#include "camss.h" + +struct fract { + u8 numerator; + u8 denominator; +}; + +/* + * struct camss_format_info - ISP media bus format information + * @code: V4L2 media bus format code + * @pixelformat: V4L2 pixel format FCC identifier + * @planes: Number of planes + * @hsub: Horizontal subsampling (for each plane) + * @vsub: Vertical subsampling (for each plane) + * @bpp: Bits per pixel when stored in memory (for each plane) + */ +struct camss_format_info { + u32 code; + u32 pixelformat; + u8 planes; + struct fract hsub[3]; + struct fract vsub[3]; + unsigned int bpp[3]; +}; + +static const struct camss_format_info formats_rdi[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_UYVY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_VYUY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_YUYV, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_YVYU, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_PIX_FMT_SBGGR8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_PIX_FMT_SGBRG8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_PIX_FMT_SGRBG8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_PIX_FMT_SRGGB8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_PIX_FMT_SBGGR10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_PIX_FMT_SGBRG10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_PIX_FMT_SGRBG10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_PIX_FMT_SRGGB10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SBGGR12_1X12, V4L2_PIX_FMT_SBGGR12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SGBRG12_1X12, V4L2_PIX_FMT_SGBRG12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SGRBG12_1X12, V4L2_PIX_FMT_SGRBG12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SRGGB12_1X12, V4L2_PIX_FMT_SRGGB12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, +}; + +static const struct camss_format_info formats_pix[] = { + { MEDIA_BUS_FMT_YUYV8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, +}; + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static int video_find_format(u32 code, u32 pixelformat, + const struct camss_format_info *formats, + unsigned int nformats) +{ + int i; + + for (i = 0; i < nformats; i++) { + if (formats[i].code == code && + formats[i].pixelformat == pixelformat) + return i; + } + + for (i = 0; i < nformats; i++) + if (formats[i].code == code) + return i; + + WARN_ON(1); + + return -EINVAL; +} + +/* + * video_mbus_to_pix_mp - Convert v4l2_mbus_framefmt to v4l2_pix_format_mplane + * @mbus: v4l2_mbus_framefmt format (input) + * @pix: v4l2_pix_format_mplane format (output) + * @f: a pointer to formats array element to be used for the conversion + * @alignment: bytesperline alignment value + * + * Fill the output pix structure with information from the input mbus format. + * + * Return 0 on success or a negative error code otherwise + */ +static int video_mbus_to_pix_mp(const struct v4l2_mbus_framefmt *mbus, + struct v4l2_pix_format_mplane *pix, + const struct camss_format_info *f, + unsigned int alignment) +{ + unsigned int i; + u32 bytesperline; + + memset(pix, 0, sizeof(*pix)); + v4l2_fill_pix_format_mplane(pix, mbus); + pix->pixelformat = f->pixelformat; + pix->num_planes = f->planes; + for (i = 0; i < pix->num_planes; i++) { + bytesperline = pix->width / f->hsub[i].numerator * + f->hsub[i].denominator * f->bpp[i] / 8; + bytesperline = ALIGN(bytesperline, alignment); + pix->plane_fmt[i].bytesperline = bytesperline; + pix->plane_fmt[i].sizeimage = pix->height / + f->vsub[i].numerator * f->vsub[i].denominator * + bytesperline; + } + + return 0; +} + +static struct v4l2_subdev *video_remote_subdev(struct camss_video *video, + u32 *pad) +{ + struct media_pad *remote; + + remote = media_entity_remote_pad(&video->pad); + + if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) + return NULL; + + if (pad) + *pad = remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +static int video_get_subdev_format(struct camss_video *video, + struct v4l2_format *format) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + u32 pad; + int ret; + + subdev = video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EPIPE; + + fmt.pad = pad; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + ret = video_find_format(fmt.format.code, + format->fmt.pix_mp.pixelformat, + video->formats, video->nformats); + if (ret < 0) + return ret; + + format->type = video->type; + + return video_mbus_to_pix_mp(&fmt.format, &format->fmt.pix_mp, + &video->formats[ret], video->bpl_alignment); +} + +/* ----------------------------------------------------------------------------- + * Video queue operations + */ + +static int video_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct camss_video *video = vb2_get_drv_priv(q); + const struct v4l2_pix_format_mplane *format = + &video->active_fmt.fmt.pix_mp; + unsigned int i; + + if (*num_planes) { + if (*num_planes != format->num_planes) + return -EINVAL; + + for (i = 0; i < *num_planes; i++) + if (sizes[i] < format->plane_fmt[i].sizeimage) + return -EINVAL; + + return 0; + } + + *num_planes = format->num_planes; + + for (i = 0; i < *num_planes; i++) + sizes[i] = format->plane_fmt[i].sizeimage; + + return 0; +} + +static int video_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer, + vb); + const struct v4l2_pix_format_mplane *format = + &video->active_fmt.fmt.pix_mp; + struct sg_table *sgt; + unsigned int i; + + for (i = 0; i < format->num_planes; i++) { + sgt = vb2_dma_sg_plane_desc(vb, i); + if (!sgt) + return -EFAULT; + + buffer->addr[i] = sg_dma_address(sgt->sgl); + } + + if (format->pixelformat == V4L2_PIX_FMT_NV12 || + format->pixelformat == V4L2_PIX_FMT_NV21 || + format->pixelformat == V4L2_PIX_FMT_NV16 || + format->pixelformat == V4L2_PIX_FMT_NV61) + buffer->addr[1] = buffer->addr[0] + + format->plane_fmt[0].bytesperline * + format->height; + + return 0; +} + +static int video_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); + const struct v4l2_pix_format_mplane *format = + &video->active_fmt.fmt.pix_mp; + unsigned int i; + + for (i = 0; i < format->num_planes; i++) { + if (format->plane_fmt[i].sizeimage > vb2_plane_size(vb, i)) + return -EINVAL; + + vb2_set_plane_payload(vb, i, format->plane_fmt[i].sizeimage); + } + + vbuf->field = V4L2_FIELD_NONE; + + return 0; +} + +static void video_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer, + vb); + + video->ops->queue_buffer(video, buffer); +} + +static int video_check_format(struct camss_video *video) +{ + struct v4l2_pix_format_mplane *pix = &video->active_fmt.fmt.pix_mp; + struct v4l2_format format; + struct v4l2_pix_format_mplane *sd_pix = &format.fmt.pix_mp; + int ret; + + sd_pix->pixelformat = pix->pixelformat; + ret = video_get_subdev_format(video, &format); + if (ret < 0) + return ret; + + if (pix->pixelformat != sd_pix->pixelformat || + pix->height != sd_pix->height || + pix->width != sd_pix->width || + pix->num_planes != sd_pix->num_planes || + pix->field != format.fmt.pix_mp.field) + return -EPIPE; + + return 0; +} + +static int video_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct camss_video *video = vb2_get_drv_priv(q); + struct video_device *vdev = &video->vdev; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + int ret; + + ret = media_pipeline_start(&vdev->entity, &video->pipe); + if (ret < 0) + return ret; + + ret = video_check_format(video); + if (ret < 0) + goto error; + + entity = &vdev->entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_entity_remote_pad(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto error; + } + + return 0; + +error: + media_pipeline_stop(&vdev->entity); + + video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void video_stop_streaming(struct vb2_queue *q) +{ + struct camss_video *video = vb2_get_drv_priv(q); + struct video_device *vdev = &video->vdev; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + + entity = &vdev->entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_entity_remote_pad(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + v4l2_subdev_call(subdev, video, s_stream, 0); + } + + media_pipeline_stop(&vdev->entity); + + video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops msm_video_vb2_q_ops = { + .queue_setup = video_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_init = video_buf_init, + .buf_prepare = video_buf_prepare, + .buf_queue = video_buf_queue, + .start_streaming = video_start_streaming, + .stop_streaming = video_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct camss_video *video = video_drvdata(file); + + strlcpy(cap->driver, "qcom-camss", sizeof(cap->driver)); + strlcpy(cap->card, "Qualcomm Camera Subsystem", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(video->camss->dev)); + + return 0; +} + +static int video_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct camss_video *video = video_drvdata(file); + int i, j, k; + + if (f->type != video->type) + return -EINVAL; + + if (f->index >= video->nformats) + return -EINVAL; + + /* find index "i" of "k"th unique pixelformat in formats array */ + k = -1; + for (i = 0; i < video->nformats; i++) { + for (j = 0; j < i; j++) { + if (video->formats[i].pixelformat == + video->formats[j].pixelformat) + break; + } + + if (j == i) + k++; + + if (k == f->index) + break; + } + + if (k < f->index) + return -EINVAL; + + f->pixelformat = video->formats[i].pixelformat; + + return 0; +} + +static int video_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct camss_video *video = video_drvdata(file); + + *f = video->active_fmt; + + return 0; +} + +static int __video_try_fmt(struct camss_video *video, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp; + const struct camss_format_info *fi; + struct v4l2_plane_pix_format *p; + u32 bytesperline[3] = { 0 }; + u32 sizeimage[3] = { 0 }; + u32 width, height; + u32 bpl, lines; + int i, j; + + pix_mp = &f->fmt.pix_mp; + + if (video->line_based) + for (i = 0; i < pix_mp->num_planes && i < 3; i++) { + p = &pix_mp->plane_fmt[i]; + bytesperline[i] = clamp_t(u32, p->bytesperline, + 1, 65528); + sizeimage[i] = clamp_t(u32, p->sizeimage, + bytesperline[i], + bytesperline[i] * 4096); + } + + for (j = 0; j < video->nformats; j++) + if (pix_mp->pixelformat == video->formats[j].pixelformat) + break; + + if (j == video->nformats) + j = 0; /* default format */ + + fi = &video->formats[j]; + width = pix_mp->width; + height = pix_mp->height; + + memset(pix_mp, 0, sizeof(*pix_mp)); + + pix_mp->pixelformat = fi->pixelformat; + pix_mp->width = clamp_t(u32, width, 1, 8191); + pix_mp->height = clamp_t(u32, height, 1, 8191); + pix_mp->num_planes = fi->planes; + for (i = 0; i < pix_mp->num_planes; i++) { + bpl = pix_mp->width / fi->hsub[i].numerator * + fi->hsub[i].denominator * fi->bpp[i] / 8; + bpl = ALIGN(bpl, video->bpl_alignment); + pix_mp->plane_fmt[i].bytesperline = bpl; + pix_mp->plane_fmt[i].sizeimage = pix_mp->height / + fi->vsub[i].numerator * fi->vsub[i].denominator * bpl; + } + + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->flags = 0; + pix_mp->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace); + pix_mp->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + pix_mp->colorspace, pix_mp->ycbcr_enc); + pix_mp->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix_mp->colorspace); + + if (video->line_based) + for (i = 0; i < pix_mp->num_planes; i++) { + p = &pix_mp->plane_fmt[i]; + p->bytesperline = clamp_t(u32, p->bytesperline, + 1, 65528); + p->sizeimage = clamp_t(u32, p->sizeimage, + p->bytesperline, + p->bytesperline * 4096); + lines = p->sizeimage / p->bytesperline; + + if (p->bytesperline < bytesperline[i]) + p->bytesperline = ALIGN(bytesperline[i], 8); + + if (p->sizeimage < p->bytesperline * lines) + p->sizeimage = p->bytesperline * lines; + + if (p->sizeimage < sizeimage[i]) + p->sizeimage = sizeimage[i]; + } + + return 0; +} + +static int video_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct camss_video *video = video_drvdata(file); + + return __video_try_fmt(video, f); +} + +static int video_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct camss_video *video = video_drvdata(file); + int ret; + + if (vb2_is_busy(&video->vb2_q)) + return -EBUSY; + + ret = __video_try_fmt(video, f); + if (ret < 0) + return ret; + + video->active_fmt = *f; + + return 0; +} + +static int video_enum_input(struct file *file, void *fh, + struct v4l2_input *input) +{ + if (input->index > 0) + return -EINVAL; + + strlcpy(input->name, "camera", sizeof(input->name)); + input->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int video_g_input(struct file *file, void *fh, unsigned int *input) +{ + *input = 0; + + return 0; +} + +static int video_s_input(struct file *file, void *fh, unsigned int input) +{ + return input == 0 ? 0 : -EINVAL; +} + +static const struct v4l2_ioctl_ops msm_vid_ioctl_ops = { + .vidioc_querycap = video_querycap, + .vidioc_enum_fmt_vid_cap_mplane = video_enum_fmt, + .vidioc_g_fmt_vid_cap_mplane = video_g_fmt, + .vidioc_s_fmt_vid_cap_mplane = video_s_fmt, + .vidioc_try_fmt_vid_cap_mplane = video_try_fmt, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_input = video_enum_input, + .vidioc_g_input = video_g_input, + .vidioc_s_input = video_s_input, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 file operations + */ + +static int video_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct camss_video *video = video_drvdata(file); + struct v4l2_fh *vfh; + int ret; + + mutex_lock(&video->lock); + + vfh = kzalloc(sizeof(*vfh), GFP_KERNEL); + if (vfh == NULL) { + ret = -ENOMEM; + goto error_alloc; + } + + v4l2_fh_init(vfh, vdev); + v4l2_fh_add(vfh); + + file->private_data = vfh; + + ret = v4l2_pipeline_pm_use(&vdev->entity, 1); + if (ret < 0) { + dev_err(video->camss->dev, "Failed to power up pipeline: %d\n", + ret); + goto error_pm_use; + } + + mutex_unlock(&video->lock); + + return 0; + +error_pm_use: + v4l2_fh_release(file); + +error_alloc: + mutex_unlock(&video->lock); + + return ret; +} + +static int video_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + + vb2_fop_release(file); + + v4l2_pipeline_pm_use(&vdev->entity, 0); + + file->private_data = NULL; + + return 0; +} + +static const struct v4l2_file_operations msm_vid_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = video_open, + .release = video_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +/* ----------------------------------------------------------------------------- + * CAMSS video core + */ + +static void msm_video_release(struct video_device *vdev) +{ + struct camss_video *video = video_get_drvdata(vdev); + + media_entity_cleanup(&vdev->entity); + + mutex_destroy(&video->q_lock); + mutex_destroy(&video->lock); + + if (atomic_dec_and_test(&video->camss->ref_count)) + camss_delete(video->camss); +} + +/* + * msm_video_init_format - Helper function to initialize format + * @video: struct camss_video + * + * Initialize pad format with default value. + * + * Return 0 on success or a negative error code otherwise + */ +static int msm_video_init_format(struct camss_video *video) +{ + int ret; + struct v4l2_format format = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .fmt.pix_mp = { + .width = 1920, + .height = 1080, + .pixelformat = video->formats[0].pixelformat, + }, + }; + + ret = __video_try_fmt(video, &format); + if (ret < 0) + return ret; + + video->active_fmt = format; + + return 0; +} + +/* + * msm_video_register - Register a video device node + * @video: struct camss_video + * @v4l2_dev: V4L2 device + * @name: name to be used for the video device node + * + * Initialize and register a video device node to a V4L2 device. Also + * initialize the vb2 queue. + * + * Return 0 on success or a negative error code otherwise + */ + +int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev, + const char *name, int is_pix) +{ + struct media_pad *pad = &video->pad; + struct video_device *vdev; + struct vb2_queue *q; + int ret; + + vdev = &video->vdev; + + mutex_init(&video->q_lock); + + q = &video->vb2_q; + q->drv_priv = video; + q->mem_ops = &vb2_dma_sg_memops; + q->ops = &msm_video_vb2_q_ops; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_DMABUF | VB2_MMAP | VB2_READ; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->buf_struct_size = sizeof(struct camss_buffer); + q->dev = video->camss->dev; + q->lock = &video->q_lock; + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to init vb2 queue: %d\n", ret); + goto error_vb2_init; + } + + pad->flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, pad); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to init video entity: %d\n", + ret); + goto error_media_init; + } + + mutex_init(&video->lock); + + video->formats = formats_rdi; + video->nformats = ARRAY_SIZE(formats_rdi); + if (is_pix) { + video->formats = formats_pix; + video->nformats = ARRAY_SIZE(formats_pix); + } + + ret = msm_video_init_format(video); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to init format: %d\n", ret); + goto error_video_register; + } + + vdev->fops = &msm_vid_fops; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + vdev->ioctl_ops = &msm_vid_ioctl_ops; + vdev->release = msm_video_release; + vdev->v4l2_dev = v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->queue = &video->vb2_q; + vdev->lock = &video->lock; + strlcpy(vdev->name, name, sizeof(vdev->name)); + + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to register video device: %d\n", + ret); + goto error_video_register; + } + + video_set_drvdata(vdev, video); + atomic_inc(&video->camss->ref_count); + + return 0; + +error_video_register: + media_entity_cleanup(&vdev->entity); + mutex_destroy(&video->lock); +error_media_init: + vb2_queue_release(&video->vb2_q); +error_vb2_init: + mutex_destroy(&video->q_lock); + + return ret; +} + +void msm_video_stop_streaming(struct camss_video *video) +{ + if (vb2_is_streaming(&video->vb2_q)) + vb2_queue_release(&video->vb2_q); +} + +void msm_video_unregister(struct camss_video *video) +{ + atomic_inc(&video->camss->ref_count); + video_unregister_device(&video->vdev); + atomic_dec(&video->camss->ref_count); +} diff --git a/drivers/media/platform/qcom/camss/camss-video.h b/drivers/media/platform/qcom/camss/camss-video.h new file mode 100644 index 000000000000..821c1ef6b5c7 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-video.h @@ -0,0 +1,70 @@ +/* + * camss-video.h + * + * Qualcomm MSM Camera Subsystem - V4L2 device node + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#ifndef QC_MSM_CAMSS_VIDEO_H +#define QC_MSM_CAMSS_VIDEO_H + +#include +#include +#include +#include +#include +#include +#include +#include + +struct camss_buffer { + struct vb2_v4l2_buffer vb; + dma_addr_t addr[3]; + struct list_head queue; +}; + +struct camss_video; + +struct camss_video_ops { + int (*queue_buffer)(struct camss_video *vid, struct camss_buffer *buf); + int (*flush_buffers)(struct camss_video *vid, + enum vb2_buffer_state state); +}; + +struct camss_format_info; + +struct camss_video { + struct camss *camss; + struct vb2_queue vb2_q; + struct video_device vdev; + struct media_pad pad; + struct v4l2_format active_fmt; + enum v4l2_buf_type type; + struct media_pipeline pipe; + const struct camss_video_ops *ops; + struct mutex lock; + struct mutex q_lock; + unsigned int bpl_alignment; + unsigned int line_based; + const struct camss_format_info *formats; + unsigned int nformats; +}; + +void msm_video_stop_streaming(struct camss_video *video); + +int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev, + const char *name, int is_pix); + +void msm_video_unregister(struct camss_video *video); + +#endif /* QC_MSM_CAMSS_VIDEO_H */ diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c new file mode 100644 index 000000000000..d1d27fca2958 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss.c @@ -0,0 +1,751 @@ +/* + * camss.c + * + * Qualcomm MSM Camera Subsystem - Core + * + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "camss.h" + +#define CAMSS_CLOCK_MARGIN_NUMERATOR 105 +#define CAMSS_CLOCK_MARGIN_DENOMINATOR 100 + +static const struct resources csiphy_res[] = { + /* CSIPHY0 */ + { + .regulator = { NULL }, + .clock = { "camss_top_ahb", "ispif_ahb", + "camss_ahb", "csiphy0_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000 } }, + .reg = { "csiphy0", "csiphy0_clk_mux" }, + .interrupt = { "csiphy0" } + }, + + /* CSIPHY1 */ + { + .regulator = { NULL }, + .clock = { "camss_top_ahb", "ispif_ahb", + "camss_ahb", "csiphy1_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000 } }, + .reg = { "csiphy1", "csiphy1_clk_mux" }, + .interrupt = { "csiphy1" } + } +}; + +static const struct resources csid_res[] = { + /* CSID0 */ + { + .regulator = { "vdda" }, + .clock = { "camss_top_ahb", "ispif_ahb", + "csi0_ahb", "camss_ahb", + "csi0", "csi0_phy", "csi0_pix", "csi0_rdi" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" } + }, + + /* CSID1 */ + { + .regulator = { "vdda" }, + .clock = { "camss_top_ahb", "ispif_ahb", + "csi1_ahb", "camss_ahb", + "csi1", "csi1_phy", "csi1_pix", "csi1_rdi" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" } + }, +}; + +static const struct resources_ispif ispif_res = { + /* ISPIF */ + .clock = { "camss_top_ahb", "camss_ahb", "ispif_ahb", + "csi0", "csi0_pix", "csi0_rdi", + "csi1", "csi1_pix", "csi1_rdi" }, + .clock_for_reset = { "camss_vfe_vfe", "camss_csi_vfe" }, + .reg = { "ispif", "csi_clk_mux" }, + .interrupt = "ispif" + +}; + +static const struct resources vfe_res = { + /* VFE0 */ + .regulator = { NULL }, + .clock = { "camss_top_ahb", "camss_vfe_vfe", "camss_csi_vfe", + "iface", "bus", "camss_ahb" }, + .clock_rate = { { 0 }, + { 50000000, 80000000, 100000000, 160000000, + 177780000, 200000000, 266670000, 320000000, + 400000000, 465000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" } +}; + +/* + * camss_add_clock_margin - Add margin to clock frequency rate + * @rate: Clock frequency rate + * + * When making calculations with physical clock frequency values + * some safety margin must be added. Add it. + */ +inline void camss_add_clock_margin(u64 *rate) +{ + *rate *= CAMSS_CLOCK_MARGIN_NUMERATOR; + *rate = div_u64(*rate, CAMSS_CLOCK_MARGIN_DENOMINATOR); +} + +/* + * camss_enable_clocks - Enable multiple clocks + * @nclocks: Number of clocks in clock array + * @clock: Clock array + * @dev: Device + * + * Return 0 on success or a negative error code otherwise + */ +int camss_enable_clocks(int nclocks, struct camss_clock *clock, + struct device *dev) +{ + int ret; + int i; + + for (i = 0; i < nclocks; i++) { + ret = clk_prepare_enable(clock[i].clk); + if (ret) { + dev_err(dev, "clock enable failed: %d\n", ret); + goto error; + } + } + + return 0; + +error: + for (i--; i >= 0; i--) + clk_disable_unprepare(clock[i].clk); + + return ret; +} + +/* + * camss_disable_clocks - Disable multiple clocks + * @nclocks: Number of clocks in clock array + * @clock: Clock array + */ +void camss_disable_clocks(int nclocks, struct camss_clock *clock) +{ + int i; + + for (i = nclocks - 1; i >= 0; i--) + clk_disable_unprepare(clock[i].clk); +} + +/* + * camss_find_sensor - Find a linked media entity which represents a sensor + * @entity: Media entity to start searching from + * + * Return a pointer to sensor media entity or NULL if not found + */ +static struct media_entity *camss_find_sensor(struct media_entity *entity) +{ + struct media_pad *pad; + + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + return NULL; + + pad = media_entity_remote_pad(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + return NULL; + + entity = pad->entity; + + if (entity->function == MEDIA_ENT_F_CAM_SENSOR) + return entity; + } +} + +/* + * camss_get_pixel_clock - Get pixel clock rate from sensor + * @entity: Media entity in the current pipeline + * @pixel_clock: Received pixel clock value + * + * Return 0 on success or a negative error code otherwise + */ +int camss_get_pixel_clock(struct media_entity *entity, u32 *pixel_clock) +{ + struct media_entity *sensor; + struct v4l2_subdev *subdev; + struct v4l2_ctrl *ctrl; + + sensor = camss_find_sensor(entity); + if (!sensor) + return -ENODEV; + + subdev = media_entity_to_v4l2_subdev(sensor); + + ctrl = v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_PIXEL_RATE); + + if (!ctrl) + return -EINVAL; + + *pixel_clock = v4l2_ctrl_g_ctrl_int64(ctrl); + + return 0; +} + +/* + * camss_of_parse_endpoint_node - Parse port endpoint node + * @dev: Device + * @node: Device node to be parsed + * @csd: Parsed data from port endpoint node + * + * Return 0 on success or a negative error code on failure + */ +static int camss_of_parse_endpoint_node(struct device *dev, + struct device_node *node, + struct camss_async_subdev *csd) +{ + struct csiphy_lanes_cfg *lncfg = &csd->interface.csi2.lane_cfg; + struct v4l2_fwnode_bus_mipi_csi2 *mipi_csi2; + struct v4l2_fwnode_endpoint vep = { { 0 } }; + unsigned int i; + + v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); + + csd->interface.csiphy_id = vep.base.port; + + mipi_csi2 = &vep.bus.mipi_csi2; + lncfg->clk.pos = mipi_csi2->clock_lane; + lncfg->clk.pol = mipi_csi2->lane_polarities[0]; + lncfg->num_data = mipi_csi2->num_data_lanes; + + lncfg->data = devm_kcalloc(dev, + lncfg->num_data, sizeof(*lncfg->data), + GFP_KERNEL); + if (!lncfg->data) + return -ENOMEM; + + for (i = 0; i < lncfg->num_data; i++) { + lncfg->data[i].pos = mipi_csi2->data_lanes[i]; + lncfg->data[i].pol = mipi_csi2->lane_polarities[i + 1]; + } + + return 0; +} + +/* + * camss_of_parse_ports - Parse ports node + * @dev: Device + * @notifier: v4l2_device notifier data + * + * Return number of "port" nodes found in "ports" node + */ +static int camss_of_parse_ports(struct device *dev, + struct v4l2_async_notifier *notifier) +{ + struct device_node *node = NULL; + struct device_node *remote = NULL; + unsigned int size, i; + int ret; + + while ((node = of_graph_get_next_endpoint(dev->of_node, node))) + if (of_device_is_available(node)) + notifier->num_subdevs++; + + size = sizeof(*notifier->subdevs) * notifier->num_subdevs; + notifier->subdevs = devm_kzalloc(dev, size, GFP_KERNEL); + if (!notifier->subdevs) { + dev_err(dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + i = 0; + while ((node = of_graph_get_next_endpoint(dev->of_node, node))) { + struct camss_async_subdev *csd; + + if (!of_device_is_available(node)) + continue; + + csd = devm_kzalloc(dev, sizeof(*csd), GFP_KERNEL); + if (!csd) { + of_node_put(node); + dev_err(dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + notifier->subdevs[i++] = &csd->asd; + + ret = camss_of_parse_endpoint_node(dev, node, csd); + if (ret < 0) { + of_node_put(node); + return ret; + } + + remote = of_graph_get_remote_port_parent(node); + of_node_put(node); + + if (!remote) { + dev_err(dev, "Cannot get remote parent\n"); + return -EINVAL; + } + + csd->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + csd->asd.match.fwnode = of_fwnode_handle(remote); + } + + return notifier->num_subdevs; +} + +/* + * camss_init_subdevices - Initialize subdev structures and resources + * @camss: CAMSS device + * + * Return 0 on success or a negative error code on failure + */ +static int camss_init_subdevices(struct camss *camss) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(camss->csiphy); i++) { + ret = msm_csiphy_subdev_init(&camss->csiphy[i], + &csiphy_res[i], i); + if (ret < 0) { + dev_err(camss->dev, + "Failed to init csiphy%d sub-device: %d\n", + i, ret); + return ret; + } + } + + for (i = 0; i < ARRAY_SIZE(camss->csid); i++) { + ret = msm_csid_subdev_init(&camss->csid[i], + &csid_res[i], i); + if (ret < 0) { + dev_err(camss->dev, + "Failed to init csid%d sub-device: %d\n", + i, ret); + return ret; + } + } + + ret = msm_ispif_subdev_init(&camss->ispif, &ispif_res); + if (ret < 0) { + dev_err(camss->dev, "Failed to init ispif sub-device: %d\n", + ret); + return ret; + } + + ret = msm_vfe_subdev_init(&camss->vfe, &vfe_res); + if (ret < 0) { + dev_err(camss->dev, "Fail to init vfe sub-device: %d\n", ret); + return ret; + } + + return 0; +} + +/* + * camss_register_entities - Register subdev nodes and create links + * @camss: CAMSS device + * + * Return 0 on success or a negative error code on failure + */ +static int camss_register_entities(struct camss *camss) +{ + int i, j; + int ret; + + for (i = 0; i < ARRAY_SIZE(camss->csiphy); i++) { + ret = msm_csiphy_register_entity(&camss->csiphy[i], + &camss->v4l2_dev); + if (ret < 0) { + dev_err(camss->dev, + "Failed to register csiphy%d entity: %d\n", + i, ret); + goto err_reg_csiphy; + } + } + + for (i = 0; i < ARRAY_SIZE(camss->csid); i++) { + ret = msm_csid_register_entity(&camss->csid[i], + &camss->v4l2_dev); + if (ret < 0) { + dev_err(camss->dev, + "Failed to register csid%d entity: %d\n", + i, ret); + goto err_reg_csid; + } + } + + ret = msm_ispif_register_entities(&camss->ispif, &camss->v4l2_dev); + if (ret < 0) { + dev_err(camss->dev, "Failed to register ispif entities: %d\n", + ret); + goto err_reg_ispif; + } + + ret = msm_vfe_register_entities(&camss->vfe, &camss->v4l2_dev); + if (ret < 0) { + dev_err(camss->dev, "Failed to register vfe entities: %d\n", + ret); + goto err_reg_vfe; + } + + for (i = 0; i < ARRAY_SIZE(camss->csiphy); i++) { + for (j = 0; j < ARRAY_SIZE(camss->csid); j++) { + ret = media_create_pad_link( + &camss->csiphy[i].subdev.entity, + MSM_CSIPHY_PAD_SRC, + &camss->csid[j].subdev.entity, + MSM_CSID_PAD_SINK, + 0); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + camss->csiphy[i].subdev.entity.name, + camss->csid[j].subdev.entity.name, + ret); + goto err_link; + } + } + } + + for (i = 0; i < ARRAY_SIZE(camss->csid); i++) { + for (j = 0; j < ARRAY_SIZE(camss->ispif.line); j++) { + ret = media_create_pad_link( + &camss->csid[i].subdev.entity, + MSM_CSID_PAD_SRC, + &camss->ispif.line[j].subdev.entity, + MSM_ISPIF_PAD_SINK, + 0); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + camss->csid[i].subdev.entity.name, + camss->ispif.line[j].subdev.entity.name, + ret); + goto err_link; + } + } + } + + for (i = 0; i < ARRAY_SIZE(camss->ispif.line); i++) { + for (j = 0; j < ARRAY_SIZE(camss->vfe.line); j++) { + ret = media_create_pad_link( + &camss->ispif.line[i].subdev.entity, + MSM_ISPIF_PAD_SRC, + &camss->vfe.line[j].subdev.entity, + MSM_VFE_PAD_SINK, + 0); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + camss->ispif.line[i].subdev.entity.name, + camss->vfe.line[j].subdev.entity.name, + ret); + goto err_link; + } + } + } + + return 0; + +err_link: + msm_vfe_unregister_entities(&camss->vfe); +err_reg_vfe: + msm_ispif_unregister_entities(&camss->ispif); +err_reg_ispif: + + i = ARRAY_SIZE(camss->csid); +err_reg_csid: + for (i--; i >= 0; i--) + msm_csid_unregister_entity(&camss->csid[i]); + + i = ARRAY_SIZE(camss->csiphy); +err_reg_csiphy: + for (i--; i >= 0; i--) + msm_csiphy_unregister_entity(&camss->csiphy[i]); + + return ret; +} + +/* + * camss_unregister_entities - Unregister subdev nodes + * @camss: CAMSS device + * + * Return 0 on success or a negative error code on failure + */ +static void camss_unregister_entities(struct camss *camss) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(camss->csiphy); i++) + msm_csiphy_unregister_entity(&camss->csiphy[i]); + + for (i = 0; i < ARRAY_SIZE(camss->csid); i++) + msm_csid_unregister_entity(&camss->csid[i]); + + msm_ispif_unregister_entities(&camss->ispif); + msm_vfe_unregister_entities(&camss->vfe); +} + +static int camss_subdev_notifier_bound(struct v4l2_async_notifier *async, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct camss *camss = container_of(async, struct camss, notifier); + struct camss_async_subdev *csd = + container_of(asd, struct camss_async_subdev, asd); + u8 id = csd->interface.csiphy_id; + struct csiphy_device *csiphy = &camss->csiphy[id]; + + csiphy->cfg.csi2 = &csd->interface.csi2; + subdev->host_priv = csiphy; + + return 0; +} + +static int camss_subdev_notifier_complete(struct v4l2_async_notifier *async) +{ + struct camss *camss = container_of(async, struct camss, notifier); + struct v4l2_device *v4l2_dev = &camss->v4l2_dev; + struct v4l2_subdev *sd; + int ret; + + list_for_each_entry(sd, &v4l2_dev->subdevs, list) { + if (sd->host_priv) { + struct media_entity *sensor = &sd->entity; + struct csiphy_device *csiphy = + (struct csiphy_device *) sd->host_priv; + struct media_entity *input = &csiphy->subdev.entity; + unsigned int i; + + for (i = 0; i < sensor->num_pads; i++) { + if (sensor->pads[i].flags & MEDIA_PAD_FL_SOURCE) + break; + } + if (i == sensor->num_pads) { + dev_err(camss->dev, + "No source pad in external entity\n"); + return -EINVAL; + } + + ret = media_create_pad_link(sensor, i, + input, MSM_CSIPHY_PAD_SINK, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + sensor->name, input->name, ret); + return ret; + } + } + } + + ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev); + if (ret < 0) + return ret; + + return media_device_register(&camss->media_dev); +} + +static const struct v4l2_async_notifier_operations camss_subdev_notifier_ops = { + .bound = camss_subdev_notifier_bound, + .complete = camss_subdev_notifier_complete, +}; + +static const struct media_device_ops camss_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +/* + * camss_probe - Probe CAMSS platform device + * @pdev: Pointer to CAMSS platform device + * + * Return 0 on success or a negative error code on failure + */ +static int camss_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct camss *camss; + int ret; + + camss = kzalloc(sizeof(*camss), GFP_KERNEL); + if (!camss) + return -ENOMEM; + + atomic_set(&camss->ref_count, 0); + camss->dev = dev; + platform_set_drvdata(pdev, camss); + + ret = camss_of_parse_ports(dev, &camss->notifier); + if (ret < 0) + return ret; + + ret = camss_init_subdevices(camss); + if (ret < 0) + return ret; + + ret = dma_set_mask_and_coherent(dev, 0xffffffff); + if (ret) + return ret; + + camss->media_dev.dev = camss->dev; + strlcpy(camss->media_dev.model, "Qualcomm Camera Subsystem", + sizeof(camss->media_dev.model)); + camss->media_dev.ops = &camss_media_ops; + media_device_init(&camss->media_dev); + + camss->v4l2_dev.mdev = &camss->media_dev; + ret = v4l2_device_register(camss->dev, &camss->v4l2_dev); + if (ret < 0) { + dev_err(dev, "Failed to register V4L2 device: %d\n", ret); + return ret; + } + + ret = camss_register_entities(camss); + if (ret < 0) + goto err_register_entities; + + if (camss->notifier.num_subdevs) { + camss->notifier.ops = &camss_subdev_notifier_ops; + + ret = v4l2_async_notifier_register(&camss->v4l2_dev, + &camss->notifier); + if (ret) { + dev_err(dev, + "Failed to register async subdev nodes: %d\n", + ret); + goto err_register_subdevs; + } + } else { + ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev); + if (ret < 0) { + dev_err(dev, "Failed to register subdev nodes: %d\n", + ret); + goto err_register_subdevs; + } + + ret = media_device_register(&camss->media_dev); + if (ret < 0) { + dev_err(dev, "Failed to register media device: %d\n", + ret); + goto err_register_subdevs; + } + } + + return 0; + +err_register_subdevs: + camss_unregister_entities(camss); +err_register_entities: + v4l2_device_unregister(&camss->v4l2_dev); + + return ret; +} + +void camss_delete(struct camss *camss) +{ + v4l2_device_unregister(&camss->v4l2_dev); + media_device_unregister(&camss->media_dev); + media_device_cleanup(&camss->media_dev); + + kfree(camss); +} + +/* + * camss_remove - Remove CAMSS platform device + * @pdev: Pointer to CAMSS platform device + * + * Always returns 0. + */ +static int camss_remove(struct platform_device *pdev) +{ + struct camss *camss = platform_get_drvdata(pdev); + + msm_vfe_stop_streaming(&camss->vfe); + + v4l2_async_notifier_unregister(&camss->notifier); + camss_unregister_entities(camss); + + if (atomic_read(&camss->ref_count) == 0) + camss_delete(camss); + + return 0; +} + +static const struct of_device_id camss_dt_match[] = { + { .compatible = "qcom,msm8916-camss" }, + { } +}; + +MODULE_DEVICE_TABLE(of, camss_dt_match); + +static struct platform_driver qcom_camss_driver = { + .probe = camss_probe, + .remove = camss_remove, + .driver = { + .name = "qcom-camss", + .of_match_table = camss_dt_match, + }, +}; + +module_platform_driver(qcom_camss_driver); + +MODULE_ALIAS("platform:qcom-camss"); +MODULE_DESCRIPTION("Qualcomm Camera Subsystem driver"); +MODULE_AUTHOR("Todor Tomov "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h new file mode 100644 index 000000000000..0e7cfe6bfbe5 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss.h @@ -0,0 +1,106 @@ +/* + * camss.h + * + * Qualcomm MSM Camera Subsystem - Core + * + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + * + * 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. + */ +#ifndef QC_MSM_CAMSS_H +#define QC_MSM_CAMSS_H + +#include +#include +#include +#include +#include +#include +#include + +#include "camss-csid.h" +#include "camss-csiphy.h" +#include "camss-ispif.h" +#include "camss-vfe.h" + +#define CAMSS_CSID_NUM 2 +#define CAMSS_CSIPHY_NUM 2 + +#define to_camss(ptr_module) \ + container_of(ptr_module, struct camss, ptr_module) + +#define to_device(ptr_module) \ + (to_camss(ptr_module)->dev) + +#define module_pointer(ptr_module, index) \ + ((const struct ptr_module##_device (*)[]) &(ptr_module[-(index)])) + +#define to_camss_index(ptr_module, index) \ + container_of(module_pointer(ptr_module, index), \ + struct camss, ptr_module) + +#define to_device_index(ptr_module, index) \ + (to_camss_index(ptr_module, index)->dev) + +#define CAMSS_RES_MAX 15 + +struct resources { + char *regulator[CAMSS_RES_MAX]; + char *clock[CAMSS_RES_MAX]; + u32 clock_rate[CAMSS_RES_MAX][CAMSS_RES_MAX]; + char *reg[CAMSS_RES_MAX]; + char *interrupt[CAMSS_RES_MAX]; +}; + +struct resources_ispif { + char *clock[CAMSS_RES_MAX]; + char *clock_for_reset[CAMSS_RES_MAX]; + char *reg[CAMSS_RES_MAX]; + char *interrupt; +}; + +struct camss { + struct v4l2_device v4l2_dev; + struct v4l2_async_notifier notifier; + struct media_device media_dev; + struct device *dev; + struct csiphy_device csiphy[CAMSS_CSIPHY_NUM]; + struct csid_device csid[CAMSS_CSID_NUM]; + struct ispif_device ispif; + struct vfe_device vfe; + atomic_t ref_count; +}; + +struct camss_camera_interface { + u8 csiphy_id; + struct csiphy_csi2_cfg csi2; +}; + +struct camss_async_subdev { + struct camss_camera_interface interface; + struct v4l2_async_subdev asd; +}; + +struct camss_clock { + struct clk *clk; + const char *name; + u32 *freq; + u32 nfreqs; +}; + +void camss_add_clock_margin(u64 *rate); +int camss_enable_clocks(int nclocks, struct camss_clock *clock, + struct device *dev); +void camss_disable_clocks(int nclocks, struct camss_clock *clock); +int camss_get_pixel_clock(struct media_entity *entity, u32 *pixel_clock); +void camss_delete(struct camss *camss); + +#endif /* QC_MSM_CAMSS_H */ -- cgit v1.2.3-59-g8ed1b From e9340c9205c3b1cea8f1b799b5f8e034d6e3e000 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Jul 2018 17:37:16 +0300 Subject: MAINTAINERS: Replace Heikki as maintainer of Intel pinctrl Heikki has another priorities and no time to maintain Intel pinctrl driver. As we decided off line I'm going to replace him. Signed-off-by: Andy Shevchenko Reviewed-by: Heikki Krogerus Acked-by: Mika Westerberg Signed-off-by: Linus Walleij --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9d5eeff51b5f..6fcf510e77a1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11249,7 +11249,7 @@ F: Documentation/devicetree/bindings/pinctrl/fsl,* PIN CONTROLLER - INTEL M: Mika Westerberg -M: Heikki Krogerus +M: Andy Shevchenko S: Maintained F: drivers/pinctrl/intel/ -- cgit v1.2.3-59-g8ed1b From 7d2eb6de6e6315ad5b93b848ba14360b2370ac4d Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 8 Aug 2018 01:52:49 +0800 Subject: MAINTAINERS: add an entry for MediaTek Bluetooth driver Add an entry for the MediaTek Bluetooth driver. Signed-off-by: Sean Wang Signed-off-by: Marcel Holtmann --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 82f277462349..9777a1dd6ff6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8988,6 +8988,14 @@ F: include/uapi/linux/meye.h F: include/uapi/linux/ivtv* F: include/uapi/linux/uvcvideo.h +MEDIATEK BLUETOOTH DRIVER +M: Sean Wang +L: linux-bluetooth@vger.kernel.org +L: linux-mediatek@lists.infradead.org (moderated for non-subscribers) +S: Maintained +F: Documentation/devicetree/bindings/net/mediatek-bluetooth.txt +F: drivers/bluetooth/btmtkuart.c + MEDIATEK CIR DRIVER M: Sean Wang S: Maintained -- cgit v1.2.3-59-g8ed1b From e64e84987de5486839afaccd191df71012ac1800 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 9 Aug 2018 13:40:46 +0200 Subject: platform/x86: Add ACPI i2c-multi-instantiate pseudo driver On systems with ACPI instantiated i2c-clients, normally there is 1 fw_node per i2c-device and that fw-node contains 1 I2cSerialBus resource for that 1 i2c-device. But in some rare cases the manufacturer has decided to describe multiple i2c-devices in a single ACPI fwnode with multiple I2cSerialBus resources. An earlier attempt to fix this in the i2c-core resulted in a lot of extra code to support this corner-case. This commit introduces a new i2c-multi-instantiate driver which fixes this in a different way. This new driver can be built as a module which will only loaded on affected systems. This driver will instantiate a new i2c-client per I2cSerialBus resource, using the driver_data from the acpi_device_id it is binding to to tell it which chip-type (and optional irq-resource) to use when instantiating. Note this driver depends on a platform device being instantiated for the ACPI fwnode, see the i2c_multi_instantiate_ids list of ACPI device-ids in drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). Acked-by: Andy Shevchenko Acked-by: Wolfram Sang Signed-off-by: Hans de Goede Signed-off-by: Rafael J. Wysocki --- MAINTAINERS | 6 ++ drivers/platform/x86/Kconfig | 11 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/i2c-multi-instantiate.c | 132 +++++++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 drivers/platform/x86/i2c-multi-instantiate.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 7cebd5bba8a8..a7cf519991ec 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -367,6 +367,12 @@ L: linux-acpi@vger.kernel.org S: Maintained F: drivers/acpi/arm64 +ACPI I2C MULTI INSTANTIATE DRIVER +M: Hans de Goede +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/i2c-multi-instantiate.c + ACPI PMIC DRIVERS M: "Rafael J. Wysocki" M: Len Brown diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ac4d48830415..107d336453b2 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1218,6 +1218,17 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. +config I2C_MULTI_INSTANTIATE + tristate "I2C multi instantiate pseudo device driver" + depends on I2C && ACPI + help + Some ACPI-based systems list multiple i2c-devices in a single ACPI + firmware-node. This driver will instantiate separate i2c-clients + for each device in the firmware-node. + + To compile this driver as a module, choose M here: the module + will be called i2c-multi-instantiate. + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 2ba6cb795338..50dc8f280914 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -91,3 +91,4 @@ obj-$(CONFIG_PMC_ATOM) += pmc_atom.o obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o +obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o diff --git a/drivers/platform/x86/i2c-multi-instantiate.c b/drivers/platform/x86/i2c-multi-instantiate.c new file mode 100644 index 000000000000..5456581b473c --- /dev/null +++ b/drivers/platform/x86/i2c-multi-instantiate.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * I2C multi-instantiate driver, pseudo driver to instantiate multiple + * i2c-clients from a single fwnode. + * + * Copyright 2018 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include + +struct i2c_inst_data { + const char *type; + int gpio_irq_idx; +}; + +struct i2c_multi_inst_data { + int num_clients; + struct i2c_client *clients[0]; +}; + +static int i2c_multi_inst_probe(struct platform_device *pdev) +{ + struct i2c_multi_inst_data *multi; + const struct acpi_device_id *match; + const struct i2c_inst_data *inst_data; + struct i2c_board_info board_info = {}; + struct device *dev = &pdev->dev; + struct acpi_device *adev; + char name[32]; + int i, ret; + + match = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!match) { + dev_err(dev, "Error ACPI match data is missing\n"); + return -ENODEV; + } + inst_data = (const struct i2c_inst_data *)match->driver_data; + + adev = ACPI_COMPANION(dev); + + /* Count number of clients to instantiate */ + for (i = 0; inst_data[i].type; i++) {} + + multi = devm_kmalloc(dev, + offsetof(struct i2c_multi_inst_data, clients[i]), + GFP_KERNEL); + if (!multi) + return -ENOMEM; + + multi->num_clients = i; + + for (i = 0; i < multi->num_clients; i++) { + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, inst_data[i].type, I2C_NAME_SIZE); + snprintf(name, sizeof(name), "%s-%s", match->id, + inst_data[i].type); + board_info.dev_name = name; + board_info.irq = 0; + if (inst_data[i].gpio_irq_idx != -1) { + ret = acpi_dev_gpio_irq_get(adev, + inst_data[i].gpio_irq_idx); + if (ret < 0) { + dev_err(dev, "Error requesting irq at index %d: %d\n", + inst_data[i].gpio_irq_idx, ret); + goto error; + } + board_info.irq = ret; + } + multi->clients[i] = i2c_acpi_new_device(dev, i, &board_info); + if (!multi->clients[i]) { + dev_err(dev, "Error creating i2c-client, idx %d\n", i); + ret = -ENODEV; + goto error; + } + } + + platform_set_drvdata(pdev, multi); + return 0; + +error: + while (--i >= 0) + i2c_unregister_device(multi->clients[i]); + + return ret; +} + +static int i2c_multi_inst_remove(struct platform_device *pdev) +{ + struct i2c_multi_inst_data *multi = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < multi->num_clients; i++) + i2c_unregister_device(multi->clients[i]); + + return 0; +} + +static const struct i2c_inst_data bsg1160_data[] = { + { "bmc150_accel", 0 }, + { "bmc150_magn", -1 }, + { "bmg160", -1 }, + {} +}; + +/* + * Note new device-ids must also be added to i2c_multi_instantiate_ids in + * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). + */ +static const struct acpi_device_id i2c_multi_inst_acpi_ids[] = { + { "BSG1160", (unsigned long)bsg1160_data }, + { } +}; +MODULE_DEVICE_TABLE(acpi, i2c_multi_inst_acpi_ids); + +static struct platform_driver i2c_multi_inst_driver = { + .driver = { + .name = "I2C multi instantiate pseudo device driver", + .acpi_match_table = ACPI_PTR(i2c_multi_inst_acpi_ids), + }, + .probe = i2c_multi_inst_probe, + .remove = i2c_multi_inst_remove, +}; +module_platform_driver(i2c_multi_inst_driver); + +MODULE_DESCRIPTION("I2C multi instantiate pseudo device driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b